The Rest of the Django App – the View and Controller Tiers

As is the way of Software Projects, I’m starting to get a bit of pressure from the customer about delivery.
As is slightly less usual in such circumstances, the question I’m being asked is “when are you going to get out there and mow that lawn ?”
Fortunately, Django is “for perfectionists with deadlines” …or minions with gardening chores waiting (probably) so I’d better crack on.

Now, I could do with some assistance. Fortunately, these guys will be around to help :

Pay bananas, get minions.

In case you haven’t been following the story to date, this project is to create an Application to allow my better half to look at which movies we have on DVD or Blu-Ray.

So far my Django journey has consisted of :

Django follows the Model-View-Controller (MVC) pattern of application design. Having spent some time looking at the Database (Model) layer, we’re now going to turn our attention to the View (what the end-user sees) and the Controller ( the application logic that makes the application work).

Recap of the Current Application state

After developing the data model, which looks like this :

…the application codebase currently looks like this :

tree -L 2 --dirsfirst --noreport

We have a useable database for our application. Now we need to provide a means of presenting the application data to our users.

Before I go any further, I’m going to try and simplify matters somewhat when it comes to talking about file locations.

I’m going to set an environment variable called $DJANGO_HOME to hold the root directory of the Python virtual environment we’ll be using.
This is the directory that has manage.py in it.

To do this, I’ve written the following shell script, which also starts the virtual environment :

#!/bin/sh

export DJANGO_HOME=`pwd`
source dvdsenv/bin/activate
echo $DJANGO_HOME

Once we’ve granted execute permissions on the script…

chmod a+x set_virt.sh

…we can set our environment variable and start the virtual environment…

. ./set_virt.sh

For the remainder of this post, I’ll be referencing file locations using $DJANGO_HOME to denote the root directory of the python virtual environment.

Now, let’s take a first look at how to retrieve the application data from the database and present it to the users…

Our First Django Page

In $DJANGO_HOME/dvds we need to create some controller code. Confusingly, this is in a file called views.py :

from django.shortcuts import render
from django.views.generic import ListView

from .models import Title

class TitleList(ListView) :
    model = Title

There’s not really much to it. A simple ListView class based on the Title model object, which will list all of the records in the Title table.

Now, $DJANGO_HOME/dvds/urls.py (having stripped out the default comments for brevity):

from django.conf.urls import url
from django.contrib import admin

from dvds.views import TitleList
app_name = 'dvds'

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', TitleList.as_view(), name="main"),
]

So, we’ve imported the TitleList view we’ve just created in views.py and then added a pattern so that we can navigate to it.

If we go ahead and run this now, we probably won’t get the result we’re hoping for …

…so let’s make one…

First, we want to create a directory in which to hold the templates. We’ll also want to keep templates from different applications separate so…

cd $DJANGO_HOME/dvds
mkdir templates
cd templates
mkdir dvds

Now, in the newly created directory, we want to create a file called title_list.html, which looks something like this…

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Deb's DVD Library</title>
    </head>
    <body>
        <h1>DVD Library List</h1>
        <table border="1">
            <tr>
                <th>Title</th>
                <th>Released</th>
                <th>Certificate</th>
                <th>Format</th>
                <th>Director</th>
                <th>Synopsis</th>
                <th>series</th>
                <th>No. In Series</th>
                <th>Categories</th>
            </tr>
            {% for title in object_list %}
                <tr>
                    <td>{{title.title_name}}</td>
                    <td>{{title.year_released}}</td>
                    <td>{{title.bbfc_certificate}}</td>
                    <td>{{title.get_media_format_display}}</td>
                    <td>{{title.director}}</td>
                    <td>{{title.synopsis}}</td>
                    <td>{{title.series}}</td>
                    <td>{{title.number_in_series}}</td>
                    <td>
                        {% for cat in title.categories.all %}
                            {{cat}}&nbsp;
                        {% endfor %}
                    </td>
                </tr>
            {% endfor %}
        </table>
    </body>
</html>

This should look fairly familiar if you’ve used languages such as PHP. Effectively, the {% %} tags indicate programmatic structures ( in this case, for loops), and the {{}} encase actual values pulled from the database.

