strona główna

Archive for the 'Python' Category

Skróty: render_to_response

Wednesday, October 1st, 2008

Twórcy Django są bardzo skuteczni w eliminowaniu miejsc, które wymagałyby złamania zasady DRY. Tym dziwniejszy jest wyjątek render_to_response: teoretycznie skrót, w praktyce – funkcja wymagająca codziennego wpisywania prawie identycznego kodu.

Wygląda to tak:

 
from django.shortcuts import render_to_response
from django.template import RequestContext
def some_view(request):
    return render_to_response('testapp/some_view.html',
                              context_instance = RequestContext(request),
                              dictionary = {'title': 'A very simple view',
                                            'text': 'Some text here'})
 

Parametr context_instance w praktyce zawsze wygląda tak samo. Nazwa szablonu często, chociaż nie zawsze, przypomina nazwę funkcji obsługującej dany adres. Parametr dictionary bywa na tyle rozbudowany, że najczęściej buduje się go wcześniej, żeby uniknąć bardzo długich lub sztucznie podzielonych linii. Łączny poziom powtarzalności jest bardzo wyraźny po napisaniu kilku-kilkunastu widoków, zwłaszcza jeśli część z nich zawiera więcej niż jedną ścieżkę kończącą się wywołaniem render_to_response.

A przecież to może wyglądać tak:

 
@with_template
def simple_view(request):
    return {'title': 'A very simple view',
            'text': 'Some text here'}
 

W którymś momencie ta powtarzalność zirytowała mnie na tyle, że poświęciłem trochę czasu na napisanie odpowiedniego dekoratora. Dla lenistwa.

Działa tak:

  • jeśli funkcja zwraca HttpResponse, to dekorator nic nie zmienia – przekazuje ten wynik wyżej,
  • w przeciwnym wypadku zakłada, że wynikiem jest słownik (lub coś, co słownik udaje). W takiej sytuacji przekazuje go do render_to_response, zakładając że wymagany szablon to "nazwa_aplikacji/nazwa_funkcji.html", a context_instance to standardowy RequestContext.
  • dodatkowo, funkcja może zmienić nazwę szablonu: wystarczy, że w słowniku zwróci element 'template_name'.

Przykłady:

 
# plik testapp/views.py
 
@with_template
def simple_view(request):
    return {'title': 'A very simple view',
            'text': 'Some text here'}
 
@with_template
def simple_view_with_locals(request):
    title = 'Another simple view'
    text = 'Some text here'
    return locals()
 
@with_template
def complex_view(request):
    if request.REQUEST.get('redirect_me'):
        return HttpResponseRedirect('http://google.com/')
    else:
        template_name = 'testapp/some_other_template.html'
        return locals()
 
@with_template('testapp/yet_another_template.html')
def view_with_template_override(request):
    title = 'Another simple view'
    text = 'Some text here'
    return locals()
 
@login_required
@with_template
def authenticated_view(request):
    text = 'Welcome, ' + request.user.username
    return locals()
 

Można też po prostu zwrócić locals() – słownik zawierający wszystkie zmienne lokalne. Możliwe jest też, jak w ostatnim przykładzie, użycie innych dekoratorów, na przykład login_required.

Dekorator with_template ma też dodatkową, dużą zaletę: ułatwia wprowadzenie konwencji, zgodnie z którą nazwa szablonu wynika z nazwy aplikacji i funkcji. Konwencje, ogólnie, to dobra rzecz. Ta konkretna sprawia, że oszczędza się czas który normalnie poświęca się na decyzję jak nazwać szablon, wpisanie jego nazwy oraz późniejsze przeglądanie kodu żeby dowiedzieć się, jakiego szablonu należy się spodziewać pod danym adresem.

Owszem, można – jak zauważył ktoś podczas europythonowej prezentacji – dopisać informację o nazwie szablonu do dokumentacji funkcji, ale konwencje działają jak makra lub funkcje w dokumentacji: określa się je w jednym miejscu i stosuje w wielu, bez potrzeby zaglądania do tekstu.

 
# plik testapp/urls.py
 
