form with manytomany object creation - django

I'm trying to make own form adding object Announcement
models.py:
class Announcement(models.Model):
person = models.ForeignKey('Person')
source = models.CharField(max_length=30)
date = models.DateField(default=datetime.now())
valid_date = models.DateField(null=True,blank=True)
class Person(models.Model):
names = models.ManyToManyField('Name')
birth_date = models.DateField(null=True, blank=True)
class Name(models.Model):
first_name = models.CharField(max_length=50,blank=True)
middle_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
(It maybe loos weird, but in my concept each Person can have more than one Name, and also the same Name can be assigned to different Persons)
forms.py
from django import forms
from backoffice.models import Announcement
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
fields = ('person','signature','organisation','source', 'date')
And everything works perfectly but I have to choose Person from selectbox. And it is expected behaviour.
But in my case i'm definetely sure, that person doesn't exists in base (all announcements are for different person for very long time - so i want to change person select box to three fields, and create new person (with new names) everytime I save the announcement.
I think I know how to save many to many, that's why i don't put the views.py, but I don't know how to set the forms.py to get fields.
I tried
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
fields = ('person__names__first_name','signature','organisation','source', 'date')
but got Unknown field(s) (person__names__first_name) specified for Announcement

person__name__first_name will not really work in the forms, that only works for the django admin
you have to create a custom field for the first name and then create a logic for saving on either
def clean(self):
// logic here
or
def save(self, commit=True):
// put clean data here
announcement_form = super(AnnouncementForm, self).save(commit=False)
announcement_form.save()

Related

how to filter a model in a listview

I have a page that displays a list of interviewers and some important info about each user.
One of the things that I want it to show is the number of users who have been interviewed by that specific interviewer.
I wrote a view like this:
class ManagerUsers(ListView):
model = User
template_name = 'reg/manager-users.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['scientific_interviewers'] = User.objects.filter(role='theory_interviewer').all()
context['interviewed_number'] = len(ScientificInfo.objects.filter(user__role='applicant', is_approved=True, interviewer=?????))
the interviewer field should be equal to that object's user but I don't know what to do exactly.
the output should be something like this:
object 1 : user's name, user's other info, user's interviewed_number
....
these are my models:
USER_ROLE_CHOICES = (('0', 'applicant'),
('1', 'theory_interviewer'),)
class User(AbstractUser):
id = models.AutoField(primary_key=True)
role = models.CharField(max_length=25, null=True, choices=USER_ROLE_CHOICES, default=USER_ROLE_CHOICES[0][0])
username = models.CharField(unique=True, max_length=13)
first_name = models.CharField(max_length=32, null=True, default=None)
last_name = models.CharField(max_length=64, null=True, default=None)
class ScientificInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user')
interviewer = models.OneToOneField(User, on_delete=models.CASCADE, related_name='interviewer')
is_approved = boolean field
You can override the .get_queryset() method [Django-doc] to return only the interviewers. By using .annotate(…) [Django-doc] you can add an extra attribute to these Users:
from django.db.models import Count
class ManagerUsersView(ListView):
model = User
context_object_name = 'scientific_interviewers'
template_name = 'reg/manager-users.html'
def get_queryset(self):
return super().get_querset().filter(
role='1'
).annotate(
interviewed_number=Count('interviewer', filter=Q(interviewer__user__role='0', interviewer__is_approved=True))
)
The Users that arise from this queryset will have an extra attribute .interviewed_number with the number of approved ScientificInfos where that user was the interviewer.
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to ManagerUsersView, instead of ManagerUsers.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the ScientificInfo
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the interviewer relation to interviews.

Django referencing a specific object in a many-to-one relationship

Let's say I have the following models:
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
I'd like to add a favorite_article field to my Reporter model that will reference a specific Article from reporter.articles.
One option is put the information into the Article model instead:
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField()
But this doesn't seem like a very clean solution. Is there a better method to do this?
The approach you've suggested will work, however in its current form it allows for multiple Articles to be the favorite of one Reporter. With a bit of extra processing you can ensure that only one (at most) Article per Reporter is the favorite.
Making a few modifications to a couple of the answers to the question Unique BooleanField value in Django? we can restrict one True value per Reporter rather than one True value for the entire Article model. The approach is to check for other favorite Articles for the same Reporter and set them to not be favorites when saving an instance (rather than using a validation restriction).
I'd also suggest using a single transaction in the save method so that if saving the instance fails the other instances are not modified.
Here's an example:
from django.db import transaction
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField(default=False)
def save(self, *args, **kwargs):
with transaction.atomic():
if self.is_favorite:
reporter_id = self.reporter.id if self.reporter is not None else self.reporter_id
other_favorites = Article.objects.filter(is_favorite=True, reporter_id=reporter_id)
if self.pk is not None: # is None when creating a new instance
other_favorites.exclude(pk=self.pk)
other_favorites.update(is_favorite=False)
return super().save(*args, **kwargs)
I've also changed the approach to use a filter rather than a get just in case.
Then to get the favorite article for a reporter, you can use:
try:
favorite_article = reporter.articles.get(is_favorite=True)
except Article.DoesNotExist:
favorite_article = None
which you could wrap into a method/property of the Reporter class.

Class based views how to handle OneToOneField in a model

I have 2 models:
class CompanyInfo(models.Model):
name = models.CharField(_('name'), max_length=100)
address = models.OneToOneField(Location)
class Location(models.Model):
address_1 = models.CharField(_("address"), max_length=128)
address_2 = models.CharField(_("address cont'd"), max_length=128, blank=True)
city = models.CharField(_("city"), max_length=64, default="")
state = USStateField(_("state"), default="")
zip_code = models.CharField(_("zip code"), max_length=5, default="")
When I use CBVs and prints out the form on the template. It shows name with an input field and address as a multiple choice selection.
Is there anyway in which I could convert the multiple choice to act as multiple input fields and the state to be multiple choice.
I figured I could have 2 form for these, but how would I incorporate a form inside of a form?
Also, the way in which the models and fields are must not be changed. For this example sure location fields could be in the company info, but I simply want how to do something similar when it doesn't make sense to include all these fields into the same model.
So, far only thing I can come up with is using function based views and just dealing with 2 forms at a time.
Here are my forms:
class CompanyInfoForm(ModelForm):
class Meta:
model = CompanyInfo
exclude = ['address']
class LocationForm(ModelForm):
class Meta:
model = Location
fields = '__all__'
then class based views:
class CompanyInfoCreate(CreateView):
model = CompanyInfo
form_class = CompanyInfoForm
class LocationCreate(CreateView):
model = Location
form_class = LocationForm
However, this is very helpful since these forms can only be done 1 at a time. I would like LocationView to be in place of the address location or of the sort.
Perhaps, these types of views have their strength in dealing with forms at an individual level.

How to order the results of a ForeignKey relationship in a Django form?

I have this models in Django
class Country(models.Model):
name = models.CharField(max_length=80)
class Person(models.Model):
first_name = models.CharField(max_length=100, db_index=True)
last_name = models.CharField(max_length=100, db_index=True)
country = models.ForeignKey(Country)
and this ModelForm
class PersonForm(forms.ModelForm):
class Meta:
model = Person
when I use this form in a template, everything works fine, but the country list in the <select> appears disordered. How can I order it?
You can use the ordering property:
class Country(models.Model):
name = models.CharField(max_length=80)
class Meta:
ordering = ["name"]
If you set the ordering to the Country class, it shall display them as you want.
If you can't or don't want to use the ordering attribute in class Meta of model, you also can do this:
You need make a Form object, something like:
from django import forms
class PersonForm(forms.ModelForm):
country = forms.ModelChoiceField(queryset=Country.objects.all().order_by('name'))
class Meta:
model = Person
field types for formsmodels
There's 2 good answers here, but I wanted to retain help_text, blank, and other settings from the model without having to repeat them and also not change the default ordering on the model. Here's what I did:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
self.fields['country'].queryset = self.fields['country'].queryset.order_by('name')
Essentially I just updated the queryset on the automatically added field to order the way I wanted.
try adding this into class Meta, inside class Person:
ordering = ['country']
http://docs.djangoproject.com/en/dev/ref/models/options/#ordering
In view.py
First: you create the form
form = YourForm(request.POST)
Later your set the query:
form.fields['country '].queryset = YourDBTable.objects.all().order_by('Your_Attr')

How to save a ManyToMany relationship from a ModelForm in Django

I'm not sure to save my ManyToMany relationship. I found my exact problem in this thread: Django embedded ManyToMany form, except instead of Sales and Products models, I have models that make up a movie.
I tried the solution, but I receive a syntax error. I don't understand how Django should link the EquipmentModel, LightModel, and ActorModel to the ManyToMany relationship in MovieModel. So far (before trying the other thread's solution), the CharFields that are displayed on the form for LightModel, EquipmentModel, and ActorModel are not linked to the ManyToManyField in MovieModel. So when I save the forms and try to access a particular Movie's actors, all I see is a blank list. The solution from the other thread seems to make sense since it tries to link the models to the ManyToMany relationship in MovieModel, but I don't understand how Django knows which MovieModel to add to (how does it get the correct movieID?).
On a side note, is there a way to check for duplicate movies when the user presses the 'Submit' button on the form? I want to avoid creating duplicates.
views.py:
def add_movie(request, movieID=""):
if request.method == "POST":
form = MovieModelForm(request.POST)
eform = EquipmentModelForm(request.POST)
lform = LightModelForm(request.POST)
aform = ActorModelForm(request.POST)
print 'checking form'
print request.POST.items()
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
print 'form is valid'
movie_to_add = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
movie_to_add.actors.add(a)
movie_to_add.lights.add(l)
movie_to_add.equipments.add(e)
# return HttpResponseRedirect('/data')
else:
# code for create forms ....
return render_to_response('add_movie.html', {'form':form, 'eform':eform,'lform':lform, 'aform':aform,}, context_instance=RequestContext(request))
Other code that may help:
forms.py
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
models.py
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
Edit: removed unnecessary init and fields thanks to DTing
Edit2: Fixed!
There is a whole lot of stuff going wrong here in addition to what spulec said.
Your models.py look okay.
class EquipmentModel(models.Model):
equip = models.CharField(max_length=20)
class ActorModel(models.Model):
actor = models.CharField(max_length=20)
class LightModel(models.Model):
light = models.CharField(max_length=20)
class MovieModel(models.Model):
rank = models.DecimalField(max_digits=5000, decimal_places=3)
title = models.CharField(max_length=20)
equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
actors = models.ManyToManyField(ActorModel, blank=True, null=True)
lights = models.ManyToManyField(LightModel, blank=True, null=True)
def __str__(self):
return self.title
You don't need to override the __init__ method on forms if you are not changing anything on init. You also don't need to be explicit about the fields if you want to include them all.
class LightModelForm(forms.ModelForm):
class Meta:
model = LightModel
class ActorModelForm(forms.ModelForm):
class Meta:
model = ActorModel
class EquipmentModelForm(forms.ModelForm):
class Meta:
model = EquipmentModel
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
fields = ("title", "rank")
your view doesn't really make sense unless for every movie you are trying to add you also want to:
add a new movie to the db using the submitted post data
create one actor object and add to db
create one light object and add to db
create one equipment object and add to db
take those three objects and add them to another movie's m2m relationships.
This other movie is some movie that you pulled from the urlconf and passed to your view, not the one you just created.
This all seems a little strange.
what i think you want to do is create all the equipment, actors and lights objects so they are in your db already, and use the default m2m formfield widget to select them when adding a movie.
so:
forms.py
class MovieModelForm(forms.ModelForm):
class Meta:
model = MovieModel
urls.py:
url(r'^add_movie/$', add_movie)
views.py:
def add_movie(request):
if request.method=='POST':
form = MovieModelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('success')
else:
form = MovieModelForm()
context = {'form':form }
return render_to_response('some_template.html', context,context_instance=RequestContext(request))
you could combine adding actors, lights, and equipment into the same form but that's a bit much for me to write out right now.
As far as modifying your original code to add those lights, actors, and equipment to the movie you just created, you could do this:
if form.is_valid() and eform.is_valid() and lform.is_valid() and aform.is_valid():
new_movie = form.save()
e = eform.save()
l = lform.save()
a = aform.save()
new_movie.actors.add(a)
new_movie.lights.add(l)
new_movie.equipments.add(e)
Change it to:
movie_to_add = get_object_or_404(MovieModel, id=movieID)