Die erste Applikation

Nun beginnen wir mit der ersten Applikation für unser Projekt “Kochbuch”.

Das Datenmodell ist wie folgt aufgebaut:

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];
  

}

  • unsere App heisst recipes
  • es gibt zwei Models: Recipe und Category
  • im Datenmodell fett gedruckte Felder sind Pflichtfelder
  • das Feld id wird vom Django ORM automatisch als Primärschlüssel angelegt
  • beide Models sind durch eine n-m Verknüpfung category verbunden
  • Recipe.author ist mit dem User Model verbunden

Anlegen der Applikation

Die Applikation soll die Rezepte verwalten, also nennen wir sie recipes:

$ cd cookbook
$ python manage.py startapp recipes

Das Kommando legt ein Verzeichnis recipes an, in dem sich vier Dateien befinden:

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

Die Datei __init__.py definiert, wie schon beim Konfigurationsverzeichnis, dass das Verzeichnis recipes ein Python Paket ist. Die Models der Applikation werden wir gleich in der Datei models.py anlegen. Tests werden in der Datei tests.py erstellt. Die Datei views.py enthält die Views der Applikation.

Die Models

Öffne die Datei models.py in einem Texteditor. Sie enthält nur einen import:

from django.db import models

Damit wird das Paket, dass die Felder und andere Teile des ORMs enthält, geladen.

Damit du später keine Probleme mit dem Encoding bekommst füge noch vor dem import folgende Zeile hinzu:

# encoding: utf-8

Ein Model für die Kategorien

Unter diesen beiden Zeilen beginnen wir mit dem ersten Model für die Kategorien:

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

Das Model hat nun drei Attribute, die drei Feldern in einer Tabelle entsprechen. Die Feldtypen definieren den Datentyp.

Das Attribut name entspricht zum Beispiel einem VARCHAR(100) in der Datenbank.

Als ersten Parameter kann man optional einen Titel für das Feld angeben, der dann in der Admin-Applikation benutzt wird.

Der Parameter blank=True ermöglicht es dieses Feld leer zu lassen. Alle Felder eines Models sind also Pflichtfelder.

Nun wird die Klasse Category noch mit dem folgenden Code erweitert:

class Meta:
    verbose_name = u'Kategorie'
    verbose_name_plural = u'Kategorien'

def __unicode__(self):
    return self.name

Die Klasse Meta hat zwei Attribute, die den Namen des Models bestimmen.

Die Methode __unicode__ soll einen Unicode-String zurückgeben. Dies wird zum Beispiel in der Admin-Applikation benutzt.

Das Model für die Rezepte

Jetzt legen wir das zweite Model für die Rezepte an:

class Recipe(models.Model):
    """Recipe model."""
    title = models.CharField(u'Titel', max_length=255)
    slug = models.SlugField(unique=True)
    ingredients = models.TextField(u'Zutaten',
        help_text=u'Eine Zutat pro Zeile angeben')
    preparation = models.TextField(u'Zubereitung')
    time_for_preparation = models.IntegerField(u'Zubereitungszeit',
        help_text=u'Zeit in Minuten angeben', blank=True, null=True)
    number_of_portions = models.PositiveIntegerField(u'Anzahl der Portionen')

Das Model ist dem ersten ähnlich. Neu ist der Parameter help_text, der in der Bearbeitungsansicht der Admin-Applikation als Hilfe benutzt wird.

Neu ist auch das IntegerField. Wenn man bei diesem keine Eingabe verlangt sollte man den Parameter null=True benutzen, denn sonst wird ein leerer String benutzt.

Außerdem bekommt das Model noch fünf weitere Felder:

difficulty = models.SmallIntegerField(u'Schwierigkeitsgrad')
category = models.ManyToManyField(Category, verbose_name=u'Kategorien')
author = models.ForeignKey(User, verbose_name=u'Autor')
date_created = models.DateTimeField(editable=False)
date_updated = models.DateTimeField(editable=False)

Hier stellen wir eine Relation zum Model Category mit Hilfe des Feldtyps ManyToManyField her. Da dieser als erstes Argument die Klasse erwartet, mit der die Relation hergestellt werden soll, müssen wir den Bezeichner des Felds in der Admin-Applikation mit dem Parameter verbose_name angeben.

Den Autor eines Rezepts legen wir über einen ForeignKey fest, also eine 1-n Beziehnung.

