Django App Features

Description

This is a continuation of the constructions in the Django document. Where we left off was the creation of web response created by a call to HttpResponse.

In theory, we could create complete web applications in this manner; however, there is a much better approach. Django supports a separate HTML template language whereby Python data sent to these templates can be embedded within the templates, providing their dynamic content.

Run the development server and keep up the URL:
http://localhost:8000

Eclipse: Install an Editors for HTML and SQL

Open Eclipse MarketPlace and enter this into the Find box:
Find:
Several entries should come up, but choose
HTML Editor (WTP) Luna.
Click Install and then follow through, pretty much the same sequence as the PyDev installation. You don't have to restart Eclipse until after the SQL editor installation.

Enter this into the Find box of Eclipse MarketPlace:
Find:
Only one entry should come up:
DBViewer Plugin ....
Click Install and then follow through, similar to others, rebooting at the end.

Use templates

Although we initiated the "blog" app, it now needs to be listed in Django's INSTALLED_APPS list. Right now, the reason is that Django will search this list of directories for available template subdirectories. It will also be necessary to have this in place when the models are installed.

Edit appsite/settings.py and make the following changes:

appsite/appsite/settings.py
INSTALLED_APPS = (
   ...
   'blog',   # add this
)
By default, Django looks for the templates subdirectory of the application, which you need to create. In Eclipse, right-click on the blog folder and use
New ⇾ Folder
setting the name to be
templates
Otherwise, from the shell, do this (then refresh in Eclipse):
--- in ~/workspace/appsite ----
$ mkdir blog/templates
The template files will have the .html extension, suggesting that they generate HTML; however, the content is not HTML. In many cases there is very little HTML. These template files will be controlled by Django's rendering process which uses special control syntax within the files to be interpreted by Python.

Within blog/templates create these two template files. In Eclipse use
New ⇾ Other ⇾ Web ⇾ HTML File
giving the name base. Here is the content:

appsite/blog/templates/base.html
{% load staticfiles %} <!--  for the static function -->
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>Blog</title>
 
  {% block localstyle %}{% endblock %}
</head>
 
<body>
<div class="container">
  <div class="header">
    <h2>Blog {% block subtitle %}{% endblock %}</h2>
  </div>
  <div class="navigation">
    <a href="/blog">Home</a>
    <a href="/blog/listing">Categories</a>
  </div>
  <div class="content">
    {% block content %}{% endblock %}
  </div><!-- content -->
</div><!-- container -->
 
</body>
</html>
select

blog/templates/index.html
{% extends 'base.html' %}
 
{% block content %}
The blog index in a template.
{% endblock %}
select
To make this structure "kick in" modify the index function in the views module:

blog/views.py
def index(request):
    #return HttpResponse("The blog index.")
    return render(request, 'index.html')
Refresh the development server to see the change. Only the Home navigational link works at the moment.

You can probably figure out what is going on:

Allow static files

Static files are usually CSS stylesheets, JavaScript scripts, and image files which are used within the website, but not processed by Django. Static files can be accessed anywhere within the file system, but making them directly accessible by Apache serves other purposes as well and so we will use the subdirectory off of the (Ubuntu) Apache root.

Start by creating the target directory. It makes life easier if this directory is owned by you, the admin:
$ sudo mkdir /var/www/html/static
$ sudo chown LOGIN /var/www/html/static
$ mkdir -p /var/www/html/static/appsite/css     (no sudo necessary)
Edit appsite/settings.py and make modifications at the end of the file:

appsite/appsite/settings.py
# modify this
STATIC_URL = '/static/appsite/'   # make sure to have a trailing "/"
 
# add this entry 
STATICFILES_DIRS = (
    '/var/www/html/static/appsite',
)
The STATIC_URL is what is used in the stylesheet creation template when the function static is called. The STATICFILES_DIRS is a list of directories where Django searches for files referenced.

Create the layout stylesheet

Create the CSS file (as you)
$ nano /var/www/html/static/appsite/css/layout.css
with this content:

/var/www/html/static/appsite/css/layout.css
body { 
  margin: 0; 
  padding: 0 20px; 
  font-family: sans-serif;
  font-size: 14px;
}
.header h2 { 
  text-align: center;
  color: red;
}
.navigation {
  height: 25px;
  line-height: 25px;
  border: solid 1px #009;
  border-width: 1px 0;
  margin: 10px 0;
}
.navigation a {
  padding-right: 15px;
  font-weight: bold;
  color: #c00;
  text-decoration: none;
}
.navigation a:hover {
  color: #00c;
}
select

