The first application

Now we start with the first application for our Cookbook project.

Here is the data model:

digraph name {
  fontname = "Helvetica"
  fontsize = 8

  node [
    fontname = "Helvetica"
    fontsize = 8
    shape = "plaintext"
  ]
  edge [
    fontname = "Helvetica"
    fontsize = 8
  ]



subgraph cluster_recipes_models {
  label=<
        <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
        <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER"
        ><FONT FACE="Helvetica Bold" COLOR="Black" POINT-SIZE="12"
        >recipes</FONT></TD></TR>
        </TABLE>
        >
  color=olivedrab4
  style="rounded"


    recipes_models_Category [label=<
    <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
     <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
     ><FONT FACE="Helvetica Bold" COLOR="white"
     >Category</FONT></TD></TR>
    
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">id</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">AutoField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">name</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">CharField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">slug</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">SlugField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">description</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">TextField</FONT
        ></TD></TR>
        
    
    </TABLE>
    >]

    recipes_models_Recipe [label=<
    <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
     <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
     ><FONT FACE="Helvetica Bold" COLOR="white"
     >Recipe</FONT></TD></TR>
    
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">id</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">AutoField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">title</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">CharField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">slug</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">SlugField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">ingredients</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">TextField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">preparation</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">TextField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">time_for_preparation</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT COLOR="#7B7B7B" FACE="Helvetica Bold">IntegerField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">number_of_portions</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">PositiveIntegerField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">difficulty</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">SmallIntegerField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">date_created</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">DateTimeField</FONT
        ></TD></TR>
        
        <TR><TD ALIGN="LEFT" BORDER="0"
        ><FONT FACE="Helvetica Bold">date_updated</FONT
        ></TD>
        <TD ALIGN="LEFT"
        ><FONT FACE="Helvetica Bold">DateTimeField</FONT
        ></TD></TR>
        
    
    </TABLE>
    >]


}


  

  
  
  django_contrib_auth_models_User [label=<
      <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
      <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4"
      ><FONT FACE="Helvetica Bold" COLOR="white"
      >User</FONT></TD></TR>
      </TABLE>
      >]
  
  recipes_models_Recipe -> django_contrib_auth_models_User
  [label="author (recipe)"] [arrowhead=none, arrowtail=dot];
  
  
  recipes_models_Recipe -> recipes_models_Category
  [label="category (recipe)"] [arrowhead=dot arrowtail=dot, dir=both];
  

}

  • the app’s name is recipes
  • there are two data models: Recipe and Category
  • bold fields in the model are required
  • the field id will be generated automatically by Django’s ORM and used as primary key
  • both models are connected through a many-to-many relationship named category
  • Recipe.author is connected to the User model

Creating the application

The application has to store recipes, so we name it recipes:

$ cd cookbook
$ python manage.py startapp recipes

This command creates a folder recipes which contains four files:

