• No results found

Pro Django 1

N/A
N/A
Protected

Academic year: 2021

Share "Pro Django 1"

Copied!
63
0
0

Loading.... (view fulltext now)

Full text

(1)
(2)

REVISION HISTORY

NUMBER DATE DESCRIPTION NAME

(3)

Contents

1 About This Course 1

1.1 Course Description . . . 1

1.1.1 Target Student . . . 1

1.1.2 Course Prerequisites . . . 1

1.2 Course Objectives . . . 1

1.3 Set Up . . . 2

2 Environment and Installation 3 2.1 Django Developer Environment Setup . . . 3

2.1.1 virtualenv . . . 3 2.2 PRACTICE ACTIVITY . . . 4 2.2.1 pip . . . 4 2.3 PRACTICE ACTIVITY . . . 5 2.3.1 virtualenvwrapper . . . 5 2.4 REVIEW QUESTIONS. . . 5

3 Reviewing the Basics 6 3.1 Lesson Objectives . . . 6 3.2 Introduction . . . 6 3.3 A Django Project . . . 6 3.3.1 __init__.py . . . 7 3.3.2 settings.py . . . 7 3.3.3 templates . . . 7 3.3.4 urls.py. . . 7 3.4 Django Applications . . . 7 3.4.1 admin.py . . . 7 3.4.2 models.py. . . 7 3.4.3 views.py . . . 8

3.5 Best Practices and the Django tutorial . . . 8

3.6 PRACTICE ACTIVITY . . . 8

(4)

4 More Tools 11 4.1 Lesson Objectives . . . 11 4.2 debug_toolbar . . . 11 4.3 PRACTICE ACTIVITY . . . 11 4.4 django_extensions. . . 12 4.5 PRACTICE ACTIVITY . . . 12

4.6 More ToolsFollow-up. . . 13

5 Data Migrations 14 5.1 Lesson Objectives . . . 14

5.2 Why migrations? . . . 14

5.3 PRACTICE ACTIVITY . . . 14

5.4 Django database creation support. . . 15

5.4.1 syncdb . . . 15 5.5 Using south . . . 16 5.6 PRACTICE ACTIVITY . . . 16 5.7 MigrationsFollow-up. . . 17 6 Adding Media 18 6.1 Lesson Objectives . . . 18 6.2 Static media . . . 18 6.2.1 STATIC_ROOT . . . 18 6.2.2 STATIC_URL . . . 18 6.2.3 STATICFILES_DIRS . . . 19 6.2.4 STATICFILES_FINDERS . . . 19 6.3 PRACTICE ACTIVITY . . . 19

6.4 Adding MediaFollow-up . . . 19

7 Users and Authentication 20 7.1 Lesson Objectives . . . 20

7.2 Users and Permissions via contrib.auth . . . 20

7.3 Users in Requests . . . 20

7.4 Users in the admin . . . 21

7.5 Login/Logout . . . 22

7.6 PRACTICE ACTIVITY . . . 23

(5)

8 Intro to Forms 24

8.1 Lesson Objectives . . . 24

8.2 Forms . . . 24

8.3 Working with forms in views . . . 24

8.4 Displaying forms in templates . . . 25

8.5 Validation . . . 25

8.6 PRACTICE ACTIVITY . . . 26

8.7 Intro To FormsFollow-up . . . 26

9 More Forms 27 9.1 Lesson Objectives . . . 27

9.2 Field and Widgets . . . 27

9.3 Modifying Form Output . . . 28

9.4 PRACTICE ACTIVITY . . . 28

9.5 Dynamic Choices . . . 29

9.6 PRACTICE ACTIVITY . . . 29

9.7 More FormsFollow-up . . . 29

10 Writing Tests with Django 30 10.1 Lesson Objectives . . . 30

10.2 Running Tests . . . 30

10.3 Writing Tests . . . 31

10.4 PRACTICE ACTIVITY . . . 32

10.5 Writing Tests Follow-up. . . 32

11 Customizing Templates 33 11.1 Lesson Objectives . . . 33

11.2 Template Loading . . . 33

11.3 PRACTICE ACTIVITY . . . 33

11.4 Builtin Tags and Filters . . . 34

11.5 PRACTICE ACTIVITY . . . 34

11.6 Custom template filters . . . 34

11.7 PRACTICE ACTIVITY . . . 35

11.8 Custom template tags . . . 35

(6)

12 Even more (Model)Forms 36

12.1 Lesson Objectives . . . 36

12.2 ModelForms. . . 36

12.2.1 Customizing fields and widgets . . . 36

12.2.2 Saving ModelForms . . . 37 12.3 User Profiles. . . 37 12.4 PRACTICE ACTIVITY . . . 38 12.5 Nicer Forms . . . 38 12.5.1 django-floppyforms. . . 38 12.5.2 django-crispy-forms . . . 38 12.6 PRACTICE ACTIVITY . . . 39

12.7 Even More (Model)Forms Follow-up . . . 39

13 Tying it all together: Lab 1 40 13.1 Lesson Objectives . . . 40

13.1.1 What We Want . . . 40

13.1.2 How to Get There. . . 40

13.1.3 Real World Data . . . 41

13.2 PRACTICE ACTIVITY . . . 41 14 QuerySets 42 14.1 Lesson Objectives . . . 42 14.2 QuerySet Basics. . . 42 14.3 Spanning Relationships . . . 42 14.4 Query Operators. . . 43 14.5 PRACTICE ACTIVITY . . . 43

14.6 Aggregation and Annotation . . . 44

14.7 PRACTICE ACTIVITY . . . 44

14.8 QuerySets Follow-up . . . 45

15 Performance 46 15.1 Lesson Objectives . . . 46

15.2 QuerySet Performance Basics . . . 46

15.3 PRACTICE ACTIVITY . . . 46 15.4 Denormalisation. . . 47 15.5 Signals. . . 47 15.6 PRACTICE ACTIVITY . . . 47 15.7 Caching . . . 47 15.8 Practice Activity . . . 48 15.9 Performance Follow-up . . . 48

(7)

16 Deploying Django Web Apps 49

16.1 Lesson Objectives . . . 49

16.2 Execution Model . . . 49

16.2.1 Where should my application live?. . . 49

16.2.2 Tradeoffs - and an alternative. . . 49

16.3 Scripting Deployment with Fabric . . . 50

16.4 Practice Activity . . . 50

16.5 Deployment Follow-up . . . 51

17 Using Celery 52 17.1 Lesson Objectives . . . 52

17.2 Celery and Asynchronous Jobs . . . 52

17.3 Configuring celery for development . . . 52

17.4 PRACTICE ACTIVITY . . . 53

17.5 Celery Follow-up . . . 53

18 REST with Tastypie 54 18.1 Lesson Objectives . . . 54

18.2 REST API’s . . . 54

18.3 Consuming our API . . . 55

18.4 PRACTICE ACTIVITY . . . 56

(8)

Chapter 1

About This Course

1.1

Course Description

This course is designed to get new users up to speed on advanced usages and best practices for the Django Framework. Continuing where the Django Tutorial leaves off we will explore advanced usage of Models and Managers, the Form library including ModelForms and FormSets, and many of the built in applications that support web development with Django. We will also explore standard third party tools and applications that enable us to change our database structure over time, debug and profile our apps, and ease our interactions with the built in management shell.

