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 pathfrom 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 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
anddjango.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:
admin.py
.- File with admin definitions for the app -- such definitions are needed to access model class instances from the Django admin.apps.py
.- File with configuration parameters for the app.__init__.py
.- Python file to allow app packages to be imported from other directories. Note__init__.py
is not a Django specific file, it's a generic file used in almost all Python applications.migrations
.- Directory that contains migrations applied to the app's database definitions (i.e. model classes).models.py
.- File with database definitions (i.e. model classes) for the app.tests.py
.- File with test definitions for the app.views.py
.- File with view definitions (i.e. controller methods) for the app.
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 TemplateViewfrom 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 theBASE_DIR
-- where themanage.py
file is . But because we're placing all apps inPROJECT_DIR
-- where theurls.py
andsettings.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 inapps.py
. The benefits of this manual update and usingPROJECT_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, theINSTALLED_APPS
variable also supports using an app's configuration class, that is, the class inside an app'sapps.py
file, like the one shown in listing 1-25. This means thecoffeehouse.about
line added in listing 1-26 can be substituted forcoffeehouse.about.apps.AboutConfig
[16].
Caution If you receive the errordjango.core.exceptions.ImproperlyConfigured: Cannot import 'about'. Check that 'coffeehouse.about.apps.AboutConfig.name' is correct.
, this means the app'sname
wasn't properly updated in itsapps.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.