Examples of this in the above listing include :

  • Line 20 – loop through the Object List
  • Line 25 – display the plain db column values until here, where we use the built-in “get display” to retrieve the display value from the MEDIA_FORMAT_CHOICE list we’ve assigned to the media_format column.
  • Line 31 – loop through all the values in the nested category column.
  • Line 36 – close the for loop

The effect of this is quite exciting… from a programming perspective ….

Not only have we managed to display all of the Title data we’ve added to our application, we can also see that :

  • Django is smart enough to return the name of the Series rather than the series id
  • we can display all of the categories by means of a for loop through the categories column in the Titles table

At this point thought, the look-and-feel is rather…utilitarian. Not only that, it looks like I may be in for some fairly tedious copy-and-paste action unless I can find a way to re-use some of this code.
Maybe Django can help…

A Site Map

Before we go too much further, it’s probably worth pausing to consider which pages our application will ultimately comprise of.
We already have our main page, but we’ll probably want to search for titles in our database.
Given that all the DML in the application is done through the Django Admin site that we get out of the box, we’ll also need to link to that.

Ultimately then, our application pages will be :

  • Main Page – listing of titles in the library
  • Search Page – to enter search criteria
  • Results Page – to display search results

To make the application look a bit nicer, each page will need to use the same style sheet.

cd $DJANGO_HOME/dvds
mkdir static
mkdir static/css
mkdir static/images

The file – $DJANGO_HOME/dvds/static/css/style.css looks like this…

*
{
    font-family: sans-serif;
    font-size: large;
    background: #e4ffc9;
}

table 
{
    border-collapse: collapse;
}
table, th, td
{
    border: 1px solid black;
    text-align: left;
    vertical-align: top;
}

.explanation
{
    display: table-row;
}


.explanation .landscape
{
    vertical-align: middle;
    height: 150px;
    width: 300px;
}


.explanation .portrait
{
    vertical-align: middle;
    height: 250px;
    width: 200px;    
}

.explanation textarea
{
    vertical-align: top;
    height: 150px;
    width: 400px;
    border: none;
    background: none;
    font-size: large;
}

.row
{
    display: table-row;
}

#search_form label
{
    display : table-cell;
    text-align : right;
}

#search_form input, select
{
    display : table-cell;
    width : 300px;
}

#search_form input[type=submit]
{
    font-size : large;
    font-weight : bold;
    border-radius:25px;
    background-color : #2FC7C9;
}

Once we’ve added the style sheet, along with the images we’re going to use in the application, the file layout in the static directory should look like this :

From a layout perspective, I’d like each page to have :

  • a navigation bar
  • in-line explainatory text about what to do on the page
  • the page specific content

If only I could apply a template for this…

Templates

First off we can create a template, called base.html which will serve to apply the layout to all of our application pages.
Essentially, the pages can inherit this template and then override the various sections (blocks).
We can also use this template to include all of the tedious HTML header stuff, as well as a link to our application style sheet.

The file is saved in the same location as our existing template ($DJANGO_HOME/dvds/templates/dvds) and looks like this :

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf8">
        <title>{% block title %}Base Title{% endblock %}</title>
        <link rel="stylesheet" type="text/css" href="/static/css/style.css" />
    </head>
    <body>
        {% block breadcrumb %}{% endblock %}
        {% block explanation %}{% endblock %}
        {% block page_content %}{% endblock %}
    </body>
</html> 

Before we incorporate this into our main page, we also need to consider that the code to list titles can be used in the application results page as well as in the main page. Fortunately, we can also move this out into a template.
The file is called display_titles.html and is saved in the same templates directory as all the other html files…

<table>
    <tr>
        <th>Title</th> <th>Released</th> <th>Certificate</th>
        <th>Format</th> <th>Director</th>
        <th>Synopsis</th> <th>Series</th> <th>No. In Series</th>
        <th>Categories</th> 
    </tr>
    {% for title in titles %}
        <tr>
            <td>{{title.title_name}}</td> <td>{{title.year_released}}</td> <td>{{title.bbfc_certificate}}</td>
            <td>{{title.get_media_format_display}}</td>
            <td>{{title.director}}</td> <td>{{title.synopsis}}</td> <td>{{title.series}}</td>
            <td>{{title.number_in_series}}</td>
            <td>
                {% for cat in title.categories.all %}
                    {{cat}}&nbsp;
                {% endfor %}
            </td>
        </tr>
    {% endfor %}
