Process calculation inside view or function within model

Hi

I have a set of questionnaires which has a set of questions with answers based upon choices.

I am trying to perform some calculations based on the answers the user chooses. This has evolved over time and now the calculations have changed where now the choice also has a weight assigned. So the answer could score -1 but the weight would be -10.

The function takes the choice value and multiples this by the weight.

I tried to do this in my view, but i was advised that it might be more appropriate to perform the calculation as a function within the model?


class Choice(models.Model):
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    choice_value = models.CharField(max_length=20, blank=True)
    choice_weight = models.IntegerField(blank=True, null=True)

    def __str__(self):
        return str(self.choice_text)

    @property
    def raw_score(self):
        if not self.choice_value:
            return 0
        return choice_value_to_score_map.get(self.choice_value, 0)

    @property
    def weighted_score(self):
        return self.raw_score * self.choice_weight

choice_value_to_score_map = {
    '--': -2,
    '-': -1,
    '0': 0,
    '+': 1,
    '++': 2,
}

class ProjectQuestionnaireAnswer(models.Model):
    YN_Choices = [
        ('Yes', 'Yes'),
        ('No', 'No'),
        ('Unknown', 'Unknown')
    ]
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    answer = models.ForeignKey(Choice, on_delete=models.CASCADE,null=True)
    value = models.CharField(max_length=20, blank=True)
    notes = models.TextField(blank=True)
    response = models.ForeignKey(ProjectQuestionnaireResponse, on_delete=models.CASCADE)
    
    @property
    def score(self):
        if not self.value:
            return 0 # or maybe 0, this depends on how you like to calculate
        for choice in self.question.choice_set.all():
            if choice.choice_value == self.value:
                return choice_value_to_score_map.get(self.value, 0) * choice.choice_weight
        return 0  # or maybe 0, this depends on how you like to calculate

    class Meta:
        constraints = [
                models.UniqueConstraint(fields=['question','response'], name='project_unique_response2'),
            ]

    def __str__(self):
        return str(self.answer)

What is the benefit of doing this, does this have less of a performance impact - It doesn’t actually work at the moment, but I just trying to understand if this is the approach I should follow.

Thanks

Where you perform calculations like this doesn’t affect performance. It’s an issue of “style” and “utility”.

What I mean by the last point is that by putting that code in the model, you have those calculations available to you anywhere you use that model. (It’s implicitly available in every view.) If that code is in a view, that’s the only place where it can be used.

You also have a third option. You could create a custom manager creating a custom queryset that annotates the rows with the additional data - allowing those calculations to be performed in the database rather than in your code.

Ok, thanks, Ken. That makes sense.

Am I correct in thinking that the 3rd way would be best for performance as would be performed by the database server, and if I was concerned about performance this would be the approach to take?

I’m still struggling with annotates, but I’d go down this route if it was the way you’d go?

The kind of numbers I am looking at is around 10000 users, each completing 10 questionnaires for 10 different products, seems like a small number of users so maybe this wouldn’t be a issue?

Performance would be the last of my considerations here. I wouldn’t even begin to look at the performance effects until it was demonstrated that I had a problem needing to be resolved.

The more important factor is that regardless of the method you choose, are you going to be able to come back to this in six months, and understand what you create today?

Another factor depends upon how many different places in the system this needs to be done, and whether or not this is the end point of the calculations or the starting point of other calculations.

If there are like 20 different views that all had to display those values (directly or indirectly) I’d absolutely want to ensure the code for that is centralized - either in the model or the manager.

But if these results are being calculated in just one view for one page, I’m a lot less concerned about that.

1 Like