Obiecałem, że druga część zawierać będzie opis flatpages wykorzystywanych do wyświetlania wszystkich stron poza blogiem.
Sposób działania
Kilku dodatkowych słów wyjaśnienia wymaga system zmiany site-a, który jest sprzeżony ze zmianą języka. Zmiana języka dzięki prostemu middleware pociąga za sobą zmianę identyfikatora SITEID, a tym samym użycie innego flatpage. Oto kod middleware:
class SiteOnLocaleMiddleware(object):
def process_request(self, request):
if request.path.split('/')[0] != "admin":
settings.SITE_ID = settings.LANG_TO_SITE[request.LANGUAGE_CODE]
else:
settings.SITE_ID = 2
Pozostałe elementy mechanizmu flatpages działają jak w standardowym Django, z tym że uległy uproszczeniu, ponieważ i tak musiałem je przerobić, a nie korzystałem ze wszystkich opcji oryginalnego mechanizmu.
Kod
middleware.py, czyli FlatpageFallbackMiddleware
Ten fragment w zasadzie nie zmienił się w porówniu z oryginałem. Zmianie uległa tylko ścieżka importu widoku flatpage, więc nie będę przytaczał kodu.
models.py
Model uległ uproszczeniu i zawiera tylko najbardziej niezbędne dane dotyczące flatpage.
from appengine_django.models import BaseModel
from google.appengine.ext import db
FLATPAGE_TEMPLATES = (u'flatpages/default.html', u'flatpages/portfolio.html', 'flatpages/projects.html')
class FlatPage(BaseModel):
url = db.StringProperty('URL', required=True)
title = db.StringProperty('title', required=True)
content = db.TextProperty('content', default='')
template_name = db.StringProperty('template name', required=True, choices=FLATPAGE_TEMPLATES)
sites = db.ListProperty(db.Key, verbose_name='sites')
def __unicode__(self):
return u"%s (%s)" % (self.url, self.title)
def get_absolute_url(self):
return self.url
Proszę zwrócić uwagę na wiersz sites. To właśnie ListProperty przechowujące jako elementy klucze Key to serce mechanizmu N-M w Google App Engine. Standardowe systemy bazodanowe stosują tabelę pośredniczącą do łączenia tabel głównych. w GAE z racji braku złączeń nie byłoby to zbyt wygodne, więc listę obiektów jednej ze stron umieszcza się po drugiej stronie, najlepiej po tej, która będzie miała mniej połączeń. Jeśli użytkownik miałby należeć do wielu grup, ListProperty umieszczamy po stronie użytkownika i zapamiętujemy klucze grupy, bo w tej sytuacji lista będzie zdecydowanie mniejsza.
DataStore umożliwa filtrowanie elementów poszczególnych list. By więc pobrać wszystkich użytkowników danej grupy, wystarczy pobrać klucz grupy, a następnie zastosować go jako filtr dla pola z listą. Aby pobrać grupy użytkownika, pobieramy pole listy użytkownika, a następnie pobieramy grupy na podstawie kluczy (w DataStore pobieranie na podstawie kluczy jest najszybszą operacją). Pamiętaj, że interfesj DataStore dopuszcza podanie listy kluczy w jednym poleceniu pobierającym!
views.py
Widok to w zasadzie nieco zmodyfikowana i uproszczona wersja oryginału. Oto ona.
from flatpages.models import FlatPage
from django.template import loader, RequestContext
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.conf import settings
from sites.models import Site
def flatpage(request, url):
"""Flat page view."""
if not url.endswith('/') and settings.APPEND_SLASH:
return HttpResponseRedirect("%s/" % request.path)
if not url.startswith('/'):
url = "/" + url
f = FlatPage.all().filter('url =', url).filter('sites = ', Site.get_current().key()).get()
if f is None:
raise Http404('No FlatPage matches the given query.')
t = loader.get_template(f.template_name)
c = RequestContext(request, {
'flatpage': f,
})
response = HttpResponse(t.render(c))
return response
Jedynym fragmentem godnym zainteresowania i innym od oryginału jest:
f = FlatPage.all().filter('url =', url).filter('sites = ', Site.get_current().key()).get()
if f is None:
raise Http404('No FlatPage matches the given query.')
Zauważ, że filtracja odbywa się zarówno na podstawie adresu URL, jak i klucza site-a, więc zachodzi pierwsza z opisywanych wcześniej filtracji N-M. W odróżnieniu od ORM Django, która dla operacji get() zwraca wyjątek w przypadku nieodnalezienia wpisu, GAE zwraca None, więc obsługa jest tutaj nieco inna niż w oryginale.
admin.py
Ostatni z interesujących plików zawiera dane dla systemu administracyjnego (proszę pamiętać, że to nie jest admin z Django, ale własne rozwiązanie i z racji tego pojawia się inna składnia).
from google.appengine.ext.db import djangoforms
from myadmin.base import BaseAdmin
from myadmin.fields import KeyListPropertyField
from flatpages.models import FlatPage as FlatPageModel
from sites.models import Site as SiteModel
ADMIN_MODELS = (
('FlatPageAdmin', u'Flat page'),
)
class FlatPageForm(djangoforms.ModelForm):
sites = KeyListPropertyField(label='Sites', query=SiteModel.all().order('name'), use_field='name')
class Meta:
model = FlatPageModel
class FlatPageAdmin(BaseAdmin):
class Meta:
change_fields = ['url', 'title']
model = FlatPageModel
form = FlatPageForm
def change(self):
return {
'header': [u'Url', u'Title'],
'data': self._from_object_to_list(FlatPageModel.all().order('title'))
}
Jeśli czytałeś poprzedni wpis, powyższa składnia nie powinna stanowić zaskoczenia. Strona listy nie jest paginowana i pobiera wszystkie wpisy, ponieważ stron statycznych mam w ilości podobnej do liczby palców, więc nie stanowi to problemu.
Tutaj również pojawia się pole formularza obsługujące N-M, które opisałem dokładniej w poprzednim wpisie.
Drobny prezent
Poniżej kod obsługujący system sitemaps z Django. Tak, ten system działa w Google App Engine bez zarzutu, o ile samemu tworzy się podklasy i zapewnia metodę items(). Nie można skorzystać ze standardowej implementacji sitemap dla flatpages.
from weblog.models import Entry
from flatpages.models import FlatPage
from django.contrib.sitemaps import Sitemap
class FlatpageSitemap(Sitemap):
def items(self):
from django.contrib.sites.models import Site
return FlatPage.all().filter('sites = ', Site.get_current().key())
class BlogSitemap(Sitemap):
def items(self):
return Entry.all().order('-pub_date')
def lastmod(self, item):
return item.pub_date
sitemaps = {
'pages': FlatpageSitemap,
'blog': BlogSitemap,
}
Podsumowanie
Strony statyczne Django w wydaniu GAE nie powinny być już dla nikogo tajemnicą. Jak widać, nie ma się czego bać :)
Opis implementacji bloga pojawi się w następnych odcinkach i to nie jednym, bo nie tylko zawiera sporo kodu, ale również najwięcej technik optymalizacyjnych: denormalizacja, tworzenie grup obiektów, pobieranie danych tylko o określonym przodku, paginacja itp. Zapraszam już wkrótce.
0 comments:
Post a Comment