CreateView doesn't save related objects - python

I have two models: Student and Group. A group consists of multiple students and a student can be in only one group. Here are the models:
class Group(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Student(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
section = models.PositiveSmallIntegerField(default=1)
group = models.ForeignKey(
Group, on_delete=models.SET_NULL, null=True, blank=True
)
I am trying to build a form where I can select multiple students to create a group.
class CreateGroupForm(forms.ModelForm):
students = forms.ModelMultipleChoiceField(
required=True,
queryset=Student.objects.all()
)
class Meta:
model = Group
fields = ('students', )
I am using the following view for the form:
class SelectGroup(CreateView):
model = Group
form_class = CreateGroupForm
template_name = 'selection.html'
success_url = reverse_lazy('test')
When I submit the form, it creates a group but the group's student_set is empty. I am guessing this is because I cannot add students to a group without saving the group first. Is there a way for me to modify this view to save the students or should I use something else?

Since students is not a field of the group model, the model form's save does not know what to do with it. You have to override the save method and handle the students field manually:
class CreateGroupForm(forms.ModelForm):
# ...
def save(self, commit=True):
# you have to commit, else the reverse fk has nothing to point to
group = super(CreateGroupForm, self).save(commit=True)
group.student_set.add(*self.cleaned_data['students'])
return group
If you prefer not to remove the non-commit save option on the form, you can also override the form_valid method of the view:
class SelectGroup(CreateView):
# ...
def form_valid(self, form):
self.object.student_set.add(*self.form.cleaned_data['students'])
return super(SelectGroup, self).form_valid(form)

Related

How to add manytomanyfield attributs with form instead select option field in Django?

info: I have two models customer and items model. customer has ManyToManyField items attribute. my Createview is working fine multiple items save in database while am creating new customer i am able to select multiple items in form. but
Problem: I want to add itemform attribute with customer form. I need when i create new customer i want to Save a new item with the new customer...
Model.py
class Item(models.Model):
name = models.CharField(max_length=255)
datetime = models.DateTimeField(auto_now_add=True)
amount = models.FloatField(default=0)
remaining = models.FloatField(default=0)
class Customer(models.Model):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=11)
items = models.ManyToManyField(Items)
forms.py
class ItemForm(forms.ModelForm):
class Meta:
model = Installment
fields = '__all__'
class CustomerForm(forms.ModelForm):
class Meta:
model = Customer
fields = '__all__'
views.py
def customer_create_view(request):
if request.method == 'POST':
form = CustomerForm(request.POST)
items = ItemForm(request.POST)
if form.is_valid():
if items.is_valid():
items.save()
form.save()
return redirect("/")
else:
form = CustomerForm()
items = ItemForm()
context = {
'form': form,
'items': items
}
return render(request, 'customer/app.html', context)
You can revise your models by adding a CustomerItem model. This will handle all the records of items and customers that are related.
class Item(models.Model):
name = models.CharField(max_length=255)
datetime = models.DateTimeField(auto_now_add=True)
amount = models.FloatField(default=0)
remaining = models.FloatField(default=0)
class Customer(models.Model):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=11)
class CustomerItem(models.Model):
item = models.ForeignKey(Item, related_name='items', on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, related_name='customers', on_delete=models.CASCADE)
With this implementation, you can save whatever items you want to create while creating also customers.

django Select_related(): from another model has a ForginKey To the model that will execute Select_related