Die Zeitangaben sollen nicht in der Admin-Applikation bearbeitet werden, deshalb benutzen wir den Parameter editable=False.

Damit das Objekt User auch zur Verfügung steht muss vor dem ersten import ein weiterer eingefügt werden:

from django.contrib.auth.models import User

Wir importieren das Model User aus einer Applikation mit dem Namen auth, die Django mitbringt.

Das Feld difficulty ist vom Typ SmallIntegerField. Nun sollen die Benutzer nicht eine Zahl eingeben, sondern eine Auswahlliste benutzen. Deshalb legen wir am Anfang der Klasse eine Liste von Auswahlmöglichkeiten an:

DIFFICULTY_EASY = 1
DIFFICULTY_MEDIUM = 2
DIFFICULTY_HARD = 3
DIFFICULTIES = (
    (DIFFICULTY_EASY, u'einfach'),
    (DIFFICULTY_MEDIUM, u'normal'),
    (DIFFICULTY_HARD, u'schwer'),
)

Diese Verknüpfen wir mit dem Feld:

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

Zuletzt muss wieder eine Meta Klasse und eine __unicode__ Methode erstellt werden:

class Meta:
    verbose_name = u'Rezept'
    verbose_name_plural = u'Rezepte'
    ordering = ['-date_created']

def __unicode__(self):
    return self.title

Zusätzlich benutzen wir das Attribut ordering der Meta Klasse, um die Standardsortierung der Datensätze zu bestimmen.

Außerdem wollen wir, dass die Zeitangaben automatisch ausgefüllt werden, da sie ja nicht in der Admin-Applikation bearbeitet werden können. Dazu überschreiben wir die Methode save:

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

Das Feld date_created wird nur gefüllt, wenn das Model zum ersten mal gespeichert wird und daher noch kein Attribut id besitzt. Das Feld date_updated wird bei jedem Speichern aktualisiert. Am Ende wird die save Methode der Elternklasse mit Hilfe der Funktion super aufgerufen.

Das Paket now müssen wir ebenfalls noch importieren. Also schreiben wir an den Anfang der Datei:

from django.utils.timezone import now

Bemerkung

Mehr zum Thema import kannst du im PEP 8, in der Python Dokumentation sowie diesem kurzen Artikel nachlesen.

Die vollständige Datei

Die Datei models.py sollte nun so aussehen:

# 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):
    """Category model."""
    name = models.CharField(u'Name', max_length=100)
    slug = models.SlugField(unique=True)
    description = models.TextField(u'Beschreibung', blank=True)

    class Meta:
        verbose_name = u'Kategorie'
        verbose_name_plural = u'Kategorien'

    def __unicode__(self):
        return self.name


class Recipe(models.Model):
    """Recipe model."""
    DIFFICULTY_EASY = 1
    DIFFICULTY_MEDIUM = 2
    DIFFICULTY_HARD = 3
    DIFFICULTIES = (
        (DIFFICULTY_EASY, u'einfach'),
        (DIFFICULTY_MEDIUM, u'normal'),
        (DIFFICULTY_HARD, u'schwer'),
    )
    title = models.CharField(u'Titel', max_length=255)
    slug = models.SlugField(unique=True)
    ingredients = models.TextField(u'Zutaten',
        help_text=u'Eine Zutat pro Zeile angeben')
    preparation = models.TextField(u'Zubereitung')
    time_for_preparation = models.IntegerField(u'Zubereitungszeit',
        help_text=u'Zeit in Minuten angeben', blank=True, null=True)
    number_of_portions = models.PositiveIntegerField(u'Anzahl der Portionen')
    difficulty = models.SmallIntegerField(u'Schwierigkeitsgrad',
        choices=DIFFICULTIES, default=DIFFICULTY_MEDIUM)
    category = models.ManyToManyField(Category, verbose_name=u'Kategorien')
    author = models.ForeignKey(User, verbose_name=u'Autor')
    date_created = models.DateTimeField(editable=False)
    date_updated = models.DateTimeField(editable=False)

    class Meta:
        verbose_name = u'Rezept'
        verbose_name_plural = u'Rezepte'
        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)

Die Applikation aktivieren

Damit wir die Applikation im Projekt nutzen können müssen wir sie in die Konfiguration eintragen.

Öffne dazu die Datei settings.py und füge den Namen unserer Applikation am Ende von INSTALLED_APPS ein.

Danach sieht INSTALLED_APPS so aus:

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'
)