from django.conf.urls.defaults import *
 
urlpatterns = patterns('testapp.views',
    (r'^normal/', 'normal_view'),
    (r'^simple/', 'simple_view'),
    (r'^complex/', 'complex_view'),
    (r'^authenticated/', 'authenticated_view'),
    (r'^view_with_template_override/', 'view_with_template_override'),
)
 

I trochę testów:

 
# plik testapp/tests.py
 
class WithTemplateTestCase(TestCase):
    def test_views(self):
        response = self.client.get('/app/simple/')
        self.assertTemplateUsed(response, 'testapp/simple_view.html')
 
        response = self.client.get('/app/complex/')
        self.assertTemplateUsed(response, 'testapp/some_other_template.html')
 
        response = self.client.get('/app/complex/', data = {'redirect_me': '1'})
        self.assertEqual(response.status_code, 302)
 
        response = self.client.get('/app/view_with_template_override/')
        self.assertTemplateUsed(response, 'testapp/yet_another_template.html')
 
        from django.contrib.auth.models import User
        user, created = User.objects.get_or_create(username = 'marcink')
        user.set_password('marcink')
        user.save()
 
        self.client.login(username='marcink', password = 'marcink')
        response = self.client.get('/app/authenticated/')
        self.assertTemplateUsed(response, 'testapp/authenticated_view.html')
 

A sam dekorator wygląda tak:

 
import re
 
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
try:
    # functools appeared in Python 2.5
    from functools import wraps, update_wrapper
except ImportError:
    # luckily for us, Django already backported it
    from django.utils.functional import wraps, update_wrapper
 
def with_template(arg):
    """
    A view decorator that handles rendering.
 
    If the view returns a HttpResponse, it is passed intact; otherwise
    the returned value is passed as dictionary to render_to_response.
 
    Usage samples:
 
    @with_template
    def view_func(request, ...):
        return ...
 
    @with_template('custom/template/name.html')
    def other_view_func(request, ...):
        return ...
    """
 
    class TheWrapper(object):
        def __init__(self, default_template_name):
            self.default_template_name = default_template_name
 
        def __call__(self, func):
            def decorated_func(request, *args, **kwargs):
                ret = func(request, *args, **kwargs)
                if isinstance(ret, HttpResponse):
                    return ret
                return render_to_response(ret.get('template_name',
                                                  self.default_template_name),
                                          context_instance=RequestContext(request),
                                          dictionary=ret)
            update_wrapper(decorated_func, func)
            return decorated_func
 
    if not callable(arg):
        return TheWrapper(arg)
    else:
        app_name = re.search('([^.]+)[.]views', arg.__module__).group(1)
        default_template_name = ''.join([app_name, '/', arg.__name__, '.html'])
        return TheWrapper(default_template_name)(arg)
 

Pełen kod można znaleźć tutaj: testowy projekt with_template

Aktualizacja 2008/10/13: zgodnie z sugestią urbana dodałem możliwość przekazania nazwy szablonu jako parametru dekoratora.

Warsztaty IT - Django

Friday, September 26th, 2008

Chcecie dowiedzieć się, o co chodzi w całym tym Django? A może znacie kogoś, kto chce? Właśnie uruchomiłem stronę z informacją o nadchodzących warsztatach "Tworzenie aplikacji w Django" :)

Laboratorium komputerowe

Więcej informacji na stronie.

EuroPython 2008 - podsumowanie

Sunday, July 13th, 2008

I już niestety po konferencji, w skrócie – najlepszej z tych, na których do tej pory byłem.

EuroPython 2008

