Django: Databázový model podruhé
V minulém díle jsme se naučili ukládat záznamy do databáze, dnes se je naučíme odtamtud vybírat, upravovat a mazat. Rovněž si ukážeme vazby mezi tabulkami a několik tipů, týkajících se databázového modelu Djanga.
Seriál Hrajeme si s Djangem
- Django: Nastavení projektu a první pokusy
- Django: Databázový model
- Django: Databázový model podruhé
- Django: Administrace
- Django: Prezentace dat
Vybírání dat z tabulky
Naše databáze by měla obsahovat z minula několik záznamů, které si odtamtud zkusíme získat. To se dělá pomocí kolekce objektů QuerySet. Výstupem této třídy je instance objektu, která se chová podobně jako seznam. Metody výběru dat se volají přes atribut objects a dají se kombinovat. K nejpoužívanějším patří:
all: vybere všechny záznamy z tabulkyfilter: omezuje výběr podle jednoho nebo více parametrůexclude: vybere doplněk toho, co by vybrala metodafilterorder_by: řazení výběru podle určitého atributu
Nejjednodušší je výběr všech záznamů:
>>> from hrajeme_si.video_store.models import Store >>> Store.objects.all() [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>]
Omezování záznamů pomocí metod filter a exclude se převádí na SQL klauzuli WHERE, respektive WHERE NOT. Při zadání více parametrů se jednotlivá omezení řetězí pomocí logické spojky AND. Za názvem atributu může následovat dvojité podtržítko spolu s nějakým specifikátorem omezení, který magicky zadává klíčové argumenty WHERE klauzule. Ukážeme si to na několika komentovaných příkladech (mřížka # uvozuje komentář):
>>> Store.objects.filter(id=1) # záznam s id = 1 (odpovídá id__exact=1) [<Store: Videostore Praha 1>] >>> Store.objects.filter(id__gt=1) # záznamy s id > 1 [<Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.filter(id__gte=1) # záznamy s id ≥ 1 [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.filter(city='Praha') # město odpovídá přesně řetězci 'Praha' [] >>> Store.objects.filter(city__startswith='Praha') # město začíná řetězcem 'Praha' [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>] >>> Store.objects.filter(city__istartswith='prAhA') # to stejné, jenom ignorujeme velikost písmen [<Store: Videostore Praha 1>, <Store: Videostore Praha 2>] >>> Store.objects.filter(address__icontains='náměstí') # pobočka sídlí na náměstí [<Store: Videostore Brno>] >>> Store.objects.filter(email='') # záznamy s nevyplněným e-mailem [<Store: Videostore Praha 2>, <Store: Videostore Brno>] >>> Store.objects.exclude(email='') # záznamy s vyplněným e-mailem [<Store: Videostore Praha 1>]
Záznamy lze řadit podle jednoho či více atributů, výběr se implicitně řadí od prvního záznamu po poslední, stejně jako u SQL klauzule ORDER BY. Když potřebujeme položky seřadit obráceně, napíšeme znak mínus před název atributu. Není potřeba nejprve volat metodu all, stačí zavolat order_by rovnou:
>>> Store.objects.order_by('id') # řazení dle sloupce id vzestupně
[<Store: Videostore Praha 1>, <Store: Videostore Praha 2>, <Store: Videostore Brno>]
>>> Store.objects.order_by('-id') # řazení dle sloupce id sestupně
[<Store: Videostore Brno>, <Store: Videostore Praha 2>, <Store: Videostore Praha 1>]
>>> Store.objects.order_by('?') # náhodné řazení
[<Store: Videostore Praha 1>, <Store: Videostore Brno>, <Store: Videostore Praha 2>]
Kombinování metod výběru vypadá například takhle:
>>> Store.objects.exclude(city='Brno').filter(id__gt=0, id__lt=10).order_by('-city')
[<Store: Videostore Praha 2>, <Store: Videostore Praha 1>]
Tento příkaz vybere všechny pobočky, které nejsou v Brně, mají id větší než nula a menší než deset a seřadí je podle města, sestupně. Je dobré vědět, že se QuerySet chová líně, tedy že se SQL dotaz zavolá, až když data v aplikaci skutečně potřebujeme. Můžeme tak SQL dotaz postupně sestavovat, aniž by došlo k výraznému zpomalení.
Limitování a zjišťování počtů záznamů
Když už jsme si ukázali ekvivalenty klauzulí WHERE a ORDER BY, přidáme k tomu ještě klauzuli LIMIT. Ta má stejnou syntaxi jako indexování seznamů, protože se instance objektu QuerySet chová podobně jako pythonový seznam. Pomocí ní vybíráme podmnožinu z našich záznamů. Nejlépe to ilustrujeme na několika příkladech:
>>> Store.objects.order_by('id')[0] # první záznam v tabulce
<Store: Videostore Praha 1>
>>> Store.objects.order_by('-id')[0] # poslední záznam v tabulce
<Store: Videostore Brno>
>>> Store.objects.order_by('id')[:2] # první dva záznamy z tabulky
[<Store: Videostore Praha 1>, <Store: Videostore Praha 2>]
>>> Store.objects.order_by('-id')[:2] # poslední dva záznamy z tabulky
[<Store: Videostore Brno>, <Store: Videostore Praha 2>]
>>> Store.objects.exclude(city='Brno')[1] # druhý pobočka, která není v Brně
<Store: Videostore Praha 2>
Stejně jako u seznamů je potřeba si dát pozor na indexování mimo rozsah, protože pak je vyhozena výjimka IndexError, kterou je potřeba odchytit a zpracovat. To se naučíme v dalších dílech. Vyhození této výjimky nastává například při vybrání nulového počtu záznamů:
>>> Store.objects.filter(city='Ostrava')[0]
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/lib/python2.5/site-packages/django/db/models/query.py", line 159, in __getitem__
return list(qs)[0]
IndexError: list index out of range
A jak zjistit počet záznamů? Stačí použít zabudovanou funkci len:
>>> len(Store.objects.all()) 3 >>> len(Store.objects.filter(city='Brno')) 1 >>> len(Store.objects.filter(city='Ostrava'))
Upravování a mazání dat
Záznamy, které z databáze vybereme, jsou opět instancí našeho modelu Store. Ten jsme minule použili k přidání záznamů do tabulky. Podobně se záznamy dají upravovat a mazat. Vyzkoušíme si přestěhovat brněnskou pobočku:
>>> store_brno = Store.objects.filter(id=3)[0] >>> store_brno.address = 'Kounicova 15' >>> store_brno.postal_code = '611 00' >>> store_brno.save()
První příkaz nám vybere záznam s číslem 3, tedy naší brněnskou pobočku, a přiřadí ji do proměnné store_brno. Kratšího zápisu lze docílit i pomocí metody get, která se ovšem chová trochu jinak. Na dalších třech řádcích můžeme vidět, že došlo k upravení atributů a uložení záznamu do tabulky. Podobně funguje i mazání, jenom místo metody save zavoláme metodu delete:
>>> store_praha2 = Store.objects.filter(id=2)[0] >>> store_praha2.delete()
Tabulka teď obsahuje jenom dva záznamy, protože jsme druhou pražskou pobočku zrušili:
>>> Store.objects.all() [<Store: Videostore Praha 1>, <Store: Videostore Brno>]
Ladění SQL dotazů
Často je užitečné vědět, jaký SQL dotaz ORM transformace v Djangu vyprodukovala a jak dlouho jeho vykonání trvalo. Tyto informace je průběžně ukládají do seznamu queries z objektu django.db.connection. Takto vypadá jednoduché použití:
>>> from django.db import connection
>>> Store.objects.exclude(id=1).order_by('city')[0]
<Store: Videostore Brno>
>>> connection.queries[-1]
{'time': '0.038', 'sql': u'SELECT "video_store_store"."id", "video_store_store"."store",
"video_store_store"."address", "video_store_store"."city", "video_store_store"."postal_code",
"video_store_store"."email", "video_store_store"."description" FROM "video_store_store"
WHERE NOT ("video_store_store"."id" = 1 ) ORDER BY "video_store_store"."city" ASC LIMIT 1'}
Tento dotaz trval 38 setin sekundy a byl při něm použit uvedený SQL kód.
Vztahy mezi modely
Rozšíříme si funkcionalitu naší aplikace přidáním modelu představujícího položku v katalogu filmů videopůjčovny. Otevřeme si soubor video_store/models.py a přidáme na jeho konec definice modelů. Tentokrát to bude o něco složitější:
FORMAT_CHOICES = (
(0, 'Ostatní'),
(1, 'VHS'),
(2, 'DVD'),
(3, 'Blu-ray'),
)
class Film(models.Model):
store = models.ManyToManyField('Store')
name_czech = models.CharField('Český název filmu', max_length=100)
name_original = models.CharField('Původní název filmu', max_length=100, blank=True)
year = models.PositiveIntegerField('Rok natočení')
director = models.ForeignKey('Director', verbose_name='Režisér', null=True, blank=True)
price = models.DecimalField('Cena za půjčení', max_digits=4, decimal_places=2, default=20)
format = models.PositiveSmallIntegerField('Formát', choices=FORMAT_CHOICES)
description = models.TextField('Popis', blank=True)
added = models.DateTimeField('Čas přidání', auto_now_add=True)
def __unicode__(self):
return self.name_czech
class Meta:
ordering = ['name_czech']
verbose_name = 'film'
verbose_name_plural = 'filmy'
class Director(models.Model):
name = models.CharField('Jméno a příjmení', max_length=100)
def __unicode__(self):
return self.name
class Meta:
verbose_name = 'režisér'
verbose_name_plural = 'režiséři'
N-tice FORMAT_CHOICES je pro výběr formátu filmu v modelu Film. Ten je provázan přes cizí klíč relací 1:N (one-to-many) s pomocným modelem Director, který obsahuje režiséry. Vytváříme ho proto, abychom mohli jednoduše zobrazovat související filmy od toho stejného režiséra. Pro zjednodušení může být u každého filmu uveden nanejvýše jeden režisér. Tabulky Store a Film jsou propojené relací M:N, často nazývanou many-to-many. Každý film se může vyskytovat ve více videopůjčovnách a každá videopůjčovna zpravidla obsahuje více než jeden film. Pro lepší pochopení vztahů je dobré prostudovat přiložený diagram.

Z atributů tady máme několik nových polí: PositiveIntegerField a PositiveSmallIntegerField se používají pro ukládání přirozených čísel, liší se jenom v maximální hodnotě, která se do nich dá uložit. Pole DecimalField je určeno pro desetinná čísla (je potřeba nastavit maximální počet číslic a počet desetinných míst). Další pole DateTimeField slouží k ukládání času a data, použitý parametr auto_now_add nám usnadňuje práci, protože nastaví automaticky aktuální čas při založení. Parametr default nastavuje výchozí hodnotu atributu price. Za zmínku stojí i meta vlastnost ordering, která nastavuje výchozí řazení při výběru dat.
A na závěr si opět synchronizujeme modely s databází, abychom je měli nachystané pro další pokračování:
$ python manage.py syncdb
Creating table video_store_film
Creating table video_store_director
Installing index for video_store.Film model
Související odkazy
- Modely a tutoriál na Djangoproject.com
- Tutoriál na Djangoproject.cz
- Pátá kapitola v The Definitive Guide to Django
- Ukázkový příklad ke stažení.
Příště si z našich databázových modelů necháme vygenerovat rozhraní pro jednoduchou správu projektu.
Školení Google+ pro firmy

- Jak využít Google+ pro firemní komunikaci a marketing.
- Čím se liší Google+ od Twitteru a Facebooku z pohledu firemního využití.
- Jak využít Google+ v souladu s pravidly užívání.
- Založení Google+ Page (Stránky) krok po kroku, včetně praktických tipů.
Detailní informace o školení Google+ »
Seriál Hrajeme si s Djangem
- Django: Nastavení projektu a první pokusy
- Django: Databázový model
- Django: Databázový model podruhé
- Django: Administrace
- Django: Prezentace dat
Přehled názorů
Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.
