Blog

How to use generic relationships in Django

Naturally, the database model of our projects usually contains multiple models that are related to each other. These relationships are not always easy to define, sometimes one model can be optionally related to another.

For example, suppose we are developing an application to carry out Giveaways and raffle among the participants different types of prizes such as smartphones, gaming laptops, trips, hotel stays.

Logically, the attributes to describe a smartphone are very different from the attributes to describe a trip or a hotel stay, so we would have a model for each of these, but at the same time they are all prizes and a giveaway can raffle different types of prizes at the same time.

To solve this problem, Django has GenericForeignKey, to establish generic relationships between the models of our application.

Let's see how it works by continuing with the example of our app for giveaways.

Then you can see all the code of this tutorial in this repository.

First we define the models for the Competitors, the different types of prizes and the Giveaways.

class Competitor(models.Model):

    ig_username = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=50)
    email = models.EmailField(max_length=254)

    class Meta:

        verbose_name = 'Competitor'
        verbose_name_plural = 'Competitors'

    def __str__(self):
        return f'{self.ig_username}'


class Article(models.Model):

    name = models.CharField(max_length=50)
    model = models.CharField(max_length=50)
    description = models.TextField()
    value = models.FloatField()

    class Meta:

        verbose_name = 'Article'
        verbose_name_plural = 'Articles'

    def __str__(self):
        return f'{self.id} - {self.name}'


class DayPassResort(models.Model):

    name = models.CharField(max_length=100)
    resort = models.CharField(max_length=100)
    description = models.TextField()
    address = models.TextField()
    date = models.DateField()

    class Meta:

        verbose_name = 'Day Pass Resort'
        verbose_name_plural = 'Day Pass Resort'

    def __str__(self):
        return f'{self.id} - {self.resort}'


class GiveAway(models.Model):

    name = models.CharField(max_length=100, unique=True)
    description = models.TextField()
    date = models.DateField()
    number_of_winners = models.PositiveIntegerField()

    class Meta:

        verbose_name = 'GiveAway'
        verbose_name_plural = 'GiveAway'

    def __str__(self):
        return f'{self.name}'

Now to store the winners of a giveaway we need another model that stores the association between a participant, a giveaway and a prize.

Since there are different types of prizes we need a generic relation for them in the model. To define this relationship, I recommend using django-gfklookupwidget, a package that helps us easily manage this type of relationship from the Django admin.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
import gfklookupwidget.fields as gfklookup_fields

class GiveAwayWinner(models.Model):

    competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
    giveaway = models.ForeignKey(GiveAway, on_delete=models.CASCADE)
    limit = models.Q(app_label='giveaway', model='article') | \
        models.Q(app_label='giveaway', model='daypassresort')    
    content_type = models.ForeignKey(ContentType,  limit_choices_to=limit, on_delete=models.CASCADE)
    object_id = gfklookup_fields.GfkLookupField('content_type')
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:

        verbose_name = 'GiveAway Winner'
        verbose_name_plural = 'GiveAway Winner'

    def __str__(self):
        return f'{self.id} - {self.competitor.name} - {self.content_type} - {self.id}'

The content_type field creates a relationship with Django's ContentType model that stores information about the rest of the models we define in our project. Usually django.contrib.contenttypes is imported by default in the INSTALLED_APPS in the settings.

Limit allows us to define the models that we are going to allow for the generic relation, because could be any model and we just want to allow relations with, Article and DayPassResort.

object_id stores the idof the related object and content_object creates the relationship between the content_type and the object_id.

Once our models are defined, we generate and run the migrations, to define the admin that allows us to manage the necessary objects to organize a Giveaway.

Now we access the admin to create a couple of prizes, an Article and  a DayPassResort.

Then we create a couple of participants, because we need winning people!

We create a Giveaway.

Finally we register a Giveawaywinner, when selecting the type of content and clicking on the search icon of  the field object_id a pop-up window will appear to select an object of the selected type.

This is thanks to django-gfklookupwidget, if you don't use this package you would have to enter the object's id directly. 

Another alternative that helps us to do this is django-grappelli but personally I don't like it because it forces us to modify the admin interface of our project.

© 2024 José García. All Rigths Reserved.

CHEO
JOSE GARCIA