Bardzo wiele elementów było dokładnie takich, jakimi powinny być: bardzo dobre centrum konferencyjne z przyjemnym barem, piękne miasto, niezłe puby i restauracje, ale kluczowe było to, że impreza przyciągnęła świetnych ludzi i zapewniła wystarczająco dużo czasu, żeby ich poznać. Podczas, na przykład, dwudniowego RuPy zwyczajnie nie było kiedy; jeden wieczór to zdecydowanie za mało.

Większość czasu prelekcyjnego mnie ominęła, ze względu na – pożyczając określenie od Jarka Zgodyaspekt socjalny konferencji (będący zresztą jej najbardziej istotną częścią, zgodnie z moją Wielką Zunifikowaną Teorią Konferencji). Z prezentacji, które widziałem, największe wrażenie zrobił keynote Hansa Roslinga (jeśli jeszcze ktoś nie widział, to polecam te filmy na TED), pomimo bardzo nikłego związku z tematyką konferencji.

EuroPython 2008, Hans Rosling

Ze spraw technicznych – drobiazg, którego powinny się nauczyć organizatorzy wszelkich konferencji: zasilanie jest ważne. Sale na EP nie miały dostatecznie wielu gniazdek w podłodze, ale problem został rozwiązany przez listwy zasilające, porozrzucane wszędzie w takiej ilości że nie było żadnych problemów ze znalezieniem jakiejś z wolnym gniazdkiem w zasięgu kabla do zasilacza. Ciut gorzej było z WiFi, czasami działało słabo lub wcale.

Fotografie: Paul Boddie.

Europython 2008

Monday, July 7th, 2008

Na szybko: od wczoraj jestem w Wilnie, a dzisiaj rano opowiedziałem o szybkim tworzeniu aplikacji WWW. W skrócie: sztuka unikania pracy.

Please note: the admin samples require the newforms-admin branch of Django to run.

Featured tools and libraries:

No, I did not write any of them. I'm just a happy user.

Django na TechAuli

Friday, June 27th, 2008

Na dzisiejszej TechAuli opowiedziałem o Django – dlaczego jest fajne, szybkie i warto używać. Były trafne i ciekawe pytania z sali, być może też udało mi się namówić kilka osób do wypróbowania go w praktyce, więc prezentacja wyglądała na udaną.

RuPy 2008

Wednesday, April 16th, 2008

Wczoraj wieczorem, okrężną drogą, wróciłem z RuPy 2008.

Podsumowanie, w stylu zapożyczonym z net.to, bo pozwala na kolejność leniwą:

  • konferencja była świetnie zorganizowana,
  • lubię Poznań. Na ulicach o 4:30 w nocy są tłumy, zdecydowanie więcej ludzi niż w Krakowie o tej samej porze. O Warszawie można w tym kontekście nawet nie wspominać.
  • Hotel Ilon należy omijać – stan pokoi jest zupełnie inny niż to, co widać na zdjęciach na stronie WWW. Bait and switch, proszę Państwa.
  • kodowanie na żywo to pomyłka. Niezależnie od tego, jak bardzo zwięzły jest wybrany język, prezentacja pokazująca proces tworzenia oprogramowania co do kliknięcia i skutnięcia w klawisz ma bardzo niską gęstość informacyjną. Jeśli na dodatek prezenter nie zadba o dorzucenie co kilka minut czegoś, co ludzi rozbawi, zmartwi albo zaskoczy, publiczność odlatuje.
  • z prezentacji, które udało mi się obejrzeć:
    • ciekawe: Business Natural Languages (Jay Fields), Correlations and Conclusions (Zed Shaw) i Revolver One (pythonowy arkusz kalkulacyjny, Michael Foord z ThoughtWorks) zaprezentowany jako lightning talk,
    • wymagają jeszcze trochę pracy: TDD in Rails, Let the Python crawl,
  • blipcast był zdecydowanie mniej aktywny niż podczas Auli,
  • bardzo dawał się we znaki niedobór gniazdek elektrycznych. To naprawdę powinno być uregulowane odpowiednią ustawą :)
  • Mood Club and Cafe (miejsce Geek Party) – bardzo fajne, chociaż może trochę przyciasne jak na tak liczną grupę,
  • ciekawe różnice w sposobie prowadzenia samochodu: większość kierowców w Poznaniu i okolicach jeździ zgodnie z ograniczeniami prędkości, za to wielu wymusza pierwszeństwo i wyprzedza na ciągłej linii. Teoretycznie połączenie jest logiczne – przy założeniu, że ten z głównej jedzie z prędkością 50km/h, łatwiej jest wyliczyć jak mu się wepchnąć dokładnie przed zderzak tak, żeby nie musiał specjalnie zwalniać :)
  • ogólnie – zdecydowanie warto było pojechać.

