Dvojitá alchymie V - Traversal

25. 10. 2013 20:45 (aktualizováno) Petr Blahoš

Podívejte se na všechny díly seriálu nebo na zdrojáky příkladu.

Minule jsem sliboval to _ na lokalizaci. Ale lokalizace se ve FormAlchemy ukázala trošku složitější, než se na první pohled zdálo, proto se dnes budu věnovat něčemu jinému. Ale zajímavějšímu.

step09

Ve všech pythonových webových frameworcích, se kterými jsem se setkal, se na to, abychom se z URL dostali kód, který něco udělá, používal koncept URL dispatch/routing. Tedy, pokud (relativní) url začíná články/ a potom je číslo, a router má správné pravidlo, tak nás dovede k tomu správnému handleru. Když se ale vrátíme na začátek, tak webové stránky vlastně byly skutečně fyzicky soubory na disku v nějaké hierarchii, a webový server je dával uživateli na základě cesty v url. Vlastně se nemusíme vracet na začátek, ten koncept stále na mnoha místech funguje.

Pyramid kromě URL dispatch, které jsme až dosud používali, umí i takzvaný traversal, který si vzal inspiraci právě z adresářové struktury. Do Pyramid přišel ze Zope. Traversal nepracuje s adresáři, ale s objekty.
Příklad: /blogs/2013/03/12/jmeno-clanku
Celé to traverzování začne nějakým root objektem, který bude náš aktuální kontext. Toho se zeptáme: máš blogs? On odpoví ano, a tady je. To bude teď aktuální kontext. Zase se aktuálního kontextu zeptáme: Máš 2013? Odpoví nám ano, tady je. To bude náš nový aktuální kontext, zase se ho zeptáme: Máš 03, a tak dále, až dojdeme na konec, nebo až nám někdo řekne nemám. Tak předpokládejme, že máme kontext. V URL dispatch jsme řekli, jaké route patří handler. U traversalu nemáme routy, tak říkáme, jakému contextu patří handler. Důkladněji je to popsané zde.

Já si budu chtít vytvořit takovou strukturu, že na / najdu seznam modelů. Kliknitím na model se dostanu na url /Jmenomodelu, které mi zobrazí seznam objektů/záznamů/řádků tabulky s odkazy pro editaci a smazání. Odkaz pro editaci bude: /Jmenomodelu/primarniklic a odkaz na mazání /Jmenomodelu/primarniklic/delete, přičemž primární klíč bude sekvence dvojic sloupec=hodnota – urlencoded. Ještě pro vytvoření nového záznamu: Jmenomodelu/new. Traversal začíná s Root Resource. Pokud je url /, tak se traversal dál než k Root Resource nedostane. Pokud tam něco je, tak se v aktuálním contextu volá __getitem__. Nějak takhle (značně zkráceno):

class TopContext(object):
    # ...
    def __getitem__(self, name):
        return ModelContext(self, name)

class ModelContext(object):
    # ...
    def __getitem__(self, name):
        if "new"==name:
          return NewItemContext(self)
        params = urlparse.parse_qs(name)
        q = self.request.db.query(self.model)
        for i in get_pk_columns(self.model):
            q = q.filter(i==params[i.name][0])
        return ItemContext(self, q.one())

class NewItemContext(object):
    # ...

class ItemContext(object):
    # ...
V Top Contextu hledám model, v Model Contextu hledám konkrétní položku/řádek/objekt. V (New) Item Contextu už nehledám nic. Celé to najdete ve step09 ve model/resources.py.

Protože nemáme routy, tak nemůžeme generovat url pomocí request.route_url. Místo něj máme request.resource_url, které nám vygeneruje url pro danou resource. Resource ale musí být location-aware. To znamená, že resource má 2 property: __name__ a __parent__. To generování url si představuju tak, že se jde od aktuální resource přes __parent__ nahoru až k rootu a cestou se sbírají __name__. route_url má ty parametry, které byste asi tak čekali, třeba ocas, který se přidá k vygenerovanému url, nebo query string. Příklady uvidíte třeba v templates/formalchemy/grid_re­adonly.mako, kde přidáváme právě ten ocas kvůli generování odkazu na editaci a mazání.

Tak a teď už:

# nezapomeňte git pull
cd step09
python setup.py develop

pserve --reload development.ini
Čeho si všimnout?
  • V __init__.py už nemáme definici route, jenom konfigurátoru předhodíme root factory.
  • model/resources.py máme nadefinované ty resourcy. Vidíte tam to vyhledávání pomocí __getitem__, a taky hierarchii pomocí __parent__.
  • ItemContext generuju __name__ tak, že si zjistím primární klíč, jeho hodnoty, a udělám urlencode.
  • Naproti tomu v templates/formalchemy/grid_re­adonly.mako nevytvářím nové resources jenom proto, abych je použil k vygenerování url, url si prostě postavím ze 2–3 částí (request.resource_url(request.context, urllib.urlencode(get_pk_map(row)), „delete“)).
  • __parent__ contextu je platná resource, takže když dělám redirect ve views/views.py, tak volám request.resource_url(requ­est.context.__parent__), čímž se dostanu z resource konkrétního záznamu/objektu/řádku tabulky na resource modelu.
  • View může mít jméno. views/views.py view_config k funkci delete má parametr name=„delete“. Poslední část cesty url, pokud ji nesežral traversal se použije jako toto jméno.
  • Pokud se vám nelíbí, že nechávám termíny v angličtině, tak mě se to taky nelíbí, ale přijde mi to lepší, než hledat český ekvivalent.

Zatím mi traversal nepřináší nějakou výhodu, ale příště si ještě trochu pohrajeme. Mám na mysli něco, co by se mi s routingem dělalo špatně, jestli by to vůbec šlo, ale s traversalem to jde bez problémů.

Sdílet