Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

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.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

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 tabulky
  • filter: omezuje výběr podle jednoho nebo více parametrů
  • exclude: vybere doplněk toho, co by vybrala metoda  filter
  • order_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.

Django - model

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.

TIB2012

       

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

Příště si z našich databázových modelů necháme vygenerovat rozhraní pro jednoduchou správu projektu.

Pavel Dvořák

Pavel Dvořák

Autor je dlouhodobým studentem Fakulty informatiky, webový nadšenec a programátor — nejraději programuje v jazycích Haskell a Python.

Školení Google+ pro firmy

DW - Školení PPC
  • 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+ »

Přehled názorů

len vs .count() a dalsi drobnosti
Honza Kral 4. 9. 2009 10:16
Nový
└ 
Re: len vs .count() a dalsi drobnosti
Pavel Dvořák 4. 9. 2009 10:34
Nový
 
└ 
Re: len vs .count() a dalsi drobnosti
Tomaasch 4. 9. 2009 10:49
Nový
 
 
└ 
Re: len vs .count() a dalsi drobnosti
Pavel Dvořák 4. 9. 2009 10:57
Nový
limit
jjjjj 4. 9. 2009 17:33
Nový
└ 
Re: limit
pcicman 4. 9. 2009 18:12
Nový
Q object?
pcicman 4. 9. 2009 17:45
Nový
└ 
Re: Q object?
Pavel Dvořák 5. 9. 2009 13:49
Nový
Zjistovani vygenerovaneho SQL
Jirka Vejrazka 4. 9. 2009 21:34
Nový
├ 
Re: Zjistovani vygenerovaneho SQL
Jirka Vejrazka 4. 9. 2009 23:45
Nový
└ 
Re: Zjistovani vygenerovaneho SQL
Honza Kral 7. 9. 2009 05:05
Nový
debug toolbar, performance
Limit False 6. 9. 2009 12:22
Nový
Eager loading / N+1 query problem
Jozef 6. 9. 2009 17:58
Nový
└ 
Re: Eager loading / N+1 query problem
Mintaka 7. 9. 2009 02:47
Nový
order_by syntax
goliash 7. 9. 2009 13:49
Nový
└ 
Re: order_by syntax
Pavel Dvořák 7. 9. 2009 13:52
Nový
 
└ 
Re: order_by syntax
goliash 7. 9. 2009 13:58
Nový
       

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.

Zasílat nově přidané příspěvky e-mailem