Ciągle rozbudowuję swoją swoja pierwszą stronę napisaną w Pythonie we frameworku Django. Cały czas się uczę i stale mnie zarówno Python jak i Django zaskakują i co krok napotykam na przeszkody i szukam rozwiązań, którymi się kiedyś z Wami podzielę. Nie inaczej było gdy zapragnęłam mieć w stopce strony stały a jednak dynamiczny element
Już wyjaśniam. Stały to znaczy taki sam na wszystkich podstronach. Dynamiczny, bo jego treść jest generowana dynamicznie na podstawie zawartości bazy danych. Nie trudno sobie taki blok wyobrazić. To może być dynamicznie generowane menu, albo ramka zawierająca 5 ostatnich newsów czy 5 najgorętszych dyskusji z bloga. Sęk w tym, że treść takiego bloku nie jest ściśle związana z większością podstron na których się pojawia. Nie ma więc powodu aby była generowana w widoku. Ponad to wymagałoby to przepisywania tego samego kawałka kodu do wszystkich widoków i wysyłania go do wszystkich template. A ponieważ to jest bez sensu wiedziałam, że musi być jakieś zgrabniejsze rozwiązanie.
Różne frameworki PHPowe z jakimi pracowałam udostępniają rożne mechanizmy. Czy to wywołanie osobnego kontrolera wewnątrz szablonu (niezależnego od bieżącego routingu), który wygeneruje i wyrenderuje odpowiedni kawałek strony, czy to model HMVC. Django wydawało się nie posiadać takich mechanizmów.
Trzy możliwości
Szukając wytrwale natrafiłam na trzy możliwe rozwiązania. Pierwszym było połączenie mixins z class-based views. To rozwiązanie odrzuciłam od razu bo to powoduje albo znów duplikowanie kodu albo powiązanie wszystkich widoków aplikacji z jakimś jednym widokiem bazowym.
Drugie możliwe rozwiązanie to użycie TemplateTag. Byłam zaskoczona ta sugestią, bo w żadnym z tutoriali opisujących kodowanie i wykorzystanie TemplateTag nie spotkałam się z przykładem użycia tego mechanizmu do wyciągania danych z bazy i przygotowywania podwidoku. Być może nie bez powodu gdyż spotkałam się też z opinią, że do tego akurat nie powinno się używać TemplateTag choć jest to technicznie możliwe. Nie udało mi się jeszcze dowiedzieć dlaczego nie więc na razie i ten sposób odrzuciłam.
Wreszcie trzecia możliwość, użycie context processor. Po przestudiowaniu zagadnienia uznałam, że to rzeczywiście przyjemne i wygodne rozwiązanie i zaraz Wam je tu przedstawię.
Context processors
O context processor należy myśleć jako o dodatkowych metodach które mogą niezależnie od widoku przygotowywać i dostarczać do szablonu dodatkowe dane do wyświetlenia, na przykład właśnie zupełnie niezależne od treści (kontekstu) przygotowanej przez widok. Nie jest to rozwiązanie idealne bo o wiele wygodniej by było wykorzystać mechanizm, który w wybrane miejsce szablonu wstawi od razu sformatowany blok, ale lepsze to niż nic.
Model
Załóżmy, że mamy model, który służy do tworzenia i przechowywania pojedynczych podstron czy artykułów:
#pages/models.py class Page(models.Model): title = models.CharField(max_length=200, verbose_name='Tytuł') slug = models.SlugField(max_length=200, verbose_name='Slug', unique=True) text = TextField(verbose_name='Treść') image = models.ImageField(upload_to='home/', blank=True, verbose_name='Ilustracja') order = models.IntegerField(db_index = True,verbose_name='Kolejność') published = models.BooleanField(default=False, verbose_name='Publikacja') published_date = models.DateField( blank=True, null=True, verbose_name='Data publikacji') def __str__(self): return self.title def get_absolute_url(self): return reverse('static_page', args=(self.slug,))
Należy zwrócić uwagę na metodę get_absolute_url, która będzie niezbędna w naszym przypadku, bo posłuży do generowania linków do tych stron. To w zasadzie nie ma znaczenia gdzie w naszym projekcie ten model jest zdefiniowany.
Szablon
Wyobraźmy sobie teraz, że w naszym szablonie chcemy w jakimś miejscu wyświetlić listę tych stron jako liste odnośników do nich:
#templates/layout.html <nav> <ul> {% if static_pages %} {% for page in static_pages %} <li> <a href="{{ page.get_absolute_url }}">{{ page.title }}</a> </li> {% endfor %} {% endif %} </ul> </nav>
Możemy oczywiście listę static_pages uzyskać jako element przekazany przez kontekst (Context) z widoku do renredowanego szablonu. Jednak, jak już wspomniałam byłoby to szalenie niewygodne i wymagało powtórzenia kodu w każdym widoku jesli ta informacja w stopce ma znajdować się na każdej podstronie. Już nie wspomnę o tym w ilu miejscach trzeba by nanosić poprawki w razie jakiejś zmiany. I tu własnie w sukurs przychodzi context processor, który pozwala dodać automatycznie do bieżącego dodatkowe dane.
Definicja context processor
Context processor to zwykła funkcja, zdefiniowana w pliku context_processors.py w dowolnej aplikacji wchodzącej w skład naszego projektu (u mnie w home). Funkcja ta przyjmuje jako argument obiekt HttpRequest i zwraca jakiś słownik. W naszym przypadku będzie ona wyglądać następująco:
#home/context_processors.py from pahes.models import Page def static_pages(request): return {'static_pages': Page.objects.filter(published=True).order_by('order'), 'request': request}
W pierwszej linii widać zaimportowany model Page, następnie definicja funkcji, która pobiera określone rekordy z bazy i zwraca je jako jedyny element słownika przypisany do klucza ‘static_pages’. I to nam wystarczy.
Dołączenie context processor do projektu
Oczywiście to nie zadziała jeśli nasza definicja nie zostanie zainicjowana w projekcie. W tym celu trzeba uzupełnić definicje TEMPLATES w pliku settings.py:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.i18n', # custom context processor 'home.context_processors.static_pages', ], }, }, ]
I to w zasadzie wystarczy, żeby w kontekście znalazła się zmienna static_pages i żeby została poprawnie obsłużone w template zgodnie z tym co zakodowałam na samym początku.
WItam czy mogę w jednym procesorze dodać wiele modeli? :P
Co rozumiesz, przez dodawanie modeli w procesorze? Modele dodajemy w modelach a context processor tylko je wykorzystujemy.