Wczoraj wieczorem dokręciliśmy ostatnie śrubki w drugim z cyklu
projektów-w-tydzień. Tym razem z lekkim poślizgiem, w planach
mieliśmy zamknięcie prac w zeszłym tygodniu, w praktyce –
rzeczywiście intensywne prace zaczęliśmy w piątek, skończyliśmy
wczoraj (środa). Pierwszy
użytkownik powoli kończy przeprowadzkę.
Nowy serwis to WebComicsPot,
narzędzie do możliwie prostego publikowania w sieci komiksów, także
wielojęzycznych. Założenia były takie: samo umieszczenie plansz w
sieci jest proste i tanie, kłopotem jest za to złożenie sensownego
interfejsu pozwalającego na ich przeglądanie i przełączanie się między
tłumaczeniami. WebComicsPot rozwiązuje właśnie ten kłopot.
Jak w favpico, obsługa jest
uproszczona do granic możliwości: autor podaje tylko listę adresów pod
którymi znajdują się kolejne plansze. Serwis zakłada, że każdy adres
zawiera datę w formacie RRRR-MM-DD. Dodatkowo możliwe jest wklejenie
w stronę komiksu swojego kodu HTML i dodanie arkusza styli CSS.
Jednocześnie, to jest nasz pierwszy serwis wspierający OpenId – koniec z
wymyślaniem kolejnych haseł
Coś o stronie technicznej i organizacyjnej; zaletą tak małych
projektów jest to, że można w krótkim czasie sprawdzić w praktyce
sporo nowych narzędzi: jeśli się sprawdzą to świetnie, jeśli nie – to
upośledzają tylko jeden drobny projekt, nie ma sensu go przepisywać.
Tym razem znalazłem sporo przydatnych rzeczy.
Django-authopenid
zachowuje się bardzo sensownie, wymagała tylko drobnych poprawek
(które muszę jeszcze spakować w jeden sensowny diff i wysłać).
Instant Django to
świetny sposób na udostępnienie serwisu nieprogramiście
(np. grafikowi) pod Windows – zawiera, w jednej paczce, 2.5, django, sqlite, dość sensowny edytor tekstu i
parę skryptów które odpowiednio konfigurują środowisko. Wystarczy
dodać skrypt uruchamiający serwer (z syncdb przed startem), nauczyć
grafika korzystać z TortoiseSVN
i już, właśnie załatwiliśmy stronę techniczną współpracy.
FeedParser i FeedJack – świetne biblioteki do
obsługi RSS, używałem
ich już wcześniej w Planemoo. Tym razem FeedParser przydał się też do
czyszczenia kodu HTML wpisywanego przez autora komiksu: ze względów
bezpieczeństwa serwis usuwa część konstrukcji.
Cssutils
– podobnie jak w przypadku HTML, możliwość wpisania
dowolnego kodu CSS to dziura bezpieczeństwa ze względu na @import i
javascript w adresach. Sam cssutils nie potrafi tego wyczyścić, ale
wystarczyło utworzyć własną podklasę CSSSerializer, żeby usunąć i
@import, i podejrzane adresy:
URL_WITH_CALL_RE = re.compile(r'^url[(]["\']https?://\S+$')
class SanitizingSerializer(cssutils.CSSSerializer):
"""
Overrides some stuff in CSSSerializer to make it safer.
1. disallow ALL URLs not starting with 'http://' or 'https://'
2. remove @imports
"""
def do_css_CSSValue(self, cssvalue):
if cssvalue and isinstance(cssvalue, CSSPrimitiveValue):
if cssvalue.primitiveType == CSSPrimitiveValue.CSS_URI:
if not URL_WITH_CALL_RE.match(cssvalue._value):
return u'url("")';
return super(SanitizingSerializer, self).do_css_CSSValue(cssvalue)
def do_CSSImportRule(self, rule):
return u''
cssutils.setSerializer(SanitizingSerializer())
Django zawiera bibliotekę do komentarzy, bardzo przydatną ale z
paroma ograniczeniami; przeszkadzał mi, przede wszystkim, ścisły
rozdział komentarzy użytkowników zalogowanych i anonimowych, w
praktyce uniemożliwiający dyskusję między jednymi a drugimi. Tutaj
poszukiwania innej biblioteki niestety nie dało rezultatu, trzeba było
dostosować django.contrib.comments.
Drobiazg, ale jak przydatny: slughifi zamienia "zażółć gęślą jaźń"
na "zazolc-gesla-jazn", więc jest wersją funkcji slugify, mądrzejszą o
znajomość wielu znaków narodowych, w tym cyrylicy.
Musiałem też nieco rozbudować własną bibliotekę do obsługi tłumaczeń,
django-multilingual;
eksperymentalne zmiany okazały się bardzo przydatne więc niedługo znajdą
się też w wersji publicznej.
Napisałem też trochę kodu wspomagającego testy modułowe Django,
teraz duża część moich testów wygląda tak:
def test_adding_a_comic(self):
self.login()
self.get('/')
self.click('m-your-account')
self.click('publish-now')
self.click('cancel')
self.assertLocation('/account/ed/')
# okay, now go and publish it
self.click('publish-now')
self.assertContains(self.response, 'name="short_name"')
self.assertContains(self.response, 'name="main_language_id"')
self.assertContains(self.response, 'name="add" value="save"')
Gdzie 'm-your-account' i 'publish-now' to identyfikatory (nie tekst!)
odsyłaczy na stronie; oczywiście każdy click przechodzi do strony
wskazywanej przez dany odsyłacz. Pozwala to łatwo przetestować całe
sekwencje zdarzeń, i to w sposób niezależny od aktualnie włączonej
wersji językowej interfejsu.
A za miesiąc, wszystko na to wskazuje, coś bardziej skomplikowanego