1.1.1

Target Student

The target student has basic levels of experience with Python 2.5 or above and Django 1.4.

1.1.2

Course Prerequisites

Suggested prerequisites include the Python Fundamentals class at Marakana for basic Python knowledge and the Django Tutorial found athttps://docs.djangoproject.com/en/1.4/intro/tutorial01/

1.2

Course Objectives

In this course you will move beyond the basics of Django and gain hands on experience with advanced usage of the components that make up any Django app. You will also gain familiarity with the tools, 3rd party apps, and techniques common to advanced Django projects.

(9)

1.3

Set Up

Install the newest version of Python 2 (2.5 or greater) - Linux and Mac should already have Python installed while Windows users should seehttp://goo.gl/Y3N9T. We’ll install Django together as part of the class.

Be sure to bookmark the documentation athttps://docs.djangoproject.com/en/1.4/

The single best way to increase best way to swiftly increase your Django proficiency is to read all of the excellent and compre-hensive documentation.

(10)

Chapter 2

Environment and Installation

2.1

Django Developer Environment Setup

The Django tutorial and documentation are excellent and every Django programmer should study them thorougly. We’ll set up our development environment, however, with tools that let us easily install and manage different versions of Django and any third party apps you need.

2.1.1

virtualenv

The official documentation (https://docs.djangoproject.com/en/1.4/topics/install/#installing-official-release) suggests relying on the included setup.py file to install Django:

$ tar xzvf Django-1.4.tar.gz $ cd Django-1.4

$ sudo python setup.py install

The disadvantage of this approach is that Django ends up installed globally. The built-in installer uses setuptools and places the django directory containing importable code in your shared system packages directory. On Windows, for example, this might be in C:\Python2.7\Lib\site-packages.

Using a single global location to install Python modules means that it is impossible to to have more than one version of Django installed. It also means that you have to have administrative permissions to install Python modules. Fortunately there’s a better way to manage installation of Python modules.

virtualenv

‘virtualenv‘ is a tool to create isolated Python environments.

Ian Bicking’s tool virtualenv allows us to create Python environments that are isolated from one another. It supports all three major OS platforms (Windows/Mac/Linux) and has become a defacto standard with integration in other tools such as mod_wsgi and the pip installer.

To install virtualenv we can use easy_install, the installer provided by setuptools. This should be automatically installed with Python on Mac and Linux but Windows users may have to download and run the setuptools installer for your version of Python - you can find it athttp://pypi.python.org/pypi/setuptools

Installing virtualenv

$ easy_install virtualenv

virtualenvworks by creating a Python environment, copying or symnlinking your Python executable and creating a local directory in which to install Python modules. This is best demonstrated by example:

(11)

$ cd ~

$ mkdir pro_django $ cd pro_django/ $ virtualenv PROJ1

New python executable in PROJ1/bin/python Installing setuptools...done.

$ which python # The virtual env is not yet activated

/usr/bin/python

$ source PROJ1/bin/activate # Activate using the source command

(PROJ1)$ which python # Notice the prompt indicates the active env

/home/simeon/pro_django/PROJ1/bin/python

The virtualenv command is used to create a virtualenvironment in the directory specified. Virtualenvironments are just directories with a Python environment - they can be activated by running the activate script using the source command on the activate script on Mac/Linux or by running the activate.bat on windows.

The prompt is modified to show the currently active virtualenvironment and now running commands like python or easy_install runs a local copy in the bin directory of the virtualenvironment instead of the global shared executable.

Install Django in a virtualenv

(PROJ1)$ easy_install django==1.4 Searching for django==1.4 Reading http://pypi.python.org/simple/django/ Reading

http://www.djangoproject.com/ Best match: Django 1.4 Downloading http://media.djangoproject.com/releases/1.4/Django-1.4.tar.gz

Processing Django-1.4.tar.gz Running Django-1.4/setup.py -q bdist_egg --dist-dir /tmp/easy_install-zp16WQ/Django-1.4/egg-dist-tmp-ZJdjNy zip_safe flag not set; analyzing archive contents... ... snip output ... Installed

/home/simeon/pro_django/PROJ1/lib/python2.6/site-packages/Django-1.4-py2.6.egg Processing dependencies for django==1.4 Finished processing

dependencies for django==1.4 $ which django-admin.py /home/simeon/pro_django/PROJ1/bin/django-admin.py

Notice that easy_install was not run with administrative permissions and Django 1.4 was installed in the site-packages directory of the currently activated virtualenv. Not only is the Python code put in the right place but utilities like the django-admin.py site creation tool are put in a bin folder in the currently activated environment as well.

2.2

PRACTICE ACTIVITY

1. Lets make sure your environment is all set up. Go ahead and install setuptools if on Windows - other OS’s should already have setuptools installed. You can check by running the easy_install command in your shell - if you’ve got it your already have setuptools installed.

2. Install virtualenv using easy_install. Create a folder for the class called pro_django in your Desktop, Documents, or other easily accessible location.

3. Create a virtualenv called PROJ1. Install Django 1.4 in it - either by using easy_install or by adding a symlink that places the django folder in the site-packages directory of your virtualenv.

2.2.1

pip

Using easy_install works for installing packages but other installers provide additional features. Another Ian Bicking project named pip is widely used by Django developers. We’ll look at its features in greater detail later on but for now note that pip is a drop in replacement for easy_install that also supports

(12)

• uninstalling packages

• listing installed packages and reinstalling from a list

and of course pip also plays well with virtualenv. To use pip go ahead and easy_install it, then just use it to install python packages:

(PROJ1)$ pip install django_debug_toolbar Downloading/unpacking django-debug-toolbar

Downloading django-debug-toolbar-0.9.4.tar.gz (150Kb): 150Kb downloaded Running setup.py egg_info for package django-debug-toolbar

no previously-included directories found matching ’example’ Installing collected packages: django-debug-toolbar

Running setup.py install for django-debug-toolbar

no previously-included directories found matching ’example’ Successfully installed django-debug-toolbar

Cleaning up... (PROJ1)$ python

Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) [GCC 4.4.3] on linux2

Type "help", "copyright", "credits" or "license" for more information. >>> import debug_toolbar

>>> debug_toolbar.__file__

2.3

PRACTICE ACTIVITY

1. Go ahead and get the first set of lab files that we’ll need. You can download a sample completion of the Django tutorial by visiting the author’s github account athttps://github.com/simeonf/django-project/archive/tutorial.zip

2. Extract the .zip file to the pro_django folder you made. Enter the mysite directory. Activate the PROJ1 virtualenv and run the syncdb and shell commands via manage.py to make sure the sample Django app is working.

3. Use pip to install the django-debug-toolbar in your virtualenv. Import the module debug_toolbar and look at it’s __file__ attribute to see where the importable Python module lives.

2.3.1

virtualenvwrapper

Doug Hellman’s virtualenvwrapper is a set of helper commands for managing virtualenvs. It is written in bash so unfortunately is not available for Windows - although Windows users might check outhttps://github.com/davidmarble/virtualenvwrapper-win for an implementation in batch scripts.

The idea behind virtualenvwrapper is to store all virtualenvs in a specific place (by default ~/.virtualenv but configurable) and add utility commands for creating, activating, manually adding locations, and listing the contents of a particular virtualenv. We won’t be covering installation and usage of virtualenvwrapper in this course as it doesn’t modify the functionality of the underlying virtualenv tool, but many Python hackers find virtualenvwrapper an indispensable part of their toolkit to manage their development environment.

