5. Models erstellen

“A model is the single, definitive source of information about your data. It contains the essential fields and behaviors of the data you’re storing. Generally, each model maps to a single database table.”

Models | Django documentation

Der nächste Schritt ist das Erstellen der Datenstrukturen mit Hilfe der Models.

Models definieren, wie Daten gespeichert werden. Üblicherweise repräsentiert ein Model eine Tabelle in der Datenbank und besteht aus Feldern, Metadaten und Methoden. Durch diese Informationen kann Django automatisch eine Schnittstelle zur Datenbank generieren, die es erlaubt, objektorientiert auf diese zuzugreifen. Man spricht hierbei auch von Object-Relational-Mapping (ORM).

An entity–relationship model of our application

Bei django-marcador werden zwei Models benötigt. Diese werden im oben stehenden Diagramm dargestellt. Bookmark repräsentiert die eigentlichen Lesezeichen und enthält dementsprechend Felder für URL, Titel und Beschreibung. Darüber hinaus werden Daten über den Ersteller, die Sichtbarkeit und Zeitstempel gespeichert. Diese Daten werden später vor allem zum Filtern der Einträge verwendet. Ein weiteres Model Tag repräsentiert Schlagwörter, die einem Lesezeichen zugewiesen werden können und dem leichteren Auffinden dienen. Zwischen Bookmark und Tag besteht eine m:n Beziehung (Many-to-many) für die Django automatisch eine Zwischentabelle erstellt.

Damit Django eine Klasse als Model erkennt, muss diese von django.db.models.Model erben und in der Datei models.py stehen, die sich im Verzeichnisses einer App (hier mysite/marcador/) befindet.

5.1. Felder

Felder werden als Klassenattribute definiert und entsprechen den Spalten einer Tabelle. Django bietet verschiedene Typen von Feldern, um die Daten in einem möglichst sinnvollen Format abzulegen. So entspricht beispielsweise CharField einer Spalte vom Typ VARCHAR in einer SQL-Datenbank. Eine Liste der Feldtypen kannst du in der Django-Dokumentation finden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# encoding: utf-8
from django.contrib.auth.models import User
from django.db import models


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)


class Bookmark(models.Model):
    url = models.URLField()
    title = models.CharField('title', max_length=255)
    description = models.TextField('description', blank=True)
    is_public = models.BooleanField('public', default=True)
    date_created = models.DateTimeField('date created')
    date_updated = models.DateTimeField('date updated')
    owner = models.ForeignKey(User, verbose_name='owner',
        related_name='bookmarks')
    tags = models.ManyToManyField(Tag, blank=True)

5.2. Metadaten

Models können Metadaten enthalten, welche die Darstellung oder das Verhalten beeinflussen. Sie werden in der internen Klasse Meta definiert. Im Beispiel werden der Name für die Darstellung (singular und plural) sowie die Standardsortierung festgelegt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# encoding: utf-8
from django.contrib.auth.models import User
from django.db import models


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    class Meta:
        verbose_name = 'tag'
        verbose_name_plural = 'tags'
        ordering = ['name']


class Bookmark(models.Model):
    url = models.URLField()
    title = models.CharField('title', max_length=255)
    description = models.TextField('description', blank=True)
    is_public = models.BooleanField('public', default=True)
    date_created = models.DateTimeField('date created')
    date_updated = models.DateTimeField('date updated')
    owner = models.ForeignKey(User, verbose_name='owner',
        related_name='bookmarks')
    tags = models.ManyToManyField(Tag, blank=True)

    class Meta:
        verbose_name = 'bookmark'
        verbose_name_plural = 'bookmarks'
        ordering = ['-date_created']

5.3. Methoden

You can now add Model functionality Methods for actions that apply to a single record. For instance, it is usual to create a human readable form of the record with the method __str__.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# encoding: utf-8
from django.contrib.auth.models import User
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now


@python_2_unicode_compatible
class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    class Meta:
        verbose_name = 'tag'
        verbose_name_plural = 'tags'
        ordering = ['name']

    def __str__(self):
        return self.name


@python_2_unicode_compatible
class Bookmark(models.Model):
    url = models.URLField()
    title = models.CharField('title', max_length=255)
    description = models.TextField('description', blank=True)
    is_public = models.BooleanField('public', default=True)
    date_created = models.DateTimeField('date created')
    date_updated = models.DateTimeField('date updated')
    owner = models.ForeignKey(User, verbose_name='owner',
        related_name='bookmarks')
    tags = models.ManyToManyField(Tag, blank=True)

    class Meta:
        verbose_name = 'bookmark'
        verbose_name_plural = 'bookmarks'
        ordering = ['-date_created']

    def __str__(self):
        return '%s (%s)' % (self.title, self.url)

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

