Why is this always returning zero

The function to calculate the score is always returning zero regardless of the values.

    @property
    def score(self):
        if not self.value:
            return 0 
        for choice in self.question.choice_set.all():
            if choice.choice_value == self.value:
                return choice_value_to_score_map.get(self.value) * choice.choice_weight
        return 0  
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 
        for choice in self.question.choice_set.all():
            if choice.choice_value == self.value:
                return choice_value_to_score_map.get(self.value) * choice.choice_weight
        return 0  

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

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

If I print out the value of answer.score it always returns 0 when using:

  all_q_answered = ProjectQuestionnaireAnswer.objects.filter(response = q_response, answer__isnull=False).values('question__choice__choice_weight')

            total_score = 0
            for answer in q_answered:
                total_score = total_score + answer.score

What am i missing here?

Hey there!
There’s a few question that you need to ask yourself to respond to this.
First, is self.value a value that will evaluate to True?
Second are there any choices into self.question.choice_set.all() you can check using .count()
Third, are choice.choice_value the exact same of self.value?

You can check this by running on debug mode on PyCharm/Vscode, or doing on the oldschool way: print your way through the console.

Thanks, leandrodesouzadev.
I’ve debugged and there are so many things wrong :frowning:

Thanks for helping out. I will see if I can fix it.

I’ve corrected the function. but this if choice.choice_text == self.answer never seems to evaluate to true even when the two matches and I’m not sure why.

I printed out both values and they are == but the function never moves onto the if true section.

        for choice in self.question.choice_set.all():
            if choice.choice_text == self.answer:
                print("This One Matches")
                return choice_value_to_score_map.get(self.answer,0) * choice.choice_weight

The print('This One Matches') never fires

Why would that be?

Thanks

Do more debugging - perhaps add print statements after the for but before the if to print the values being tested.

Note, you can also do a lot of this from within the Django shell to explore your data interactively.

Also it may be helpful to post here the Question model.

That’s what I did Ken. Not all match but some do, but still the print('This One Matches') never fires. Ill keep trying but I suspect it’s something to do with self.question.choice_set.all():?

That’s not what you’re showing here. What is the output you’re getting from those extra print statements?

Sorry, i meant this is what I tried before posting.

No evidence of prior failed or failing project
No evidence of prior failed or failing project
No match
        for choice in self.question.choice_set.all():
            print(choice.choice_text)
            print(self.answer)
            if choice.choice_text == self.answer:
                print("This One Matches")
                return choice_value_to_score_map.get(self.answer,0) * choice.choice_weight
            else: 
                print('No match')

So the first two matches, but still throws No Match ?

Next step is to check the data type for these to ensure you’re testing apples to apples.
e.g. print(type(choice.choice_text)), print(type(self.answer))

That’s it

<class 'str'>
<class 'app.models.Choice'>

Why is that happening then?

Because those are the types of objects you’re referencing.

(Without seeing those classes, I can’t be any more specific than that. It’s also not clear which one is which.)

Ok, I see answer now - it’s defined as: answer = models.ForeignKey(Choice, on_delete=models.CASCADE,null=True)

So answer is a reference to an object of type Choice. It is not a reference to a string.

self.answer is returning the <class 'app.models.Choice'> which is from:


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.answer:
            return 0 # or maybe 0, this depends on how you like to calculate
        for choice in self.question.choice_set.all():
            print(type(choice.choice_text))
            print(type(self.answer))
            if choice.choice_text == self.answer:
                print("This One Matches")
                return choice_value_to_score_map.get(self.answer,0) * choice.choice_weight
            else: 
                print('No match')
                
        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)
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,
}

Ok, but you don’t want the Choice class at that location, you want one of the attributes.
Which field in that class do you want?

Also from a more general perspective:

This is not a good way to do this.

Your query should get the correct entry directly instead of searching through all.

Each choice is assigned a value and a weight. Where the value might be 2 and the weight might be 10. Therefore the calculation should be 2 x 10

And this is from Choice.choice_value and Choice.choice_weight

What I was hoping to do is to calculate the value and weight associated with the Choice.answer

Does that make sense?

Yes, absolutely. But this has nothing to do with anything we’re currently discussing.

The original issue is that you’re not performing a proper comparison. You’re trying to compare a string to an instance of a Model.

But as I see it, this is an X-Y Problem. You’re manually searching through a queryset rather than constructing a query to directly retrieve the desired value. If you write the right query, it eliminates the need for the manual search.

Haha X-Y is exactly the problem.

So the queryset is where im going wrong and need to refine that to select only the values i need?.

I’m i still falling over on the basics here?

Correct!

How would you identify which instance of choice that you need in this case?

Objeccts.filter rather than set.all ?
Where the filter would pass the parameters I need to get the specific values?

Correct.

Or, since you’re only looking for one specific row, get rather than filter.