Home > Mobile >  Django : Recalculating mean value in the database after creating a new instance
Django : Recalculating mean value in the database after creating a new instance

Time:11-09

I have informations about companies presented in a table. One of the field of this table is the mean value of each note the company received ('note_moyenne' in models.FicheIdentification). By clicking on a button, people are able to submit a new note for the company ('note' in models.EvaluationGenerale). I want the mean value of the notes to update in the database each time someone submit a new note.

Here is my models.py :

class FicheIdentification(models.Model):
    entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
    note_moyenne=models.IntegerField()

    def __str__(self):
        return self.entreprise.nom_entreprise

class EvaluationGenerale(models.Model):
    entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
    note=models.IntegerField()
    commentaires=models.CharField(max_length=1000)
    date_evaluation=models.DateField(auto_now_add=True)

    def __str__(self):
        return self.commentaires

views.py :

class CreerEvaluationGenerale(CreateView):
    form_class = FormulaireEvaluationGenerale
    model = EvaluationGenerale
    def form_valid(self, form):
        form.instance.entreprise=Entreprise.objects.filter(siret=self.kwargs['siret']).first()
        return super(CreerEvaluationGenerale, self).form_valid(form)
    def get_success_url(self):
        return reverse('details-evaluations')

Currently I just display the mean value in my table using this

def render_evaluation(self, record):
        return (EvaluationGenerale.objects.filter(entreprise=record.entreprise.siret).aggregate(Avg('note'))['note__avg'])

but I really don't like this solution as I want the value to be stored in the database, in FicheIdentification.note_moyenne.

I thought about creating a UpdateView class but couldn't manage to link it with my CreateView.

Any help or documentation would be really appreciated, I'm a bit lost right know...

CodePudding user response:

I see two ways of doing it.

Either a listener post_save on EvaluationGenerale (doc). You'll be able to compute the new average each time a new EvaluationGenerale is entered in DB.

@receiver(post_save, sender=EvaluationGenerale)
def evaluation_general_note_moyenne_computer_post_save_listener(sender, instance, **kwargs):
    entreprise = instance.entreprise 
    entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0])
    entreprise.save()

post save listener will only trigger on instance.save() and models.objects.create() not on queryset.update() or model.objects.bulk_create().

Either overriding the save (doc) function of your form to compute the average after the creation of the new EvaluationGenerale

def save(self):
    instance = super.save()
    entreprise = instance.entreprise 
    entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0]
    entreprise.save() 
    return instance

CodePudding user response:

Assuming there is as single FicheIdentification object per enterprise, you could update the note_moyenne field when you save the EvaluationGenerale object, like:

obj = FicheIdentification(...)

FicheIdentification.objects.filter(entreprise=record.entreprise.siret).update(note_moyenne=obj.aggregate(Avg('note'))['note__avg']

obj.save()

Please let me know if it works.

CodePudding user response:

Typically, you would not store calculated fields. The usual way is not to store the average, but to use an annotation/aggregation in your query.

To centralize this to your model, you would want to write a custom model manager to implement this, so it can be reused anywhere you use your model without rewriting the logic.

class MyModelManager(models.Manager):
    def note_average(self, **filter_kwargs):
        qs = self.get_queryset()
        # replace `...` with your aggregation as needed
        return qs.filter(**filter_kwargs).aggregate(...) 

class EvaluationGenerale(models.Model):
    objects = MyModelManager()  # override the default manager
    # ... the rest of the model as-is

Then you can use something like the following in your view(s):

EvaluationGenerale.objects.note_average(entreprise=record.entreprise.siret)

See for additional reference: How to add a calculated field to a Django model

  • Related