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-11 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/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 3 -- the one below
django.contrib import
admin -- and line 9 -- the one below
path('admin/',
admin.site.urls), -- as illustrated in listing 1-16.
Listing 1-16. 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-16,
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-16 tells Django that requests for the
home page should return the content in the template
homepage.html. The
path method is a 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. 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-17
into it. Save the file on your system, in a sub-directory called
templates in your Django project's
PROJECT_DIR.
Listing 1-17. 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-18
illustrates a sample
DIRS definition in
settings.py using the
PROJECT_DIR
reference variable set dynamically at the top of
settings.py.
Listing 1-18. 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-18 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. 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 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.messagesand
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:
__init__.py.- Python file to allow app packages to be imported from other directories. Note
__init__.pyis 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).
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.
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-19.
Listing 1-19. 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-19 -- 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
method in listing 1-19 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-20 illustrates how to add a url to the
urls.py file
linked to the
contact method in listing 1-19.
Listing 1-20. 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_viewsurlpatterns = [ 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-20 is an import statement to gain access to the
contact method in listing 1-19. 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
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-20 uses a path to match requests on the
about url directory (e.g.) 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. 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. This last step is important so Django can find other app constructs
you create later (e.g. database model definitions, static
resources, 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 naming convention 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 -- this requires manually updating the app's name in
apps.py -- the benefits for 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 contents in listing 1-21.
Listing 1-21. 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-21, 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-22.
Listing 1-22. 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 illustrated in line 8 of
listing 1-22, the second step to configure apps in a 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_APPSvariable also supports using an app's configuration class, that is, the class inside an app's
apps.pyfile, like the one shown in listing 1-21. This means the
coffeehouse.aboutline added in listing 1-22 can be substituted for
coffeehouse.about.apps.AboutConfig[9].
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
namewasn't properly updated in its
apps.pyfile, as described in listing 1-21.
Looking back at the changes in listing 1-21, while optional, doing them allowed the
INSTALLED_APPS configuration in listing 1-22 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. ↑