Get Choice values for question

Hi,

I have a set of different choices for a question, where each choice has a specific choice value. I need to perform some calculations based on the choice being selected.

I need to get the lowest and highest available value for all the choices a question has. My choice_value are

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

but not all questions will have the same number of choices and therefore 1 question may have a lower value, i.e. -1 and the highest value of 0

I was thinking I would create a new function within the model, but I can’t work out how I get these values cause I am confused about how to get the question id for the question being asked

choices_values = Choice.objects.filter(question__question_id=1).choice_value but this throws a error:
Related Field got invalid lookup: question_id

I dont understand why Choice.objects.filter(question__question_id=1) doesn’t return the first question

I’ve also tried ProjectQuestionnaireQuestion.objects.get(pk=1) but this doesn’t return a value.

Is this because ProjectQuestionnaireQuestion has a FK to Questionnaire

class ProjectQuestionnaireQuestion(models.Model):
    questionnaire = models.ForeignKey(ProjectQuestionnaire, on_delete=models.CASCADE)
    sequence = models.IntegerField()
    question = models.TextField()
    description = models.TextField(blank=True)
    heading = models.CharField(max_length=150, blank=True

If i was to get this working i could then order by highest/lowest and select first/last - Is this the correct approach?

    @property
    def value_score(self):
        return self.lowest_value + self.highest_value

Model

class Choice(models.Model):
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200, null=True)
    choice_value = models.CharField(max_length=20, blank=True)
    choice_weight = models.DecimalField( max_digits=5, decimal_places=2,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

    class Meta:
        verbose_name = "Report Answer Choice" 

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

class ProjectQuestionnaireQuestion(models.Model):
    questionnaire = models.ForeignKey(ProjectQuestionnaire, on_delete=models.CASCADE)
    sequence = models.IntegerField()
    question = models.TextField()
    description = models.TextField(blank=True)
    heading = models.CharField(max_length=150, blank=True)
...

Thanks

You’re “reaching too far”.

In Choice, question is the FK to ProjectQuestionnaireQuestion.

So

only needs to be:
Choice.objects.filter(question_id=1).choice_value

Or, you could (less efficiently) write this as:
Choice.objects.filter(question__id=1).choice_value

The first is a comparison of the question field in Choice, the second is a comparison of the id field in the question object.

Thank you, Ken.

I did try this, but it just returns an empty query set.

In [3]: Choice.objects.filter(question_id=1)
Out[3]: <QuerySet []>

In [4]: Choice.objects.filter(question_id=2)
Out[4]: <QuerySet []>

In [5]: Choice.objects.filter(question_id=3)
Out[5]: <QuerySet []>

or

In [9]: q = Choice.objects.filter(question_id=1).choice_value
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 q = Choice.objects.filter(question_id=1).choice_value

AttributeError: 'QuerySet' object has no attribute 'choice_value'

Why is that?

Issue this query:
ProjectQuestionnaireQuestion.objects.all().values('id', flat=True)
I’m guessing your entries for this table don’t include pks of 1, 2, or 3.

That query returns a queryset - a list of Choice objects - not an individual instance of Choice.
If you want an attribute of the individual entries in that list, you need to iterate through the list and retrieve the attribute for each entry.

You’re right. my PKs start higher.

This is handy ProjectQuestionnaireQuestion.objects.all().values('id') :slight_smile:

So would i run q = Choice.objects.filter(question_id=173) and then iterate through q to get the choice_values?

If you need all the individual values, yes.

If you’re just looking to retrieve the highest and lowest values, you could use aggregation over that queryset to retrieve that.

1 Like

ah yeah, I forgot about aggregation. I’ll give that a go.
Thanks, Ken.

Tom.

Hi, Ken.

Im running q = Choice.objects.aggregate(Max('choice_weight'),Min('choice_weight'))

which is returning:

{'choice_weight__max': Decimal('20.00'),
 'choice_weight__min': Decimal('-8.00')}

How do i reference the values so that i can perform a calculation of choice_weight__max + choice_weight__min

i’ve tried q[0] but throws an error KeyError and if i use q.keys() it prints the keys of the dict, but i still can’t reference these.

And is there a way to do this in the original queryset to return the actual result rather than the max/min values?

I’ve looked through the docs, but can’t find the answer that works for me

Thanks

Ignore this. i should have been using q[‘keyName’]

It’s really quite easy - see Query Expressions | Django documentation | Django

Don’t say that! I’m not sure im understanding how this works.
q = Choice.objects.aggregate(max_value=Max('choice_weight'),min_value=Min('choice_weight')),F('min_value')*-1+F('max_value')

On the right lines?

You’re close, but the expression would be an annotation added to the query.

Sorry, Ken. Are you saying i should replace the aggregate with annotate or combine both?
q = Choice.objects.annotate(max_value=Max('choice_weight'),min_value=Min('choice_weight')),F('min_value')*-1+F('max_value')

This returns

 '...(remaining elements truncated)...']>,
 <CombinedExpression: F(min_value) * Value(-1) + F(max_value)>)

Before adding this addition, what was your query?

You mean this?
q = Choice.objects.aggregate(Max('choice_weight'),Min('choice_weight'))

Yes.

Now, what I wrote was:

(emphasis added)

Not to remove or replace anything. You add an annotation to that query to perform the addition.

I had already tried adding the annotate, but I couldn’t get it to work with the aggregate.

q = Choice.objects.aggregate(Max('choice_weight'),Min('choice_weight')).annotate(F('min_value')*-1+F('max_value'))

'dict' object has no attribute 'annotate'

Sorry, my mistake. I forgot about that aspect of it. Yes, you are right - that aggregation would need to be replaced by an annotation and then they can be combined.

So what you’re looking at (combining everything you’ve identified so far) should probably be closer to:

q = Choice.objects.filter(question_id=173).annotate(
    max_weight=Max('choice_weight'), 
    min_weight=Min('choice_weight'), 
    weight_range=F('max_weight') - F('min_weight')
)

[Edit] ARGH! Yes I reversed them again. That should be aggregate in the query above, not annotate.

No problem, Ken.

When i run:

                q = Choice.objects.filter(question_id=133).aggregate(
                    max_weight=Max('choice_weight'), 
                    min_weight=Min('choice_weight'), 
                    weight_range=F('min_weight') *-1 + F('max_weight')
                )
                print(q)

it throws this error AttributeError: 'CombinedExpression' object has no attribute 'name' which is being caused by weight_range=F('min_weight') *-1 + F('max_weight')

I’m thinking that when using F it needs to reference an existing field when in an aggregate, but seems to work in annotate but I’m trying to avoid returning a queryset

Thanks

I’m thinking that it can’t parse that expression as written.

Try changing that to just -

e.g.: