30 December 2008

Rafał Jońca na Google App Engine, cz. 2

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