2.4

REVIEW QUESTIONS

1. What is virtualenv? What problems does it solve. 2. What is pip? What problems does it solve?

(13)

Chapter 3

Reviewing the Basics

3.1

Lesson Objectives

This lesson will review the basic components of Django by reviewing the code required to complete the tutorial. We’ll also look at a few places where the tutorial’s advice could be improved with best practices common to many Django projects.

3.2

Introduction

In this course you will move beyond the basics of Django and gain hands on experience with advanced usage of the components that make up any Django app. You will also gain familiarity with the tools, 3rd party apps, and techniques common to advanced Django projects.

Let’s start with what is hopefully familiar ground - the Django tutorial. Many Django programmers get introduced to Django by walking through the four part tutorial before diving deeper into the excellent documentation. Let’s review the basic com-penents of the Django framework by looking at the code required to complete the Django tutorial for Django v1.4. (See https://docs.djangoproject.com/en/1.4/intro/tutorial01/)

3.3

A Django Project

If you haven’t ever completed the Django tutorial now would be the time to start. Your instructor will step you through the 4 part tutorial - go ahead and start athttps://docs.djangoproject.com/en/1.4/intro/tutorial01/

Alternatively you may already have downloaded a sample completion from the author’s github account at https://github.com/-simeonf/django-project/archive/tutorial.zip- if you’ve previously completed the tutorial your instructor may just have you start from this sample completion. Go ahead and save the .zip file to your pro_django directory and extract it. The file listing should look something like:

(PROJ1)$ ls -l total 12

-rwxr--r-- 1 simeon simeon 249 2012-12-10 15:19 manage.py drwxr-xr-x 2 simeon simeon 4096 2012-12-10 15:36 mysite drwxr-xr-x 2 simeon simeon 4096 2012-12-10 15:36 polls This is the root of your Django project. Let’s look at the project first: (PROJ1)$ cd mysite

(PROJ1)$ ls -l total 72

-rw-r--r-- 1 simeon simeon 0 2012-03-03 15:39 __init__.py -rw-r--r-- 1 simeon simeon 4956 2012-03-03 15:53 settings.py

(14)

drwxr-xr-x 3 simeon simeon 4096 2012-03-03 15:54 templates -rw-r--r-- 1 simeon simeon 476 2012-03-03 15:59 urls.py -rw-r--r-- 1 simeon simeon 1134 2012-03-03 15:59 wsgi.py

3.3.1

__init__.py

This file tells Python that this directory is a module that can be imported. Modules in Python are a file or a directory that contains a file called __init__.py

3.3.2

settings.py

The settings.py contains the configuration information for our Django project - database settings, installed applications, middle-ware, email settings, etc.

3.3.3

templates

This is a directory to hold our custom templates. By default this directory is usually called templates because Django looks for a directory called templates in our app directories when we have the app_directories template loader enabled. This templates directory is our main template directory for our project and the absolute path must be specified in our settings.py.

3.3.4

urls.py

The top level urls.py contains mapping for path prefixes to views or to other url files.

3.4

Django Applications

Let’s also take a peek inside our app. We’ll look only at the most significant files for now: (PROJ1)$ ls -l polls

total 24

-rw-r--r-- 1 simeon simeon 587 2012-03-03 15:49 admin.py -rw-r--r-- 1 simeon simeon 0 2012-03-03 15:41 __init__.py -rw-r--r-- 1 simeon simeon 529 2012-03-03 15:50 models.py drwxr-xr-x 3 simeon simeon 4096 2012-03-03 16:55 templates -rw-r--r-- 1 simeon simeon 383 2012-03-03 15:41 tests.py -rw-r--r-- 1 simeon simeon 984 2012-03-03 20:05 urls.py -rw-r--r-- 1 simeon simeon 1623 2012-03-03 20:07 views.py

3.4.1

admin.py

admin.pyis the file that determines how our poll shows up in the built-in Django admin application.

3.4.2

models.py

models.pycontains our models. Models use a declarative syntax to represent database tables and allow us to perform queries on our data using Django’s ORM. Models are the "mandatory" part of a Django application but an app can have an empty models.py.

(15)

3.4.3

views.py

Django follows a variant of the Model-View-Controller (MVC) pattern - preferring the acronym MTV for Model, Template, and View. Views are simply python callables that accept an HTTP request object and return an HTTP response object. From the tutorial’s point of view most of our programming happens in views and views are always python functions. As we’ll see this isn’t always the best practice.

3.5

Best Practices and the Django tutorial

Let’s start looking at some places we might want to diverge from the tutorial a little bit to make our life easier as we tackle real-world development tasks.

The default settings.py that the startproject generates for us has a sample template_dirs configuration in a comment TEMPLATE_DIRS = (

# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows.

# Don’t forget to use absolute paths, not relative paths.

)

TEMPLATE_DIRS paths need to be absolute paths. But in practice we may not want to hard code our template directory and require that other users of our code have the exact same path structure we do. We want to build relocatable projects with reusable applications whenever possible. One common way to solve this problem is to include the os module and use the builtin variable __file__ to find the parent directory of our settings file. Our settings file might look like this:

from os.path import abspath, dirname, join DIR = dirname(abspath(dirname(__file__))) ... snip ...

TEMPLATE_DIRS = (

join(DIR, "templates"), )

The same trick also works to relatively specify the MEDIA_ROOT that will hold uploaded files. Later on we’ll discuss the best ways of maintaining multiple settings files for different environments.

It’s also worth noting that the tutorial initially has you specify all your urls in the main urls.py file. In part three we move all the urls that start with a common prefix ("/poll/") into the app level url.py and use include in the main urls.py. This is good but the comments in the generated urls.py may mislead us at this point:

urlpatterns = patterns(’’,

# Examples:

# url(r’^$’, ’mysite.views.home’, name=’home’), # url(r’^mysite/’, include(’mysite.foo.urls’)),

)

If we followed the advice in the comment we’d end up including the project name "mysite" in the include string. This may work but far better is to simply refer to the application without the project name. Again - we want our projects to be movable (from development to production servers, for instance) and we want our applications to be re-usable. Never use the project name in an import statement.

3.6

PRACTICE ACTIVITY

The tutorial didn’t teach about Django’s template inheritance patterns and our poll pages currently aren’t complete html pages. Let’s go ahead and make our templates a little more realistic.

(16)

1. Make sure your polls application is working: add a poll via the admin and visithttp://localhost:8000/polls/to make sure it shows up.

2. Take a few minutes to read the Django documentation on template inheritance at https://docs.djangoproject.com/en/1.3/-topics/templates/#id1

3. Let’s add a base template that represents the design of our site. We’re not too concerned with design but let’s make a simple html page with a Django block called content where we want individual apps to put their output. This should go in mysite/templates/base.html. This template might look like:

<html> <head>

<title>Pro Django Class - {% block title %}{% endblock %}</title> </head> <body> <h1>Header</h1> {% block content %} {% endblock %} <h3>Footer</h3> </body> </html>

4. Let’s also add a application base template. An extremely common design pattern is that we want to share a template between all the pages of our app - perhaps to show things like breadcrumb navigation between our different pages. Also - our application may not know that our project base template defines a block called content - so the application base template is where we can configure how our application templates will connect to our project templates. This template might look like:

{% extends ’base.html’ %} {% block content %}

{% block poll %} {% endblock %} {% endblock %}

5. Edit the three existing templates in mysite/polls/templates/polls to extend the application base template. You ought to end up with something like this:

(17)

3.7

Reviewing the Basics Follow-up

You should be fairly comfortable and familiar with all the material we’ve covered so far - mapping urls to views and using models are basic Django concepts. Building on the tutorial, we should understand Django’s template inheritance model. We’re now ready to begin building a more complex Django application.

(18)

Chapter 4

More Tools

4.1

Lesson Objectives

This lesson will review some commonly used 3rd party Django applications. We’ll roughly profile our execution speed, list our sql queries, view the templates used to inspect a page and the contents of the template contexts they rendered.

We’ll also look at a few tricks to ease writing views and interacting with the shell.

4.2

debug_toolbar

We’ve already installed the django-debug-toolbar package (see the section on pip in Basic Tools). Let’s edit our settings to enable the debug_toolbar and use it to measure our page build time, count our SQL queries, and exampine the templates used to render a page.

4.3

PRACTICE ACTIVITY

1. Add debug_toolbar to the list of installed apps in your settings.py file.

2. add the debug_toolbar.middleware.DebugToolbarMiddleware middleware to the list of middleware classes in your set-tings.py file.

3. Add a new configuration directive to your settings.py file. It should look like INTERNAL_IPS = (’127.0.0.1’,)

4. Start the built-in Django testing server with the runserver command. Open a browser and visithttp://localhost:8000/polls/ The debug toolbar is now activated and you should see something like:

(19)

Figure 4.1: Base Template

For more configuration options visithttps://github.com/django-debug-toolbar/django-debug-toolbar

5. Click on the button image on the right side of the screen and use the toolbar to answer the following questions: how long did it take to build the page? How many sql queries were executed? What tables did they touch? And how many templates were used?

4.4

django_extensions

Another third party app that I frequently use is django_extensions. This is an app that provides many additional management commands, some additional model field types, and a faciity to create cron-style jobs, among other things. For the moment we’ll just explore one of the new management commands it offers.

4.5

PRACTICE ACTIVITY

1. Install the django-extensions package using pip. Add django_extensions to the list of installed apps in your settings.py file. 2. Run the shell_plus command provided by django_extensions with

python manage.py shell_plus

This command works just like the built-in shell command but autoloads the models for all your installed apps. You should see the list of autoloaded models when your shell starts up.

(20)

(PROJ1)$ ./manage.py shell_plus

From ’auth’ autoload: Permission, Group, User, Message From ’contenttypes’ autoload: ContentType

From ’sessions’ autoload: Session From ’sites’ autoload: Site From ’admin’ autoload: LogEntry From ’polls’ autoload: Poll, Choice

Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) [GCC 4.4.3] on linux2

Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole)

