Django Tutorial 1

From 118wiki

Jump to: navigation, search

Contents

First Django Tutorial

Preliminaries (Step 0)

Log into one of the machines on the department's Linux cluster. If you're doing this remotely, the correct procedure would be to

  • ssh linux.divms.uiowa.edu (and log in; linux.cs.uiowa.edu also works)
  • ssh l-lnxYYY (where YYY is one of the existing machines, which you see listed in the message that appears when you first log in to linux.divms.uiowa.edu)

Try the following two commands:

  • echo $PYTHONPATH
    You should see the response
    /group/class/c118/py-lib
    (if you don't see this, then your .profile or .cshrc file needs to be set up as described in the Django page)
  • echo $PATH
    You should see a long string printed, with /group/class/c118/bin near the end of the string

You need these two Unix shell environment variables, $PYTHONPATH so that you automatically pick up Django's code, and $PATH only for one command, which is to create a Django project.

Create a New Django Project (Step 1)

Suppose you are in your main directory (in my case, this is /space/herman and in your case, it will be /space/your-user-id). Provided your $PATH is set up correctly, execute the command
django-admin.py startproject mydemo
and you create a new project called mydemo -- this "project" is nothing more than a directory with some files. A good thing to do now is to list the directory:
> cd mydemo
> ls
  __init__.py manage.py settings.py urls.py

At this point you have an empty project. Each of the files manage.py, settings.py, and urls.py will need to be customized for your project, and we do that in later steps.

Run a Django Webserver (Step 2)

This is just an exercise in connectivity, to verify that the software runs correctly. Execute the following command (still in the mydemo directory):

> python manage.py runserver 0.0.0.0:8910
  Validating models...
  0 errors found.
  Django version 0.95, using settings 'mydemo.settings'
  Development server is running at http://0.0.0.0:8910/
  Quit the server with CONTROL-C.
Notice that Django is using settings defined in mydemo/settings.py, which is called "mydemo.settings" in the message above.
NOTE: if the command above fails, complaining that port 8910 is already in use, then perhaps another student is testing Django. Just pick another number in the range 8000-16000, and try again.
Now, you should attempt to get some response from the server using a browser. If you're logged in directly to a Linux machine in the lab, then just enter the URL http://localhost:8910 into your browser. In response, you should see something approximately looking like this:
Image:TutOne-1.jpg

HOWEVER, if you're logged in remotely (via ssh), then you need to remember the name of the host you selected in Step 0; suppose you selected l-lnx014 as your host. Then the URL to use would be http://l-lnx014.divms.uiowa.edu:8910 to bring up the page.

Notes:

  1. You should see some new messages on the console of the command that invoked and is running your server, showing a brief log of the HTTP requests and responses.
  2. Use Ctl-C to kill the server (necessary to go on with the next steps).
  3. In subsequent steps, for those logging in remotely via ssh, I'll skip mentioning the customized URL you need to use.

Create Application (Step 3)

To create a first application, called the "medicine" application, enter the command (again, within the mydemo directory):

 > python manage.py startapp medicine

This should not have any response, but it should create a directory:

 > ls 
   __init__.py manage.py  settings.py medicine urls.py

There is now a medicine directory:

 > ls medicine
   __init__.py  models.py  views.py

The files models.py and views.py, among others, will need to be customized for your new "medicine" application.

Create Models (Step 4)

Now you'll need to create models for the persistent data to be used in your application. The file models.py is initially almost empty:

 > cat medicine/models.py 
   from django.db import models
   # Create your models here.

It's your task now to add some Python code, which consists of classes and subclasses representing the objects in your data model. This would normally require reading the Django documentation and learning the conventions, but we can skip that and instead execute:

 > cp /group/class/c118/tutor/models.py medicine/models.py

This copies a preconstructed file defining models into your medicine/models.py, replacing the nearly empty file. Curious? Here's what it contains:

from django.db import models
import datetime
health_range = ( (1, 'Harmless'), (2, 'Healthy'), (3, 'Lifesaving'),)
class Bottle(models.Model):
 label = models.CharField(maxlength=60,unique=True)
 def __str__(self): return self.label  # optional, but handy
 class Admin: pass  # allows administrator to add/modify bottles
class Pill(models.Model):
 label = models.CharField(maxlength=128)
 buy_date = models.DateTimeField(default=datetime.datetime.now)
 rank = models.IntegerField(choices=health_range, default=1)
 pill_list = models.ForeignKey(Bottle)
 def __str__(self): return self.label  # optional but handy
 class Admin: pass # allows administrator to add/modify pills

Synchronize Persistent Storage (Step 5)

Any time you change your model definitions (the file medicine/models.py), then you need to give Django the opportunity to create and execute SQL statements so that your model is reflected in the database. This is the first time we've done this step, we need to specify to Django which type of database to use and where to place it. Edit the file settings.py in the mydemo directory, and find the lines:

 DATABASE_ENGINE =    
 DATABASE_NAME =  

Change these to:

 DATABASE_ENGINE = 'sqlite3'   
 DATABASE_NAME = '/space/my-user-id/mydemo/sqliteDB' 
This will tell Django that we're using sqlite (called sqlite3 at the command line), and to put the database in your mydemo directory, calling it sqliteDB. Save the changed file.
Note: you can also just specify DATABASE_NAME = 'sqliteDB' and the database will be put in the mydemo directory (which is your current working directory, at this point in the tutorial).

Now execute the command

 > python manage.py syncdb
   Creating table auth_message
   Creating table auth_group
   Creating table auth_user
   Creating table auth_permission
   Creating many-to-many tables for Group model
   Creating many-to-many tables for User model
   Creating table django_content_type
   Creating table django_session
   Creating table django_site
   You just installed Django's auth system, 
   which means you don't have any superusers defined.
   Would you like to create one now? (yes/no): 

At this point, respond yes and specify a user name (you can let it default to your own), an email address, and a password -- we don't need high security, so make the password something simple to type and remember. You should see many messages in response.

IMPORTANT Note: every time you change mydemo/models.py, you should repeat this step (you only need to specify the user/password once to create the initial database, and subsequent changes will only add new modeled data to the database). If you're really unhappy with your database, you can just delete the file sqliteDB, but this will remove any data you have generated and saved in it.

Install Applications (Step 6)

Now Django needs to have some applications to manipulate your models. Edit the file settings.py and find the lines:

 INSTALLED_APPS = (
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.sites',
 )

At the end of the list, add the following line:

   'mydemo.medicine',

so that the list should look like this:


 INSTALLED_APPS = (
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.sites',
   'mydemo.medicine',
 )

The trailing comma is needed for Django. This tells Django that there is an application in the medicine subdirectory. Now, again execute the command:

 > python manage.py syncdb

You should see messages about creating some SQL tables.

Install Administrative Interface (Step 7)

Technically, this could have been combined with Step 6, but it's good to show things step-by-step. Edit again the file settings.py, and add to the list:

  INSTALLED_APPS = (
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.sites',
   'mydemo.medicine',
   'django.contrib.admin',
  )

This adds another, optional Django application, which is a prebuilt way to administer the data models in your medicine application. Save the file and again execute:

 > python manage.py syncdb

You'll see some messages about adding another table.

Also, edit the file urls.py in the mydemo directory and "uncomment" the line needed for administration:

  (r'^admin/', include('django.contrib.admin.urls')),

This is a regular expression (I skipped that in class) which matches a URN with "admin/" at the start of the string, invoking the admin application in that case of an HTTP request.

Run the Server to Administrate (Step 8)

Now execute the same command you did in Step 2:

> python manage.py runserver 0.0.0.0:8910
But for the URL, enter http://localhost:8910/admin/ (or whatever is needed for connectivity in your case). You should see a small box asking for a name and password (that you specified in Step 6). After you give the name and password, you should see a page something like this:
Image:TutOne-2.jpg

This main page of the admin system shows a list of installed models, classified by application. Click on a model to see a list of objects for that model. You can also change existing objects or add new ones.

Create a bottle object by clicking "Add" next to "Bottle", filling in a label, and save it. You might want to add several bottles.

Also, from the main admin page, you can add pills, give some detail, and assign each pill you create to a bottle that you created earlier (Django generates a drop-down menu showing all the bottles that have been created so far).

At the end of this step, kill the server.

Create a View (Step 9)

The default admin application isn't intended for all users of your web site. Your users need views, and you need to specify some Python code for methods that support these views. This is done by editting the file medicine/views.py. Again, to save you time, just copy a pre-made views file:

 > cp /group/class/c118/tutor/views.py medicine/views.py

Curious? Here's what's in the views.py file:

# Create your views here.
from django.shortcuts import render_to_response
from mydemo.medicine.models import Bottle
def inventory(request):
 bl = []
 for e in Bottle.objects.all():
  a = {}
  a['bottle_label'] = str(e)
  a['pill_count'] = e.pill_set.count()
  bl.append(a)
  del a
 return render_to_response('inventory.html', { 'bl': bl })

This defines the "inventory" method, which responds to an HTTP request. We need to connect a URL to this method later, by editting another file. Notice above that the method refers to "mydemo.medicine.models" (which is models.py in mydemo/medicine). It also refers to a file called "inventory.html", which doesn't exist yet -- we have to create that as well.

Create a Template (Step 10)

The file inventory.html needs to be created. This file is a "template" for a web page, which is to say that it has some areas for fill-in-the-blank at run time, so that the web page will be dynamic (basically it is like a large format statement). To save you time, just execute the commands (from the mydemo directory):

 > mkdir templates
 > cp /group/class/c118/tutor/inventory.html templates

Now, if you look at templates/inventory.html, you'll find it contains mostly HTML, but with some strange extra things with curly braces so that Django knows to replace the named objects with values.

A template contains HTML combined with some Python-like statements (within curly braces, and within a curly-brace percent combination) that produce values, iterate through lists, and so fill out the template before it's sent to the browser. The view creates a dictionary, and this dictionary is used to guide template substitition. This technique is patterned on one of the newer features of the Python % operator (see Python Docs): the following Python code

  a = { }  # empty dictionary
  a['number'] = 3
  a['type'] = "Maximum Velocity"
  a['level'] = "Warning" 
  b = "%(level)s from backend processor -- %(number)d is %(type)s"
  c = b % a

results in variable c equal to the string "Warning from backend processor -- 3 is Maximum Velocity"

You will seem something similar to this technique in how templates work.

Connecting Views and Templates (Step 11)

Finally, you need to tell Django where the templates are. Edit again the file settings.py, and change the list for TEMPLATE_DIRS to be:

 TEMPLATE_DIRS = (
   # Put strings here, like "/home/html/django_templates".
   # Always use forward slashes, even on Windows.
   '/space/your-user-id/mydemo/templates',
 )

Notice that a trailing comma is needed for Django.

Connect a URN to the Application (Step 12)

Edit the file urls.py in mydemo, adding another regular expression to the list of URL patterns:

 urlpatterns = patterns(,
    (r'^admin/', include('django.contrib.admin.urls')),
    (r'^inventory/$', 'mydemo.medicine.views.inventory'),
 )

This tells Django that an HTTP request for the URN "inventory/" will invoke the inventory method, previously defined in the views.py file.

Test the Report (Step 13)

Start the server, as in Step 2:

 > python manage.py runserver 0.0.0.0:8910

and try the URL in your browser: http://localhost:8910/inventory/ This won't show much, but you should see some report on how many items are present.

Embellish the Report (Step 14)

Optionally, you could try to expand on the inventory view of the medicine application. For example, suppose we change the medicine/views.py to:

 from django.shortcuts import render_to_response
 from mydemo.medicine.models import Bottle, Pill
 def inventory(request):
  bl = []
  for e in Bottle.objects.all():
   a = {}
   a['bottle_label'] = str(e)
   a['pill_count'] = e.pill_set.count()
   bl.append(a)
   del a
  pl = []
  for e in Pill.objects.all(): 
   pl.append( {'pill_label' : str(e)} ) 
  return render_to_response('inventory.html', { 'bl': bl , 'pl':pl } )


Now the view imports the Pill model along with the Bottle model, and builds a dictionary of dictionaries (confusing at first) for the Pill objects. The inventory method now returns a pair of dictionaries, one for the Bottle objects, one for the Pill objects. To exploit this, we also can change the template to include:

  <h1>Pills:</h1>	 
 {% for e in pl %}
   <h2>{{ e.pill_label }}</h2>
 {% endfor %}

And with this, the pill labels will be listed in the inventory view.

Post-Django Tutorial Processing

What has actually been done by Django to save the modelled data? Django built some SQL statements, executed sqlite commands, and built a database. We can actually look at what Django did by executing sqlite3 directly (just for the sake of curiosity):

 > sqlite3 sqliteDB 
 SQLite version 3.2.3
 Enter ".help" for instructions
 sqlite> .tables
 auth_group                  django_admin_log          
 auth_group_permissions      django_content_type       
 auth_message                django_session            
 auth_permission             django_site               
 auth_user                   medicine_bottle           
 auth_user_groups            medicine_pill             
 auth_user_user_permissions

Notice that Django built many tables for administration, session logs, and so on. Only two relate to our own models, medicine_bottle and medicine_pill. We can look at those:

 sqlite> .schema medicine_bottle
 CREATE TABLE "medicine_bottle" (
   "id" integer NOT NULL PRIMARY KEY,
   "label" varchar(60) NOT NULL UNIQUE
 );

We see that medicine_bottle is a table with two columns, id and label. Here are the current items:

 sqlite> select * from medicine_bottle;
 1|aspirin
 2|vitamin C
 3|placebo

Also, we can look at the medicine_pill table's contents:

 sqlite> select * from medicine_pill;
 1|blue pill|2006-11-28 10:32:01|1|3
 2|blue pill|2006-11-28 10:32:12|2|2
 3|gelcap|2006-11-28 10:32:29|3|1
 4|orange capsule|2006-11-28 10:42:44|2|2

Asynchronous Updates (outside of Django)

In fact, we could add new rows, delete rows, and change the database outside of Django's awareness. For those of you not expert in SQL syntax, some examples are shown in the sqlite documentation. For instance, we could try:

  sqlite>insert into medicine_pill 
  ...> values(5,'micropill','2006-11-25 20:50:00',1,2);

(By the way, you can also execute SQL commands from Python programs in much the same way as you would from the command line.) This change to the database is actually reflected in the report view.

But what happens if one program updates the database at the same time that the Django server is running? Full-blown SQL servers take care of this, because they have transaction managers for concurrency, as we studied previously. But sqlite is more primitive. So far as I know, any update operation will lock the entire database. I read one complaint by a programmer who ran into problems when using sqlite for a multithreaded application: you shouldn't try high-volume concurrent updates with sqlite.


Full Inventory (a requested example)

Suppose we would like to modify the /inventory/ page to show not only the bottles, but pills within the bottles. Here is an example of how to do this. First, we modify views.py as follows:

from django.shortcuts import render_to_response
from mydemo.medicine.models import Bottle, Pill
def inventory(request):
  bl = []
  for e in Bottle.objects.all():
   a = {}
   a['bottle_label'] = str(e)
   a['pill_count'] = e.pill_set.count()
   if e.pill_set.count()>0:
     blpills = []
     for p in e.pill_set.all():
        s = {}
        s['pill_label'] = str(p)
        blpills.append( s )
     a['blpills'] = blpills
   else:
     a['blpills'] = []
   bl.append(a)
  return render_to_response('inventory.html', { 'bl': bl } )

Just as before, the render function takes a dictionary (only containing an entry for 'bl') which is a list of dictionaries, one per bottle. But unlike before, when each bottle's dictionary had only entries with strings or numbers, now a bottle has a 'blpills' entry, which is a list of dictionaries, one per pill in the bottle. For example, bl might be something like

 [ {'bottle_label' : 'aspirin', 'pill_count' : 3,
    'blpills' : [ { 'pill_label' : 'gelcap' },
                  { 'pill_label' : 'capsule' },
                  { 'pill_label' : 'micropill' } ] }
   {'bottle_label' : 'vitamin C', pill_count: 1,
    'blpills' : [ { 'pill_label' : 'chewable' } ] } 
 ]

Now, to render this meaningfully, we need a new template, the body of which is:

 <body>
   <h1>Bottle Inventory</h1>
{% for e in bl %}
   <h2>{{ e.bottle_label }}</h2>
   <ul>
   <li>Number of items: {{ e.pill_count }}</li>
   <li>Pills in Bottle:</li>
   <ul>
{% for p in e.blpills %}
   <li>{{ p.pill_label }}</li>
{% endfor %}
   </ul>
   </ul>
{% endfor %}
 </body>
Observe this has a nested loop for pills within the loop for bottles. The iteration over e.blpills goes through the list of pills for a bottle and displays, for each item found, the dictionary entry for 'pill_label'.
Personal tools