Poprzedni wpis (Zrób swoje własne Django, odc. 1 - middleware) | Następny wpis (Mam szczęście do szatanów)

Zrób swoje własne Django, odc. 2 - procesory kontekstu

Obiecałem, że będzie o procesorach kontekstu jak w Django (a może nawet trochę lepszych), więc proszę.

Procesory kontekstu

Procesor kontekstu wg. Django to kod wykonywalny, który przyjmując obiekt request zwraca słownik, który następnie jest dodawany do kontekstu przed renderowaniem szablonu. W ten sposób można zapewnić sobie dostępność pewnych danych w kontekście bez potrzeby pamiętania o tym, by te dane tam umieścić. W Django procesory kontekstu są wywoływane wtedy, gdy używa się klasy RequestContext. Pół biedy, gdy chodzi o mój kod — już ja o to zadbam, żeby tam był użyty RequestContext, ale problemem może być kod, którego autor o tym nie pomyślał.

Używając Jinja2 i Werkzeug nie możemy liczyć na tak silną integrację systemu szablonów z modelem żądania i odpowiedzi (założenia obydwu komponentów właśnie taką integrację wykluczają). Na szczęście zarówno Jinja2 jak i Werkzeug dostarczają nam wszystkiego czego potrzeba, żeby zaimplementować sobie taką funkcjonalność. Niestety, nie będzie tak prosto jak w przypadku middleware.

Werkzeug posiada coś, co jest nazywane local proxy — jest to wariant thread local storage, specjalnie przystosowany do potrzeb aplikacji webowych. Zazwyczaj umieszcza się w nim kod aplikacji WSGI (klasy lub funkcji), ale nie ma przeszkód, żeby umieścić tam cokolwiek innego, np. obiekt request:

def __call__(self, environ, start_response):
    local.application = self
    local.request = request = Request(environ)

Tak umieszczony obiekt request będzie następnie dostępny w dowolnej innej części aplikacji w trakcie jej czasu życia (czyli przetwarzania żądania i produkowania odpowiedzi). Teraz w okolicy mojego kodu renderującego szablon mogę zrobić taki myk:

def build_globals():
    jinja_globals = {}
    for item in getattr(settings, 'CONTEXT_PROCESSORS', []):
        try:
            processor = import_string(item)
            jinja_globals.update(processor(local.request))
        except (ImportError, AttributeError):
            pass
    return jinja_globals

jinja_env = Environment(loader=FileSystemLoader(settings.TEMPLATE_DIRS))
jinja_env.globals.update(build_globals())

Chodzi o to, że Jinja2 posiada tzw. globalne zasoby — można je zmodyfikować po utworzeniu środowiska a przed wyrenderowaniem pierwszego szablonu. Od teraz w każdym szablonie będzie dostępne to, co znajduje się w moim słowniku jinja_env.globals. A co się tam może znaleźć? Na przykład to:

def messages(request):
    messages = request.environ['beaker.session'].get('messages', [])
    request.environ['beaker.session']['messages'] = []
    request.environ['beaker.session'].save()
    return {
        'MESSAGES': messages,
    }

Czemu jest to lepsze rozwiązanie niż w Django? Ponieważ nie trzeba pamiętać o tym, żeby kontekst był obiektem klasy RequestContext. Dzięki buforowaniu środowiska przez Jinja2, narzut jest minimalny, a prostota rozwiązania kusząca. Czyżby więc było to rozwiązanie idealne? W moim przypadku nie. Trzeba bardzo uważać na to, by zaimportować ten kod w odpowiednim momencie (dla uproszczenia umieściłem go w przestrzeni globalnej). Ale działa i robi dokładnie to, co chciałem.

Ten kod nie powstałby, gdyby nie Ali Afshar (tak, ten od PIDA), który podał rozwiązanie tego problemu na StackOverflow.

Komentarze (1)

#1 sebpa skomentował(-a) 16 lutego 2009 o 10:21

"Ponieważ nie trzeba pamiętać o tym, żeby kontekst był obiektem klasy RequestContext."

mysle ze bardzo czesto programisci stosuja rozwiazanie w django uzywajace dekoratora dzieki ktoremu mozemu po prostu zwrocic sobie zwykly slownik, a dekorator zalatwi sprawe

jesli dobrze pamietam byla nawet taka prezentacja na europythonie 2008 o tym jak sobie ulatwic zycie w django w ten sposob

Skomentujesz?

* 


* 


* oznacza pole wymagane