7401ICT: Django IV: More techniques


(Under construction)

State maintenance

HTTP is a stateless protocol, i.e., every request is independent of all previous requests.

But applications require state to be maintained as users move from one page to another. Examples of state that needs to be maintained include the identity of the current user, the contents of an (invalid) form, the itinerary of a vacation, and the contents of a shopping cart.

There are many ways an application can maintain state. The most important ways are cookies and sessions, but URLs, hidden form elements and frames, and databases may also be used.

Cookies are small, limited-duration, browser-specific, text files, associated with particular URLs, stored on client computers. All browsers provide preferences for managing their cookies. (Django requires cookies to be enabled.)

Sessions are dictionary-like objects, on the server or in the database, that map variables to values, and persist across successive HTTP requests (from the same browser). They can be used to store the logged-in user, length of session, session history, shopping cart, etc.

To enable sessions, do the following:

You may then store and retrieve session variables in views using the dictionary request.session, e.g., request.session["user"] = "me". There are various options for setting the duration or termination condition of a session and programmers should call the function request.session.flush() when a session terminates.

Django stores session variables in a database table; PHP stores session variables in the file system, which provides faster access. To change Django to store session variables in the file system instead of a database, set SESSION_ENGINE in settings.py to "django.contrib.sessions.backends.file". You might also need to change the SESSION_FILE_PATH setting.

See How to use sessions in the documentation for more details.

See the simple user authentication project (user_auth.zip) or the weblog project (below) as examples.

User authentication

The admin interface provides one way to authorise users to create, update and delete objects through the admin interface. You can also create superusers from the command line using the manage.py createsuperuser command. These are OK for group Weblogs, etc. But they're not enough if we want to allow readers to register, login, logout, and so on.

However, the same library can be used to register and authenticate users for discussion groups, shopping sites, etc.

See User authentication in Django in the documentation.

See also Chapter 14: Sessions, Users, and Registration of The Django Book.

The main module is django.contrib.auth.models. The main class in this module is User:

from django.contrib.auth.models import User

The class User has fields username, first_name, last_name, email, password, and others (as visible from the admin interface).

The class User has instance methods is_authenticated(), get_full_name(), check_password(password), set_password(password), get_profile() (described later), and many others.

As seen from the admin interface, users may or may not be staff, may or may not be active, may or may not be superusers. Users may or may not have various permissions. Each user may be in one or more groups of users; permissions may be granted to or revoked from all users in a group. (These groups and permissions will probably not be required in this course.)

To create a user in a view, use the create_user() function:

user = User.objects.create_user(username, email, password)

When the created user is saved in the database, the password is encrypted by a one-way hash function so that it is secure.

Django provides many standard views and forms to support user registration, login and logout using this module.

See the simple user authentication project (user_auth) as a very simple example of how this works.

User profiles

Often we want to store more information about a user than username, first and last names, email and password. This requires the use of a "user profile":

class UserProfile:
    user = models.OneToOneField(User)
    birthdate = models.DateField(blank=true)
    phone = models.CharField(max_length=20)
    ...

The function user.get_profile() returns the profile with this extra information associated with user.

To make this work, it is necessary to add the assignment

AUTH_PROFILE_MODULE = 'app.UserProfile'
to file settings.py. Here, app is the app in which the class UserProfile is defined.

Form classes based on classes User and UserProfile can be defined for entering user information. This can be a bit tricky.

See the extended user authentication project (user_profile) to see how project user_auth can be extended to provide long user creation forms and additional information about each user in (user) profiles.

Higher-level operations

User authentication can be closely linked with HTTP requests.

With appropriate middleware enabled (it is enabled by default), views may refer to the field request.user whose value is either the currently logged-in user (in this session), or an anonymous user. It's used as follows:

if request.user.is_authenticated():
    # Do something for authenticated users.
else:
    # Do something for anonymous users.

A common pattern for logging in is the following:

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        if user.is_active:
            login(request, user)
            # Redirect to a success page.
        else:
            # Return a 'disabled account' error message
    else:
        # Return an 'invalid login' error message.

In fact, we can use Python decorators to do this even more simply, for example:

@login_required
def add_item_to_cart(request, item_id):
    # get item with given id and add it to the items in the current user's cart
    ...

If the user is not logged in, a login form is presented, and after a successful login the view is executed. It's possible to set which URL is used to redirect to the login form.

Higher-level applications

Categories and tags

In many applications, particularly shopping applications, administrators define a fixed set of categories, and registered users who add items state which category that item belongs to. Normally an item belongs to a single category. To implement this, a separate Category model is required, and each item has a foreign key to a category.

In other applications, users define their own categories, called tags. In such cases, each user-added item may belong to one or more tags. Again, a separate Tag model is required, but now each item has a many-to-many field to a set of tags.

To make this easier, there is an application for this purpose:

Comments

Django's comments frameworks allows users to add comments to news articles, blog entries, photos, whatever.

See Django's comments framework for a description of how to do this. The basic steps for adding and viewing comments associated with some object are as follows:

Default versions of preview.html and posted.html may be found in /path/to/django_package/contrib/comments/templates/comments/. On dwarf (in 2011), the /path/to/django_package/ is /usr/lib/python2.6/site-packages/django/. These and other templates may be edited to taste, for example, you may add the following line to your version of posted.html:

<a href="{{ comment.content_object.get_absolute_url }}">Return to the entry.</a>

See the weblog project (weblog.zip, coltrane.zip) from Practical Django Projects, Chapters 4 to 7, as an example.

An important issue when providing a comment feature is the need to moderate comments. See Generic comment moderation and Practical Django Projects for descriptions of how to do this.

A desirable feature with comments is the ability for authors to use markup without having to use the complexity of HTML (which is normally escaped anyway). A suitable markup package for this is markdown (sic). Its use is well described in Practical Django Projects. See also my solution to Assignment 1.

Generating news feeds

Django comes with a high-level syndication-feed-generating framework that makes creating RSS and Atom feeds easy. See The syndication feed framework in the documentation.

See also my notes on XML, news feeds, Atom and AtomPub for more details.

This is also described in Practical Django Projects by James Bennett.

Middleware

To be completed...

Examples