Now we start with the first application for our Cookbook project.
Here is the data model:
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.
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
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.
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 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)
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'
)