</table>

The final template component of our application is the navigation menu. The template is called breadcrumb.html and includes all of the breadcrumb menu entries in the application :

<img src="/static/images/deb_movie_logo.jpg">&nbsp;
{% if home %}<a href="/">Home</a>&nbsp;{% endif %}
{% if admin %}|&nbsp;<a href="admin" target="_blank">Add or Edit Titles</a>{% endif %}
{% if back %}|&nbsp;<a href="javascript:history.back(-1)">Back</a>&nbsp;{% endif %}
{% if search_refine %}|&nbsp;<a href="javascript:history.back(-1)">Refine Search</a>&nbsp;{% endif %}
<!-- additional logic required to see if search is first option -->
{% if search %}|&nbsp;<a href="/search">Search</a>{% endif %}
{% if search_again %}|&nbsp;<a href="/search">Search Again</a>{% endif %}

What navigation items are displayed are dependent on how the breadcrumb template is called. Essentially we treat it like a function.

So, putting all of this together in our main page, we get something like :

{% extends "dvds/base.html" %}
{% block title %}Deb's Movie Library{% endblock %}
{% block breadcrumb %}{% include "dvds/breadcrumb.html" with search=True admin=True %}{% endblock %}
{% block explanation %}
    <div class="explanation">
        <h1>So, you'd like to watch a Film...</h1>
        <img src="static/images/avengers.jpg">&nbsp;
        <textarea>We are here to help. 
There's a list of films in the library right here. 
Alternatively, click on "Search" at the top of the page if you're looking for something specific.
        </textarea>
    </div>
{% endblock %}
{% block page_content %}{% include "dvds/display_titles.html" with titles=object_list %}{% endblock %}

To begin with, the extends tag inherits a template as the page parent. The blocks in the remainder of the page are used to override the blocks in the parent template (base.html).

The breadcrumb and page_content blocks use the include tag to import templates into the page.
In the case of the breadcrumb template, we specify the value of the search and admin parameters.
This results in these links being displayed.

When we run the application, the page will look like this :

Before we get there though, it’s probably an idea to put together the first draft of our other application pages…

Search Pages

To start with, we’ll have a fairly simple search screen, which simply involves searching on some user-entered text.

We’ll come onto the pages themselves shortly. First though…

for the search form itself, we’ll need a simple function in $DJANGO_HOME/dvds/views.py :

def search_titles(request):
    return render( request, 'dvds/search.html')

We need to add the appropriate code to execute the search. In views.py, we also add a function called search_results :

def search_results(request) :
    if 'search_string' in request.GET and request.GET['search_string'] :
        titles = Title.objects.filter(title_name__icontains=request.GET['search_string'])
    else :
        titles = Title.objects.all()
    return render( request, 'dvds/results.html', {'titles' : titles, 'search_string' : request.GET['search_string']})

So, if the user submits a search with the search_string populated, then find any titles which contain that string.
The icontains method performs a case insensitive “LIKE” comparison.
If no search string is entered, we’ll display all records in the results.
The search results are then displayed in the dvds/results.html page.
The render function is also passing a couple of arguments to the results page :

  • the titles object containing the search results
  • the original search string entered by the user

In urls.py, we need to add the appropriate entries for these pages :

...
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', TitleList.as_view(), name="main"),
    url(r'^search/$', views.search_titles),
    url(r'^results/$', views.search_results),
]

Once again, the html code resides in the templates directory. It looks rather familiar.
The search form – search.html :

{% extends "dvds/base.html" %}
{% block title %}Find a Movie{% endblock %}

