Django best practices IV: Models, forms, managers, urls, admin, views and signals.

This is the fourth post about I write about how to build a Django project from scratch, focusing on the project structure, internationalization and localization configuration, models, managers, views, forms and templates. Take a look at Part IPart II and Part III.

In this post, I’ll show you how to create your fist app, with its models, forms, managers, urls, adminviews and signals.

Create your first app

Ok, finally you can create your first app. You should know that I am not interested in building a functional app in this post, but instead, I want to show you how to implement South, Internationalization and Localization properly, together with some good practices in Django.

If you followed this post from Part I, you should have created a folder named apps inside your project folder (with a __init__.py file inside). Go inside this folder and create your app (remember to have your virtual environment active). In this post, I will start building a task manager app:

$ cd apps
$ python ../../manage.py startapp taskmanager

Open your settings.py file and add your new app:

INSTALLED_APPS = (
    …
    ‘myproject.apps.taskmanager’,
    …
)

Note that as we have created the new app inside myproject folder, inside apps, we need to specify all these packages. If you have created the app in another folder, you should change this accordingly.

Open the models.py file of your new app and create your models there. A simple example might be:

# -*- coding: utf-8 -*-
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from pyproject.apps.taskmanager import managers
import datetime
from django.utils.timezone import utc

class Task(models.Model):
# Relations
user = models.ForeignKey(User,
related_name = "tasks",
verbose_name = _("user"),
)
# Attributes
name = models.CharField(max_length=130)
due_date = models.DateField(verbose_name = _("Due date"))
last_change = modelsDateField(verbose_name = _("last change"))
description = models.TextField(blank=True,
verbose_name = _("description"),
help_text = _("Enter the task description (optional)" ),
)
# Manager
objects = managers.TaskManager()

# Functions
def __unicode__(self):
return _("Task %s") % self.name

def save(self, *args, **kwargs):
self.last_change = datetime.datetime.utcnow().replace(tzinfo = utc)
super(Task, self).save(args, kwargs)

# Meta
class Meta:
verbose_name = _("Task")
verbose_name_plural = _("Tasks")
ordering = [&quot;-id&quot;] </code></pre>
There are a few things I want to highlight:

  • For readability, it is a good practice to separate the different properties of the model into relationsattributesmanager and functions. Especially if other programmers will have to deal with your code.
  • Use verbose_name to define the name that will be displayed. Otherwise, Django by default uses the object name replacing underscores with spaces.
  • Use help_text to define the help text that will be shown in forms. It should be a description of what you expect the user to write/insert in this field.
  • Use the ugettext_lazy function to indicate what will be translated. Note that at the beginning, we import that function with the short name _, so that we don’t have to type it every time we need it. I use it in every verbose_name and help_text, which are the pieces of code in this file that will be displayed to the user. Later on, I will explain where do you actually translate these words/sentences.
  • Use a custom object manager if you want to define complex queries of that object. For example, you might want to find, several times in your code, the first 5 next tasks the user has to do. You can define this query in your custom manager and use it anywhere in your code more easily.

As we don’t know yet which especial queries to define, open/create in the same folder a managers.py and define the trivial object manager:

# -*- coding: utf-8 -*-

from django.db import models

class TaskManager(models.Manager):

    pass

which inherits from the Django usual object manager and for now, does not have any special properties or queries. Stay tuned to this Blog for a post on complex Django queries. If you are impatient though, you can take a look at the Django official docs on object managers.

Once you have your models and managers defined, we can sync the database. First, it is a good practice to validate your models:

$ python manage.py validate

Correct the errors, if you have some, and then create the initial schema migration for your app:

$ python manage.py schemamigration taskmanager –init

Then, sync your database (if you have modified/included other package or app that is not managed by South) and perform the migration:

$ python manage.py syncdb
$ python manage.py migrate taskmanager

Check that your app still works locally on your machine

$ python manage.py runserver

Urls, views and forms

Once we have the models created, we can define the urls and the views. Open the main urls.py, the one that is in your project folder, and add

