I have two models : Exam
and Question
. Each question has point that automatically calculate in each exam.
So this is my files:
#Models.py
class Exam(models.Model):
questions = models.ManyToManyField(Question)
title = models.CharField()
class Question(models.Model):
title = models.CharField()
answer = models.TextField()
points = models.PositiveSmallIntegerField()
#Forms.py
class ExamForm(ModelForm):
class Meta:
model = Exam
fields = '__all__'
#Views.py
if form.is_valid():
new_exam = form.save(commit=False)
# Some modify goes here.
new_exam.save()
form.save_m2m()
return redirect('view_exam')
I did customize save()
method of Exam
model to this:
def save(self, *args, **kwargs):
self.point = 0
for question in self.questions:
self.point += question.point
super(Exam, self).save(*args, **kwargs)
But I got this error:
"<Exam: NewObject>" needs to have a value for field "id" before this many-to-many relationship can be used.
How can I do this without raising any error?
My goal: For each new exam that created, calculate the points of questions of this exam and put them into the points
field of Exam
model.
It's never a good idea to save things to your model that can be calculated from other fields/tables in your database, especially if this depends on other models. Too easy to forget to update the value when at some stage you create a new Question
for example. You'll just get inconsistency in your db.
Delete your custom save()
method because it doesn't do anything.
If you want to know the total number of points, add a custom getter on Exam
that calculates this on the fly:
#At the first of models.py -> from django.db.models import Sum
@property
def points(self):
if self.pk:
return self.questions.all().aggregate(Sum('points'))['points__sum']
else:
return 0
Or with your kind of summation:
@property
def points(self):
if self.pk:
point = 0
questions = self.questions.all()
for question in questions :
point += question.point
return point
else:
return 0
With this property, you can do exam.points
anywhere in your code and it will be up-to-date.