Oiola.com - technicznie

Monday, March 31st, 2008

W zeszłym tygodniu opublikowaliśmy kolejny serwis pod szyldem BLF:
oiola.com. O funkcjonalności można poczytać na blogu
binarylifeforms.com
, tutaj – jak poprzednio – opowiem o stronie
technicznej.

Jak już wspominałem, krótkie projekty pozwalają na szybkie i pełne
przetestowanie nowych narzędzi. Tym razem padło na:

  • Django w wersji newforms-admin,
  • deseb,
  • vobject,

oprócz tego po raz kolejny wykorzystałem BeautifulSoup, feedparser i
Instant Django, ale o nich pisałem już wcześniej, więc teraz ograniczę
się do stwierdzenia że potwierdziły swoją przydatność.

A teraz szczegółowo.

newforms-admin

Newforms to biblioteka Django służąca do możliwie wygodnego tworzenia
i obsługi formularzy – generowania HTML, czyszczenia i sprawdzania
poprawności danych.

Newformsy jednak są zarąbiste

Newforms-admin to rozwojowa wersja Django, w której interfejs
administracyjny zmieniono tak, żeby korzystać z newforms zamiast
wcześniejszej biblioteki (nazywanej teraz oldforms).

Chciałbym uzupełnić moją wczorajszą wypowiedź...

W skrócie, jest świetnie. Django słynie z dopracowanego i wygodnego
panelu administracyjno-edytorskiego, który jednak był dość sztywny:
trudno było go rozbudować w sposób chociaż trochę wychodzący poza
bazowy schemat "lista aplikacji, lista obiektów, formularz zmiany
obiektu." Wersja newforms-admin pozwala bardzo łatwo tworzyć nowe
strony, czy całe moduły funkcjonalne zintegrowane z resztą panelu. Do
tego stopnia, że w oiola.com organizatorzy imprez korzystają właśnie z
tego interfejsu, nieco rozbudowanego, ale bez żadnych zmian w kodzie
Django. Rezultatem jest ładny, sensownie wyglądający panel obsługi
wydarzeń uzyskany bardzo niewielkim kosztem (dla porównania – w
webcomicspot.com interfejs edycji komiksów pochłonął mniej więcej 50%
czasu).

To jest spory temat i planuję przygotować obszerniejszy tekst lub
prezentację na temat newforms i gałęzi newforms-admin, więc mam
prośbę: jeśli macie jakieś pytania lub sugestie (na co zwrócić uwagę,
na jakich przykładach warto to zademonstrować (byle nie kolejny
blog-w-20-minut)), napiszcie o nich w komentarzach lub prześlijcie na
mój adres email.

deseb

Django Experimental Schema Evolution Branch, wbrew temu co można
wyczytać z nazwy, nie jest gałęzią, ale biblioteką która
nieprzyzwoicie upraszcza zmiany bazy danych podczas prac nad
projektem. Podczas prac nad oiola.com najczęstszą zmianą bazy było
dodanie kolejnego pola do już istniejącego modelu, co wymaga
odpowiednio skonstruowanych poleceń "ALTER TABLE". We wcześniejszych
projektach wykonywałem te polecenia ręcznie albo tworzyłem własne
narzędzia do stopniowej ewolucji bazy (co sprawdza się szczególnie
dobrze, jeśli nad projektem pracuje kilku programistów), ale DESEB
sprowadza cały problem do wydania polecenia "./manage.py evolvedb".