Put the stylesheet into effect

Just below the title tag in base.html, add the following link tag.

appsite/blog/templates/base.html
{% load staticfiles %} <!--  for the static call -->
...
  <title>Blog</title>
 
  <link rel="stylesheet" type="text/css" 
                         href="{% static "css/layout.css" %}" />
...
Refresh the development server to see the change.

The construction of the href attribute in the stylesheet link yeilds this HTML content:
  <link rel="stylesheet" type="text/css" 
                         href="/static/appsite/css/layout.css" />
Although you could use this latter version directly, Django prefers the former version in case you relocate the static directories.

Install the Database

Django defaults to using an SQLite DBMS, but it supports others including MySQL. Start by creating a database. There are 3 settings needed to specify a MySQL database and you can choose what you like; they only have to match what is put into the settings.py file below. For the sake of definiteness, I'll use these:
appsite  = database name 
siteuser = MySQL user name
sitepass = MySQL user password
Create the following SQL setup file in the appsite project directory. From Eclipse, you just create an empty file and give it full name. The installed dbviewer plugin will recognize the syntax.

appsite/create_appsite.sql
drop database if exists appsite;
create database appsite;
create user if not exists siteuser@localhost identified by 'sitepass';
grant all on appsite.* to siteuser@localhost;
select
Then execute it from the shell:
--- in ~/workspace/appsite ----
$ sudo -H mysql < create_appsite.sql
The "if exists" and "if not exists" qualifier usages mean that if you re-execute it, the database will be re-created from scratch.

Edit appsite/settings.py and replace the DATABASES dict (set up for SQLite) by this:

appsite/appsite/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'appsite',
        'USER': 'siteuser',
        'PASSWORD': 'sitepass',
    }
}
select

Create entry model for the blog app

In Django as well as other web frameworks, database tables are referred to as models. This name conveys the idea that they are not SQL table code, and are database independent as well. Django provides its own DB-independent syntax to represent models. Unlike relational database operations, access to the model is object-oriented. This is a key characteristic of an ORM in that it hides, as much as possible, the underlying database and SQL syntax. Documentation can be found here (among other sites):
https://docs.djangoproject.com/en/1.8/topics/db/models/
We will create a single model for our blog entries. In reality there are many related models involved in a creating blog, but this gives us the basic idea. Edit the blog/models.py file. Add this code after the "Create your models here" line:

appsite/blog/models.py
from django.db import models
 
class Entry(models.Model):
    title = models.CharField(max_length=60,unique=True)
    category = models.CharField(max_length=20)
    created = models.DateTimeField(auto_now_add=True)
    visible = models.BooleanField(default=True)
    content = models.TextField()
 
    # the output of printing an entry
    def __unicode__(self):
        return self.title
Create the model table as well as other Django-specific tables by creating and running migrations:
--- in ~/workspace/appsite ----
$ python manage.py makemigrations
$ python manage.py migrate
These commands should be re-executed whenever any change is made to a model. To see how Django translates the model syntax into SQL appropriate for our MySQL database, execute:
--- in ~/workspace/appsite ----
$ python manage.py sqlmigrate blog 0001
The TABLE portion of the output is:
CREATE TABLE `blog_entry` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(60) NOT NULL UNIQUE,
    `category` varchar(20) NOT NULL,
    `created` datetime NOT NULL,
    `visible` bool NOT NULL
    `content` longtext NOT NULL,
)
Observations: You can see a listing of all the tables created in the appsite database by:
$ python manage.py dbshell
mysql> show tables;
+----------------------------+
| Tables_in_appsite          |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| blog_entry                 |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
Django supports user access credentials and authentication through its own database tables. We will not dwell on these features in our usage.

Add Blog Entries

We will write a Python module to directly add a few blog entries. Create the file:

appsite/make_entries.py
#!/usr/bin/env python
 
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "appsite.settings")
 
from blog.models import Entry
 
data = [
{
'title': 'first entry',
'category': 'django',
'content': '''\
My First Attempt
Using django
''',
},
{
'title': 'second entry',
'category': 'python',
'content': 'Great Language',
},
{
'title': 'third entry',
'category': 'python',
'content': '''\
It's OK.
A bit compulsive on indentation.
''',
},
{
'title': 'fourth entry',
'category': 'django',
'content': "best MVC system I've seen",
},
]
 
print "--- create entries ---"
 
for datum in data:
    try:
        entry = Entry(
            title=datum['title'], 
            category=datum['category'],
            content=datum['content']
        )
        entry.save()
    except Exception as err:
        print err
 
