views:

34

answers:

1

I'm finding it difficult to denormalise a field in a django model. I have:

class AnswerSet(models.Model):
    title = models.CharField(max_length=255)
    num_answers = models.PositiveIntegerField(editable=False, default=0)
    answers = models.ManyToManyField(Answer, through='AnswerSetAnswer')
    ...

class AnswerSetAnswer(models.Model):
    answer = models.ForeignKey(Answer)
    answer_set = models.ForeignKey(AnswerSet)
    ...

I want num_answers to contain a count of the number of answers in the set.

If 5 answers are initially associated with the AnswerSet "Food" and I edit one so it becomes associated with the AnswerSet "Colours", how can I recalculate the number of answers in the AnswerSet with "Food"? All the signals only seem to send the new data so I can't just override the save method.

I've tried using the m2m_changed signal, but it never gets called when I edit relationships through the admin form.

Here's my code anyway:

def update_answer_set_num_answers(sender, **kwargs):
    """
    Updates the num_answers field to reflect the number of answers
    associated with this AnswerSet
    """
    instance = kwargs.get('instance', False)

    print "no instance"              # never gets here

    if not instance:
        return

    action = kwargs.get('action')

    print "action: ", action

    if (action != 'pre_remove' and action != 'pre_add' and action != 'clear'):
        return

    reverse = kwargs.get('reverse')

    if reverse:
        answer_set = instance.answer_set
    else:
        answer_set = instance.answer_set
    num_answers = AnswerSetAnswer.objects.filter(answer_set=answer_set.id).count()

    if (action == 'pre_remove'):
        num_answers -= int(kwargs.get('pk_set'))
    elif (action == 'pre_add'):
        num_answers += int(kwargs.get('pk_set'))
    elif (action == 'clear'):
        num_answers = 0

    answer_set.num_answers = num_answers

    print 'n a: ', answer_set.num_answers
    answer_set.save()


m2m_changed.connect(update_answer_set_num_answers, \
  AnswerSet.answers.through, weak=False)
+1  A: 

Do you really need to denormalise this? You can calculate it with a simple aggregate:

from django.db.models import Count
answersets = AnswerSet.objects.all().annotate(num_answers=Count('answers')
Daniel Roseman
yes, it would be handy. i want to, for example, display 5 random answer sets that all have 3 answers. and i expect to have a lot of answer sets so this'll help things scale
Roger