Biblioteka Pythona

Tworzymy przykładowego bloga - część 2

Druga część wprowadzająca do Django. Rozbudowa aplikacji wiadomości o szablony i stronicowanie.

Teraz zajmiemy się rozbudową naszej przykładowej aplikacji. Na początek chcielibyśmy wyświetlać listę wiadomości ze stronicowaniem wyników. Django udostępnia bardzo proste rozwiązanie tego problemu. Zamiast render_to_response wystarczy użyć object_list(request, QUERYSET, paginate_by=INT, extra_context={}, template_name='SZABLON.HTML'). Oto kod widoku głównego, oraz widoku wyświetlającego wiadomości z podanej kategorii:
# -*- coding: utf-8 -*-
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.views.generic.list_detail import object_list

from news.models import *

def index(request):
	news = News.objects.all().order_by('-id')
	return object_list(
		request,
		news,
		paginate_by = 10,
		extra_context = {},
		template_name = 'index.html')

def news_by_category(request, slug):
	c = Category.objects.get(slug=slug)
	#news = News.objects.filter(category=c).order_by('-id')
	news = c.news_set.all()
	return object_list(
		request,
		news,
		paginate_by = 10,
		extra_context = {'c':c},
		template_name = 'news_by_category.html')
Szablon index.html też ulega pewnym modyfikacjom:
{% for n in object_list %}
	<h3><a href="/news/{{ n.slug }}/">{{ n.title }}</a></h3>
	{{ n.text|safe }}<br />
	<b>Dodane</b>: {{ n.date|date:"Y.m.d H:i" }}, <b>Kategorie</b>:
	{% for i in n.category.all %}
		<a href="/{{ i.slug }}/">{{ i.name }}</a>{% if not forloop.last %}, {% endif %}
	{% endfor %}
{% endfor  %}

{% if has_previous %}
	<div style="text-align:center;"><a href="/?page={{ previous }}"><b>Nowsze Wiadomości</b></a></div>
{% endif %}
{% if has_next %}
	<div style="text-align:center;"><a href="/?page={{ next }}"><b>Starsze Wiadomości</b></a></div>
{% endif %}
Lista stronicowanych elementów dostępna jest pod zmienną object_list. Dodatkowo dostępne są zmienne takie jak "has_previous" i "previous", czy "has_next" i "next", gdzie drugie zmienne z tych par zawierają numer strony stronicowania odpowiednio poprzedniej jak i następnej. Także dodaliśmy wyświetlanie listy kategorii każdej wiadomości przemiatając w pętli n.category.all. Tytuł wiadomości jest też odnośnikiem do strony, która będzie miała za zadanie wyświetlenie jego treści, a także umożliwienie komentowania. Na razie prosty widok:
def show_news(request, slug):
	news = News.objects.get(slug=slug)
	return render_to_response('show_news.html', {'news': news}, context_instance=RequestContext(request))
urls.py dla widoków wygląda następująco:
from django.conf.urls.defaults import *

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
   (r'^admin/(.*)', admin.site.root),
   (r'^news/(?P<slug>[\w\-_]+)/$', 'news.views.show_news'),
   (r'^(?P<slug>[\w\-_]+)/$', 'news.views.news_by_category'),
   (r'^/?$', 'news.views.index'),
)
W efekcie mamy praktycznie gotową aplikację wiadomości:
newdjango7.png
Teraz na chwilę zajmiemy się wyglądem. Stosujemy kilka szablonów bez stylowania itp. By strona wyglądała porządnie wykorzystamy darmowy szablon HTML HigherGround. Rozpakuj paczkę ZIP i katalog "images" przenieś do katalogu "site_media", a plik "index.html" przenieś do katalogu "templates" jako "base.html", w którym zmieniamy odnośnik do pliku CSS na:
<link rel="stylesheet" href="/site_media/images/HigherGround.css" type="text/css" />
Oraz zastępujemy całą zawartość DIVa o id "main" definicją bloka:
<div id="main">
	{% block main %}{% endblock %}
</div>
W urls.py dodajemy regułę do obsługi plików statycznych (tylko na serwerze deweloperskim!):
import os.path
...
(r'^site_media/(.*)$', 'django.views.static.serve', {'document_root': os.path.join(os.path.dirname(__file__), 'site_media')}),
Po zmodyfikowaniu index.html do postaci:
{% extends "base.html" %}

{% block main %}
{% for n in object_list %}
	<h3><a href="/news/{{ n.slug }}/">{{ n.title }}</a></h3>
	{{ n.text|safe }}<br />
	<b>Dodane</b>: {{ n.date|date:"Y.m.d H:i" }}, <b>Kategorie</b>:
	{% for i in n.category.all %}
	<a href="/{{ i.slug }}/">{{ i.name }}</a>{% if not forloop.last %}, {% endif %}
	{% endfor %}
{% endfor  %}

{% if has_previous %}
	<div style="text-align:center;"><a href="/?page={{ previous }}"><b>Nowsze Wiadomości</b></a></div>
{% endif %}
{% if has_next %}
	<div style="text-align:center;"><a href="/?page={{ next }}"><b>Starsze Wiadomości</b></a></div>
{% endif %}
{% endblock %}
Powinniśmy uzyskać listę wiadomości ładnie umieszczoną w docelowym szablonie:
newdjango8.jpg
Zastosowaliśmy tutaj dziedziczenie szablonów. Stworzyliśmy główny "szkieletowy" szablon i zdefiniowaliśmy w nim blok w miejscu, gdzie powinna wyświetlać się treść. W szablonach używanych przez widoki bezpośrednio dodaliśmy informację o dziedziczeniu szablonu - {% extends "base.html" %}, a istniejący kod umieściliśmy w bloku o zdefiniowanej wcześniej nazwie. Podobnie edytujemy pozostałe szablony. Można też zdefiniować dodatkowe bloki, np. w tagu TITLE do dynamicznego wstawiania tytułu news do tytułu strony.

Jak wyświetlić listę kategorii w menu po prawej stronie? Potrzebować będziemy listy kategorii dostępnej globalnie w każdym szablonie (a dokładniej base.html). W tym przypadku stworzymy własny procesor szablonów (TEMPLATE_CONTEXT_PROCESSOR), który doda taką listę. W katalogu projektu stwórz plik "globs.py" o kodzie:
#!/usr/bin/python
# TEMPLATE_CONTEXT_PROCESSOR

from news.models import Category

def globs(request):
	cats = Category.objects.all()
	return {'cats': cats}
Do settings.py dodaj:
TEMPLATE_CONTEXT_PROCESSORS = ("django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"globs.globs",)
I gotowe. Wpis procesora szablonów na liście "TEMPLATE_CONTEXT_PROCESSORS" ma postać "nazwa_pliku.nazwa_funkcji". Teraz w base.html możemy dodać listowanie kategorii:
<h3>Kategorie</h3>
<ul class="sidemenu">
	{% for i in cats %}
		<li>{% if i.icon %}<img src="/site_media/{{ i.icon }}" alt="" /> {% else %}<img src="/site_media/default.png" alt="" /> {% endif %}<a href="/{{ i.slug }}/">{{ i.name }}</a></li>
	{% endfor %}
</ul>
newdjango9.png