Search code examples
pythondjangodjango-modelsdjango-filter

How to prevent a field from being "removed" from the model once you turn it into a property in Django?


I have a field that is a property, because it's the average of all reviews:

ratings = models.FloatField(null=True, default=0)
@property
def ratings(self):
    return self.reviews.aggregate(avg_score=Avg('score'))['avg_score']

The problem is that if I do this, then the field "disappears." In fact, if I run makemigrations, it deletes the field. The problem is that I use django-filter, and I want to have the option of filtering by ratings (show me all books that have between a 7 and a 10). If I make it into a property, I can't do it anymore because django-filter can't "detect" the field anymore.

Is there a way for me to still have property and the field at the same time? Can I maybe make a surrogate field that gets updated everytime ratings is changed and make it equal to that?


Solution

  • Well you can not make something both a field and a property at the same time. Furthermore I would advice not to use a field in the first place, since that will introduce data duplication: you save the data in an aggregated form somewhere else. It turns out that synchronizing data, even on the same database, is a much harder problem than it appears to be.

    You can annotate(..) [Django-doc] the queryset such that the database will calculate the aggregates automatically per item it retrieves. Such queryset looks like:

    from django.db.models import Avg
    
    MyModel.objects.annotate(
        ratings=Avg('reviews__score')
    )

    If you need this value often, you can do that in the Manager [Django-doc] of the model:

    class MyModelManager(models.Manager):
    
        def get_queryset(self):
            return super().get_queryset().annotate(
                ratings=Avg('reviews__score')
            )
    
    class MyModel(models.Model):
        # … model without a ratings field
        objects = MyModelManager()