How to auto update a models attribute on creation of another model

I have been learning django and django rest from several different courses and have started my own project which will be a forum type web application.

I 4 model classes Category, SubCategory, Thread, and Post.
What I want to be able to do is have an attribute for subcategory called num_threads that can be updated when ever a thread is made for the subcategory it is related to. Similarly I will want a thread to have attributes num_posts and num_views. I have been using foreign keys to relate the models. Now I am not sure that I am doing that part correctly.
Here is my model.py:

from django.db import models
from django.contrib.auth.models import User


class Category(models.Model):
    """
    Category Class:
    Main Categories for GRDG Forum
    Creation and Deletion will be restricted to site admin
    """

    # Attributes
    name = models.CharField(max_length=32, unique=True, blank=False)
    description = models.TextField(max_length=150, blank=True)

    def __str__(self):
        return self.name


class SubCategory(models.Model):
    """
    SubCategory Class:
    SubCategories will be one to many relation with category ie: Multiple subcategories related to one category
    Creation and Deletion will be restricted to site admin
    GET methods restriction will vary by subcategory
    """

    # References
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    # Attributes
    name = models.CharField(max_length=32, unique=True, blank=False)
    description = models.TextField(max_length=150)
    num_threads = models.IntegerField(max_length=None, default=0)

    def __str__(self):
        return self.name


class Thread(models.Model):
    """
    Class Thread
    Threads will be one to many relation to subcategory ie: Multiple threads related to one subcategory
    Creation and Deletion restricted to authorized and authenticated users
    GET methods restriction will vary by parent subcategory.
    """

    # References
    subcategory = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
    user = models.ForeignKey(User, models.CASCADE)

    # Attributes
    title = models.CharField(max_length=32, unique=False, blank=False)
    num_views = models.IntegerField(max_length=None, default=0)
    num_posts = models.IntegerField(max_length=None, default=0)
    locked = models.BooleanField(default=False)
    author = models.CharField(max_length=32, blank=False)

    def __str__(self):
        return self.title


class Post(models.Model):
    """
    Class Posts
    Posts will be one to many relation to Thread ie: Multiple posts related to one Thread
    Creation and Deletion restricted to authorized and authenticated users
    """

    # References
    thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
    user = models.ForeignKey(SubCategory, on_delete=models.CASCADE)

    # Attributes
    body = models.TextField(max_length=500, blank=False)
    author = models.CharField(max_length=32, blank=False)

I have references in the model classes that assign the foreign key.
Is there a way to automatically update the a models attribute when ever another object with the foreign key is created.
IE: Increased a subcategories num_thread, when a thread with the same foreign key is created.

Looking at other question here I have seen mentions of using custom save methods and after looking at its documentation I am still not sure how it would work.

Please let me know thoughts are other resources I could use.

>Solution :

Please don’t store the number of Threads (or any other aggregate) in a model. This is a form of data duplication which is often an antipattern: it means that you will need to implement handlers when Threads are created, updated (refer to another SubCategory) or removed. It turns out that keeping these in sync is often a hard problem, even if the two tables are stored in the same database.

You can remove the num_thread field and use .annotate(…) [Django-doc] and calculate the number of related Threads when necessary with:

from django.db.models import Count

SubCategory.objects.annotate(
    num_threads=Count('thread')
)

The SubCategorys that originate from this queryset will then have an extra attribute .num_threads that contains the number of related Thread objects.

Leave a Reply