from django.config.urls import patterns, include, url
urlpatterns = patterns(”,
    …
    url(r’^taskmanager/’, include(‘myproject.apps.taskmanager.urls’, namespace=”taskmanager”)),
    …
)

Again, note the way in which a file from the taskmanager app is called, including in its path myproject.apps. Change this is you have your app in another folder.

Then, open or create a new urls.py file in your taskmanager app folder and write:

from django.conf.urls import patterns, url
from myproject.apps.taskmanager import views
urlpatterns = patterns(,
    url(r‘^$’, views.home,name=‘home’),
    url(r‘^tasks/$’, views.task_list, name=“task_list”),
    url(r‘^task/(?P<id>\d+)/$’, views.task_detail, name=“task_detail”),
    url(r‘^edit-task/$’, views.task_form, name=“task_form”),
    url(r‘^edit-task/(?P<id>\d*)/$’, views.task_form, name=“task_form”),
)

 

The first url is the home page of the app. The 2nd one contains a list of all the available tasks, and the 3rd one shows the details of the task with id=id. Finally, the 4th and 5th ones create or modify a task, respectively.

It is common to use the view names model_list for listing the elements of that model, model_detail to show the details of a specific model, and model_form to display the model form to edit a model.

Open your views.py and create your views. An example file would be:

# -*- coding: utf-8 -*-
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse

from myproject.apps.taskmanager import models
from myproject.apps.taskmanager import forms

def home(request):
return render(request,
&quot;taskmanager/home.html&quot;,
{}
)