{% block breadcrumb %}{% include "dvds/breadcrumb.html" with home=True %}{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search For A Title</h1>
        <img src="/static/images/spiderman_minion.jpg" >&nbsp;
        <textarea>Enter some text that the title your searching for contains.</textarea>
    </div>
{% endblock %}

{% block page_content %}
    <form id="search_form" action="/results" method="GET">
        <!-- Name of the movie (or part therof) -->
            <label>Title Contains &nbsp;:&nbsp;</label>
            <input name="search_string" placeholder="Words in the Title" />
            <label>&nbsp;</label><input type="submit" value="Find My Movie"/>
    </form>
{% endblock %}

The results page, results.html :

{% extends "dvds/base.html" %}
{% block title %}Search Results{% endblock %}

{% block breadcrumb %}
    {% include "dvds/breadcrumb.html" with home=True back=True search_refine=True search_again=True %}
{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search Results</h1>
        <img src="/static/images/minion_family.jpg" >&nbsp;
       <h2>You searched for titles ...</h2>
       <ul><li>containing the phrase&nbsp;<strong><em>{{search_string}}</em></strong></li></ul>
    </div>
{% endblock %}
{% block page_content %}
    {% if titles %}
        <h2>We found {{titles | length}} title{{ titles|pluralize}}...</h2>
        {% include "dvds/display_titles.html" %}
    {% else %}
        <h2>No Titles matched your search criteria</h2>
    {% endif %}
{% endblock %}

Notice that the call to breadcrumb.html provides different parameter values in the two files.
Now let’s give it all a try…

Click on the Search link from the Home Page and we should get :

Click the “Find My Movie” button and we get :

Refining the Search Functionality

As well as words in the title, it would be useful to be able to search on other criteria such as the Series to which a movie belongs, or maybe the Media Format.

In both cases our application has a limited number of values to select from. The Series table contains a list of all of the series in the application. The MEDIA_FORMAT_CHOICES list contains all of the valid media formats.

By including these objects in $DJANGO_HOME/dvds/views.py

from .models import Title, Series, MEDIA_FORMAT_CHOICES

…we can reference them in the search function we need to create (also in views.py)…

def search_titles(request):
    series_list = Series.objects.all()

    return render( request, 'dvds/search.html', {'series_list' : series_list, 'format_list':MEDIA_FORMAT_CHOICES})

The series_list and format_list objects passed in the call to render can now be used in the html template for the search screen – $DJANGO_HOME/dvds/templates/dvds/search.html :

{% extends "dvds/base.html" %}
{% block title %}Find a Movie{% endblock %}

{% block breadcrumb %}{% include "dvds/breadcrumb.html" with home=True %}{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search For A Title</h1>
        <img class="portrait" src="/static/images/spiderman_minion.jpg" >&nbsp;
        <textarea>Enter some text that the title your searching for contains, and/or a Series and/or a format.
Note - search criteria is addative
        </textarea>
    </div>
    <br />
{% endblock %}

{% block page_content %}
    <form id="search_form" action="/results" method="GET">
        <!-- Name of the movie (or part therof) -->
        <div class="row">    
            <label>Title Contains &nbsp;:&nbsp;</label>
            <input name="search_string" placeholder="Words in the Title" />
        </div>
        <!-- Series drop-down -->
        <div class="row">
            <label>Series&nbsp;:&nbsp;</label>
            <select class="drop_down_list" name="series_id">
                <option value=>None</option>
                {% for series in series_list %}
                    <option value={{series.id}}>{{series.series_name}}</option>
                {% endfor %}
            </select>
        </div>
        <!-- Media Format -->
        <div class="row">
            <label class="search_label">Media Format : </label>
            <select class="drop_down_list" name="media_format">
                <option value=>None</option>
                {% for id, value in format_list %}
                    <option value={{id}}>{{value}}</option>
                {% endfor %}
            </select>
        </div>
        <div class="row">
            <label>&nbsp;</label><input type="submit" value="Find My Movie" />
        </div>
    </form>
{% endblock %}

After all of that, our search screen now looks like this :

As for the search results, well, here’s where things get a bit tricky.
The search function in $DJANGO_HOME/dvds/views.py has changed a bit :

def search_results(request) :
    if 'search_string' in request.GET and request.GET['search_string'] :
        titles = Title.objects.filter(title_name__icontains=request.GET['search_string'])
    else :
        titles = Title.objects.all()
        search_string = None
    if 'series_id' in request.GET and request.GET['series_id'] :
        series_name = Series.objects.filter(pk = request.GET['series_id']).values_list('series_name', flat=True)[0]
        titles = titles.filter(series_id =  request.GET['series_id'])
    else :
        series_name = None
        titles = titles
    if 'media_format' in request.GET and request.GET['media_format'] :
        titles = titles.filter(media_format = request.GET['media_format'])
        # Get the display value to pass to the results page
        media_format = mf_display(request.GET['media_format'])
    else :
        titles = titles
    return render( request, 'dvds/results.html', {'titles' : titles, 'search_string' : request.GET['search_string'], 'series_name' : series_name, 'media_format': media_format})

The first point to note is that, despite the multiple assignment to the titles object, the only database call that will actually be made is the one when render is called. Prior to that, the search conditions will be added. The effect is pretty much the same as building a SQL statement dynamically, adding predicates based on various conditions, before finally executing the finished statement.

We still want to display the search criteria values that were entered.
Whilst looking up display values in the context of a record retrieved from the database is simple enough, the same is not true outside of this context.

For the Series Name, I’ve taken the rather profligate approach of performing an additional database lookup (line 7 in the listing above). In a higher-volume application, you might well look for something a bit less resource intensive.

As for the media format, views.py now also includes this function :

def mf_display( media_format) :
    for (val, mf_name) in MEDIA_FORMAT_CHOICES :
        if val == media_format :
            return mf_name
    return None

…which is invoked on line 16.

This enables us to pass the appropriate values to the results page, $DJANGO_HOME/dvds/templates/dvds/results.html, which now looks like this :

{% extends "dvds/base.html" %}
{% block title %}Search Results{% endblock %}

{% block breadcrumb %}
    {% include "dvds/breadcrumb.html" with home=True search_refine=True search_again=True %}
{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search Results</h1>
        <img class="landscape" src="/static/images/minion_family.jpg" >&nbsp;
       <h2>You searched for titles ...</h2>
       <ul>
            {% if search_string %}
                <li>containing the phrase&nbsp;<strong><em>{{search_string}}</em></strong></li>
            {% endif %}
            {% if series_name %}
                <li>in the series&nbsp;<strong><em>{{series_name}}</em></strong></li>
            {% endif %}
            {% if media_format %}
                <li>on&nbsp;<strong><em>{{media_format}}</em></strong></li>
            {% endif %}
       </ul>
    </div>
{% endblock %}
{% block page_content %}
    {% if titles %}
        <h2>We found {{titles | length}} title{{ titles|pluralize}}...</h2>
        {% include "dvds/display_titles.html" %}
    {% else %}
        <h2>No Titles matched your search criteria</h2>
    {% endif %}
{% endblock %}

The End Result

It’s probably helpful at this point to provide complete listings of all of the code we’ve changed.
This is partly to show that we’ve accomplished a fair bit with Django with surprisingly little code. The more practical reason is to help in the event that I’ve been a bit unclear as to which file a certain code snippet might be in.

Starting in $DJANGO_HOME/dvds we already had the Model layer of our application when we started :

models.py

from django.db import models
from django.core.validators import MinValueValidator

# Allowable values Lists
MEDIA_FORMAT_CHOICES = (
    ('BR', 'Blu Ray'),
    ('DVD', 'DVD'),
)

# British Board of Film Classification Certificates
# as per the official BBFC site - http://www.bbfc.co.uk
BBFC_CHOICES = (
    ('U', 'U'),
    ('PG', 'PG'),
    ('12A', '12A'),
    ('15', '15'),
    ('18', '18'),
    ('R18', 'R18'),
)

class Category(models.Model) :
    # Object properties defined here map directly to database columns.
    # Note that Django creates a synthetic key by default so no need to
    # specify one here
    category_name = models.CharField(max_length = 50, unique = True)
        
    def __str__(self):
        return self.category_name

    class Meta :
        # set the default behaviour to be returning categories in alphabetical order by category_name
        ordering = ["category_name"]
        # Define the correct plural of "Category". Among other places, this is referenced in the Admin application
        verbose_name_plural = "categories"

class Series(models.Model) :
    series_name = models.CharField( max_length = 50, unique = True)

    def __str__(self) :
        return self.series_name

    class Meta :
        ordering = ["series_name"]
        verbose_name_plural = "series"

class Title( models.Model) :

    # For optional fields, blank = True means you can leave the field blank when entering the record details
    # null = True means that the column is nullable in the database 
    title_name = models.CharField( max_length = 250)
    year_released = models.IntegerField(validators=[MinValueValidator(1878)]) # Movies invented around 1878.
    bbfc_certificate = models.CharField("BBFC Certificate", max_length = 3, choices = BBFC_CHOICES)
    media_format = models.CharField("Format", max_length = 3, choices = MEDIA_FORMAT_CHOICES)
    director = models.CharField(max_length = 100, null=True, blank=True)
    synopsis = models.CharField( max_length = 4000, null = True, blank = True)
    series = models.ForeignKey( Series, on_delete = models.CASCADE, null = True, blank = True)
    number_in_series = models.IntegerField(null = True, blank = True)
    categories = models.ManyToManyField(Category, blank = True)

    class Meta :
        ordering = ["series", "number_in_series", "title_name"]
        # Natural Key for a Title record is title_name, year_released and media_format - we have some films on DVD AND Blu-Ray.
        unique_together = ('title_name', 'year_released', 'media_format',)

    def __str__(self) :
        return self.title_name

Additionally, we now have the following files which pretty much comprise the Controller layer of our MVC application :

urls.py

from django.conf.urls import url
from django.contrib import admin

from dvds.views import TitleList
from dvds import views
app_name = 'dvds'

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', TitleList.as_view(), name="main"),
    url(r'^search/$', views.search_titles),
    url(r'^results/$', views.search_results),
]

views.py

from django.shortcuts import render
from django.views.generic import ListView

from .models import Title, Series, MEDIA_FORMAT_CHOICES

def mf_display( media_format) :
    # Get the display value for a MEDIA_FORMAT_CHOICES entry.
    # NOTE this is for use in the search results screen where we confirm
    # the search criteria entered so it's NOT in the context of a Title record at that point.
    for (val, mf_name) in MEDIA_FORMAT_CHOICES :
        if val == media_format :
            return mf_name
    return None

class TitleList(ListView):
    model = Title

def search_titles(request):
    series_list = Series.objects.all()

    return render( request, 'dvds/search.html', {'series_list' : series_list, 'format_list':MEDIA_FORMAT_CHOICES})

def search_results(request) :
    if 'search_string' in request.GET and request.GET['search_string'] :
        titles = Title.objects.filter(title_name__icontains=request.GET['search_string'])
    else :
        titles = Title.objects.all()
        search_string = None
    if 'series_id' in request.GET and request.GET['series_id'] :
        series_name = Series.objects.filter(pk = request.GET['series_id']).values_list('series_name', flat=True)[0]
        titles = titles.filter(series_id =  request.GET['series_id'])
    else :
        series_name = None
        titles = titles
    if 'media_format' in request.GET and request.GET['media_format'] :
        titles = titles.filter(media_format = request.GET['media_format'])
        # Get the display value to pass to the results page
        media_format = mf_display(request.GET['media_format'])
    else :
        titles = titles
        media_format = None
    return render( request, 'dvds/results.html', {'titles' : titles, 'search_string' : request.GET['search_string'], 'series_name' : series_name, 'media_format': media_format})

The View MVC Layer is found in $DJANGO_HOME/dvds/templates/dvds. First, the templates that are extended or included :

base.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf8">
        <title>{% block title %}Base Title{% endblock %}</title>
        <link rel="stylesheet" type="text/css" href="/static/css/style.css" />
    </head>
    <body>
        {% block breadcrumb %}{% endblock %}
        {% block explanation %}{% endblock %}
        {% block page_content %}{% endblock %}
    </body>
</html> 

breadcrumb.html

<img src="/static/images/deb_movie_logo.jpg">&nbsp;
{% if home %}<a href="/">Home</a>&nbsp;{% endif %}
{% if admin %}|&nbsp;<a href="admin" target="_blank">Add or Edit Titles</a>{% endif %}
{% if back %}|&nbsp;<a href="javascript:history.back(-1)">Back</a>&nbsp;{% endif %}
{% if search_refine %}|&nbsp;<a href="javascript:history.back(-1)">Refine Search</a>&nbsp;{% endif %}
<!-- additional logic required to see if search is first option -->
{% if search %}|&nbsp;<a href="/search">Search</a>{% endif %}
{% if search_again %}|&nbsp;<a href="/search">Search Again</a>{% endif %}

display_titles.html

<table>
    <tr>
        <th>Title</th> <th>Released</th> <th>Certificate</th>
        <th>Format</th> <th>Director</th>
        <th>Synopsis</th> <th>Series</th> <th>No. In Series</th>
        <th>Categories</th> 
    </tr>
    {% for title in titles %}
        <tr>
            <td>{{title.title_name}}</td> <td>{{title.year_released}}</td> <td>{{title.bbfc_certificate}}</td>
            <td>{{title.get_media_format_display}}</td>
            <td>{{title.director}}</td> <td>{{title.synopsis}}</td> <td>{{title.series}}</td>
            <td>{{title.number_in_series}}</td>
            <td>
                {% for cat in title.categories.all %}
                    {{cat}}&nbsp;
                {% endfor %}
            </td>
        </tr>
    {% endfor %}
</table>

Finally, the application pages themselves, starting with :

title_list.html

{% extends "dvds/base.html" %}
{% block title %}Deb's Movie Library{% endblock %}
{% block breadcrumb %}{% include "dvds/breadcrumb.html" with search=True admin=True %}{% endblock %}
{% block explanation %}
    <div class="explanation">
        <h1>So, you'd like to watch a Film...</h1>
        <img class="landscape" src="static/images/avengers.jpg">&nbsp;
        <textarea>We are here to help. 
There's a list of films in the library right here. 
Alternatively, click on "Search" at the top of the page if you're looking for something specific.
        </textarea>
    </div>
{% endblock %}
{% block page_content %}{% include "dvds/display_titles.html" with titles=object_list %}{% endblock %}

…which looks like this :

search.html

{% extends "dvds/base.html" %}
{% block title %}Find a Movie{% endblock %}

{% block breadcrumb %}{% include "dvds/breadcrumb.html" with home=True %}{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search For A Title</h1>
        <img class="portrait" src="/static/images/spiderman_minion.jpg" >&nbsp;
        <textarea>Enter some text that the title your searching for contains, and/or a Series and/or a format.
Note - search criteria is addative
        </textarea>
    </div>
    <br />
{% endblock %}

{% block page_content %}
    <form id="search_form" action="/results" method="GET">
        <!-- Name of the movie (or part therof) -->
        <div class="row">    
            <label>Title Contains &nbsp;:&nbsp;</label>
            <input name="search_string" placeholder="Words in the Title" />
        </div>
        <!-- Series drop-down -->
        <div class="row">
            <label>Series&nbsp;:&nbsp;</label>
            <select class="drop_down_list" name="series_id">
                <option value=>None</option>
                {% for series in series_list %}
                    <option value={{series.id}}>{{series.series_name}}</option>
                {% endfor %}
            </select>
        </div>
        <!-- Media Format -->
        <div class="row">
            <label class="search_label">Media Format : </label>
            <select class="drop_down_list" name="media_format">
                <option value=>None</option>
                {% for id, value in format_list %}
                    <option value={{id}}>{{value}}</option>
                {% endfor %}
            </select>
        </div>
        <div class="row">
            <label>&nbsp;</label><input type="submit" value="Find My Movie" />
        </div>
    </form>
{% endblock %}

…which, remember, looks like this :

Finally, we have :

results.html


{% extends "dvds/base.html" %}
{% block title %}Search Results{% endblock %}

{% block breadcrumb %}
    {% include "dvds/breadcrumb.html" with home=True search_refine=True search_again=True %}
{% endblock %}

{% block explanation %}
    <div class="explanation">
        <h1>Search Results</h1>
        <img class="landscape" src="/static/images/minion_family.jpg" >&nbsp;
       <h2>You searched for titles ...</h2>
       <ul>
            {% if search_string %}
                <li>containing the phrase&nbsp;<strong><em>{{search_string}}</em></strong></li>
            {% endif %}
            {% if series_name %}
                <li>in the series&nbsp;<strong><em>{{series_name}}</em></strong></li>
            {% endif %}
            {% if media_format %}
                <li>on&nbsp;<strong><em>{{media_format}}</em></s
                trong></li>
            {% endif %}
       </ul>
    </div>
{% endblock %}
{% block page_content %}
    {% if titles %}
        <h2>We found {{titles | length}} title{{ titles|pluralize}}...</h2>
        {% include "dvds/display_titles.html" %}
    {% else %}
        <h2>No Titles matched your search criteria</h2>
    {% endif %}
{% endblock %}

which now looks like this :

Obviously, as more films get added to the library,further enhancements will be needed.
For now though, 1.0 is ready to see the light of day…

Bananas all round !

Advertisements

One thought on “The Rest of the Django App – the View and Controller Tiers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s