Pisałem już o uproszczeniu współpracy w zespole dzięki Instant Django;
po dodaniu do projektu deseb wystarcza dopisać do skryptu startowego:

python manage.py evolvedb –noinput –dont-save"

I już, od tego momentu przestały być potrzebne maile "pojawiły się
nowe pola, usuń plik z bazą danych."

vobject

Ważnym drobiazgiem w oiola.com jest możliwość łatwego dodania
informacji o wydarzeniu do kalendarza. Odpowiednim standardem w sieci
jest iCalendar, a biblioteka vobject służy właśnie do obsługi plików w tym formacie,
dzięki czemu bardzo krótki kod wzorowany na tym:

Exposing calendar events using iCalendar in Django

Pozwala na udostępnienie w sieci informacji o wydarzeniach z taką samą
łatwością, jak RSS:

class EventCalendarFeed(ICalendarFeed):

    def items(self, id, ext_id=None):
        return [get_event_or_404(id, ext_id)]

    def item_uid(self, item):
        return str(item.id)

    def item_start(self, item):
        return item.start.replace(tzinfo=tzlocal())

    def item_end(self, item):
        return item.end

    def item_summary(self, item):
        return str(item)

    def item_uri(self, item):
        return item.get_absolute_url()

Odpowiedni fragment urls.py:

    url(r'^e/(?P[^-/]+)/ical/$', EventCalendarFeed()),

podsumowanie

Po raz kolejny poświęciłem więcej czasu na szukanie i uczenie się
istniejących bibliotek niż na pisanie kodu, to się po prostu opłaca.
Najwięcej chyba pochłonęło poznawanie newforms-admin – to wersja
eksperymentalna, dokumentacja nie jest kompletna, na szczęście Python
ma bardzo przejrzystą składnię :)

Komiksy w sieci

Friday, February 15th, 2008

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.

FeedParserFeedJack – ś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 :)

Minimalizm

Saturday, January 12th, 2008

Favpico

To był interesujący eksperyment: jak już napisał Janek na blogu Planemoo, postanowiliśmy stworzyć skończony, zamknięty i dopracowany serwis WWW w ciągu tygodnia (czasu rzeczywistego, a nie liczonego w godzinach-poświęconych-tylko-na-ten-projekt). Efektem jest Favpico, a przykład działania widać we wklejce po prawej stronie mojego bloga.

Serwis pozwala na tworzenie stron z listami odsyłaczy, na przykład stron startowych albo (ahem) listy znajomych. Nie jest to, oczywiście, pierwszy ani ostatni tego typu serwis, ale ten jest najprawdopodobniej najprostszy – każda strona to lista odsyłaczy reprezentowanych przez ich favicony, tak jak tu.

Przy tak niewielkiej ilości czasu konieczne było ciągłe cięcie funkcjonalności. Czy potrzebna jest zmiana koloru tła? Jasne, ale nie ma na to czasu. Usuwanie stron? To samo. Opisy przy odsyłaczach? To już barok. Odrobina składni pozwalająca na łatwiejsze tworzenie odsyłaczy do innych stron wewnątrz favpico? Barok, barok, barok. Co by tu jeszcze usunąć?

Efekt jest fajny: kompletny, zamknięty, przydatny.

Przy okazji, jeśli czyta to ktoś zajmujący się stronami Bootstrapa albo Auli Polskiej to mam prośbę: dodajcie do swoich stron favikonki. Psujecie mi stronę startową :)

Django-multilingual: status update

Thursday, February 1st, 2007

Django-multilingual keeps getting better. The last big missing piece of functionality is a better (read: working correctly in all situations) inline editor for translated content, but I hope to have that ready Really Soon Now. I mean it, I need it :)

Also, I started writing an Introduction to Django-multilingual.