This course will become read-only in the near future. Tell us at community.p2pu.org if that is a problem.

List views, detail views, and a look at django templates


This week we will be going a bit deeper into views and the django template system. Because django is so robust, it's easy to get lost in all of the things it can do. We'll go over some "situational" django development and stay focused on common problems that come up on modern web apps. I'll leave it to you to go deeper. 

 
Let's set 2 new views. One for browsing all of the recipes in our database and another for viewing all the details for a single recipe. I want to point out that django has some built in ways of doing this called "generic views." However, I want to do this manually so you get a better understanding of how the tempting system works. I also find some of the generic views to be too limiting for non trivial apps, but you should definitely look into them. 
 
# Add this to recipe.views
 
def browse_all(request):
    all_recipes = Recipe.objects.all()
    variables = {'all_recipes':all_recipes}
 
    return direct_to_template(request, 'recipes/browse_all.html', variables)
 
def view(request, recipe_id=None):
    # Check to see if there is a recipe otherwise return a 404
    try:
        recipe = Recipe.objects.get(pk=recipe_id)
    except Recipe.DoesNotExist:
        raise Http404
    variables = {'recipe':recipe}
    return direct_to_template(request, 'recipes/detail.html', variables)
 
Add these to your url conf:
    url(r'^recipe/browse/all$', 'recipe.views.browse_all', name='browse_all'),
     # This pattern will create an argument for our view function called recipe_id
    url(r'^recipe/(?P<recipe_id>[0-9]+)/view$', 'recipe.views.view', name='recipe_detail'),
 
Now that we have those set up let's create our templates. Create a folder called recipes in the templates folder.
 
# base.html
{% comment %}
Typically, your base.html will have the header, footer and body set up and you would leave certain blocks that are filled in by other templates. Your other templates can inherit this one by using the 'extends' tag. 
{% endcomment %}
<html>
<body>
<h1>This is from base.html</h1>
{% block content %}
{% endblock %}
</body>
</html>
 
# browse_all.html
{% extends 'base.html' %}
{% block content %}
<h2>This is from browse_all.html</h2>
{% endblock %}
 
Fire that up and take a look at /browse/all in your browser. Try changing the text around and you'll see how browse_all is inheriting the base template. This will prevent you from copying and pasting code and having an unmanageable project. This is also useful for more than just headers and footers, but also for creating a base for all the browse types so you don't have to keep repeating yourself. 
 
Now let's play with the data on the template layer. 
 
# browse_all.html
{% extends 'base.html' %}
{% block content %}
<h2>This is from browse_all.html</h2>
{% for recipe in all_recipes %}
{{ recipe.title }}, {{ recipe.food_type.all }}
{% endfor %}
{% endblock %}
 
Look familiar? It's almost the same as if you were to do it in the interpreter. all_recipes is just a list of objects. Each of those objects have attributes defined in your model. We're simply using a for loop to iterate through the list. One thing to watch out for is that you need to close all your tags. So if you use a for statement you need to close it with 'end for' or else it will throw an error. 
 
Wait, the food_type looks bad on the template…
Related managers are another list of objects. So we could loop through them with a 'for' statement on the template or we could use one of the most useful tricks I know. @property will add a property to your model that can be accessed on the template. While this is overkill for the situation we see right here, I'm using it as an example of how it works. It can save your life though. 
 
#recipe.models
from django.db import models
from django.contrib.auth.models import User
 
# python module for dates and time
import datetime
 
class FoodType(models.Model):
    # always set a character length
    # blank = False and null = False make the field required
    name = models.CharField(max_length = 100, blank = False, null = False)
   
    def __unicode__(self):
        return '%s' % self.name
 
class Recipe(models.Model):
    title = models.CharField(max_length = 100, blank = False, null = False)
    # this is a many to many since a dish can have more than one food type
    food_type = models.ManyToManyField(FoodType)
    added_by = models.ForeignKey(User)
    # this tells the model to automatically timestamp it when it is added
    date_added = models.DateField(auto_now_add=True, default=datetime.datetime.now)
    instructions = models.TextField(max_length = 2000, blank = False, null = False)
    ingredients = models.TextField(max_length = 1000)
   
    def __unicode__(self):
        return '%s' % self.title
 
    @property
    def food_type_list(self):
        food_list = [i.name for i in self.food_type.all()]
        return ', '.join(food_list)
 
Now all we have to do is:
 
# browse_all.html
{% extends 'base.html' %}
{% block content %}
<h2>This is from browse_all.html</h2>
{% for recipe in all_recipes %}
{{ recipe.title }}, {{ recipe.food_type_list }}
{% endfor %}
{% endblock %}
 
Now let's set up the detail pages:
 
#detail.html
{% extends 'base.html' %}
 
{% block content %}
<h1>{{ recipe.title }}</h1>
<p><b>Food Types:</b> {{ recipe.food_type_list }}</p>
<b>Ingredients:</b><br>
<p>{{ recipe.ingredients }}</p>
{% endblock %}
 
Now let's link up the browse_all template to the detail pages. Notice how we use the 'url' template tag. What this does is search your url patterns for a pattern that matches the name specified. It also allows you to pass any arguments required (in this case the pk of the recipe we want o view). Do not hardcode your urls! Use the url tag and thank me when you decide you want to change the url structure of your whole app and don't have to go change all your hardcoded urls in your entire app. 
 
#browse_all.html
{% extends 'base.html' %}
{% block content %}
<h2>This is from browse_all.html</h2>
{% for recipe in all_recipes %}
<a href="{% url recipe_detail recipe.pk %}">{{ recipe.title }}</a>, {{ recipe.food_type_list }}
{% endfor %}
{% endblock %}
 
You should now have a list of all the recipes on recipe/browse/all and can click on a link to view the detail page.
 
Hints:
You can also choose single items out of a list by doing {{ recipe_all.0 }} where 0 is the index number of the item you want. This is mainly useful if for some reason you need to access a list of tuples.
 
What about dictionaries?
Dictionaries can also be accessed in the template level. If all_recipes was a dictionary you could do something like {{ all_recipes.name }} where name is the name of the key you want to access. This is different than the usual python way of dict['key'], but it's really handy in the templates and should be your preferred data type for accessing things on the template layer. Your code will become really hard to read if you use a list of items and access them doing {{ list.0 }}. By putting that into a dict you can make something readable for other people like {{ dict.name }}. Much better!
 
Read up on the different template tags. For the most part though your data processing should be done in the view layer and not on the template itself. If you are doing complex stuff on your template than you can most likely address that all in your view and keep your template clean. 
 
Your Assignment
Create a list view and detail page for all the users in your app.  

Task Discussion