print "--- dump entries ---"        
 
entries = Entry.objects.all()
 
print entries
 
for entry in entries:
    print "{}: {}, {}, {}".format(
        entry.id, entry.title, entry.category, entry.created
    )
 
select
Run it to create and display initial blog entries.
$ python make_entries.py
If you run it again, you get duplicate entry exceptions based on the uniqueness of the title field.

You can also check out the results through mysql client:
$ echo "select * from blog_entry" | sudo -H mysql appsite
or through Django's dbshell feature:
$ python manage.py dbshell
mysql> select * from blog_entry;

Starting Over

If you need to start over with the blog_entry table, you can do this:
--- in ~/workspace/appsite ----
$ sudo -H mysql < create_appsite.sql
$ python manage.py migrate
Unfortunately, this is WAY TOO drastic; all I want to do is drop the blog_entry table and recreate it, not every table in the appsite database. However, at the moment, I cannot figure out anything better that works consistently.

Sometimes, even what you've done this much, is not enough to get going again after certain changes. So, if there's a problem, even stronger medicine is this:
--- in ~/workspace/appsite ----
$ sudo -H mysql < create_appsite.sql
$ rm blog/migrations/0*
$ python manage.py makemigrations
$ python manage.py migrate

Show Entries on the /blog website

Modify the index function in blog/views.py:

appsite/blog/views.py
# add this
from blog.models import Entry
 
# modify this
def index(request):
    entries = Entry.objects.all()
    print entries
    context = {'entries': entries}
    return render(request, 'index.html', context)
select
Change index.html:

appsite/blog/templates/index.html
{% extends 'base.html' %}
 
{% block subtitle %}- Home{% endblock %}
 
{% block content %}
{% if entries %}
  {% for entry in entries %}
    <a class='details' href="/blog/{{ entry.id }}">{{ entry.title }}</a>
    <br />
  {% endfor %}
{% else %}
  <p>No entries are available.</p>
{% endif %}
{% endblock %}
select
Refresh the development site to see the listing of the blog entries. A few key observations:

Entry details

We've made it so that the titles of the entries are hyperlinks, supposedly to a page giving the "details" of each entry via the URL:
http://localhost:8000/blog/ENTRY_ID/
Create the template file:

appsite/blog/templates/details.html
{% extends 'base.html' %}
 
{% block subtitle %}- Details{% endblock %}
 
{% block localstyle %}
<style type='text/css'>
.entry td,th {
  padding: 5px;
  vertical-align: top;
}
.entry th {
  text-align: right;
}
.entry pre {
  margin: 0;
  font-family: inherit;
}
</style>
{% endblock %}
 
 
{% block content %}
<table class='entry'>
<tr><th>id:</th><td>{{entry.id}}</td></tr>
<tr><th>title:</th><td>{{entry.title}}</td></tr>
<tr><th>category:</th><td>{{entry.category}}</td></tr>
<tr><th>created:</th><td>{{entry.created}}</td></tr>
<tr><th>content:</th><td><pre>{{entry.content}}</pre></td></tr>
</table>
{% endblock %}
select
Add a details function and support code to blog/views.py.

appsite/blog/views.py
from django.shortcuts import get_object_or_404
 
def details(request, entry_id):
    entry = get_object_or_404(Entry, pk = entry_id)
    return render(request, 'details.html', {'entry': entry})
select
Modify blog/urls.py, adding a new entry into the urlpatterns list which matches what we're looking for:

appsite/blog/urls.py
urlpatterns = [
    url(r'^$', views.index, name='index'),
 
    # add this:
    url(r'^(?P<entry_id>\d+)/$', views.details),
]
Test it by clicking on the entry title hyperlinks to reveal the full details of each of the selected object.

Say we choose the first entry with URL:
http://localhost:8000/blog/1/
The URL introduced matches this request:
url(r'^(?P<entry_id>\d+)/$', views.details)
It's the parentheized groups in the URL which form the arguments to the defails function. In particular, the "\d+" matches the "1". The Django-special syntax
?P<entry_id>
means to pass the following named argument
entry_id = 1
to the details function:
def details(request, entry_id): ...
We could just as well have used the following URL, which more obviously matches:
url(r'^(\d+)/$', views.details),
The difference in this latter version is that the call to details passes 1 as a positional argument. Django seems to prefer the former style.

Another observations is regarding the code in the details function, in particular the usage of:
entry = get_object_or_404(Entry, pk = entry_id)
The meaning is to get an entry whose id primary key (pk) value is entry_id. If there is no such entry, generate a "404" error response. Try, for example, entering this URL by hand:
http://localhost:8000/blog/11/
The other oddity to note is that the URL will always maintain itself with a terminal "/" no matter whether you try to omit it.

