Set up content: Understand urls, templates and apps

Content in Django projects works with three major building blocks: urls, templates and apps. You create and configure Django urls, templates and apps separately, though you connect one to another to fulfill content delivery, which is part of Django's loosely coupled architecture design principles.

Urls define the entry points or where to access content. Templates define the end points that give form to the final content. And apps serve as the middleware between urls and templates, altering or adding content from a database or user interactions. To run static content you only need to create and configure Django urls and templates. To run dynamic content -- built from a database or user interactions -- you need to create and configure Django apps, in addition to urls and templates.

But before describing how to create and configure urls, templates and apps, it's very important you understand how each of these parts works with one another. Figure 1-4 shows the Django workflow for user requests and how they work with Django urls, templates and apps.

Figure 1-4 Django workflow for urls, templates and apps

As you can see in figure 1-4, there are two separate pipelines to deliver either static or dynamic content. More importantly, notice how each of the different Django layers is loosely coupled (e.g. you can forgo the apps layer if it isn't required and the urls layer & templates layer are still able to communicate with one another).

Create and configure Django urls

The main entry point for Django urls is the urls.py file created when you start a project -- if you're unfamiliar with a Django project structure, see listing 1-15 earlier in the chapter. If you open the urls.py file, you'll notice it only has one access path to admin/ (i.e. a project's admin/ url) which is the Django admin -- I will discuss the Django admin in the next and final section of this chapter.

Now that you're familiar with the urls.py file syntax, let's activate a url to view custom content on the home page of a Django project.

Open the urls.py file and add line three and six highlighted in listing 1-20.

Listing 1-20. Django url for home page to template

from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('',TemplateView.as_view(template_name='homepage.html')),
    path('admin/', admin.site.urls),
]

As show in listing 1-20, urlpatterns is a Python list of path() statements. The path method comes from the django.urls package. The path method you just added defines the access path for the home page -- an empty string '' -- followed by the action TemplateView.as_view(template_name='homepage.html'). This last action is a helper method to direct the requesting party to a template which takes the argument template_name='homepage.html'.

In summary, the path method you added in listing 1-20 tells Django that requests for the home page should return the content in the template homepage.html. The path method is very versatile and can accept several variations, as I'll describe shortly and extensively in the next chapter.

Now lets test the home page. Start the development web server by executing python manage.py runserver on the Django project's BASE_DIR. Open a browser on the default address http://127.0.0.1:8000/. What do you see ? An error page with TemplateDoesNotExist at / homepage.html. This error is caused because Django can't locate the homepage.html template defined for the url. In the next section, I'll show you how to configure and create templates.

Create and configure Django templates

By default, Django templates are interpreted as HTML. This means Django templates are expected to have a standard HTML document structure and HTML tags (e.g. <html>, <body>). You can use a regular text editor to create Django templates and save the files with an .html extension.

Lets create a template for the url in the past section. In a text editor, create a file named homepage.html and place the contents of listing 1-21 into it. Save the file on your system, in a sub-directory called templates in your Django project's PROJECT_DIR.

Listing 1-21. Template homepage.html

<html>
 <body>
  <h4>Home page for Django</h4>
 </body>
</html>

Once you have a directory with Django templates, you need to configure a Django project so it can find the templates in this directory. In the settings.py file of the Django project, you need to define the template directory in the DIRS property of the TEMPLATES variable. The DIRS property is a list, so you can define several directories to locate templates, though I recommend you only use a single directory with various sub-directories for classification.

As I recommended previously, you should aim to keep Django templates inside a sub-directory -- using an obvious name like templates -- in a Django project's PROJECT_DIR. So for example, if the absolute path to a Django project PROJECT_DIR is /www/STORE/coffeehouse/, the recommended location for a DIRS value would be /www/STORE/coffeehouse/templates/. Listing 1-22 illustrates a sample DIRS definition in settings.py using the PROJECT_DIR reference variable set dynamically at the top of settings.py.

Listing 1-22. TEMPLATES and DIRS definition in settings.py

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_DIR = Path(__file__).resolve().parent

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [ PROJECT_DIR / 'templates' ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

An important take away from listing 1-22 is that it doesn't use hard-coded directory paths, instead it uses the PROJECT_DIR variable which is determined dynamically. This may seem trivial at the moment, but it's a good practice once the location of a Django project has a tendency to change (e.g. group development, deployment to production).

Finally, start the Django development web server once again and open a browser on the default address http://127.0.0.1:8000/. Instead of the error page you saw in the previous section, you should now see the contents of the template homepage.html on the home page.

Create and configure Django apps

Django apps are used to group application functionality. If you want to work with content from a database or user interactions you have to create and configure Django apps. A project can contain as many apps as you need. For example, if you have a project for a coffeehouse, you can create an app for stores, another app for menu items, another app for about information and create additional apps as they're needed. There's no hard-rule to the number of apps in a project. Whether to make code management simpler or delegate app work to a team, the purpose of Django apps is to group application functionality to make work easier.

Django apps are normally contained in sub-directories inside a project. This approach makes it easier to use Python references and naming conventions. If the project name is coffeehouse, the functionality of an app named stores is easily referred through Python packages as coffeehouse.stores.

Because apps provide a modular way to group application functionality, it's common for other people or groups to distribute Django apps with popular functionality. For example, if a Django project requires forum functionality, instead of writing a forum app from scratch, you can leverage one of several Django forum apps. The more general purpose the functionality you're looking for, the more likely you'll be able to find a Django app created by a third party.

You already worked with Django apps!

You may not have realized it, but in listing 1-19 in the previous section when you set up a database for a Django project you already worked with Django apps when you invoked the migrate operation.

By default, all Django projects are enabled with six apps provided by the framework. These apps are django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages and django.contrib.staticfiles. When you triggered the migrate operation, Django created the database models for these pre-installed apps.

Next, lets create a small Django app. Go to the PROJECT_DIR -- where the urls.py and settings.py files are -- and execute the command django-admin startapp about to create an app called about. A sub-directory named about is created containing the app. By default, upon creating an app its sub-directory includes the following:

Next, open the views.py file and add the contents from listing 1-23.

Listing 1-23. Handler view method in views.py

from django.shortcuts import render

def contact(request):
    # Content from request or database extracted here 
    # and passed to the template for display
    return render(request,'about/contact.html')

The contact method in listing 1-23 -- like all other methods in views.py files -- is a controller method with access to a user's web request. Notice the input for the contact method is called request. Inside this type of method you can access content from a web request (e.g. IP address, session) using the request reference or access information from a database, so that toward the end you pass this information to a template. If you look at the last line of the contact method, it finishes with a return statement to the Django helper method render. In this case, the render method returns control to the about/contact.html template.

Because the contact method in listing 1-23 returns control to the template about/contact.html, you'll also need to create a sub-directory called about with a template called contact.html inside your templates directory (i.e. the one defined in the DIRS property of the TEMPLATES variable).

The contact method by itself does nothing, it needs to be called by a url. Listing 1-24 highlights the lines you need to the urls.py file so a url path is linked to the contact method in listing 1-23.

Listing 1-24. Django url for view method

from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
from coffeehouse.about import views as about_views

urlpatterns = [
    path('',TemplateView.as_view(template_name='homepage.html')),
    path('about/', about_views.contact),
    path('admin/', admin.site.urls),
]

The first thing that's declared in listing 1-24 is an import statement to gain access to the contact method in listing 1-23. In this case, because the app is named about and it's under the coffeehouse project folder it says from coffeehouse.about, followed by import views which gives us access to the app's views.py file where the contact method is located.

The import statement ends with as about_views to assign it a unique qualifier, which is important if you plan to work with multiple apps. For example, import statements without the as keyword, such as from coffeehouse.about import views, from coffeehouse.items import views or from coffeehouse.stores import views can import conflicting view method references (e.g. three methods named index), so the Python as qualifier is a safeguard to ensure you don't unintentionally use a method with the same name from another app.

The new path definition in listing 1-24 uses a path to match requests on the about url directory (e.g. http://127.0.0.1:8000/about/) and instead of directing the request to a template, control is given to the about_views.contact method -- where about_views refers to the imported reference described in the previous paragraph.

Next, start the Django development web server and open a browser on the address http://127.0.0.1:8000/about/. Notice how a request on the about url directory displays the underlying about/contact.html template defined in the contact method in views.py.

Finally, although you can now access an app's views.py methods, you also need to configure the app, both inside the app's apps.py file and the project's settings.py file. The first step is optional, but required to add a namespace to an app to simplify things later in its life-cycle, while the second step is important so Django can find other app constructs you create later (e.g. app database model definitions, app static resources, app custom template tags).

By default, Django apps are created without a namespace. For example, in the app you just created with django-admin startapp about, the app's default name is about. This simplifies things when an app is created, but this lack of namespaces can complicate things later on (e.g. if you want to use another app named about or share this app). Therefore, it's always best to do a little work to configure an app with a namespace by leveraging a Django's project name, allowing us to reference an app with a namespace (e.g. coffeehouse.about instead of simply about).

Note Adding namespaces to apps and the following apps configuration change wouldn't be a problem if all apps were placed in the BASE_DIR -- where the manage.py file is . But because we're placing all apps in PROJECT_DIR -- where the urls.py and settings.py files are -- and leveraging a Django project's name as part of the app's namesapce, this requires manually updating an app's name in apps.py. The benefits of this manual update and using PROJECT_DIR for apps should be clear shortly.

Next, open the apps.py file in the about app and update it to reflect the highlighted contents in listing 1-25.

Listing 1-25. Update app apps.py to include namespace settings.py

from django.apps import AppConfig


class AboutConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'coffeehouse.about'

With the change in listing 1-25, the about app name reflects the directory structure of its location and the app is ready to be registered.

Next, open the Django project's settings.py file and look for the INSTALLED_APPS variable. You'll see a series of apps already defined on the INSTALLED_APPS. Notice how the installed apps belong to the django.contrib package, this means they're provided by the Django framework itself. Add the coffeehouse.about app to the list, as illustrated in line 8 of listing 1-26.

Listing 1-26. Add app to INSTALLED_APPS in Django settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'coffeehouse.about',
]

As highlighted in the last line of listing 1-26, the second step to configure apps in a Django project is to add the app package as a string to the INSTALLED_APPS variable. Though the coffeehouse.about app is still practically empty, adding the app to the INSTALLED_APPS variable is an important configuration step for future actions, such as database operations and static resources associated with the app, among other things.

Tip In addition to supporting an app's package name, the INSTALLED_APPS variable also supports using an app's configuration class, that is, the class inside an app's apps.py file, like the one shown in listing 1-25. This means the coffeehouse.about line added in listing 1-26 can be substituted for coffeehouse.about.apps.AboutConfig [16].
Caution If you receive the error django.core.exceptions.ImproperlyConfigured: Cannot import 'about'. Check that 'coffeehouse.about.apps.AboutConfig.name' is correct., this means the app's name wasn't properly updated in its apps.py file, as described in listing 1-25.

Looking back at the changes in listing 1-25, while optional, doing them allows the INSTALLED_APPS configuration in listing 1-26 to use a namespaced package -- coffeehouse.about -- vs. a flat package -- about. As Django projects start to grow, having apps with namespaces always eases work, whether because of more complex app configuration requirements or the need to share apps across other apps or projects.

  1. https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-INSTALLED_APPS