Foreign Keys with Models - Django Tutorial




Welcome to part 9 of the web development with Python and Django tutorial series. In this tutorial, we're going to work on the organization of tutorials through the use of foreign keys which will allow us to tie certain elements in a database table to another database table's data.

Consider our current problem. We have some tutorials, but they're very short. Once we start adding the full length of tutorials, our home page will be nearly impossible to navigate. So then maybe we just show cards with the tutorial's title? The problem here is we still have over a thousand tutorials. Way too much!

... ok ok, why don't we order by series? Well, we still have over 50 series, which is still a lot of clutter.

So, we organize tutorials by series, and those series by category like "data analysis" or "fundamentals."

Ideally though, however, we'd just want to pick the series for a tutorial, and not need to pick both the series AND category every time, plus this would take up unnecessary database space.

Thus, what we instead do is create 2 new models: series and category. Then, tutorials will have a foreign key that point to the series they belong to, and series will point to a specific category, and this is how we have relationships between tables. Let's see how this works.

To begin, let's start by creating the TutorialCategory model.

mysite/main/models.py
class TutorialCategory(models.Model):

    tutorial_category = models.CharField(max_length=200)
    category_summary = models.CharField(max_length=200)
    category_slug = models.CharField(max_length=200, default=1)

    class Meta:
        # Gives the proper plural name for admin
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.tutorial_category

The category_slug is the URL that we want to point to this category. For example if the category is "Data Analysis," then what's the URL that will point to this category, should a user click on the "Data Analysis" card.

Next, we'll make another model TutorialSeries, which will have a foreign key that points to the TutorialCategory model

class TutorialSeries(models.Model):
    tutorial_series = models.CharField(max_length=200)

    tutorial_category = models.ForeignKey(TutorialCategory, default=1, verbose_name="Category", on_delete=models.SET_DEFAULT)
    series_summary = models.CharField(max_length=200)

    class Meta:
        # otherwise we get "Tutorial Seriess in admin"
        verbose_name_plural = "Series"

    def __str__(self):
        return self.tutorial_series

The only new thing here that you may not understand is the on_delete bit. Basically, we need to know what to do with the referenced objects when the main one is deleted. When we delete a category, we don't really want to delete the tutorials from that category, nor visa versa, so instead we're opting to SET_DEFAULT here. If the category gets deleted, then the tutorials that have that category will have their categories set to their default values rather than deleted.

Now, finally, the tutorial itself:

class Tutorial(models.Model):
    tutorial_title = models.CharField(max_length=200)
    tutorial_content = models.TextField()
    tutorial_published = models.DateTimeField('date published')
    #https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ForeignKey.on_delete
    tutorial_series = models.ForeignKey(TutorialSeries, default=1, verbose_name="Series", on_delete=models.SET_DEFAULT)
    tutorial_slug = models.CharField(max_length=200, default=1)
    def __str__(self):
        return self.tutorial_title

Okay, that's a lot of changes! We've definitely changed and added models, so it's time for a makemigrations and migrate!

python manage.py makemigrations
Migrations for 'main':
  main\migrations\0005_auto_20190116_1349.py
    - Create model TutorialCategory
    - Create model TutorialSeries
    - Add field tutorial_slug to tutorial
    - Alter field tutorial_published on tutorial
    - Add field tutorial_series to tutorial
python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, main, sessions
Running migrations:
  Applying main.0005_auto_20190116_1349... OK

Now that we've done all this, we also need to make modifications to our Admin page to see categories, series, and to be able to pair tutorials to series.

In mysite/main/admin.py

from .models import Tutorial, TutorialSeries, TutorialCategory
...
class TutorialAdmin(admin.ModelAdmin):

    fieldsets = [
        ("Title/date", {'fields': ["tutorial_title", "tutorial_published"]}),
        ("URL", {'fields': ["tutorial_slug"]}),
        ("Series", {'fields': ["tutorial_series"]}),
        ("Content", {"fields": ["tutorial_content"]})
    ]

    formfield_overrides = {
        models.TextField: {'widget': TinyMCE(attrs={'cols': 80, 'rows': 30})},
        }


admin.site.register(TutorialSeries)
admin.site.register(TutorialCategory)
admin.site.register(Tutorial,TutorialAdmin)

Now that we've done that, it's time to go into the admin, create 2 or 3 categories, a few tutorial series, and a few tutorials per series. You're free to just put gobbly goop (yes that's a technical term) in there, just to see how this all works. In the next tutorial, we'll start off assuming you've got a few categories, series, and tutorials per series, and show how we can go about organizing these on our website.

The next tutorial:





  • Django Web Development with Python Introduction
  • Models - Django Tutorial
  • Admin and Apps - Django Tutorial
  • Views and Templates - Django Tutorial
  • CSS - Django Tutorial
  • User Registration - Django Tutorial
  • Messages - Django Tutorial
  • User Login and Logout - Django Tutorial
  • Foreign Keys with Models - Django Tutorial
  • Working with Foreign Keys - Django Tutorial
  • Dynamic sidebar - Django Tutorial
  • Deploying to a Server - Django Tutorial