Manipulating Entry Categories

Create the new template file:

appsite/blog/templates/listing.html
{% extends 'base.html' %}
 
{% block subtitle %}- By Category{% endblock %}
 
{% block localstyle %}
<style type='text/css'>
.leftSide {
  padding-right: 20px;
  vertical-align: top;
}
h3 {
  margin: 0 0 5px 0;
}
</style>
{% endblock %}
 
{% block content %}
<table>
<tr>
 
<td class='leftSide'>
<h3>Categories</h3>
  {% for category in categories %}
    <a href="/blog/listing/{{category}}">{{category}}</a>
    <br />
  {% endfor %}
</td> 
 
<td>
{% if entries %}
<h3>Entries with category "{{category}}"</h3>
  {% for entry in entries %}
    <a class='details' href="/blog/{{ entry.id }}">{{ entry.title }}</a>
    <br />
  {% endfor %}
 
{% endif %}
</td>
 
</tr>
</table>
{% endblock %}
select
Add a listing function to blog/views.py.

appsite/blog/views.py
def listing(request, category=None):
    print category
 
    # demo code
    print Entry.objects.values('category')
    print Entry.objects.values_list('category')
    print Entry.objects.values_list('category').distinct()
 
    categories = Entry.objects.values_list('category',flat=True).distinct()
    print categories
 
    entries = None
    if category:
        entries = Entry.objects.filter(category = category)
        print entries
 
    context = {
        'categories': categories,
        'entries': entries,
        'category': category
    }
 
    return render(request, 'listing.html', context)
select
Modify blog/urls.py, adding two new entries into the urlpatterns which match what we're looking for:

appsite/blog/urls.py
urlpatterns = [
    url(r'^$', views.index, name='index'),
 
    url(r'^(?P<entry_id>\d+)/$', views.details), # add
 
    # add these:
    url(r'^listing/$', views.listing),
    url(r'^listing/(?P<category>[\w ]+)/?$', views.listing),
]
Now the Categories navigational hyperlink should work, as well as selection of individual categories to reveal entries which have the given category.

The URL generated for the Categories link is:
http://localhost:8000/blog/listing/
which matches the URL:
url(r'^listing/$', views.listing),
If we click the category, say, "django", we generate the URL:
http://localhost:8000/blog/listing/django
now the request matches the URL:
url(r'^listing/(?P<category>[\w ]+)/?$', views.listing),
In this case the "[\w ]+" means any string of "identifier" or space characters. It would call the listing function with the named argument
category=django
A terminal "/" is optional and ignored — "/" is does not belong to [\w ].

The code in the listing function wants to deliver a list of all available categories. Since these are duplicates, we're trying to get the Django equivalent of the SQL statement.
SELECT DISTINCT category FROM blog_entry;
It turns out to be what you see, using the values_list and distinct functions:
categories = Entry.objects.values_list('category',flat=True).distinct()
The three print statements prior to obtaining the correct version illustrate some of the Django peculiarities towards realizing the correct way to do it.

Manage entries by the admin feature

You can manage the contents of models through Django's admin feature. First you have to set a superuser password:
--- in ~/workspace/appsite ----
$ python manage.py createsuperuser
Username (leave blank to use 'LOGIN'): 
Email address: admin@MACHINE.cs
Password: WHATEVER
Password (again): WHATEVER
Superuser created successfully.
Access the admin page through this URL. Give your login and password you used in the setup stage:
http://localhost:8000/admin
You'll see two user access-related models: Groups and Users. In order to get access to the Entry model objects, you need to add the following code to blog/admin.py after the the "Register your models here" comment line:

appsite/blog/admin.py
from django.contrib import admin
 
# Register your models here
 
from blog.models import Entry
admin.site.register(Entry)
After this refresh the browser to see access to the desired model via the name Entrys (no one said they could spell!). Click on Entrys to get access to the objects.

Example: Use entry visibility

We want the visibility flag in the entry to dictate whether this entry should be shown or not. The way to do so is to modify the blog/views.py script which retrieves the entries for display. In particular, replace the first line as indicated:

blog/views.py
...
def index(request):
    #entries = Entry.objects.all()
    entries = Entry.objects.filter(visible=True)
    ...
Next, access the admin site:
http://localhost:8000/admin
Access the second entry. Uncheck the visibility checkbox and click the Save button. Refresh the blog site and observe that the second entry is no longer shown.


© Robert M. Kline