recipes/
|-- __init__.py
|-- models.py
|-- tests.py
`-- views.py

As in the cookbook directory, the file __index__.py makes the folder a Python package. The application’s models go into models.py. Tests go into tests.py and views into views.py.

The models

Open the file models.py in a text editor. It only contains an import:

from django.db import models

The models module contains the fields and other parts of the ORM.

To make your life easier with special characters, add the following line above the import:

# encoding: utf-8

The model for the categories

Below these two lines we start with the first model for the categories:

class Category(models.Model):
    """
    A model class describing a category.
    """
    name = models.CharField(u'Name', max_length=100)
    slug = models.SlugField(unique=True)
    description = models.TextField(u'Description', blank=True)

The model has three attributes which correspond to three rows in a table. The field types define data type.

For instance the attribute name leads to VARCHAR(100) in the database.

The first parameter is optional and can be used to give the field a name which will be used as label in the admin application.

The parameter blank=True allows the field to be empty. All fields are required in default.

Now we extend the class Category with the following code:

class Meta:
    verbose_name = u'Category'
    verbose_name_plural = u'Categories'

def __unicode__(self):
    return self.name

The class Meta has two attributes which define the model’s name.

The __unicode__ method returns a unicode string. This will be used in the admin application amongst other things.

The models for the recipes

Now we create the second model for the recipes:

class Recipe(models.Model):
    """
    A model describing a coobook recipe.
    """
    title = models.CharField(u'Title', max_length=255)
    slug = models.SlugField(unique=True)
    ingredients = models.TextField(u'Indigrents',
        help_text=u'One indigrent per line')
    preparation = models.TextField(u'Preparation')
    time_for_preparation = models.IntegerField(u'Preparation time',
        help_text=u'In minutes', blank=True, null=True)
    number_of_portions = models.PositiveIntegerField(u'Number of portions')

This models is similar to the first one. We introduced the parameter help_text which will be shown as help text in the admin application’s edit mode.

There is also an IntegerField. You should use null=True here if no input is required, because otherwise an empty string will be used.

Now add five more fields to the model:

difficulty = models.SmallIntegerField(u'Difficulty')
category = models.ManyToManyField(Category, verbose_name=u'Categories')
author = models.ForeignKey(User, verbose_name=u'Author')
date_created = models.DateTimeField(editable=False)
date_updated = models.DateTimeField(editable=False)

Here we use a ManyToManyField to create a relation to the Category model. The ManyToManyField expects the related model class as first argument. Therefore we have to define the label in the admin interface with the named parameter verbose_name.

The recipe’s author is stored in a ForeignKey field which represents a many-to-one relation.

The time values shouldn’t be editable in the admin application, so we set the parameter editable=False.

The User object has to be imported to be available. We import it from Django’s auth application:

from django.contrib.auth.models import User

The field difficulty is a SmallIntegerField. Because the users shouldn’t have to enter a number but get a list, we create the choices at the beginning of the model class:

DIFFICULTY_EASY = 1
DIFFICULTY_MEDIUM = 2
DIFFICULTY_HARD = 3
DIFFICULTIES = (
    (DIFFICULTY_EASY, u'easy'),
    (DIFFICULTY_MEDIUM, u'normal'),
    (DIFFICULTY_HARD, u'hard'),
)

These we link with the field difficulty:

difficulty = models.SmallIntegerField(u'Difficulty',
    choices=DIFFICULTIES, default=DIFFICULTY_MEDIUM)

We also add a Meta class and a __unicode__ method:

class Meta:
    verbose_name = u'Recipe'
    verbose_name_plural = u'Recipes'
    ordering = ['-date_created']

def __unicode__(self):
    return self.title

In addition we use the attribute ordering in the Meta class to define the default ordering.

The time values should be filled automatically because we didn’t allow them to be edited in the admin application. To make this happen we override the save method:

def save(self, *args, **kwargs):
    if not self.id:
        self.date_created = now()
    self.date_updated = now()
    super(Recipe, self).save(*args, **kwargs)

The field date_created only gets filled when the model is saved the first time what is determined by id not having a value. The field date_updated will be refreshed every time the model gets saved. At the bottom of the save method we call the super function.

The package now also has to be imported so we add the following line to the top of the file:

from django.utils.timezone import now

Note

You can read more about import in PEP 8, in the Python documentation and in this article.

The whole file

The file models.py now should look like this:

# encoding: utf-8
from django.contrib.auth.models import User
from django.db import models
from django.utils.timezone import now


class Category(models.Model):
    """
    A model class describing a category.
    """
    name = models.CharField(u'Name', max_length=100)
    slug = models.SlugField(unique=True)
    description = models.TextField(u'Description', blank=True)

    class Meta:
        verbose_name = u'Category'
        verbose_name_plural = u'Categories'

    def __unicode__(self):
        return self.name


class Recipe(models.Model):
    """
    A model describing a coobook recipe.
    """
    DIFFICULTY_EASY = 1
    DIFFICULTY_MEDIUM = 2
    DIFFICULTY_HARD = 3
    DIFFICULTIES = (
        (DIFFICULTY_EASY, u'easy'),
        (DIFFICULTY_MEDIUM, u'normal'),
        (DIFFICULTY_HARD, u'hard'),
    )
    title = models.CharField(u'Title', max_length=255)
    slug = models.SlugField(unique=True)
    ingredients = models.TextField(u'Indigrents',
        help_text=u'One indigrent per line')
    preparation = models.TextField(u'Preparation')
    time_for_preparation = models.IntegerField(u'Preparation time',
        help_text=u'Zeit in Minuten angeben', blank=True, null=True)
    number_of_portions = models.PositiveIntegerField(u'Number of portions')
    difficulty = models.SmallIntegerField(u'Difficulty',
        choices=DIFFICULTIES, default=DIFFICULTY_MEDIUM)
    category = models.ManyToManyField(Category, verbose_name=u'Categories')
    author = models.ForeignKey(User, verbose_name=u'Author')
    date_created = models.DateTimeField(editable=False)
    date_updated = models.DateTimeField(editable=False)

    class Meta:
        verbose_name = u'Recipe'
        verbose_name_plural = u'Recipes'
        ordering = ['-date_created']

    def __unicode__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.id:
            self.date_created = now()
        self.date_updated = now()
        super(Recipe, self).save(*args, **kwargs)

Activating the application

To use the application in our project, it has to be activated in the configuration.

Open the file settings.py and add the application’s name the end of INSTALLED_APPS.

INSTALLED_APPS now looks like this:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    'recipes'
)