>>>

3. Use the User model in the interactive shell to find the admin user you created and print out the associated email address (run python manage.py createsuperuser if you don’t have a superuser created yet.) With shell_plus you don’t have to remember from django.contrib.auth.model import User in order to use the User model.

4.6

More Tools Follow-up

We’ve only scratched the surface of these apps, in particular django_extensions can also help us write management commands more simply, generate graphs of our data model and much much more. Django has a huge ecosystem of 3rd party apps so part of your development as a skilled Django developer means getting acquainted with what’s out there. Remember - the best code is code you didn’t have to write yourself. Always check for existing Django apps before writing one yourself.

(21)

Chapter 5

Data Migrations

5.1

Lesson Objectives

The single most necessary feature that is not baked into Django is support for evolving your database tables as you make changes to your models. We’ll learn to use the popular django application south to provide migration support for our models.

5.2

Why migrations?

It’s a fact of life that our data model will change over time. Many projects are exploratory in nature: we start coding, establish a working prototype, and then refine it. But what happens when we change our models file?

Let’s find out:

5.3

PRACTICE ACTIVITY

1. Let’s make our polls application more useful. First let’s make owners for each Poll object. We can do this by adding a ForeignKey field named user to the Poll model that points to the built in django.contrib.models.User model. See the Related Objects documentation (https://docs.djangoproject.com/en/1.3/ref/models/relations/) if you haven’t done this before.

2. Use the runserver command to start the development webserver and navigate tohttp://localhost:8000/polls/in your browser. What do you see?

(22)

Figure 5.1: Base Template

5.4

Django database creation support

How can we solve this problem? Let’s review the support Django provides for creating our database tables.

5.4.1

syncdb

The builtin management command syncdb does not handle changes to existing tables, only creating tables for newly added models. To quote the Django documentation

syncdb will only create tables for models which have not yet been installed. It will never issue ALTER TABLE statements to match changes made to a model class after installation. Changes to model classes and database schemas often involve some form of ambiguity and, in those cases, Django would have to guess at the correct changes to make. There is a risk that critical data would be lost in the process.

If you have made changes to a model and wish to alter the database tables to match, use the sql command to display the new SQL structure and compare that to your existing table schema to work out the changes.

https://docs.djangoproject.com/en/1.3/ref/django-admin/#syncdb

One way that suggests itself is just to delete the tables (the sqlclear command will give us the necessary sql to run on our database) and then use syncdb to recreate brand new tables.

(23)

5.5

Using south

southto the rescue! We’ll be using south to create database migrations. South works by keeping adding a table to keep track of the current migration for your app. When you make changes to your model you’ll write a migration that south can run to change the database to keep it in sync with your model. Migrations are sequentially named files stored in the migrations directory of your app. The new command migrate can then see what what migrations exist for your app and what migrations have already been run and apply any migrations that are needed.

If writing database migrations every time you make a model change sounds like a lot of work, don’t despair! South is very smart and can write most migrations for us.

5.6

PRACTICE ACTIVITY

1. Install south using pip. Add south to your INSTALLED_APPS tuple in settings.py and run syncdb to create the necessary tables for south to store migration history. Notice that the syncdb command has been altered - now the output shows the apps that have no migrations and the tables south will manage.

(PROJ1)$ ./manage.py syncdb Syncing...

Creating tables ...

Creating table south_migrationhistory Installing custom SQL ... Installing indexes ... No fixtures found. Synced: > django.contrib.auth > django.contrib.contenttypes > django.contrib.sessions > django.contrib.sites > django.contrib.messages > django.contrib.staticfiles > django.contrib.admin > django.contrib.admindocs > polls > debug_toolbar > django_extensions > south

Not synced (use migrations):

-(use ./manage.py migrate to migrate these)

2. Comment out the new field user in mysite/polls/models.py that you added in the previous practice activity - we need to get our app set up with south before we make any changes. Be sure to at least read the south tutorial - but you should be able to simply run

(PROJ1)$ python manage.py convert_to_south polls

3. Uncomment your user field on the model for Poll. We’re now ready to let south generate a migration for this change. But think about our database table - we haven’t specified a default value and the column type is a foreign key linking to the User table. How will the migration know what data to provide?

In fact, south will note that this is a problem and provide us with the opportunity to either add a default to our field definition or provide a one-off value that the migration will use for the currently existing rows that are getting a new column. Enter the number 1 at the prompt - assuming you created an admin user and the admin user has an id of 1 this will assign the existing poll to the admin user. To create the migration run

(24)

(PROJ1)$ python manage.py schemamigration polls --auto

4. Look at the migration that south created - it should be the migration starting with 0002 in your mysite/polls/migrations directory. You can now run the migrate command (try it first with the --list option) to apply your new migration.

5. Did it work? Start the dev server and use the admin to look at your existing Poll object. Does it have an assigned user? Hint:

(PROJ1)$ cat polls/admin.py ... snip ...

class PollAdmin(admin.ModelAdmin): fieldsets = [

(None, {’fields’:[’question’, ’user’]}),

(’Date Information’, {’fields’:[’pub_date’], ’classes’: [’collapse’]}) ]

inlines = [ChoiceInline]

list_display = (’question’, ’pub_date’, ’was_published_today’) lists_filter = [’pub_date’]

search_fields = [’question’] date_hierarchy = ’pub_date’ admin.site.register(Poll, PollAdmin)

5.7

Migrations Follow-up

While Django has no built in support for making changes to our models and keeping our database tables in sync, the 3rd party application south provides all the support we need to pursue an evolutionary strategy with our data models.

Be sure to read parts 2 (http://south.aeracode.org/docs/tutorial/part2.html) and 3 (http://south.aeracode.org/docs/tutorial/part3.html) of the south tutorial. Not only can we add simple columns - south can be used to add complex field types and create data-only migrations.

(25)

Chapter 6

Adding Media

6.1

Lesson Objectives

This lesson will demonstrate Django’s support for static media and demonstrate best practices for including resources like css, images and Javascript. In the process we’ll use Twitter Bootstrap to make our sample site look a little nicer.

6.2

Static media

Web applications typically rely on a variety of static resources - css, images, Javascript and a variety of rich media files like audio and video clips.

Django doesn’t have much involvement with your static files when you have successfully finished and deployed your application. On the way though it provides a lot of functionality for configuring, collecting, and managing your static files.

For example: you can specify a static files url value so that you can refer to the location of static files in your templates without hardcoding a path. You can specify a file-based location so that user uploaded files will be put in the right place. Via the included django.contrib.staticfiles app you can collect collect static files from locations you specify and put them in a location you specify - this feature is very useful for deployment when static files may be served directly from the webroot (eg: /var/www/html) but the Python source of your Django project files should not be in the webroot and browsable.

There is also some suport for configuring how the media for the contrib admin application is served.

Finally there is built in support for serving static files when running the development server and an application for collecting static files from individual applications and placing them in the appropriate directory. This is a lot of functionality and configuring it can be confusing so we’ll start simply with the STATIC_* configuration variables..

The settings that start with STATIC all have to do with collecting your static files, putting them someplace, and telling your application what url to use to refer to them.

6.2.1

STATIC_ROOT

STATIC_ROOT` is the place that static files will end up if they need to be collected from individual application’s /static/ subdi-rectory. We don’t have any static files that are specific to an application yet so we’re going to leave this blank.

6.2.2

STATIC_URL

(26)

6.2.3

STATICFILES_DIRS

STATICFILES_DIRStakes a tuple of paths that represent the location of additional static files that are not associated with any particular app. Files in the STATICFILES_DIRS are automatically served by runserver if the django.contrib.staticfiles is enabled. We’ll be using this functionality

6.2.4

STATICFILES_FINDERS

This is a list of "finder" classes. The built in FileSystemFinder and AppDirectoriesFinder handle looking in the /static subdirectories of our applications and in the paths specified in STATICFILES_DIRS. We don’t need to make any changes here.

6.3

PRACTICE ACTIVITY

1. Create a directory called media in your pro_django folder. We’ll try to keep just python code and Django templates in our mysite folder. A directory listing should look like:

(PROJ1)$ ls -l

-rwxr--r-- 1 simeon simeon 249 2012-12-10 15:19 manage.py drwxr-xr-x 2 simeon simeon 4096 2012-12-10 21:58 media drwxr-xr-x 2 simeon simeon 4096 2012-12-10 21:21 mysite drwxr-xr-x 3 simeon simeon 4096 2012-12-10 21:53 polls

-rw-r--r-- 1 simeon simeon 78 2012-12-10 21:20 requirements.txt drwxr-xr-x 3 simeon simeon 4096 2012-12-10 21:05 templates

drwxr-xr-x 5 simeon simeon 4096 2012-03-10 10:41 PROJ1

2. We won’t worry about uploaded media files yet and will only modify two of the settings. Find the STATIC_ROOT and STATIC_URL configuration variables in your settings.py and adjust them to look like:

STATIC_URL = ’/static/’

STATICFILES_DIRS = ( join(DIR, "media"), )

Notice that the path to the media directory is not hardcoded - os.path.dirname gives the parent directory of our current location.

Add django.contrib.staticfiles to the list of installed applications.

3. Let’s go ahead and add a css file. Use a text editor to create a file in the media directory called test.css. Add a css comment like

/* this is a comment */

and try to load the file by visitinghttp://localhost:8000/static/test.css Can you see the file contents?

4. If this was successful, lets add some more static resources and make our templates a little nicer. Download Twitter Boot-strap fromhttp://twitter.github.com/bootstrap/. Unzip the file in our media directory and pull the updated mysite/tem-plates/base.html template fromhttps://github.com/simeonf/django-tutorial/tree/users

Reload the site - the addition of some .css styles makes our site look a lot better!

6.4

Adding Media Follow-up

Django’s configuration settings around serving and setting up static files can be a little confusing. We’re starting out sim-ply, however, by specifying only the STATIC_URL and the STATICFILES_DIRS configuration variables and letting the staticfilesapp automatically serve our media when running the development server.

(27)

Chapter 7

Users and Authentication

7.1

Lesson Objectives

This lesson will introduce Django’s user and permission model. We will learn to check the current user and the user’s permissions and how to limit data in our views and in the built-in admin app based on user.

We’ll make our tutorial app a little more useful by improving our templates, views and the admin.

7.2

Users and Permissions via contrib.auth

Authentication support is included with Django in the django.contrib.auth application - see https://docs.djangoproject.com/-en/1.3/topics/auth/for details. Included are models to store users, groups and permissions and methods for authenticating users. Django provides the ability to add custom authentication backends but by default the authentication mechanism checks the ses-sion that accompanies an HTTP request to see if it includes a logged-in user. If not the user must enter a username and password that matches an enty in the User model.

This is appropriate for most web applications - including ours. Let’s modify our sample application to support the following features:

• only logged in users with appropriate permission can vote on polls • everyone can see poll results

• only the owner of a poll can edit it in the admin

• our application should provide login/logout functionality

7.3

Users in Requests

Django includes two middlewares that add information about the currently logged in user to the request object that is passed to all views. By default the SessionMiddleware and AuthenticationMiddleware are enabled and allow us to access the request.user object. This object will return either a User object or an instance of AnonymousUser. You can check with the request.user.is_authenticated() method in view code or refer to user variable in templates that have been supplied a RequestContext.

As we saw in the tutorial we can render our templates with a RequestContext explicitly using the render_to_response shortcut:

return render_to_response(’polls/detail.html’, {

’poll’: poll,

’error_message’: "You didn’t select a choice.", }, context_instance=RequestContext(request))

(28)

My experience has been that we might as well always use a RequestContext: the extra overhead of adding additional context variables via your installed TEMPLATE_CONTEXT_PROCESSORS is neglible and it is nice not to have to think about whether or not the user variable will exist in your templates. Django 1.3 includes a new shortcut render that assumes you want a RequestContext without making you specify it. Get into the habit of using it instead of render_to_response:

return render(request, ’polls/detail.html’, {

’poll’: poll,

’error_message’: "You didn’t select a choice.", })

Once we have a user variable in a template we can check that the user is logged {% if user.is_authenticated %}

<p>Logged in user: {{ user }} {% else %}

<p>You are not logged in. {% endif %}

7.4

Users in the admin

The included contrib.admin app is a large swiss-army-chainsaw of CRUD functionality for your apps. Be sure to look at the docs athttps://docs.djangoproject.com/en/1.3/ref/contrib/admin/- in general the admin’s approach is to limit access to logged in users with the is_staff attribute set to True. You should have been prompted to create a superuser when you created your database (if not run the createsuperuser command via manage.py) and superusers by default have both the .is_staff and .is_superuserattributes set to True.

>>> User.objects.all() [<User: admin>] >>> admin = User.objects.all()[0] >>> admin.is_staff True >>> admin.is_superuser True

Django allows us to set permissions for specific users but users with is_superuser set to True will always have every permission.

Django doesn’t provide explicit support for "row-level" permissions but it does allow our ModelAdmin objects to define a queryset function that takes a request object and returns the queryset that will be used by the admin. This allows us to check the logged in user in the admin and filter the data the current user can see:

class PollAdmin(admin.ModelAdmin): fieldsets = [

(None, {’fields’:[’user’, ’question’]}),

(’Date Information’, {’fields’:[’pub_date’], ’classes’: [’collapse’]}) ]

inlines = [ChoiceInline]

list_display = (’question’, ’pub_date’, ’was_published_today’) lists_filter = [’pub_date’]

search_fields = [’question’] date_hierarchy = ’pub_date’

def queryset(self, request):

qs = super(PollAdmin, self).queryset(request)

if request.user.is_superuser:

return qs

return qs.filter(user=request.user) admin.site.register(Poll, PollAdmin)

(29)

To check this functionality you’ll have to add a new user while logged in to the admin site (http://localhost:8000/admin/) as the superuser. You may notice when you create a user you can also set permissions. Django by default creates three permissions (add, change, and delete) for each model in your installed apps. Be sure to give your new user permission to edit polls and choices.

Figure 7.1: Permissions

If our new user account is_active, is_staff, and has Poll and Choice permissions then it will be able to edit Poll objects. But it should only be able to see Poll objects that are explicitly assigned to it. Of course if it assigns a poll to some other user it will no longer be able to see it. We can get around this limitation by adding another method to our ModelAdmin class:

def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "user" and not request.user.is_superuser:

kwargs[’queryset’] = User.objects.filter(id=request.user.id)

return super(PollAdmin, self)\

.formfield_for_foreignkey(db_field, request, **kwargs)

7.5

Login/Logout

So far you’ve probably been logging in and logging out by visiting the admin site which lets you login. But we’d like to provide our own customized loging and logout screens to our users. We can take advantage of the pre-built views that come with the django.contrib.auth view simply by adding them to our urls.py and passing a few configuration parameters.

(30)

urlpatterns = patterns(’’,

# ... snip ...

url(r’^login/$’, ’django.contrib.auth.views.login’), url(r’^logout/$’, ’django.contrib.auth.views.logout’), )

Visitinghttp://localhost:8000/login/will cause a template error however as the views depend on template (registration/login.html) that doesn’t exist. The Django documentation supplies a sample login template to use and I’ve added it to my github users branch athttps://github.com/simeonf/django-tutorial/tree/users. Adding a directory registration under your templates di-rectory and putting the login.html template in the registration didi-rectory makes the login form display when you reload the login url. Be sure to look at the contents of the template - it uses Django’s form processing library which we’ll be discussing shortly.

We still have one piece of configuration to do. The login view accepts an argument next that it will redirect the browser to when a user logs in sucessfully. We could link to our login page with "/login/?next=/polls/" if we want users who login to end up on the front page of our poll application. If the next variable isn’t present in the GET or POST, the login view redirects to settings.LOGIN_REDIRECT_URL which defaults to "/accounts/profile/". We can set this settings variable to /polls/ to make the default behavior do what we want.

Be sure to visithttp://localhost:8000/logout/ - by default it renders a "Goodbye" message using registration/logged_out.html. Since we haven’t provided this template it uses the one that is provided by the contrib.admin app. Let’s supply our own template so that we don’t see the admin application’s styles:

{% extends "base.html" %} {% block content %}

<p>You have been logged out.</p> {% endblock %}

7.6

PRACTICE ACTIVITY

We’ve covered enough of the authentication system that you should be able to upgrade our tutorial app to meet the following criteria:

1. Add a login and logout link to the base.html template and hook them up the contrib.auth login/logout views. 2. Only let logged in users vote on polls.

3. Restrict editing of polls in the admin to the user who owns a poll or the superuser. Be sure to create another non-superuser to test this functionality.

7.7

Users and Authentication Follow-up

Authentication is an important part of most webapps. We’ve covered enough to let users log in and out and to restrict access to content based on the user. Be sure to review the User Authentication documentation at https://docs.djangoproject.com/en/1.3/-topics/auth/

(31)

Chapter 8

Intro to Forms

8.1

Lesson Objectives

This lesson will introduce Django’s form library. We will learn to validate form data and handle forms in our views. We’ll also begin to discuss ways to customize the html output from our forms.

8.2

Forms

Form objects are a collection of data fields and validation rules that define acceptable data for a form. Forms are declarative in style, much like Django’s Model syntax and provide a large number of built in field types with a rich collection semantic meanings attached to them.

Most field types share common attributes like required and label that allow you to impose restrictions on the data that the field can accept and customize its display. Many field types have their own unique atttributes - max_value and min_value for numeric fields - for instance that provide simple validation. We can also specify the widget for a form which maps to an HTML input control and allows us to customize its attributes and add media that may be needed to render it.

Be sure to read the forms documentation starting athttps://docs.djangoproject.com/en/1.3/topics/forms/

The best place to put forms is in their own module. Django doesn’t create a blank forms.py in your application when you use the startapp command so you’ll have to do that yourself.

Consider a sample form in polls/forms.py

from django import forms

class RegistrationForm(forms.Form):

username = forms.CharField(max_length=30) email = forms.EmailField(label="E-mail") password1 = forms.CharField(label="Password") password2 = forms.CharField(label="Password Again")

This is a simple form with 4 fields - each of which will output an html <input type="text"> control. Let’s see how we might use form processing in a view and form rendering in a template.

8.3

Working with forms in views

Django distinguishes between bound forms and unbound forms. Basically this is the difference between forms that are handling data input (and therefore doing validation) and forms that have no data yet. Django makes this distinction because we don’t want to complain about validation errors if we haven’t entered any data in the form yet.

(32)

def contact(request):

if request.method == ’POST’: # If the form has been submitted...

form = ContactForm(request.POST) # A form bound to the POST data

if form.is_valid(): # All validation rules pass # Process the data in form.cleaned_data # ...

return HttpResponseRedirect(’/thanks/’) # Redirect after POST

else:

form = ContactForm() # An unbound form

return render_to_response(’contact.html’, {

’form’: form, })

Notice that the code creates one of two different form objects, one bound, the other unbound, depending on whether the view is handling an HTTP POST or not. Additionally there is a nested if - the bound form may or may not be valid and form processing should only occur if it is valid.

We can actually get the same effect with simpler code. I first saw the following pattern demonstrated by Daniel Greenfeld:

def register(request):

form = RegistrationForm(request.POST or None)

if form.is_valid():

# Do processing

return HttpResponseRedirect(reverse(’polls.views.index’))

return render(request, ’polls/register.html’, {’form’: form})

This is more than just a clever trick based on the shortcutting behavior of Python’s or operator - it removes duplication of code and a level of nesting in our if.

Note that the form api is fairly simple from the vantage point of our views: forms are created with request.POST or with no data and bound forms can be checked for validity. If a form is_valid() you can access the submitted data in the .cleaned_datamember variable. Notice also that the pattern remains to issue a redirect when a form is sucessfully pro-cessed. This prevents the user from accidentally resubmitting the form by pressing refresh in their browser.

8.4

Displaying forms in templates

In the view above the form is passed to a template as the context variable form. In the template we can simply evaluate the form variable and the default action is to render all the form fields as html input controls inside a table structure. Django doesn’t provide the <form> or <table> tags for us so we supply them ourselves:

<form action="" method="POST"> {% csrf_token %}

<h1>Enter your information to sign up</h1> <table>

{{ form }}

<tr><th></th><td><input type="submit" value="Submit"></td></tr> </table>

</form>

{% endblock %}

Notice the template tag csrf_token - that should always be the first thing in your form and emits the token Django uses to prevent CSRF attacks - seehttps://docs.djangoproject.com/en/1.3/ref/contrib/csrf/for details.

8.5

Validation

Django provides many hooks for validating our form fields and raising validation errors. See https://docs.djangoproject.com/en/-1.3/ref/forms/validation/for details - among our options are providing validator functions to our fields, writing custom fields that

(33)

do their own validation, providing clean_fieldname methods, and overriding the built in clean method. We’ll look at the last two options for improving our form:

class RegistrationForm(forms.Form):

username = forms.CharField(max_length=30) email = forms.EmailField(label="E-mail") password1 = forms.CharField(label="Password") password2 = forms.CharField(label="Password Again")

def clean_username(self):

username = self.cleaned_data[’username’]

if not username.isalpha():

raise forms.ValidationError("Usernames must contain only letters!")

return username

def clean(self):

data = self.cleaned_data

if not data.get(’password1’) == data.get(’password2’):

raise forms.ValidationError("Passwords must match!")

return data

Our clean_username method will automatically be called by our form after all other individual validations have run. It must return the cleaned data (modified if appropriate) and raise a forms.ValidationError if there was an error - the result will be an html error message attached to the control.

The clean method is called last and allows us to do validations that depend on several field values. We don’t have any guarantee that fields exist in the cleaned_data - but we can check the values if any and either raise validation errors or return the cleaned data. before

8.6

PRACTICE ACTIVITY

Let’s take what we’ve learned about forms so far and create a registration page for our poll application. This will mean adding a registration form to handle the data, a view which creates a new user when the form is valid, and a template to display the form. Go ahead and add a "Register" link to the base template as well.

8.7

Intro To Forms Follow-up

Form handling is a huge area in most data heavy Django applications and getting a handle on Forms, Fields, Widgets, and Validators will take some time and experience. As always - be sure to read all of the excellent Django documentation in the forms section.

(34)

Chapter 9

More Forms

9.1

Lesson Objectives

In Intro to Forms we learned to add custom validation to forms and how to instantiate and process our forms in our views. This lesson will focus on customizing the html output of our forms by using widgets and exploring alternative methods for rendering and layout.

9.2

Field and Widgets

We added validation to our RegistrationForm with custom validation methods - but it still isn’t very sophisticated in either presentation or validation. We can use specific field types to automatically provide some forms of validation and we can use widgetsto further customize the html display of our form components.

Be sure to check out Django’s extensive documentation athttps://docs.djangoproject.com/en/1.3/ref/forms/fields/of all the built in field types. One field type that looks promising is the RegexField which is basically a Charfield that validates submitted data against a regular expression. Using a Regexfield for our username would automatically provide the validation we supplied with the clean_username method. This might look like:

username = forms.RegexField(regex=r’^[\w]+$’, max_length=30,

error_messages={’invalid’:

"This value must contain only letters, " "numbers and underscores."}

)

We might also notice that our password fields are showing their contents when we type. We’re currently using a CharField for our password entry and looking at the list of fields shows no "PasswordField" to use. We can however choose a different widget when rendering our CharField:

password1 = forms.CharField(widget=forms.PasswordInput(attrs={’class’: ’required’}, render_value=False),

label="Password")

Django distinguishes between form fields which have data implications and form widgets which only effect the html display of the form field. Note that if we supply the widget we can also pass the attrs argument which is a dict whose keys and values will be used as the attributes of our html tag. This allows us, for example, to specify classes that can be used to style the html our form will generate.

The pattern of picking a widget to customize the output of a particular form field is a common one in Django forms. For instance consider that there is only one ChoiceField for selecting an item or items from a list. The ChoiceField renders with a Selectwidget by default - but we could use the same control and a RadioSelect widget to also allow the user to pick one

(35)

item from a list. If we wanted to let the user make multiple selections we might use a SelectMultiple widget to display a multi-select combobox or a CheckboxSelectMultiple widget to show a series of checkboxes. In each case the Field provides validation while the Widget controls display output.

9.3

Modifying Form Output

It’s also worth noting that we can change the way we output our forms as a whole. So far we’ve simply evaluated the template variable containing the form. This by default outputs a series of table rows containing the label and input controls for each form field like:

<tr>

<th><label for="id_username">Username:</label></th>

<td><input id="id_username" type="text" class="required" name="username" maxlength="30" / ←-></td>

</tr>

Django allows us to customize the row output by providing a class or classes that will be applied to rows with required inputs and rows with and error message:

class RegistrationForm(Form):

error_css_class = ’control-group error’ # Works with twitter bootstrap

required_css_class = ’required’

We can also choose to call the as_ul or as_p methods which emit <p> and <li> tags instead of table rows. And if none of the predefined methods of layout meet our criteria we can always manually specify the layout of the form by accessing the fields directly.

<div>

<label for="username">Username</label> {{ form.username }} {{ form.username.errors }} </div>

This style is to be avoided if possible since changes to the form will require changes to the template as well. With strong css skills the layout of your forms is not very dependent on the html structure - but you may find times when you have to manually specify your form layout to precisely match a design.

9.4

PRACTICE ACTIVITY

Can we improve our registration form? Try the following:

• add the control-group and error classes to form rows with errors - this will apply styles to the input control from our twitter-bootstrap styles.

• make sure the form validates usernames to limit to letters and numbers. Usernames should also be checked against the database to make sure they won’t conflict with an existing user.

• The password fields should be displayed in a "password" input control that conceals typing from the user. The two passwords should match.

• Play around with using css to customise the layout of the generated form. Do error messages display nicely? Is there enough space for the labels? Do we have good alignment for our labels, form inputs, and error messages?

(36)

9.5

Dynamic Choices

One other common usage pattern is to write a form that selects a particular object from the database. Django provides a ModelChoiceField for this situation but it requires a queryset as part of its construction. Frequently, however, we can’t write the queryset when the form is defined because the queryset will differ depending on the view. For example - we might want to write a form that displays a ModelChoiceField that allows the user to select a Choice for a particular Poll - but when a different Poll is selected we need to change the list of Choices as well.

The most common way of addressing this pattern is provide a blank queryset of the appropriate Model and let the view set a queryset directly on the field after the form is created. For example:

class ChoiceForm(forms.Form):

choice = forms.ModelChoiceField(queryset=Choice.objects.none())

def detail(request, poll_id):

poll = get_object_or_404(Poll, pk=poll_id) form = ChoiceForm()

form.fields[’choice’].queryset = poll.choice_set.all()

return render(request, ’polls/detail.html’, {’poll’: poll, ’form’: form})

9.6

PRACTICE ACTIVITY

Our detail page for an individual poll still has manually constructed radio inputs and the detail view is manually checking POST to validate the data. Update the detail and vote views and the detail.html template to use a form instead.

9.7

More Forms Follow-up

We still haven’t explored all the features of the form library - but we do have the ability to customize the html the form classes produce and have seena few more field and widget types. Be sure to look at the list of fields https://docs.djangoproject.com/en/-1.3/ref/forms/fields/and the list of widgetshttps://docs.djangoproject.com/en/1.3/ref/forms/widgets/in the official documenta-tion.

(37)

Chapter 10

Writing Tests with Django

10.1

Lesson Objectives

This lesson will introduce Django’s test integration. We will learn to write unit tests that test our Python code as well as use the Django test client to simulate browser requests. We’ll also learn how to run our tests and which kinds of tests to write.

10.2

Running Tests

Django integrates the Python stdlib UnitTest module by providing a default test runner and a management command to run it. Try it by running:

$ ./manage.py test polls

Creating test database for alias ’default’... .

Ran 1 test in 0.000s OK

Destroying test database for alias ’default’... What test? What just happened?

It turns out in an effort to encourage testing the startapp command created a single (trivially nonsensical) unit test when we created our polls sample app.

$ cat polls/test.test

"""

This file demonstrates writing tests using the unittest module. These will pass when you run "manage.py test".

Replace this with more appropriate tests for your application. """

from django.test import TestCase

class SimpleTest(TestCase):

def test_basic_addition(self):

"""

Tests that 1 + 1 always equals 2. """

(38)

If you’re familiar with UnitTest this should appear straightforward. For those of you new to UnitTest - using UnitTest means subclassing UnitTest and writing methods that start with the string "test". Each method should use a self.assert* as the test - methods are available to check equality, inequality, containment, exceptions and so forth. See http://docs.python.org/-library/unittest.html#unittest.TestCase.assertEqualfor the complete list of UnitTest assert methods. Django provides us with django.test.TestCasewhich subclasses UnitTest and adds some methods for integrating with Django’s urls and ORM. For your more complicated tests you may need to define a setUp method on your test class. The setUp (note the case) method will be called befor any test* methods and can be used to create initial data. Don’t worry about messing up your database - Django actually creates a new blank database when you run your tests so any test that relies on data accessed via the ORM will need to create that data first. Of course our existing test doesn’t touch the database - it just verifies that the Python addition operator works. This isn’t really the kind of thing we’ll need to test.

10.3

Writing Tests

So what sort of tests might we write? And what sort of things should we test? Clearly we don’t need to test that + works in Python code. Similarly there’s no point in testing core Django functionality - if Django itself isn’t working we have problems bigger than we can fix. What we want to test is our application functionality. Django provides us with a request simulator that we can use to simulate loading a particular url but I’d like to strongly discourage its use.

Tests written checking if a view works rely on the url mapping being correct, the view code working, any included templates rendering correctly and correct behavior of any model of form code we might write. In short - they aren’t unit tests because they aren’t testing a particular unit in isolation. They’re also likely to be much slower than genuine unit tests.

Let me show you an example:

class RealUnitTests(TestCase):

def test_username_clean(self):

data = dict(username=’bogus#’, email="[email protected]", password1="test", password2="test")

form = RegistrationForm(data)

form.is_valid() # Must call to set up form.cleaned_data # Did we get an error for username?

self.assertIn(’username’, form.errors)

class IntegrationTests(TestCase):

def setUp(self):

self.user = User.objects.create_user(username="bob",

email="[email protected]", password="test") self.poll = Poll(user=self.user,

question="Test Poll #1", pub_date=datetime.now()) self.poll.save()

def test_index_view(self):

# Assumes named url poll_index

resp = self.client.get(reverse(’poll_index’)) self.assertIn("Test Poll #1", resp.content)

Notice the difference between these two tests - one creates a form with sample data and tests a the custom clean_username method. No requests are made, no database access occurs.

The second example requires sample data and then simulates a load of a particular url. This sort of test is useful - but it isn’t really a unit test as it tests too many different pieces at once.

Write lots of unit tests - be sure to see the testing documentation athttps://docs.djangoproject.com/en/1.3/topics/testing/to ex-plore the different places you can put tests and the different tests you can write. Also write integration tests that use the built-in request.client or more sophisticated tools like Twill or Selenium to test the pages of your web application - but store those in their own app and run those test separately.

(39)

10.4

PRACTICE ACTIVITY

Write your own tests for the poll app. Test at least:

1. Write integration tests for each of your pages. Can you test posting to the vote url? 2. Write unit tests for any custom Model methods or Form methods you’ve added.

10.5

Writing Tests Follow-up

Testing is a vast field, particularly in web apps. Best practices in the area include test automation via a continuous integration server like Jenkins and the use of sophisticated browser emulation tools like Selenium. It’s also important that all the tests pass (so failures are noticed) and that all the tests run swiftly so that actually running them is encouraged. Simply starting to write a few Unit Tests, however, has the benefit of catching your code errors and also forcing you to write more testable code. Make an effort - every commit should have a test along with it that runs fast and passes!

References

Related documents

§ All pharmaceutical waste is collected in hazardous waste containers. § Mixed waste is removed to the central hazardous

2012: NLDC received grant funding for treatment and additional surveys, chemical treatment of Island Lake and the Spider-Island channel was unsuccessful due to use of

Pendekatan etnografi komunikasi digunakan untuk menuntun peneliti memahami bagaimana makna bahasa, komunikasi, dan kebudayaan saling bekerja sama untuk menghasilkan

The Investment Board cannot invest more than 20% of assets (at cost) outside Canada. As a result, at least 80% of cash flows at cost are being invested in Canadian equities and up to

Point Cloud Generation from Aerial Image Data Acquired by a Quadrocopter Type Micro Unmanned Aerial Vehicle and a Digital Still Camera. Direct Georeferencing of Ultrahigh-Resolution

After you process the baseline, you should test again the result of the processing, optimize the result, and transform the coordinate to the needed national coordinate or

Upon arriving at the laser center, you will be greeted and have the opportunity to ask any remaining questions regarding this consent. Before proceeding with your ASA procedure,

Financial planning is a systematic approach whereby the financial planner helps the customer to maximize his existing financial resources by utilizing financial tools to