def task_list(request):
tasks = models.Task.objects.all()
return render(request,
&quot;taskmanager/tasks_list.html&quot;,
{&#39;tasks&#39;: tasks}
)

def task_detail(request, id):
id = int(id)
task = get_object_or_404(models.Task, id=id)
return render(request,
&quot;taskmanager/task_detail.html&quot;,
{&#39;task&#39;: task}
)

def task_form(request, id=None):
if request.method == &quot;POST&quot;:
task_form = forms.TaskForm(request.POST)
if task_form.is_valid():
task = task_form.save()
return HttpResponseRedirect(reverse(&#39;taskmanager:home&#39;))
else:
if id:
task = get_object_or_404(models.Task, id=id)
task_form = forms.TaskForm(instance=task)
else:
task_form = forms.TaskForm()
return render(request,
&quot;taskmanager/task_form.html&quot;,
{&#39;task_form&#39;: task_form}
)

</code></pre>Again, I want to point out some things:

  • The shortcut render lets you load a template, create a context adding a bunch of variables by default, such as information about the current logged-in user, or the current language, render it and return an HttpResponse, all in one function. It is a shortcut for render_to_response together with RequestContext. Note: the information added by default depends on the template context processors that you have included in your settings file.
  • The shortcut get_object_or_404, as its name describes, returns the object if it exists, or a 404 error if it doesn’t.
  • The view task_form behaves differently weather the request method is post, in which case your are submitting the form, or not. Note that if the form is not valid the view renders the same response as if the request method is not POST.
  • The function HttpResponseRedirect redirects the request to a new url.
  • The function reverse is used to obtain that url from a shortcut name. Note that the argument of reverse is ‘taskmanager:task_list’, where taskmanager is the name of the namespace (defined in the main url.py file) and task_list is the name of your view (defined in your app url.py file). Therefore, response is redirected to the url of the tasks list.

As we have used a form to create a new task, we need to define it in the proper file. In the same app folder, open/create the file forms.py and write:

# -*- coding: utf-8 -*-
from django import forms
from myproject.apps.taskmanager import models
class TaskForm(forms.ModelForm):
    class Meta:
        model = models.Task
        exclude = [‘last_change’]

Again, this is a simple Django Model form that is written here just for completeness. For more complex forms, wait for new posts or consult the Django official docs.

This basic form takes as a model your Task model and creates all the necessary input fields excluding the last_change one. Remember that when we defined this model, we set a custom save method that upgrades this field automatically.

Templates

Assuming you followed Part II: Project structure and HTML5 Boilerplate implementation,  you’ll have a base.html file inside your mytemplates/templates folder, and with a “Hello world” message in it. Let’s embrace that message with a new block tag:

{% block content %} 
<p>Hello world! This is HTML5 Boilerplate. </p> 
{% endblock %}

The idea is the following: we will use this base.html file as a template from which the other app templates files will inherit to. Thus, we have to indicate where in the main template we want the new code to be included. This is done by the block tags of Django. Note that you can include multiple block tags, and the name of each block tag is up to you.

In the other app template files, we will include the same block content tag to indicate the code to be displayed. If that template does not contain any block content tag, the default text of Hello world will be displayed.

Go to mytemplates/templates and create a new folder called taskmanager, and create the templates of your app:

$ mkdir taskmanager
$ cd taskmanager
$ touch home.html task_list.html task_detail.html task_form.html

In each file we include the tag {% extends “base.html” %} so that all these templates will inherit from the base template. As a basic example, you could write these files with:

home.html

{% extends “base.html” %}

{% block content %}
This is the Home page of the Taskmanager app
{% endblock %}

tasks_list.html

{% extends “base.html” %}

{% block content %}
{% for task in tasks %}
{{task.name}}
{% endfor %}
{% endblock %}

task_detail.html

{% extends “base.html” %}
{% block content %}
{{task.name}}
{% endblock %}

task_form.html

{% extends “base.html” %}
{% block content %}
<form action=“{% url ‘taskmanager:task_form’%}” method=“POST”>
{% csrf_token %}
<table>
    {{ task_form.as_table }}
</table>
<input type=“submit” value=“Submit”>
</form>
{% endblock %}

In the task_form.html template, note that the form tag has an action argument. In this case, when we submit the form we go to the same url in which this template is rendered, and we could also write action=””. Moreover, note the csrf_token tag. This is a special tag that is used for security purposes, to control that the user who submits the form is the one that recieve it form the server.

Run your app locally to make sure everything works as expected.

$ python manage.py runserver

And try every url to check all the templates you have created. In your edit task url you should also see the help text you have included in your models.

Admin

When I installed Django, the admin was activated by default. However, if you have a version older than 1.6, you will have to activate it yourself, see the official Django docs.

It is very useful to set Django admin to manage all your app models. This way, you can control if a new instance has been created, if a signal that creates another instance has worked, or delete test instances. For each model of your app I recommend you write, in the corresponding admin.py file, something like

from django.contrib import admin
from taskbuster.apps.taskmanager import models
class TaskAdminModel(admin.ModelAdmin):
    class Meta:
        models.Task
admin.site.register(models.Task, TaskAdminModel)

This simple code registers the Task model your Admin app, with all its fields and properties. Wait for new posts for more complex examples 🙂

Signals

In the app that I am working on I need to create another model instance just after a new user has been created. You might have something similar, if you want to create a Profile instance of each user.

To implement this we can work with signals. Django-registration (check out Part III: Install South, Localization, Internationalization and Django-registration) uses two custom signals that are sent when 1) a user is activated and 2) when a user is registered.  Note the two steps in registering a user: first, a user is registered but not active. Second, an email is sent to the user with a link to activate his account. Here, I will show how to create a new instance model when the signal 2) is sent.

Signals must be defined in a module that gets imported early by Django, so that the signal handling gets registered before any signals need to be sent. Thus, it is recommended that your define them in your models.py file.

Open the models.py file that contains the model of the instance you want to create and write:

from registration.signals import user_registered
from django.dispatch import receiver
@receiver(user_registered)
def new_profile(sender, user, request, **kwargs):

    Profile(user=user).save()

Sync your database and migrate your app, if needed, and check that when you create a new user account, an instance of the Profile model is also created (you can use the admin interface).

That’s all for today! 🙂

Please give a +1 if you want more Django posts! 🙂

Google+TwitterLinkedInFacebookReddit

Please, add +Marina Mele in your comments. This way I will get a notification email and I will answer you as soon as possible! :-)