I have two model, one for Question and two for answer
tow have a forginkey to one and one have a forginkey to users
for this purpose I use a To be grouped together at the same endpoint I add the second to the first in serializer
With this solution, Django accessed the database with the number of answers in the second form and I cannot use Select_related here
the question is, how can I reduce database hits to the second model
Models.py
class Consultation(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
create_at = models.DateTimeField(auto_now_add=True)
files = models.FileField(upload_to="media/files", null=True, blank=True,
validators=[FileExtensionValidator(['pdf', 'jpg', 'png'])])
def __str__(self):
return self.user.username
class Meta:
ordering = ['-create_at']
class ConsultaionAnswer(models.Model):
consultation = models.ForeignKey(
Consultation, on_delete=models.CASCADE, related_name='reply')
answer = models.TextField(null=True, blank=True)
def __str__(self):
return self.consultation.content[:20] + "..."
serializers.py
class ConsultaionAnswerSerilaizers(serializers.ModelSerializer):
class Meta:
model = ConsultaionAnswer
fields = ('answer',)
class ConsultationSerilaizers(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
username = serializers.StringRelatedField(
source='user.username',
read_only=True,
)
reply = ConsultaionAnswerSerilaizers(read_only=True, many=True)
class Meta:
model = Consultation
fields = (
"id",
"user",
"content",
'create_at',
'files',
'username',
"reply"
)
views.py
class ConsultationView(APIView):
serializer_class = ConsultationSerilaizers
def get(self, request, format=None):
consultat = Consultation.objects.select_related().filter(user=request.user)
serializer = self.serializer_class(
consultat, many=True, context={'request': request})
return Response(serializer.data)
django debug tool bar
This should works:
consultat = Consultation.objects.prefetch_related('reply').filter(user=request.user)
Since it is a one-to-many relation, you'll need a prefetch_related instead (see this link). And you also need to specify which field(s) you want to prefetch as well.
prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related,

How to pre fill fields in a form when using django-bootstrap-modal-forms

I am using django-bootstrap-modal-forms and it works perfectly as in documentation when using fields form my model. Some of the fields are ForeignKeys and they are displayed properly for user to select a value from database table that is referenced by the key, but instead of that I need to put username of the current user.
I tried to change how the CreateView class handles fields, but with no luck. Probably doing something wrong.
models.py
class userSchoolYear(models.Model):
user_in_school = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)
school = models.ForeignKey(sifMusicSchool, on_delete=models.CASCADE)
school_year = models.ForeignKey(sifSchoolYear, on_delete=models.CASCADE)
school_year_grade = models.CharField(max_length=4, choices=IZBOR_RAZREDA, default=None, null=True)
user_instrument = models.ForeignKey(instType, on_delete=models.CASCADE, default=None, null=True)
user_profesor = models.ForeignKey(profSchool, on_delete=models.CASCADE, default=None, null=True)
views.py
class SchoolYearCreateView(BSModalCreateView):
template_name = 'school_year.html'
form_class = SchoolYearForm
success_message = 'Success!'
success_url = reverse_lazy('school')
def __init__(self, *args, **kwargs):
self.form_class.user_in_school = 'johnny' ### HERE
print(user.username)
super().__init__(*args, **kwargs)
forms.py
class SchoolYearForm(BSModalForm):
class Meta:
model = userSchoolYear
fields = '__all__'
Thanks to the author Uroš Trstenjak I was able to find a solution. I was wrong trying to set field values from views.py, instead it should be done in forms.py. So, basically I had to write a init for the form and alter fields values. Uroš pointed out that at from level I can get current user from self.request.user and it did work.

Django CreateView - Display only particular objects in foreignkey field

I have a CreateView view that holds a bunch of fields that need to be filled by the user when creating a new contact. Now, I want the user to be able to see and choose only from the categories that they'd created.
This is the model of Category:
class Category(models.Model):
class Meta:
verbose_name = _('category')
verbose_name_plural = _('categories')
name = models.CharField(max_length=100, unique=True)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
def __unicode__(self):
return self.name
This is the view:
class ContactCreate(LoginRequiredMixin, generic.edit.CreateView):
model = models.Contact
success_url = reverse_lazy('site:contacts')
fields = ['firstname', 'lastname', 'phone1', 'phone2', 'email', 'city', 'category']
template_name = 'site/contacts.html'
context_object_name = 'all_contacts'
What I need the user to see is a select that has only the categories which include the appropriate profile foreign key associated with them.
I'd be glad to get some help with this. Thank you!
You can override the get_form method of the view and set the queryset of the appropriate field:
class ContactCreate(LoginRequiredMixin, generic.edit.CreateView):
# ...
def get_form(self, *args, **kwargs):
form = super(ContactCreate, self).get_form(*args, **kwargs)
form.fields['categories'].queryset = Category.objects.filter(profile=self.request.user.profile)
return form
This, of course, assumes that your Profile model has a OneToOneField to User with related_name 'profile', otherwise you'd have to adjust the filtering.

Annotate with intermediate model in Django

I have a two models Group and User.
class User(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
users = models.ManyToManyField(User)
A user can be in multiple groups and a group can have multiple users so I have made a ManyToManyField in Group.
I have made an intermediate model to store the date when the user was added to the group
class User(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
users = models.ManyToManyField(User, through='GroupUser')
class GroupUser(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group)
It works as it should.
But I have a UserListView in which I want to annotate the number of groups each user belongs to.
I have overriden get_queryset in ListView
def get_queryset(self):
return super(UserListView, self).get_queryset().annotate(num_groups=Count(?))
but I don't know how to count correctly.
I believe this should work:
def get_queryset(self):
return super(UserListView, self).get_queryset().annotate(num_groups=Count('groupuser'))

Categories