Now that you’ve entered some data with the admin application, the next step is to show this data in the front-end. Therefore three things need to be done:
- define URLs
- write views
- create templates
First, define the URLs which route the request to the views. For now two URLs will be sufficient. Open the file urls.py and append the following code to the urlpatterns:
url(r'^recipes/(?P<slug>[-\w]+)/$', 'recipes.views.detail'),
url(r'^$', 'recipes.views.index'),
The line from our first template isn’t needed anymore and can be removed:
url(r'^home/', TemplateView.as_view(template_name='base.html'))
The file urls.py now should look like this:
from django.conf.urls import patterns, include, url
from django.views.generic.base import TemplateView
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'cookbook.views.home', name='home'),
# url(r'^cookbook/', include('cookbook.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
url(r'^recipes/(?P<slug>[-\w]+)/$', 'recipes.views.detail'),
url(r'^$', 'recipes.views.index'),
)
Note
The url function’s first argument is a raw string containing a regular expression.
If you are not familiar with regular expressions you can read more in the Regular-Expression-HOWTO, on Regular-Expressions.info or in an article from Doug Hellmann about the re-module erfahren. At RegexPlanet you can test regular expressions directly in the browser.
Now start the development server:
$ python manage.py runserver
Validating models...
0 errors found
Django version 1.4, using settings 'cookbook.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
When you open the URL http://127.0.0.1:8000/ you will see an ViewDoesNotExist exception. That’s correct because by now you didn’t create any view.
Before we write the first views we’ll look at how Django renders templates.
Django templates are normal Python objects whose constructor expects a string. The placeholders are replaced with the values from a context object.
The first example shows how a dictionary can be used as data structure
$ python manage.py shell
Note
The command shell loads the configurations from settings.py of the current project. The normal Python interpreter wouldn’t do that.
>>> from django.template import Context, Template
>>> t = Template('My name is {{ person.first_name }}.')
>>> d = {'person': {'first_name': 'Foo'}}
>>> t.render(Context(d))
u'My name is Foo.'
In the second example we use a Python object as data structure:
>>> class Person: pass
...
>>> p = Person()
>>> p.first_name = 'Bar'
>>> c = Context({'person': p})
>>> t.render(c)
u'My name is Bar.'
Lists can be used as well:
>>> t = Template('First article: {{ articles.0 }}')
>>> c = Context({'articles': ['bread', 'eggs', 'milk']})
>>> t.render(c)
u'First article: bread'
Now we have to create the views. They will use the ORM to get the data from the database.
Open the file views.py in the recipes application which you have created with the command startapp recipes.
All views return a HttpResponse object, so we write a really simple view which does exactly this:
from django.http import HttpResponse
def index(request):
return HttpResponse('My first view.')
Now open http://127.0.0.1:8000/ in the browser. You will see the string you passed to the HttpResponse object.
Instead of the string, we’ll now load a Template and render it with a Context containing a Recipe object. The rendered string from the Template is passed to the HttpResponse:
from django.http import HttpResponse
from django.template import Context, loader
from .models import Recipe
def index(request):
recipes = Recipe.objects.all()
t = loader.get_template('recipes/index.html')
c = Context({'object_list': recipes})
return HttpResponse(t.render(c))
When opening http://127.0.0.1:8000/ you will see a TemplateDoesNotExist exception. It is raised because the template doesn’t exist yet.
Open the file base.html from the templates folder and change it this way:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Cookbook{% endblock %}</title>
</head>
<body>
<h1>Cookbook</h1>
{% block content %}{% endblock %}
</body>
</html>
It now contains two blocks. These will be filled by the templates which derive from this one.
Now you have to create two folders for the templates inside the application folder: recipes/templates/recipes/. Create the file index.html inside this new folders.
{% extends "base.html" %}
{% block title %}{{ block.super }} - All Recipes{% endblock %}
{% block content %}
<h2>All Recipes</h2>
<ul>
{% for recipe in object_list %}
<li><a href="/rezept/{{ recipe.slug }}/">{{ recipe.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
The folder structure should now look like this:
cookbook
|-- cookbook
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
|-- cookbook.db
|-- manage.py
|-- recipes
| |-- __init__.py
| |-- admin.py
| |-- fixtures
| | `-- initial_data.json
| |-- models.py
| |-- templates
| | `-- recipes
| | `-- index.html
| |-- tests.py
| `-- views.py
`-- templates
`-- base.html
After restarting the development server and opening http://127.0.0.1:8000/ you will see a list of all recipes.
Now we need a second view to get the recipe details and begin with adding another import to the top views.py:
from django.http import Http404
At the bottom create a new method for the second view:
def detail(request, slug):
try:
recipe = Recipe.objects.get(slug=slug)
except Recipe.DoesNotExist:
raise Http404
t = loader.get_template('recipes/detail.html')
c = Context({'object': recipe})
return HttpResponse(t.render(c))
The whole file now looks like this:
from django.http import Http404, HttpResponse
from django.template import Context, loader
from .models import Recipe
def index(request):
recipes = Recipe.objects.all()
t = loader.get_template('recipes/index.html')
c = Context({'object_list': recipes})
return HttpResponse(t.render(c))
def detail(request, slug):
try:
recipe = Recipe.objects.get(slug=slug)
except Recipe.DoesNotExist:
raise Http404
t = loader.get_template('recipes/detail.html')
c = Context({'object': recipe})
return HttpResponse(t.render(c))
Now we need the second template recipes/detail.html inside the same folder index.html is located.
{% extends "base.html" %}
{% block title %}{{ block.super }} - {{ object.title }}{% endblock %}
{% block content %}
<h2>{{ object.title }}</h2>
<p>{{ object.number_of_portions }} portions.</p>
<h3>Indigrents</h3>
{{ object.ingredients|linebreaks }}
<h3>Preparation</h3>
{{ object.preparation|linebreaks }}
<p>Preparation time: {{ object.time_for_preparation }} minutes</p>
{% endblock %}
Now you can see the recipe details when clicking the links on the start page.
The Django template engine ignores variables which aren’t defined as key in the context. This is reasonable in a production environment, where you want the site to work even when a variable is missing.
You can add a line to settings.py to change this behavior.
TEMPLATE_STRING_IF_INVALID = ‘TEMPLATE NAME ERROR’
Remember to remove this setting when the site goes live.
Django’s template engine escapes all HTML and JavaScript in the context because of security reasons. Imagine a user writes the following text into the field preparation of his recipe:
<script>alert("The world's best recipe!")</script>
The HTML would look like this:
<p><script>alert("The world's best recipe!")</script></p>
The JavaScript won’t be executed.
It’s also possible to remove all HTML tags. To do this, you have to use the striptags filter:
{% block content %}
<h2>{{ object.title }}</h2>
<p>{{ object.number_of_portions }} portions.</p>
<h3>Indigrents</h3>
{{ object.ingredients|linebreaks }}
<h3>Preparation</h3>
{{ object.preparation|striptags|linebreaks }}
<p>Preparation time: {{ object.time_for_preparation }} minutes</p>
{% endblock %}
Now the HTML looks like this
<p>alert("The world's best recipe!")</p>
If you are sure that HTML and JavaScript should be rendered and execured, you can use the safe filter to allow it explicitly.
{% block content %}
<h2>{{ object.title }}</h2>
<p>{{ object.number_of_portions }} portions.</p>
<h3>Indigrents</h3>
{{ object.ingredients|linebreaks }}
<h3>Preparation</h3>
{{ object.preparation|safe|linebreaks }}
<p>Preparation time: {{ object.time_for_preparation }} minutes</p>
{% endblock %}
Now the JavaScript will be executed by the browser.
<p><script>alert("The world's best recipe!")</script></p>
Note
This can lead to XSS attacks and should only be used when you are absolutely sure what you are doing.