Working with Foreign Keys - Django Tutorial




Welcome to part 10 of the web development with Django tutorial series. In this tutorial, we're going to show an example of how we can interact with our various tables linked together by foreign keys.

If you have not created your tutorials, series, and categories, I've hosted my version 9 with all of those added here: Part 9 with added tutorials, series, and categories.

On PythonProgramming.net, every series has always been contained all within "one slash." Back in the early days, files were served exactly where they sat on the server, so you organized them usually in directories. So you might have something like pythonprogramming.net/data-analysis/pandas-intro, because you had to. Nowadays, especially with a model like what we're using, we don't need this. I am not a fan of long URLs, so I opted historically to not do this and instead do pythonprogramming.net/pandas-intro.

That said, this means every single tutorial resides under ONE slash. With a framework like Django in mind, I might not have set out to do this, because it means we have to handle for this all within a single view function pretty much, other than for pages where we know the exact "slug." This will all hopefully make more sense as we go, so now I am just going to get started.

For now, I am going to refer to a case where you just have one slash example: pythonprogramming.net/data-analysis/pandas-intro as a "single" slug. At the moment, we have two cases for a "single slug:"

  • Category (data analysis, fundamentals...etc)
  • Specific tutorial

So, we begin filling our function with:

from .models import Tutorial, TutorialCategory, TutorialSeries
from django.http import HttpResponse

...

def single_slug(request, single_slug):
    categories = [c.category_slug for c in TutorialCategory.objects.all()]
    if single_slug in categories:
      return HttpResponse(f"{single_slug} is a category")

    tutorials = [t.tutorial_slug for t in Tutorial.objects.all()]
    if single_slug in tutorials:
      return HttpResponse(f"{single_slug} is a Tutorial")

    return HttpResponse(f"'{single_slug}' does not correspond to anything we know of!")

Next, let's go ahead and modify the homepage function as well to instead give us the categories to iterate over, rather than the tutorials:

def homepage(request):
    return render(request=request,
                  template_name='main/categories.html',
                  context={"categories": TutorialCategory.objects.all})

Finally, let's add that mysite/main/templates/categories.html:

{% extends 'main/header.html' %}

{% block content %}

    <div class="row">
        {% for cat in categories %}
            <div class="col s12 m6 l4">
                <a href="{{cat.category_slug}}", style="color:#000">
                    <div class="card hoverable">
                        <div class="card-content">
                            <div class="card-title">{{cat.tutorial_category}}</div>
                            <!--<p style="font-size:70%">Published {{tut.tutorial_published}}</p>-->
                            <p>{{cat.category_summary}}</p>
                        </div>
                    </div>
                </a>
            </div>
        {% endfor %}
    </div>
{% endblock %}

Finally, we just need to add in our controller bit in the urls.py. This is where we've unfortunately got this "single slug" every where thing going on, because we can actually have variables within our URL handling:

    path("<single_slug>", views.single_slug, name="single_slug"),

With that added to our views, go ahead and refresh the homepage and you should instead see:

python tutorials

Now click on one of the categories. You should get a message like: machine-learning is a category.

Okay, let's populate this page correctly now. If it's a category, we want to display the tutorial series that fall under that category rather than just serving this message!

Once we know the category, we want to then get all of the tutorial series objects that match that category. We will do this with a filter:

matching_series = TutorialSeries.objects.filter(tutorial_category__category_slug=single_slug)

Next, when someone clicks on a tutorial series, what should happen? For now, we'd like to send them to part 1. Maybe later, we might rather have the functionality in place to send the user back to wherever they left off if they have started. Regardless, this will link to some tutorial specifically. To do this, I am going to just use a dictionary to map the series to part 1:

        for m in matching_series.all():
            part_one = Tutorial.objects.filter(tutorial_series__tutorial_series=m.tutorial_series).earliest("tutorial_published")
            series_urls[m] = part_one.tutorial_slug

Then we'd render. Full code for this views.py function now:

z
def single_slug(request, single_slug):
    # first check to see if the url is in categories.

    categories = [c.category_slug for c in TutorialCategory.objects.all()]
    if single_slug in categories:
        matching_series = TutorialSeries.objects.filter(tutorial_category__category_slug=single_slug)
        series_urls = {}

        for m in matching_series.all():
            part_one = Tutorial.objects.filter(tutorial_series__tutorial_series=m.tutorial_series).earliest("tutorial_published")
            series_urls[m] = part_one.tutorial_slug

        return render(request=request,
                      template_name='main/category.html',
                      context={"tutorial_series": matching_series, "part_ones": series_urls})

Now we need to make this category.html page:

mysite/main/templates/main/category.html

{% extends 'main/header.html' %}

{% block content %}

    <div class="row">
        {% for tut, partone in part_ones.items %}

            <div class="col s12 m6 l4">
                <a href="{{partone}}", style="color:#000">
                    <div class="card hoverable">
                        <div class="card-content">
                            <div class="card-title">{{tut.tutorial_series}}</div>
                            <!--<p style="font-size:70%">Published {{tut.tutorial_published}}</p>-->
                            <p>{{tut.series_summary}}</p>
                        </div>
                    </div>
                </a>
            </div>
        {% endfor %}
    </div>
{% endblock %}

Now, if we click on a category, we get something like:

python tutorials

Now, if we click on a tutorial, we get a message like pandas-intro is a Tutorial.

So we need to handle for a specific tutorial, which I am going to save for the next tutorial along with the tutorial sidebar.

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