Beim Bookmark-Model wird außerdem die Methode save() überschrieben, um beim Speichern automatisch das richtige Erstellungs- bzw. Änderungsdatum zu setzen. Der Zustand des Felds id wird benutzt, um zu entscheiden, ob diese Model Instanz schon einmal gespeichert wurde oder nicht. Das Feld id existiert an jedem Model, denn wenn es nicht explizit deklariert wird erzeugt es Django automatisch. Es ist der Primärschlüssel des Models (damit kann jeder Datensatz eindeutig identifiziert werden). Existiert dieser nicht, wurde das Model noch nie gespeichert. Die Funktion super() ruft zuletzt die Methode save() an der Klasse auf, von der unsere Klasse geerbt hat.

Bemerkung

Marcador unterstützt Python 2 und 3. Dies wird durch die Verwendung des Dekorators @python_2_unicode_compatible und von __str__ anstelle von __unicode__ erreicht, dass du in älteren Versionen der Dokumentation finden wirst.

5.4. Manager

Für jedes Model existiert ein Manager, über den die Abfragen (Queries) erfolgen. Sofern nicht anders festgelegt, wird er über das Attribut objects der Model-Klasse angesprochen. Durch das Überschreiben oder Hinzufügen eines Managers können die Abfragen beeinflusst werden.

Die Django-Dokumentation beschreibt die Verwendung von Managern wie folgt:

“To retrieve objects from your database, construct a QuerySet via a Manager on your model class.

A QuerySet represents a collection of objects from your database. It can have zero, one or many filters – criteria that narrow down the collection based on given parameters. In SQL terms, a QuerySet equates to a SELECT statement, and a filter is a limiting clause such as WHERE or LIMIT.”

Making queries | Django documentation

Most of the QuerySet methods return a new QuerySet so you can combine multiple of them. To access the items in a QuerySet you can loop over it or simply use the item’s index. For example the following Python code returns the first item of a QuerySet which contains all public bookmarks ordered by their title:

>>> Bookmark.objects.filter(is_public=True).order_by('title')[0]

The SQL query executed against the database for the Python code above looks like this:

SELECT "marcador_bookmark"."id",
       "marcador_bookmark"."url",
       "marcador_bookmark"."title",
       "marcador_bookmark"."description",
       "marcador_bookmark"."is_public",
       "marcador_bookmark"."date_created",
       "marcador_bookmark"."date_updated",
       "marcador_bookmark"."owner_id"
FROM "marcador_bookmark"
WHERE "marcador_bookmark"."is_public" = True
ORDER BY "marcador_bookmark"."title" ASC LIMIT 1

Eine Übersicht aller QuerySet-Methoden ist der Dokumentation zu entnehmen.

In unserem Beispiel werden wir oft nur öffentliche Lesezeichen anzeigen, daher wird dem Model Bookmark ein weiterer Manager hinzugefügt, der nur die öffentlichen Lesezeichen zurück gibt. Er wird über das Attribut public referenziert. Damit der Default-Manager bestehen bleibt, muss er noch einmal explizit dem Attribut objects zugewiesen werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class PublicBookmarkManager(models.Manager):
    def get_queryset(self):
        qs = super(PublicBookmarkManager, self).get_queryset()
        return qs.filter(is_public=True)


@python_2_unicode_compatible
class Bookmark(models.Model):
    url = models.URLField()
    title = models.CharField('title', max_length=255)
    description = models.TextField('description', blank=True)
    is_public = models.BooleanField('public', default=True)
    date_created = models.DateTimeField('date created')
    date_updated = models.DateTimeField('date updated')
    owner = models.ForeignKey(User, verbose_name='owner',
        related_name='bookmarks')
    tags = models.ManyToManyField(Tag, blank=True)

    objects = models.Manager()
    public = PublicBookmarkManager()

    class Meta:
        verbose_name = 'bookmark'
        verbose_name_plural = 'bookmarks'
        ordering = ['-date_created']

    def __str__(self):
        return '%s (%s)' % (self.title, self.url)

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

5.5. The complete file

Am Schluss sieht die vollständige Datei models.py wie folgt aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# encoding: utf-8
from django.contrib.auth.models import User
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now


@python_2_unicode_compatible
class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    class Meta:
        verbose_name = 'tag'
        verbose_name_plural = 'tags'
        ordering = ['name']

    def __str__(self):
        return self.name


class PublicBookmarkManager(models.Manager):
    def get_queryset(self):
        qs = super(PublicBookmarkManager, self).get_queryset()
        return qs.filter(is_public=True)


@python_2_unicode_compatible
class Bookmark(models.Model):
    url = models.URLField()
    title = models.CharField('title', max_length=255)
    description = models.TextField('description', blank=True)
    is_public = models.BooleanField('public', default=True)
    date_created = models.DateTimeField('date created')
    date_updated = models.DateTimeField('date updated')
    owner = models.ForeignKey(User, verbose_name='owner',
        related_name='bookmarks')
    tags = models.ManyToManyField(Tag, blank=True)

    objects = models.Manager()
    public = PublicBookmarkManager()

    class Meta:
        verbose_name = 'bookmark'
        verbose_name_plural = 'bookmarks'
        ordering = ['-date_created']

    def __str__(self):
        return '%s (%s)' % (self.title, self.url)

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