Django 1.2

Właśnie przestawiłem ten serwis na Django 1.2.1. Migracja i dostosowanie kodu zajęły nie więcej niż pół godziny, pomimo sporych zmian jakie były wprowadzone w wersji 1.2. Jak zwykle jestem pod wrażeniem dbałości o zgodność wsteczną i wzorowej dokumentacji projektu.

I niestety tradycyjnie już memory footprint aplikacji wzrósł — nieznacznie, ale od 1.0.x w sumie jest to już ok. 50% większe zapotrzebowanie na pamięć. Z wersji na wersję Django pożera wciąż więcej i więcej.

Zapraszam na PyconPL 2009

Jeżeli ktoś jeszcze się zastanawia, czy wybrać się na PyconPL do Ustronia w październiku, to może przekonam go tym, że będę tam trzymał spicz na temat przygód Adama Słodowego w krainie ramówek webowych — rozłożę na czynniki pierwsze jakąś ramówkę (pewnie będzie to najbliższe mi Django) i spróbuję złożyć coś podobnego używając zamienników.

Dawno nie dawałem żadnego występu publicznego (od ostatniego WarPY w październiku minie 2 lata...), więc trzeba przygotować lepszy show. :)

wMiastoWzięci.pl potrzebują trochę uwagi

W rzeczy samej, serwis wmiastowzieci.pl potrzebuje cokolwiek więcej uwagi. Po niezbyt przyjemnych (i mało produktywnych) przejściach z Google AppEngine przyszedł czas, żeby poświęcić trochę czasu tradycyjnym aplikacjom, a wmiastowzieci.pl to aplikacja tradycyjna aż do bólu: Django, FastCGI i MySQL (do tego parę drobiazgów).

Obiecuję sobie, że w najbliższym czasie trochę nad tym serwisem popracuję.

Oko na RSS

Nie nie chodzi o żadne feedy, tylko o tzw. resident set aplikacji w Django. Obserwuję tę wartość od dłuższego czasu dla tej małej aplikacyjki i z pewnym niepokojem obserwuję jak rośnie, od ~15MB pod koniec 2007 roku przez ~17MB w okolicach wydania 1.0 do ~19MB z wersją 1.1-beta1 (przy wręcz zmniejszającym się feature secie aplikacji). A mój niepokój bierze się z mojej zadziwiająco dobrej pamięci, jak na mój podeszły wiek.

Kiedyś, w zamierzchłych czasach (okolice Slackware 9.0, czyli początek 2003 roku), była sobie fajna, mała przeglądarka WWW, która nazywała się Phoenix. Wyrosła z potrzeby istnienia po prostu przeglądarki i w swoich bebechach była Mozillą (tak się kiedyś nazywała przeglądarka wyrosła z Netscape Communicatora) bez wszystkiego tego, co nie służyło przeglądaniu stron WWW. Była szybka, miała małe wymagania i wszyscy ją pokochali od razu. Pokochali ją tak bardzo, że Mozilla Foundation postanowiła skupić na niej swoje wysiłki developerskie. W szybkim tempie (od wersji 0.4 Oceano z listopada 2002 do wersji 0.8 Royal Oak z lutego 2004) program zbliżył się apetytem na zasoby do swojego rodzica i przestał być postrzegany jako lekki i szybki. Po 7 latach istnienia Mozilla Firefox wciąż bohatersko zwalcza bloat, który był głównym powodem jego wypączkowania z projektu Mozilla Suite.

Na ile ostatnie develpmenta w Django przypominają to, co stało się z Phoeniksem? Na tyle, że nagle wszyscy pokochali Django i zaczęło ono obrastać w rzeczy, które już nie mieszczą się w legendarnych 80% (i django.contrib.gis to naprawdę mały pikuś w tym zestawie). Wygląda na to, że zadowalanie coraz większej rzeszy użytkowników daje w wyniku coraz większe zapotrzebowanie na zasoby...

Rekapitulując, ja też uważam, że martwię się na zapas. Sytuacja sama w sobie nie wygląda jeszcze na niepokojącą, niepokojący jedynie może być ten trend. Czy doprowadzi do tego, że Django przejdzie na ciemną stronę mocy, to się dopiero okaże.

Igranie z przyszłością

Zmiany się szykują... Wspominałem o planach przepisania silnika tego bloga przy użyciu narzędziówki, ale im dłużej nad tym siedzę, tym bardziej jestem przekonany, że nie ma to wielkiego sensu — zbyt wiele rzeczy wymagałoby ręcznej ingerencji, patchowania komponentów i rwania włosów z głowy, bo coś nie działa, jak na przykład odkrycie z wczoraj:

  • nie działa Beaker z powodu błędu w kodzie backendów sesyjnych wykorzystujących bazę danych i memcached (pliki działają OK, ale ich nie chcę);
  • w związku z tym, że nie działa Beaker, nie działa też AuthKit, więc nici z ułatwień autentykacji (np. po OpenID).

Oczywiście, byłbym w stanie naprawić Beaker'a, powstaje jedynie pytanie, czy to się w ogóle opłaca? Alternatywą jest przejrzenie i odchudzenie istniejącej aplikacji w Django, a przy okazji zoptymalizowanie jej nieco, wykorzystując doświadczenie, jakiego nabrałem w ciągu tych kilkunastu miesięcy. To też mogłoby być nienajgorsze rozwiązanie, a na pewno szybsze. Będę żałował Jinja2, ale jak mnie żal przyciśnie, to podłączę sobie ten silnik do Django, żeby stonować trochę ten żal...

Poważnie się zastanawiam i jeszcze nie podjąłem żadnej decyzji — na razie kod wykorzystujący narzędziówkę poszedł do oddzielnego brancha w repozytorium, a w trunku pojawił się kod wersji działającej obecnie, ale nie ma to jakiegoś symbolicznego znaczenia (tak sobie to tłumaczę). Uch, jak ja lubię mieć takie dylematy... :)

Magia zrozumiana

Jestem po kilku tygodniach dłubania aplikacji webowej przy użyciu zestawu narzędzi (Werkzeug, SQLAlchemy lub datastore, Jinja2, WTForms) zamiast Django. Zawsze uważałem Django za kawał świetnego softu, ale dopiero teraz doceniam to, na ile ramówka aplikacyjna ułatwia życie programisty, załatwiając masę rzeczy z kategorii boilerplate przy użyciu kawałka swojej magii.

Narzędziówka jest pozbawiona zupełnie magii. To surowy zestaw narzędzi (niektóre z nich, jak SQLAlchemy mają własną magię wewnętrzną do załatwiania swoich spraw) i żeby zagrały ze sobą w zgodnej orkiestrze i razem umożliwiły napisanie dobrego kodu aplikacji, trzeba napisać sporo rzeczy: procesory żądań (w Django nazywa się to middleware), procesory kontekstu, podsystem konfiguracyjny, skróty ułatwiające życie (jak render_to_response() czy funkcja do odwracania URL-i), podsystem autoryzacji. W Django to wszystko już jest, gotowe i zainstalowane, a użycie jest tylko kwestią podłączenia w konfiguracji lub nawet zaimportowania odpowiedniego modułu — i za sprawą magicznej różdżki masz sitemapę, masz feed RSS/Atom, masz bufor stron i masę innych drobiazgów, na napisanie których poświęciłbyś trochę czasu, bo trzeba to mieć, pomimo tego, że te wszystkie rzeczy nie są corem aplikacji. Łatwo jest to wszystko docenić: działające zręby bloga w Django (tego bloga) miałem po około 2 godzinach roboty (wpisy, etykiety, archiwum, feed z wpisów i sitemapa), a powtórzenie tego samego wyczynu z użyciem zestawu narzędzi zajęło mi ponad 6 godzin pracy, z czego co najmniej 3 spędziłem na użeraniu się z duperelami. Warto było, bo moja wiedza na temat działania aplikacji w środowisku WSGI jest teraz o wiele większa. I lepiej rozumiem bebechy Django.

Z drugiej strony nie można powiedzieć, żeby ta wyprawa miała jedynie edukacyjną wartość — dorobiłem się sporej ilości kodu narzędziowego, który może mi się kiedyś przydać. Zobaczyłem też, jak wydajna i oszczędna w wykorzystaniu zasobów może być mała aplikacja, jeżeli się ją rozsądnie napisze, dla porównania aplikacja tego bloga żre na megiteam.pl ~20MB pamięci rezydentnej, a moja reimplementacja przy użyciu zestawu narzędzi jedynie ~12MB — różnica jest kolosalna (nie wspominam już o tym, że jest o wiele szybsza, prawdopodobnie dzięki Jinja2). Przyszłością tego bloga jest właśnie reimplementacja przy użyciu zestawu narzędzi, głównie w celach oszczędnościowych.

Napisawszy tyle wypadałoby przejść do jakiejś konkluzji... Jak dla mnie to każdy kto robi w webie przy użyciu pythonowych ramówek (nie tylko Django, to się tyczy również Pylons i TurboGears w takim samym stopniu), powinien zrobić sobie taką krótką wycieczkę do źródeł. Będzie mógł wtedy dostrzec zarówno mocne jak i słabe strony używanej przez siebie ramówki, a przede wszystkim zrozumie, jak jej wszystkie komponenty współgrają ze sobą, tworząc całość, która tak bardzo ułatwia pracę.

Nowe zabawki

W końcu się przemogłem i postanowiłem w celach edukacyjnych przemóc się i spróbować zrobić jakąś aplikację na Google AppEngine. Ponieważ chciałem mieć coś więcej, niż tylko naukę odpalania aplikacji pod Django na nowym środowisku, wybrałem sobie narzędziówkę z kosmosu: Werkzeug, Jinja2 i WTForms. I jak do tej pory psioczyłem na AppEngine, to teraz spodobało mi się to środowisko. Co prawda pod względem dołączonych baterii moja nowa narzędziówka jest bardzo uboga w porównaniu do wszystkiego tego, co przychodzi z Django, to jednak dzięki temu zorientowałem się, jak dużo rzeczy z Django tak naprawdę jest mi zupełnie niepotrzebne. Okazało się też, jak prosto można zaimplementować te rzeczy, które są mi potrzebne lub ułatwiają życie.

Więcej na ten temat wkrótce.

Django admin widget for displaying movies

Recently I had to make a widget for Django admin to display movies (H.264 encoded, MPEG-4 and FLV). We already have a widget for displaying the movie inline within the admin change form, but I had to create a widget that opens separate entity (lightbox-like overlay or popup browser window). For a start I took the popup browser — this seemed easier because one of the requirements was that the movie player will not load with the page (I am not that good in javascript, mind you...). What it takes to have a widget that shows some content in separate browser window?

  • you can open window by manually appropriate javascript code, but I prefer to use some ready-made plugin for jQuery, like jquery-popupwindow;
  • you'll need Django view (and template) that displays actual page with embedded player;
  • to embed the player (which is Flash object) you'd need swfobject and the player itself, like JW Player (it plays both mp4 and flv movies — but watch out, it's free only for strictly non-commercial use);
  • a Django's forms widget that will have something that can be clicked to open new window.

Now for the fun part: actual code. The widget code is a mish-mash of my previous experiences in writing custom Django widgets:

class AdminPopupMovieWidget(forms.FileInput):

    class Media:
        js = (
            'js/jquery.js',
            'js/jquery.popupwindow.js',
        )

    def render(self, name, value, attrs=None):
        output = []
        if value and hasattr(value, 'url'):
            width = getattr(value, 'width', 600)
            height = getattr(value, 'height', 500)
            ctx = {
                'url': value.url,
                'href': '%s?url=%s' % (reverse('movie-show'), value.url),
                'class': 'popupwindow',
                'rel': 'width:%(width)d,height:%(height)d' % locals(),
            }
            output.append(u'<a href="%(href)s" class="%(class)s" rel="%(rel)s">%(url)s</a>' % ctx)
            output.append(u'<br />')
        output.append(super(AdminPopupMovieWidget, self).render(name, value, attrs))
        output.append('''<script type="text/javascript">
            $(".popupwindow").popupwindow();
        </script>''')
        return mark_safe(u''.join(output))

The code above produces a widget that has a clickable link with the title of movie URL. The anchor is linked to the URL of the view that produces HTML to display, like the code below:

@require_GET
def view_movie(request):
    movie_url = request.GET.get('url')
    if not movie_url:
        raise Http404
    ctx = {
        'url': movie_url,
    }
    return render_to_response('movie/view.html', ctx,
        context_instance=RequestContext(request))

The only variable that is passed in the context is the URL of the movie, but you are free to include as much as you'd like. Of course, nothing forces you to use urls, you can call the view with a PK to the movie-holding object, but this will require changes to the above view code.

But that's not all. You have to register your model in Django admin application. If you just register it with default modelform, the instances will display themselves with default admin widget. Two more classes are required:

class MovieAdminModelForm(forms.ModelForm):
    movie = forms.FileField(widget=widgets.AdminPopupMovieWidget)

    class Meta:
        model = Movie

class MovieAdmin(admin.ModelAdmin):
    form = MovieAdminModelForm

Is this all? Yes, I think.

Pownce is shutting down

Nie używałem zanadto, ale miałem sentyment, bo serwis był zrobiony w Django. A teraz przejęło ich Six Apart i zabawa się skończyła. To chyba normalne w interwebie, ale żal jakiś jest...

Czas się ujawnić

To uczucie nieporównywalne z niczym: budujesz aplikację przez kilka tygodni lub miesięcu i przez cały czas zastanawiasz się czy ktoś będzie tego używał?. W takiej niepewności trwasz dopóki nie zauważysz, że jednak ktoś zdecydował się jej użyć, a wtedy... No i wtedy właśnie przychodzi ten strzał adrenaliny, który mogę porównać tylko z paroma rzeczami (niestety, nie mogę napisać, co to za rzeczy, bo każda z nich jest albo szkodliwa albo zakazana przez prawo — tę stronę przecież czytują dzieci...). Dziś rano spojrzałem w panel administracyjny jednego z moich prywatnych serwisów i poczułem właśnie TO.

Pomimo trzymania aplikacji w niemal całkowitej tajemnicy do czasu ukończenia prac (wiedziała o niej tylko moja żona i Google, o ile ktoś wiedział czego ma szukać), ktoś zajrzał na wMiastoWzięci.pl i dodał wydarzenie (choćby z tego powodu warte uwiecznienia). Nie będę tego ukrywał dłużej, to mój serwis. Wciąż jeszcze jest szlifowany, wciąż nie wszystko w nim działa, ale jak widać komuś to nie przeszkadzało. Co nie działa:

  • zgłaszanie obiektów (wydarzenia, zdjęcia, recenzje) do moderacji
  • komentarze do wydarzeń i miejsc
  • przeglądanie wielu etykiet na raz

Dla spragnionych informacji technicznych: serwis działa na Django-trunk, baza to MySQL 5.0 (z backendem MyISAM), deployment na FastCGI/lighttpd.

A skoro już poszło w świat, to skończył się luźny czas developmentu w modelu langsam, langsam i trzeba w szybkim tempie dorobić brakujące funkcje serwisu. ;)

Eureka!

Pomimo tego, że robię w Django od paru lat, wciąż zdarzają mi się odkrycia na miarę archimedejskiej eureki. Dziś właśnie zauważyłem, że skrót render_to_response może przyjmować jako pierwszy argument nie tylko nazwę szablonu, ale także listę nazw szablonów. Dzięki temu wiekopomnemu odkryciu kod, który wyglądał dość marnie:

templates = [
    'flagging/%s_flagging_form.html' % obj._meta.module_name,
    'flagging/flagging_form.html',
]
template = loader.select_template(templates)
return HttpResponse(template.render(ctx))

teraz może wyglądać trochę ładniej:

templates = [
    'flagging/%s_flagging_form.html' % obj._meta.module_name,
    'flagging/flagging_form.html',
]
return render_to_response(templates, ctx)

Świadomie używam określeń ładnie i brzydko, bo chodzi tu jedynie o estetykę (obydwa warianty robią dokładnie to samo pod spodem).

Na swoje usprawiedliwienie mogę powiedzieć tylko tyle, że takie zachowanie render_to_response jest obecnie nieudokumentowane (ale zgłosiłem odpowiedniego patcha).

Introducing django-confirmation

I'm pleased to announce my first (hopefully) reusable app for Django: django-confirmation. The idea for this app came from my personal need to handle confirming object's creation on one of my sites. I found few apps performing similar tasks, but both are targeting single classes of objects, while I needed more generic approach (I hate this word...) — there are several classes of objects that need to be confirmed separately but using the same mechanism.

The use case for this app is as follows:

  • non-registered/not-logged-in user creates an object;
  • application sends an email asking user to click (within configurable period of time) on provided link to make the object "active";
  • user clicks a link and makes the object "active" or
  • key expires after specified amount of time.

The implementation is based on what I found in other apps, specially django-registration and django-email-confirmation, but with added support for confirming generic Django model instances (as long as they can have different activity states, that is).

Code reuse that isn't

Django jest fantastyczne m.in. w tym, że istnieje masa podłączalnych aplikacji, które w sposób wystarczająco generyczny realizują często spotykane zadania, jak np. django-registration do rejestrowania użytkowników, czy django-tagging do etykietowania obiektów. Część z tych aplikacji jest naprawdę wysokiej jakości, w dodatku łatwo konfigurowalnych i możliwych do dopasowania w dość szerokim zakresie. Są jednak takie, które dobrze się zapowiadają od dłuższego czasu, jednak na tym się kończy. Co bardziej zaskakujące, wszystkie te potencjalnie użyteczne aplikacje pochodzą z projektu Pinax.

W jednym z projektów potrzebuję dodać funkcję powiadamiania użytkowników o wystąpieniu pewnych zdarzeń. Jest aplikacja django-notification, ale w stanie obecnym nie nadaje się do wykorzystania poza Pinaxem (lub w sposób jakkolwiek różny od tego, jak to jest robione w Pinaksie). Zgłoszoneproblemy z tym związane, jednak zostały odłożone na lepsze czasy. Będę musiał wziąć kod django-notification i wzorując się na nim napisać swoją własną aplikacyjkę, bo na te lepsze czasy się nie doczekam.

Inny przykład. Niedawno potrzebowałem dodać do pewnego projektu możliwość tworzenia obiektów przez niezalogowanych użytkowników — wymagane jest wtedy podanie adresu email i potwierdzenie dodania obiektu przez standardowe kliknięcie w przesłany link. Znalazłem aplikację django-email-confirmation, która robi coś podobnego, ale nie nadaje się do potwierdzania jakichkolwiek innych obiektów, niż jej własne (a ja potrzebowałem zastosować ten mechanizm do 3-4 różnych klas obiektów). Trzeba było napisać własną, na szczęście okazało się to dość proste, przyjmując django-email-confirmation za wzór.

I to samo mam za każdym razem, gdy trafiam na aplikację, która mogłaby mi się przydać, a pochodzi z Pinaxa — bez zakodowania własnego rozwiązania bazującego na tym kodzie się nie obejdzie. Rozumiem, że Pinax jest rozwijany w normalny dla OS/FS sposób, to znaczy w czasie wolnym i nikt nie bierze za to pieniędzy, ale skoro tak to wygląda, to jaki jest sens wydzielać aplikacje jako niby reużywalne, skoro tak naprawdę nie dają się użyć nigdzie poza aplikacjami typu Pinaxa i ich reużywalność to głęboki mit?

Revival

Poszukuję współpracownika do odtworzenia pewnej kultowej gry planszowej w realiach interwebu (Django 1.x + jQuery), może być na AppEngine, ale nie musi. Projekt może być Open Source, ale nie musi (bo w sumie kogo to obchodzi...). Wskazany wiek > 30 lat (młodsi raczej nie będą mieli odpowiedniego szacunku dla tej gry ;)). Gra jest turową strategią, więc łatwo nie będzie.

Kontakt tam gdzie zawsze - Jarek kropka Zgoda na usłudze pocztowej Google®. Proszę, tylko poważne propozycje...

[edit]: kilka osób mnie już o to pytało, nie chodzi o grę Revival (nawet nie wiem, co to takiego).

Krewni i znajomi królika

Wszyscy zaangażowani w przedsięwzięcie już roztrąbili wieści o uruchomieniu Django.pl, więc przyszedł czas na niezaangażowanych. Zajrzałem i... nic. Pomijając tłumaczenie tego, co jest w oryginalnym serwisie, to tam po prostu nie ma niczego nowego. Nie wiem, czego się spodziewałem. Może czegoś bardziej lokalnego? Czegoś, czego w oryginalnym serwisie nie ma? Polskiej specyfiki?

E, jak zwykle się czepiam. Fajnie, że serwis wystartował, teraz trzeba go rozwinąć w coś unikalnego (i pilnować, żeby nie poszedł w maliny). Tłumaczenie przyda się początkującym, a fajnie jest też mieć coś lokalnego.

MySQLdb zje także i Twojego psa

Przynajmniej Pythoniarze powinni się trzymać z daleka od tej bazy, bo jej adapter jest obciążony kilkoma bardzo, ale to bardzo poważnymi błędami (abstrahując zupełnie od problemów, jakie ta baza ma sama ze sobą):

Moim zdaniem kicha. Pierwszy daje się obejść (kosztem nieprzenośnego kodu), ale drugi zupełnie mnie zabija, zwłaszcza, że płacę m.in. za zużycie pamięci...

Środa, więc będzie o środowisku

Czas przyszedł, żeby pochwalić się środowiskiem developerskim. Na linuksie było różowo, na Macu już tak różowo nie jest, ale wydaje mi się, że doszedłem wreszcie do stanu, że infrastruktura nie przeszkadza mi w programowaniu, a środowisko z czasem mi się zintegrowało. Więc jak wygląda to środowisko programisty aplikacji webowych?

TextMate

The missing editor. Vim jest bardzo dobry jako edytor. Jest tak dobry, że nie ma lepszych, ale to tylko edytor. TextMate to coś po środku między Vimem a Eclipse — to jeszcze nie środowisko zintegrowane, ale już dużo więcej niż edytor, głównie za sprawą bundles, czyli dodatkowych funkcji związanych z trybem edycyjnym. Na linuksie kiedyś podobnie bogate w funkcje było Kate, ale już nie jest, odkąd jakaś mądra głowa postanowiła wyrzucić z niego funkcję projektu. Do podobnego poziomu obecnie próbuje doszlusować PIDA, ale idzie to bardzo opornie (z tego co wiem, to z powodu braku siły roboczej — taka mała podpowiedź dla tych, co chcieliby się wykazać w jakimś projekcie). Dla łyżki dziegciu — PIDA ma hook do wykonywania automatycznych akcji przy zapisie bufora, za to TextMate ma okno wyboru pliku do otwarcia z inteligentnym podpowiadaniem (pod Cmd+T).

Od For fun and profit
Od For fun and profit

Virtualenv

Nie zna życia, kto nie służył w marynarce, a nie zasmakował programowania w Pythonie ktoś, kto nie potrzebował kiedyś mieć zainstalowanych dwóch różnych wersji tej samej biblioteki. Lub wręcz z podwórka Django: 0.96.x, 1.0.x i trunk równolegle. Zamiast robić dziwne myki z PYTHONPATH, tworzy się izolowane środowisko z oddzielnym interpreterem i oddzielną biblioteką. Żyć, nie umierać i tworzyć izolowane środowiska. Wygoda polega przede wszystkim na tym, że utworzenie takiego środowiska jest szybkie i nie wymaga szczególnych zabiegów.

Firebug

Tylko dla niego trzymam Firefoksa na Macu. Debugger JavaScriptu jest wielki. Można go określić jako missing plugin for Safari.

SQLite/PySQLite

Nie ma aplikacji webowych bez baz danych, a dzięki ORM można nie babrać się z ustawianiem MySQL czy innego PostgreSQL tylko po to, żeby sobie podevelopować. Dopóki aplikacja porusza się w obrębie tego, na co pozwala ORM, to nie ma powodu, żeby używać innej bazy danych, bo po prostu od strony storage nie widać różnic. Niezwykle bardzo cenię sobie to, że start z aplikacją jest tak prosty i później naprawdę nie ma dla mnie różnicy, na jakiej bazie stoi moja aplikacja (tak długo, jak długo mogę zrobić django-admin.py dumpdata --format=xml). SQLite to fajna, bezobsługowa baza danych, która ma wszystko to, co jest potrzebne w momencie developmentu.

Rzeczy, których jeszcze nie używam, ale pewnie będę używał w niedalekiej przyszłości

Pozostałe rzeczy mają znaczenie drugorzędne (albo nawet trzeciorzędne), bo są albo specyficzne dla moich upodobań (subversion, MacPorts), albo specyficzne dla moich upodobań (Django), albo dla odmiany specyficzne dla moich upodobań (jQuery). Jestem w stanie robić programy w każdym środowisku, ale z moich ostatnich doświadczeń wynika, że jedyny warunek jest taki, żeby to było środowisko przynajmniej trochę uniksawe. :)

Newbies atakują

Jak w Django odczytać dane z pliku?

Once again, I think you're confusing ideas here; Django is simply a set of Python libraries you use to write code, in Python, for web applications. The code in your Django applications is Python code. Not some sort of special separate "Django code", but just plain old ordinary everyday Python code doing the sorts of things plain old ordinary everyday Python code does: importing things from libraries and using them.

Once you get over that conceptual problem, I think you'll have a much easier time of it. (z #)

Skąd się ludziom bierze wizja, że Django to coś nie z tej ziemi? Po PHP? Railsach? Springu? Pamiętam z dawnych czasów, że pytanie "jak odczytać dane z pliku tekstowego?" było jednym z najczęściej zadawanych na pl.comp.lang.php. Czy to stąd? A może stąd, że pisząc aplikację w RoR tak naprawdę nie ma się wiele kontaktu z językiem Ruby, a samo Ruby cierpi z powodu ubogiej biblioteki (zarówno standardowej, jak i 3rd party code)? Czy może chodzi o to, że robiąc w Springu + Hibernate tak naprawdę dłubie się w plikach XML, a kod w Javie (o ile w ogóle jakiś się pisze) jest powtarzalny aż do bólu tyłka?

Dużo złośliwych pytań i nie ukrywam, że mocno tendencyjnych. Nie oczekuję odpowiedzi. :)

Django feeds (twisted way)

Yesterday on #django-newbie IRC channel somebody asked, if it is possible to create syndication feed that gets items filtered by some parameter, that does not come from the URL, but comes from the request instead. Barely remembering my previous work with feeds I replied that there has to be some way to achieve such effect by wrapping feed view in custom view and manipulate call parameters. After long thinking I came to following code (tested and working):

Here's a snippet of the basic urlconf module before applying any changes:

feeds = {
    'label': feeds.LatestEntriesByUser,
}
urlpatterns = patterns('django.contrib.syndication.views',
    (r'^feeds/(?P<url>.*)/$', 'feed', {'feed_dict': feeds}),
)

Using this urlconf, everything that is requested from the /feeds url, is being served by default syndication framework's view, but for the sake of completeness I'll wrap the default view with my own, customized view function. To achieve this, the above urlconf line must be changed to:

(r'^feeds/(?P<url>.*)/$', myapp.views.feeds, {'feed_dict': feeds}),

Then comes my view function in myapp.views:

from django.contrib.syndication.views import feed as orig_feed
def feeds(request, url, feed_dict):
    username = request.user.username
    url = '%s/%s' % (url, username)
    return orig_feed(request, url, feed_dict)

As shown above, I'm modifying the url parameter to contain an extra bit: the username I got from request. In the case of the feed 'articles' and username 'joe', the url that gets passed to built-in view will be /feeds/articles/joe/. So how do I handle this extra bit in my feed class?

Not surprisingly, this case is described in Django documentation (did I say the docs for Django are awesome?) - the feeds reference has a chapter on advanced feeds that covers the exactly identical case. Following this example, I'll add get_object method to my LatestEntriesByUser class:

def get_object(self, bits):
    return Entry.objects.get(user__username=bits[0])

Obvious? No. Easy? Yes. Documented? Yes!

django.forms mnie pokonało (przynajmniej trochę)

Robiąc ostanio w Django trafiłem na pewien problem, którego nie jestem w stanie rozwiązać (przynajmniej elegancko) do dziś.

Załóżmy, że mamy klasę, reprezentującą formularz. Ma ona sobie jakieś pola, powiedzmy:

class MyForm(forms.Form):
    name = forms.CharField(label='name')
    description = forms.CharField(label='description', widget=forms.Textarea)
    
    class Media:
        js = ('js/jquery.js', )

Jak widać, będziemy używać jakiegoś JavaScriptu. Problem polega teraz na tym, że chcę dodać trochę dynamiki do wyrenderowanego formularza — na przykład przy polu name dodać link, po którego kliknięciu pojawi się wiersz z polem description, a sam wiersz z polem description ma być początkowo ukryty, na przykład przy użyciu reguły CSS display: none;. Niestety, o ile można dodać definicję atrybutów do widgetu, to, jak się okazuje, nie ma możliwości określenia dodatkowych atrybutów dla całego pola (co wydaje się logiczne, ponieważ pole jest pewnym abstraktem i nie ma własnej warstwy prezentacji). Takich atrybutów, które na przykład mogłyby określić klasę CSS dla elementu obejmującego zarówno label, jak i widget, czy może nawet coś, co mogłoby być dodatkowym tekstem, wyświetlanym oprócz etykiety i widgetu.

Próbowałem paru podejść i żadne nie było zadowalające — albo powodowało, że cały dokument się nie walidował (dodawanie własnego atrybutu do widgetu i następnie wyświetlanie całego wiersza z klasą, będącą wartością tego atrybutu), albo było potwornie brzydkie (słownik z konfiguracją dodatowych atrybutów dla każdego z pól, któremu chcę coś dodać lub przedefiniowanie wszystkich wbudowanych klas pól, żeby akceptowały dodatkowe argumenty). Inne sposoby, których nie próbowałem, ale też przychodziły mi do głowy, były o wiele bardziej hackerskie, np. klasa domieszkowa do wstrzyknięcia w klasy pól wbudowanych przy użyciu jakiegoś rodzaju monkey patchingu. Nie wiem, może i by to zadziałało, ale moje upodobanie do prostych rozwiązań mocno by ucierpiało.

Efekt jest taki, że robię to nieelegancko i wciąż szukam inspiracji, żeby wymyślić sposób zrobienia tego ładnie. Każda sugestia mile widziana.

Moja pierwsza aplikacja na AppEngine

W celach zupełnie edukacyjnych postanowiłem zrobić jakąś aplikacyjkę na Google AppEngine, na początek w Django 1.0 (na próby ujarzmienia WebOb czas przyjdzie później).

Napisałem kawałek kodu, zdeployowałem, co mogę powiedzieć po tych paru godzinach... Django musi zostać mocno okrojone, żeby zadziałało poprawnie. Ograniczenie do 1000 plików na aplikację oznacza, że z samego Django trzeba wywalić wszystko, co niepotrzebne lub nieużywalne, bo samo Django liczy sobie około 900 plików i na aplikację nie zostanie wiele miejsca. Przydaje się AppEngine Helper for Django, dzięki temu jest sporo prościej. django.forms można używać, ale ostrożnie i w razie potrzeby podpierając się google.appengine.ext.db.djangoforms (oryginalne ModelChoiceField nie działa). Modele przypominają modele Django, używa się ich podobnie, ale API nie jest takie samo, więc trzeba uważać. Jeszcze nie rozgryzłem autoryzacji, ale nie wygląda jakoś tragicznie.

W sumie wygląda nieźle, ale praca z takim okrojonym Django jest dość stresująca. It's a challenge, baby. :)

Nowe komentarze zlądowały

Zaktualizowałem Django na zgodowie.org do wersji 1.0 (a nawet lepiej). Co za tym idzie, są już też nowe komentarze. Udało mi się (przy użyciu brudnych sztuczek, bo inaczej się nie dało) doprowadzić do tego, że najważniejsza zmiana, czyli wymaganie podania emaila przy dodawaniu komentarza, nie jest widoczna — nie jestem zainteresowany kolekcjonowaniem adresów email moich czytelników (prywatnie uważam to za idiotyzm). URL na razie nie jest wykorzystywany, ale myślę, że będzie już niedługo — jak tylko znajdę trochę czasu.

Jak na razie wydaje się, że wszystko działa...

Przybywa

Przybywa dokumentacji w moich django-tutors — skończyłem (a przynajmniej tak mi się wydaje) odcinki o uploadowaniu plików do ścieżek określanych statycznie i dynamicznie, właśnie kończę odcinek o upload handlers. Na napisanie czeka jeszcze odcinek o storage backends, a potem... A potem to się zobaczy.

Padła propozycja, żeby napisać coś o uploadowaniu wielu plików na raz przy użyciu jakiegoś flashowego uploadera, ale nie wiem, czy taki dość specyficzny temat mi odpowiada. A może ktoś zechciałby napisać taki artykuł? Projekt django-tutors jest otwarty, kto chce może dostarczyć co chce, to niekoniecznie musi być komplet (dokument + przykładowy kod), jeżeli czegoś będzie brakowało, to w razie potrzeby się dopisze. Wymaganie właściwie jest tylko jedno, to musi działać z fabrycznym Django 1.0 (czyli bez przeróbek w kodzie ramówki).

Jeżeli ktoś chce dołożyć swoje 3 grosze do projektu, to proszę się zgłosić mailem na moje konto na gmailu (jarek.zgoda i tak dalej), albo odezwać się na jabberze, też na moje konto na gmailu.

django-tutors

Moje niedawne przejścia z uploadem plików popchnęły mnie do tego, żeby spisać moje doświadczenia w postaci samouczków. Na początek poszedł właśnie upload plików, jak do tej pory napisałem o uploadzie do statycznej ścieżki.

django-tutors

Wszelka pomoc mile widziana — fragmenty szkoleniowego kodu, samouczki, itp. Jeżeli ktoś chciałby zredagować jakiś artykuł, to oczywiście zapraszam. Język: polski. Na razie wyłącznie.

Musi poczekać

Od kilku tygodni mamy w Django nową ramówkę do komentarzy. Kod jest pokłosiem projektu Google Summer of Code™ i trafił do repozytorium Django właściwie bez poważnego przejrzenia, w wielkim pośpiechu, już po wydaniu którejś bety. Resultatem jest fura błędów i niedoróbek, które znalazły się w finalnym wydaniu Django. Nie chodzi bynajmniej o to, że ktoś to marnie napisał — dla każdego projektu programistycznego normalne jest to, że pierwsza wersja służy temu, by poprawić podstawowe błędy i doszlifować ją w trakcie używania. Nowa ramówka nie miała szansy sprawdzić się w boju, pod czujnym okiem dziesiątek czy setek użytkowników.

Próbowałem doprowadzić do tego, by zmiana ramówki komentarzy była niedostrzegalna dla moich czytelników. Niestety, okazało się, że w obecnym stanie ramówki jest to po prostu niemożliwe bez napisania... własnej ramówki. Nie wszystko daje się owrapować i nie wszystko można podmienić, nawet przy bardzo dynamicznej naturze Pythona. Dlatego wspomniane prace konserwatorskie zostają odłożone na bliżej nieokreśloną przyszłość, gdy ramówka otrzyma wreszcie wymaganą konfigurowalność. W sumie, patrząc na to z innej strony, skoro działa, to po co poprawiać? ;)

Nowe logo dla Django

The Framework for Ponies

the web framework for ponies with magical powers

Różowe podoba mi się bardziej. Przypomina mi trochę mój pomysł na logo i maskotkę firmy, w której do niedawna pracowałem — różowego pluszowego króliczka w czarnym lateksie. Propozycja nie przeszła, ale jestem do niem mocno przywiązany (nadal uważam, że była najlepsza).

Przerwa technologiczna

W ciągu najbliższych kilku dni będę musiał wyłączyć komentarze na godzinę czy dwie, żeby przeprowadzić aktualizację do nowszego Django (z nową ramówką do komentarzy). Ale jeszcze chwilę poczekam, może poprawią zgłoszone przeze mnie błędy. ;)

Jest Django-1.0

Wydane

jarek:~/install/django$ svn log | grep zgoda | wc -l
         17
   

Niewiele, ale duma mnie rozpiera — dokonaliśmy tego!

Będzie, czy nie będzie?

Dziś ma zostać wydane Django 1.0.

Będzie?

Nie będzie?

Upload plików w Django (po nowemu)

To jest głównie a note to self, żeby mieć to pod ręką w razie potrzeby.

Zakładając, że mam funkcję i model:

def get_userphoto_upload_path(instance, filename):
       return 'users/%s/photos/%s' % (str(instance.user_id), filename)

class UserPhoto(models.Model): user = models.ForeignKey(User, verbose_name=_('user'), related_name='photos') photo = models.ImageField(_('photo'), upload_to=get_userphoto_upload_path) caption = models.CharField(_('caption'), max_length=200, null=True, blank=True)

w klasie formularza metoda save() powinna zawierać kod:

def save(self, user):
       user_photo = UserPhoto(user=user)
       if 'caption' in self.cleaned_data:
           user_photo.caption = self.cleaned_data['caption']
       data = self.cleaned_data['photo']
       user_photo.photo.save(data.name, data)
       return user_photo

Zawsze zapominam, jak powinno się to robić. Może teraz będzie mi łatwiej trafić na ślad własnych dokonań. ;)

Nowe comments w Django

Dziś w Django pojawiła się zrefaktoryzowana ramówka do komentarzy. Tym razem przed aktualizacją powstrzymuje mnie comment-utils, które chroni ten piękny kawałek internetu przed spamem. Parę dni i będzie, a jak nie, to popracuję nad tym osobiście. :)

Django po polsku gotowe na 1.0

Tytuł cokolwiek na wyrost, jak sądzę, ale mam powody do dumy. W spolszczeniu Django nie brakuje już żadnych tekstów, jedynie dwa (oba dotyczące GIS) powinny zostać przejrzane przez kogoś, kto się na tym temacie zna. Biorąc pod uwagę nadchodzący string-freeze, mogę się spodziewać, że w takim stanie dotrwamy do finalnego wydania.

Jam-ci to sprawił. Tymi recami!

NFA jest wielkie!

Dziś w pracy miałem robotę głównie poszukiwawczą — było kilka rzeczy, których nie wiadomo było jak zrobić, choć wiadomo było, że się da. Na przykład na początek: jak w adminie Django wyświetlić obrazek na formularzu edycji obiektu, zamiast zwykłego linku do niego, jak to jest domyślnie? Na szczęście wiedziałem jak się to robi, więc nie miałem zbyt trudnego zadania na początek.

W nowym Django (czyli beta-1 lub nowszym) w adminie jest to o tyle proste, że dla każdego indywidualnego pola formularza można przypisać odpowiedni widget, czyli reprezentację w HTML. Wystarczyło utworzyć sobie odpowiednią klasę dla takiego widgetu:

class AdminImageWidget(forms.FileInput):

    """
    Widget do wyświetlania obrazka w adminie (change form)
    """

    def render(self, name, value, attrs=None):
        output = []
        file_name = str(value)
        if value and hasattr(value, 'url'):
            output.append('&lt;img src="%(url)s" alt="image" /&gt;&lt;br /&gt;' % {'url': value.url})
            output.append('%s ' % _('Change:'))
        output.append(super(AdminImageWidget, self).render(name, value, attrs))
        return mark_safe(u''.join(output))

i następnie użyć jej w klasie administracyjnej dla modelu:

class MovieImageAdmin(admin.ModelAdmin):
    list_display = ('movie', 'width', 'height', 'shot_at')
    search_fields = ['movie__title']
    save_on_top = True
    list_filter = ('movie', 'width')

    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'image':
            kwargs['widget'] = AdminImageWidget
        return db_field.formfield(**kwargs)

Metoda formfield_for_dbfield jest wywoływana dla każdego pola formularza i w tym przypadku sprawdzeniu podlega nazwa pola, ale można równie dobrze sprawdzić jego typ lub inny atrybut, aby dopasować właściwy widget. W ten sposób można także dostosować wyświetlanie plików video czy reprezentację dla plików dźwiękowych.

No dobrze, to było proste i nie zajęło mi całego dnia. Ale następna robota nie była już taka oczywista. Kolega chciał, żeby główne okno aplikacji administracyjnej (to, co jest pod / w adminie) dostało kilka dodatkowych zmiennych w kontekście. W starszym Django (sprzed newforms-admin) nie byłoby to możliwe bez patchowania samego Django (co nie jest kłopotliwe, jeżeli używa się Virtualenv, ale zawsze jest uciążliwe), ale teraz dostaliśmy wszystkie potrzebne narzędzia.

Jak wiadomo, za sterowanie aplikacją administracyjną odpowiada obiekt klasy AdminSite, który jest o tyle fajny, że jest najzwyklejszym obiektem najzwyklejszej klasy. Klasy, którą oczywiście można nadpisać. Jak to może wyglądać? Na przykład tak:

class AdminSite(admin.AdminSite):

    def index(self, request, extra_context=None):
        if extra_context is None:
            extra_context = {}
        ctx = {
            'my_item': u'my_value',
        }
        extra_context.update(ctx)
        return super(AdminSite, self).index(request, extra_context)

Nieprzyjemy aspekt używania obiektu site własnej klasy AdminSite jest taki, że nie działa funkcja autodiscover() i wszystkie klasy administracyjne trzeba rejestrować własnoręcznie w tym samym miejscu, gdzie znajduje się obiekt klasy AdminSite. Ale to chyba stosunkowo mała niedogodność w porównaniu z dostarczoną mocą.

Prawie, prawie

Django jest coraz bliżej mitycznego "1.0" — właśnie wyszło 1.0-alpha-2, w którym dostaliśmy zrefaktorowane pola FileField i pochodne (czyli również ImageField), przebudowę przeszedł także podsystem sygnałów.

To tak gwoli kronikarskiej ścisłości, bo to są rzeczy, które albo wymagały ode mnie przepisania kawałka kodu, albo długo na nie czekałem.

Admin w oddzielnej subdomenie (post-NFA)

Zanim NFA zlądowało w trunku Django (określmy to umownie jako wersję 0.97-pre, w odróżnieniu od 1.0-alpha), aby mieć aplikację we własnej subdomenie trzeba było użyć specjalizowanego middleware, które podmieniało zawartość request.urlconf. W przypadku django.contrib.admin było to o tyle proste i oczywiste, że aplikacja administracyjna miała swój własny URLconf (w postaci django.contrib.admin.urls) i w najprostszym rozwiązaniu wystarczyło utrzymywać mapowanie subdomeny na URLconf. Wraz z nadejściem 1.0-alpha nie jest już tak łatwo, ponieważ tego URLconfa po prostu już nie ma. Aby to rozwiązanie działało nadal, musiałem trochę pokombinować.

Pierwsze podejście z wpisaniem na pałę django.contrib.admin.site.root jako wartości klucza admin w mapie domen oczywiście nie miało szans powodzenia (to jest funkcja, a nie moduł).

Podejście drugie: skoro jest to view, to trzeba zrobić taki URLconf, w którym będzie on wywoływany na / i wszystko, co dalej od tego URL-a. Powstał plik admin_urls.py z zawartością niemal identyczną, jak w dokumentacji Django, jedyna różnica polegała na tym, że obsługiwany URL nie zawierał admin:

urlpatterns = patterns('',
    (r'^(.*)', admin.site.root),
)

I działa.

Na głęboką wodę

Od 10 minut serwis działa na Django-1.0-alpha. Widzę parę rzeczy, które będą wymagały poprawienia, ale ogólnie jest lepiej (i mniej roboty) niż się spodziewałem. Największą zagwozdką była zmiana wprowadzająca zgodność z WSGI — wymagało to dopisania FORCE_SCRIPT_NAME do settings.py. Na szczęście (znowu!) jest to opisane w dokumentacji Django.

We're at ALPHA, man!

Django doszło z numerkiem do 1.0-alpha, co oznacza, że wszystko jest na dobrej drodze do dotrzymania obiecanego terminu wydania 1.0! Internet ma wreszcie szansę stać się cokolwiek przyjemniejszym miejscem (od strony programistycznej).

A ja siadam ponownie do sprzątania po rewolucji, jaką było dołączenie NFA do trunka...

Wszystko się rozpieprzyło, nic nie działa, aaaa!

W trunku Django zlądował właśnie branch newforms-admin, a niejako przy okazji django.newforms to już po prostu django.forms. Nic nie działa. Żadna aplikacja nie daje się uruchomić. Czeka mnie masa roboty.

I love this game!

Skaluj się, małpo!

Hehe... Widziałem już takie teksty, że niby żeby myśleć o skalowaniu, trzeba mieć po co się skalować, względnie żeby skalowanie zostawić na później, gdy już będzie potrzebne. Niejaki Ted Dziuba dał to po raz kolejny, tym razem dosadnie i z werwą.

Słyszysz? Przestań pieprzyć o skalowalności, i tak nikt nie będzie używał twojej aplikacji. No.

Archiwum is back

Poprawiono najbardziej denerwujący mnie błąd w Django z ostatnich czasów i dzięki temu wróciło archiwum. Oh, my...

Sława, sława!

http://oebfare.com/blog/2008/jun/24/django-code_swarm/

Około drugiej minuty, w prawym górnym rogu...

Zmiana planów w ostatniej chwili

Dosłownie w ostatniej chwili zdecydowałem się na zmianę dostawcy hostingu na djangohosting.ch, pomimo że byłem zdecydowany na coś innego. Po sugestii Thomasa (dzięki!) przyjrzałem się usłudze dokładniej i postanowiłem dać szansę. Wykupiłem najtańszą opcję, wypróbowałem one-click-django-installer i oczywiście zameldowałem się po SSH. Już pierwsze pół godziny sesji przekonało mnie, że jest tam o wiele przyjemniej niż na Alwaysdata — nie ma żadnych myków z przestawianiem $HOME, no i zwiększanie opcji jest bardziej granularne (oddzielnie procesy, porty, pamięć i przestrzeń dyskowa), a cała infrastruktura w dużej mierze przypomina to, co jest na MegiTeam.pl, z paroma udogodnieniami:

  • można uruchomić serwer developerski django i w razie problemów mieć dostęp do pełnego tracebacka;
  • pełna kontrola nad uruchamianiem FastCGI;
  • łatwiejszy dostęp do logów Lighttpd (chociaż jest w nich równie mało, jak w logach nginxa na megiteam).

Ogólnie wygląda to bardzo dobrze, a przy tym jest trochę taniej.

Sztuczny tłok

Dużo się ostatnio dzieje w Django — co chwilę ktoś commituje zmiany do repozytorium i można odnieść wrażenie, że opublikowanie daty wydania wersji 1.0 obudziło w developerach nowy zapał i chęć do posprintowania na zakończenie wyścigu.

To mylne wrażenie. Przytłaczająca większość pojawiających się w repozytorium zmian to są poprawki w dokumentacji, w docstringach oraz style fixes. A kod poprawiający rzeczywiste problemy czeka sobie na lepsze czasy.

Ten wpis był tytułem ochłodzenia emocji, gdyby ktoś widząc tempo commitów wnioskował z tego, jak dużo i szybko kod w Django jest poprawiany... ;)

Zdecydowałem się na dostawcę

To był tydzień pełen przemyśleń, ale w końcu doszedłem do jakichś wniosków. Mój najbliższy projekt będę hostował na Alwaysdata. Co prawda całość serwisu jest po francusku, ale jakoś daję sobie z tym radę.

Ujęło mnie to, że jest:

  • tanio (€ 6 za miesiąc w najtańszej opcji);
  • dobry support na forum (po angielsku!);
  • dość duża wolność, jak na shared plan;
  • krótki ping, serwery stoją w OVH w Roubaix (Francja).

W związku z małą ilością dostępnego miejsca na dysku, będę musiał media i uploady na S3, ale nie powinno być to jakoś szczególnie uciążliwe.

Konkurent (djangohosting.ch) przegrał głównie przez zerowy wybór w dziedzinie bazy danych (tylko MySQL, bez PostgreSQL), ale nie była to przegrana autorytatywna. Dam mu szansę następnym razem. ;)

Wybierz sobie dostawcę

A tymczasem wybór dostawcy hostingu pod Django jest zaskakująco mały, o ile ktoś celuje w użytkowników z Polski, a przy tym nie dysponuje kwotą wystarczającą do wykupienia sobie dedyka w Hetznerze.

Jest za Atlantykiem kilka firm, które chętnie przytulą większą lub mniejszą aplikację w Django za bardzo skromną kwotę, ale te miejsca mnie nie interesują. Ping po 200ms to nie jest coś, na co chciałbym narażać moich klientów. Na Ukrainie też jest pewna firma, która chętnie pohostuje aplikację za niewielkie pieniądze, ale na Ukrainę pingi z Polski lecą wolniej, niż do Francji, bo pakiety muszą zahaczyć o Frankfurt. Mili Szwajcarzy hostują się w Hetznerze, więc pingi do nich są co najmniej znośne, podobnie jak do Francuzów (ci z kolei się hostują w OVH). W Polsce mamy chyba tylko MegiTeam i ITL, ale ich ceny są zupełnie nie na miarę moich możliwości (przynajmniej o ile chodzi o cokolwiek więcej niż ten blog). W efekcie wybór jest między Francuzami a Szwajcarami, a każda z tych ofert ma znaczące niedobory (choćby w porównaniu do WebFaction).

Mam jeszcze 2 tygodnie na podjęcie decyzji.

Zniknęło archiwum

Ale wróci, gdy tylko w Django zostanie poprawiony błąd ze zgłoszenia 7155 (patch działa, ale nie będę go nakładał ręcznie, nie pali się).

QS-RF w trunku Django

Queryset-refactor zlądował w trunku Django. Wspominam z kronikarskiego obowiązku, bo nie zauważyłem żadnych problemów z moimi aplikacjami w związku z tym.

Korci mnie Django Dash

Django Dash to konkurs programistyczny podobny do Ludum Dare czy PyWeek — chodzi o to, żeby w 48 godzin wyprodukować kompletną, działającą aplikację w Django. Mam pewien pomysł i nawet wizję, jak to zrobić, ale nie jestem pewien, czy wytrzymam te 48 godzin, czy po prostu nie jestem za stary na takie kodowanie. ;)

Nie mam wizji na AppEngine

Dostałem to konto i po całym weekendzie zastanawiania się nie umiem sobie wyobrazić aplikacji, którą mógłbym napisać i odpalić na AppEngine. Ograniczenia są cokolwiek duże, ale sama perspektywa jest przez cały czas kusząca. Dam sobie jeszcze tydzień na przemyślenie, czy w ogóle w to brnąć.

Przez cały czas mam przeczucie, że to jest coś, co daje duże możliwości.

Got it, gonna try it over the weekend

Thanks for signing up to try Google App Engine! Your account has been activated, so you can begin building applications!

No to zobaczymy, co się z tego da wyciągnąć... Mam pod ręką kilka eksperymentalnych projektów, któryś z nich zląduje na AppEngine.

No i chyba mnie ominęło

Wszystko wskazuje na to, że moje zgłoszenie do testowania Google AppEngine nie zostało wylosowane, 10000 kont zostało rozdanych, a mnie zostało czekanie na następny batch i ćwiczenie na lokalnym dev serwerze. Znajomy zaproponował, że da mi jedną ze swoich trzech przydziałowych aplikacji, żebym mógł sobie potestować, więc pewnie nie będzie to trzening tak całkiem na sucho...

W każdym razie już widać, że o ile z Django na AppEngine zostaje bardzo dużo, to modeli używać trzeba tych dostarczanych przez Google. Nie minęło wiele czasu, a już pojawiły się plany rozszerzenia zaplecza bazodanowego Django o obsługę API przechowywania danych na AppEngine. Może to zaowocować szybszym merge odgałęzienia queryset-refactor do głównej gałęzi rozwojowej Django.

W obecnej formie na AppEngine nie wszystko jest zaimplementowane (nie ma np. M2M), z niektórych rzeczy trzeba zrezygnować, jak z interfejsu administracyjnego, czy z djangowego mechanizmu sesji, ale i tak jest to na tyle ciekawe, żeby chcieć dostać sztukę dla dokładniejszego przyjrzenia się. :)

Hot! (znowu się spóźniłem)

Google wystrzeliło z nowym pomysłem — AppEngine to nowy (i z opisu wynika, że rewolucyjny) hosting aplikacji webowych w ścisłej integracji z usługami Google, na razie przede wszystkim w Pythonie. W domyślnej instalacji jest Django 0.96.1, ale można też wziąć sobie wersję z SVN. Na razie trochę niejasne jest, w jaki sposób połączyć implementację modeli by Django z API, którego używania wymaga Google, ale sądzę, że wszystko się wyjaśni wkrótce.

I jak zwykle się spóźniłem z zapisaniem, trafiłem na waitlistę...

Jakiś problem z Akismet

Akismet robi jakieś hocki-klocki, gdy w komentarzu wpisuje się link do strony z plikami listy mailowej WARPY. Trzeba będzie to zbadać.

Aksimet poszedł w krzaki

Nie wiem, na którym końcu łańcucha pokarmowego został popełniony błąd, ale przez kilka godzin nie można było dodawać komentarzy dzisiaj z powodu brakującego pola user_agent, którego (zupełnie nie wiedzieć czemu) system antyspamowy nie dostawał podczas sprawdzania komentarza.

Przy okazji wyszło na jaw, że logi nginxa, jakie gromadzi megiteam.pl nie są szczególnie przydatne — nie wiadomo co jest logowane (stdout? stderr?), nie wiadomo kiedy wystąpił błąd, bo nie ma daty i czasu przy wpisach w logu fastcgi, są jedynie w access_logu, ale tam nie ma z kolei komunikatów o błędach. Zresztą, w logu fastcgi też ich nie ma, jedynie ślady w postaci komunikatów to broken pipe.

To w końcu umierają, czy nie?

Umierają, umierają, żadna przecież nie trwa wiecznie. Idea WarPy okazała się jednak nieco bardziej żywotna, niż przypuszczałem — agonia potrwa co najmniej o 2 tygodnie dłużej.

27 marca Marcin Świderski (forgems) przedstawi wprowadzenie do Django, godzina i lokalizacja ta sama co zawsze (19:00 na "elce"). A tydzień później, czyli 3 kwietnia, w ramach nadrabiania zaległości, opowiem o tłumaczeniu i lokalizowaniu programów w Pythonie (czyli o i18n i l10n).

Wyczesane zarządzanie obiektowym keszem w Django

Bardzo, bardzo dokładnie przyglądam się wszystkiemu, co związane z cache na poziomie obiektów w Django (np. projektowi django-orm-cache). Znalazłem blogowy wpis (po rosyjsku, a jakże!), który opisuje rozwiązanie bardzo podobne do tego, którego i my używamy, ale o wiele bardziej kompletne. Wypadałoby to teraz gdzieś przetestować, ale w środowisku, w którym miałoby to jakikolwiek sens, nie za bardzo możemy — po prostu nie ma na to czasu...

Ku pamięci, jak to Aleksiej Koszeliew załatwił sprawę odświeżania obiektowego cache w Django. Polecam uwadze, jeżeli kogoś też interesuje to, jak inni dają sobie radę z wygaszaniem cache...

Hi, all, I'm official

Mój pierwszy commit do repozytorium Django, jako oficjalnego maintainera polskiego tłumaczenia.

Ludzie mają problemy

A my nie! (My, czyli kilka naszych aplikacji)

Iwan Sagalajew, pracujący dla yandex.ru, podzielił się kilkoma spostrzeżeniami po nieudanym odpaleniu nowego serwisu społecznego. Czytałem to z niekłamanym zadowoleniem — większość problemów, które tam opisał, nie ma nawet szans, żeby nas dotyczyć. Po kolei:

  • zbyt długi czas zapisu danych sesji, nie dotyczy nas, bo sesje trzymamy w memcache;
  • efekt "dog-pile", nie dotyczy nas, bo rzeczy kosztowne robimy poza aplikacją (poniekąd asynchronicznie);
  • pomimo nacisków naszego DB-speca, nie normalizujemy naszego modelu nadmiernie.

Nie mam złudzeń, że w pewnym momencie będziemy musieli troszkę przyciąć nasz radosny bałaganik, ale aplikacja została zaplanowana z tak dużym zapasem, że to na pewno nie nastąpi w ciągu najbliższych kilku miesięcy...

Mała aktualizacja, pod wpływem komentarza Bluszcza — to on to wymyślił. A żeby nie rozpłynął się w samozachwycie, to wymyślił też parę marnych rzeczy, ale tego co marne pozbędziemy się prędzej czy później...

Jedno oko na Maroko

Jest kilka projektów związanych z Django, na które warto jest mieć oko, z różnych względów. Niektórych używam, więc z konieczności śledzę ich rozwój (m.in. django-registration i django-comment-utils), ale są też takie, które rokują rozwiązanie trapiących mnie problemów.

Jednym z takich projektów jest django-orm-cache, a problem, który ma on rozwiązać dotyczy każdego, kto używa cache na poziomie obiektów w Django. Został on opisany przez Davida Cramera (który także jest pomysłodawcą i jednym z developerów tego projektu). W skrócie chodzi o to, że używany na poziomie obiektów i ich grup cache staje się dodatkowym storage, w którym trzeba odzwierciedlić zmiany, wprowadzone w głównym storage (czyli w samych obiektach). Dobrego rozwiązania nie ma — można rygorystycznie pilnować wygaszania obiektów w cache przy każdej modyfikacji (przydają się tu sygnały), można wersjonować klucze przy użyciu jakiegoś znacznika, ale żadne z tych rozwiązań nie jest ani wygodne, ani łatwe. Django-orm-cache ma ułatwić życie, przejmując zadanie automatycznego wygaszania obiektów w cache przy każdej modyfikacji obiektu.

Projekt nie jest jeszcze w stanie nadającym się do użytku, ale widać w nim jakiś ruch i to dobrze wróży. Chciałbym już teraz mieć coś takiego w mojej narzędziówce w pracy...

Mówisz - masz

Bluszcz zażyczył sobie RSS-ów dla konkretnych kategorii, żeby odfiltrować offtopiki na planecie. Tradycyjnie na wyrost kazałem mu się zgłosić w przyszłym tygodniu, ale to nie byłaby aplikacja w Django, gdyby rzeczywiście miało to tyle trwać. I trwało w sumie 20 minut. Proszę, masz RSS-y z kategorii (u mnie się one nazywają etykietami). Tę, która Cię interesuje, znajdziesz na stronie z wpisami pod etykietą python.

Release early... Ale to nie my

Django zdaje się być całkiem w poprzek przyjętej zasadzie w świecie OS/FS: release early, release often. Ostatnie oficjalne wydanie Django miało miejsce 11 miesięcy temu, (23 marca 2007 roku). Rozumiałbym to, gdyby w projekcie niewiele się działo i rzeczywiście nie byłoby co wydawać, ale działo się wiele. Od tamtego czasu doprowadzono do używalności newforms, wprowadzono pełną wewnętrzną unikodowość, znacząco zmieniono także wyjście wprowadzając automatyczne eskejpowanie zmiennych podczas renderowania szablonów. Każde z tych wydarzeń (a pewnie i kilka innych) zasługiwałoby na wydanie, choćby po to, by nie powiększać przepaści pomiędzy kodem wydanym oficjalnie, a kodem rozwojowym.

Nie podoba mi się to.

Jest nas dużo, ale wciąż za mało

Na Django People jesteśmy teraz na 4 miejscu, razem z Niemcami. Uważam to za całkiem niezły wynik. Przed nami są Brazylia, Wielka Brytania i US of A (w tym przypadku chyba bardziej sprawiedliwe byłoby liczenie według stanów, ale się nie upieram), więc raczej kraje albo dużo bardziej liczne ludnościowo od nas, albo bardziej ambitne, jeżeli chodzi o pokazanie się. Mogę się mylić (i pewnie się mylę), ale każde miejsce wśród pierwszych 7-8 powinno nas zadowalać, choćby z powodu tego, ile osób przychodzi na WARPY (w tym tygodniu zostało odwołane). Python zajął już poczesne miejsce w galerii języków programowania, a Django jest już powszechnie rozpoznawane.

To dobrze. Roboty jest dużo, a ten wózek musi się toczyć.

Wszystkie ręce na pokład!

Wczoraj pojawił się serwis Django People i w szybkim tempie wskoczyliśmy (my jako reprezentacja Polski) na 5 miejsce. To by mniej-więcej odpowiadało temu, co widać było m.in. na zeszłorocznym EuroPython, ale proszę nie ustawać w wysiłkach, klikać i się rejestrować. Naszym obecnym celem, po połknięciu Francji, Hiszpanii i Chin, są Niemcy!

Tutoriale mnie dygają

W szczególności tutoriale do ramówek webowych. Każda ramówka w pewnym momencie dorabia się "20 minutes wiki tutorial" (czasem nawet w formie screencastu), ale w większości przypadków są one całkowicie bez sensu — nie pokazują tego, jak ramówka działa, nie uczą, jak jej używać, nie opisują jej komponentów. Zaczęło się od niesławnego screencastu RoR, a potem było już z górki. Pamiętam kilka lat temu, gdy w krótkim czasie wystartowały TurboGears i zaraz potem Django, że początkowo TG wygrywało popularnością właśnie z powodu posiadania takiego screencastu (i uzyskiwało opinię łatwiejszego do nauczenia). Lista mailowa TG pękała w szwach, zapowiadano nowe, ekscytujące możliwości kolejnych wydań ramówki, ale minęło kilka-kilkanaście miesięcy i to Django przejęło inicjatywę. Po kolejnych kilku-kilkunastu miesiącach TG uchroniło się przed zniknięciem, ale jak na pioniera radzi sobie coraz gorzej, już nie jest nawet numerem 2, bo w staraniach o przejęcie władzy nad sercami i umysłami developerów wyprzedziło je Pylons. Na szczęście, Pylons również posiada 20 minutes wiki tutorial, więc pozycja Django (które posiada jedynie nieoficjalny screeencast wiki na ShowMeDo) wydaje się być niezagrożona. ;)

Tak sobie właśnie przypomniałem... Jakiś czas temu prorokowałem, że z pierdyliarda ramówek webowych w Pythonie (był taki okres, kiedy powstawała jedna ramówka dziennie...), pozostanie nam 2-3 liderów i plankton — i tak się właśnie stało. Kto wymyśli tę, która zdetronizuje obecny numer 3 i stanie do wyścigu o pierwsze miejsce?

64MB RAM na Megiteam.pl

To mało czy dużo? Zależy... Moja aplikacja daje radę. Działa na jednym procesie FastCGI, swoje przydziałowe (?) 64MB RAM wykorzystuje w 100%. Szukałem jakichś wskazówek, jak ograniczyć apetyt aplikacji Django (uruchamianej na FastCGI) na RAM, ale znalazłem niewiele, a już na pewno nic nowego, nic, czego bym już nie wiedział. Być może nie jestem jeszcze aż tak bardzo zdesperowany, bo nie odczuwam, żeby aplikacji brakowało pamięci. Może to jest całkiem wystarczająca ilość, skoro aplikacja się jeszcze nie krztusi?

E, tam, nie mam chyba większych problemów... Jeszcze kilka lat temu nie przeszłoby mi przez myśl, że będzie mnie stać na hostowanie gdziekolwiek aplikacji w czymkolwiek innym, niż PHP. Zanim dorobiłem się hostingu na megiteam.pl nawet zastanawiałem się, czy nie iść na taniochę i nie przeprosić się z PHP. A tu — prawie jak spełnienie marzenia.

Aktualizacja z 18 stycznia: pani Magda Zarych, właścicielka megiteam.pl, uściśliła moje domysły. Aplikacja nie spożywa 64M, lecz w granicach 18M. Patrzyłem nie na to, co trzeba. Swoją drogą, przyjemnie, że firma wsłuchuje się w bicie serca klientów. ;)

Pylons zniechęca

Podkusiło mnie i postanowiłem zrobić tutorial Pylons, trochę z ciekawości, a trochę podążając za modą. Ogólne wrażenie było raczej... marne. Zarówno jeżeli chodzi o Pylons, jak i o sam tutorial.

Przede wszystkim, tutorial stanowił nie lada wyzwanie. W pewnym momencie zalecane do wykonania polecenie zakończyło się bardzo brzydkim tracebackiem, który niewiele mówił. Nie wiedząc, co zrobiłem źle, darowałem sobie dalsze przerabianie materiału, ale następnego dnia zaświtała mi pewna idea i po sprawdzeniu okazało się, że miałem rację — kilka sekcji dalej omówione zostało ustawienie konfiguracji, którego brakowało. Po pokonaniu tej przeszkody udało się dobrnąć do końca.

Teraz trochę o wrażeniu, jakie robi automatycznie wygenerowany szkielet aplikacji. Zawiera on dużo więcej, niż szkielet, jaki generuje Django. W prawie każdym pliku używana jest konstrukcja from package.module import * co powoduje, że właściwie nie wiadomo, skąd co pochodzi. Dodatkowo autor tutoriala ma zadziwiający zwyczaj używania jednoliterowych skrótów, jak np. "h" dla "helpers" czy "c" dla "context" (wygląda to na dość powszechny obyczaj). Ilość tekstu do napisania się zmniejsza, ale traci na tym czytelność. Do tego dokłada się jeszcze duża ilość modułów, które importowane są na wszelki wypadek, a przynajmniej bez jakiegokolwiek wytłumaczenia. Wszystko to składa się na ogólne wrażenie chaosu.

A teraz o samej ramówce, a raczej o tym, co jest zalecane jako jej elementy w wersji 0.9.6.1 (bieżącej w chwili pisania tego artykułu). SQLAlchemy jest potężne, skomplikowane i nieprzyjemne. Routes jest potężne, skomplikowane i nieprzyjemne. System szablonów Mako jest potężny, skomplikowany i nieprzyjemny. Paster jest potężny, skomplikowany i nieprzyjemny. W efekcie Pylons jako całość jest potężne, skomplikowane i nieprzyjemne. Zupełnie jak Spring. Na pewno można przy jego użyciu zrobić milion sprytnych rzeczy, ale nie chodzi o to, żeby robić sprytne rzeczy, tylko żeby zrobić co trzeba i mieć przy okazji trochę dobrej zabawy.

Konkluzja jest dość oczywista — dopóki nie będę musiał, nie porzucę Django dla Pylons. Z pracy przy projektach w Django mam przynajmniej sporą dozę radochy...

Książki, książki

Po Pro Django, Web Development Done Right szykuje się kolejna książka o tej ramówce — James Bennet zaanonsował, że pisze książkę, która omawia Django od strony praktycznej. Książka jest już listowana na Amazon.com, więc sprawa wygląda na poważną. Wypada się tylko cieszyć. Przyda się taka książka wszystkim początkującym.

Jednym z zagadnień przez tę książkę poruszanych ma być pierwsza aplikacja w Django, czyli właśnie silnik blogowy. Ja swój napisałem w ciągu kilku wieczornych sesji, właśnie jako projekt szkoleniowy z migracji na nową wersję Django (choć wcale nie był pierwszą aplikacją, moją pierwszą aplikację można już podziwiać od dłuższego czasu w Rumunii, na Węgrzech i w Wielkiej Brytanii). Trudno jednak wymagać, by ktokolwiek zaczynał zaznajamianie się z Django od dużego, komercyjnego projektu na zlecenie międzynarodowego klienta...

Co z tym Django?

Coraz bardziej niecierpliwię się tym, że Django wciąż nie ma oficjalnego wydania wersji, która miałaby pełne wsparcie dla unikodu i działające newforms. Pojawiły się pogłoski, że następne wydanie to nie będzie oczekiwane przez wszystkich 0.97, ale od razu 1.0 — to by oznaczało, że ilość rzeczy, jakie trzeba będzie zrobić podczas migracji będzie podobna, jak przy 0.91. Tutaj nie ma to wielkiego znaczenia, ale w pracy będziemy mieli dylemat...

Dobre wieści, Django znowu działa

Mądrzejsi ode mnie znaleźli błąd, poprawili i Django znowu działa przez FastCGI. Chwała nam i naszym kolegom, wiadomo komu precz!

Buggy weekend

Dwa straszliwe bugi objawiły się w projektach, które są mi z różnych powodów bardzo bliskie. Mało tego, żaden nie został jeszcze poprawiony.

Gajim ma problem z GnuPG — pewnym obejściem jest używanie gpg-agent (w sumie i tak powinienem go używać, ale chyba nie jestem jeszcze aż takim paranoikiem), ale jest możliwość doprowadzenia swojej konfiguracji do takiego stanu, że Gajim nie da się uruchomić. Wystarczy odhaczyć w ustawieniach konta "Używaj gpg-agent" i mieć wybrany jakiś klucz GPG do szyfrowania. Gajim daje segfaulta podczas uruchamiania. Sprawdzę jutro w pracy, czy ma to coś wspólnego z oprogramowaniem, jakie mam na domowej maszynce.

Django z powodu przeciekającego skądś obiektu SafeString zwraca nieprawidłową zawartość HttpResponse. Z tego powodu Flup odmawia współpracy i ogólnie w tej chwili trunk Django nie nadaje się do użycia przez FastCGI. Sprawdziłem to tymi rękami, na tym sajcie — nie działa. Nie jestem aż tak dobrze obeznany z internalsami Django, żebym zabierał się za zmiany w kodzie, zwłaszcza, że byłoby to grzebanie w HttpResponse... Nie da rady, trzeba czekać.

Trunk Django nie nadaje się do użycia!

Od rewizji 6778 trunk Django nie nadaje się do użycia przez FastCGI! Czujcie się ostrzeżeni, choć jeszcze nie wiadomo, skąd bierze się ten problem (Malcolm T. twierdzi, że SafeString nie powinno się tam pojawić...).

Niejaki problem z Flupem

Jak wiadomo, Django używa pakietu Flup żeby uruchamiać aplikacje w środowisku serwera oferującego FastCGI (Apache + mod_fastcgi, nginx, lighttpd). Trafiłem na coś, czego jeszcze do tej pory nie widziałem na oczy. Nigdzie i nigdy.

Nasza aplikacja nigdy nie wyrzuca błędu. Nie mówię o 500, w końcu istnieją aplikacje doskonałe, ale nawet 404, i to pomimo tego, że w kodzie w wielu miejscach leci wyjątek django.http.Http404. W takiej sytuacji aplikacja wyświetla jakąś statyczną stronę (nie ma to nic wspólnego z naszym szablonem 404.html czy 500.html) i zwraca 200. Dogrzebałem się, że jest to efekt działania ErrorMiddleware z Flupa, który w ten sposób próbuje zwrócić na siebie uwagę (przy okazji wysyła maila do administratora, ale u nas to nie występuje...). Podobno występuje to wtedy, gdy błąd taki nie zostanie obsłużony przez aplikację, co w naszym przypadku jest o tyle dziwne, że podobno przecież Django obsługuje wszystkie wyrzucone wyjątki przy użyciu odpowiedniej dla typu wyjątku funkcji.

Co ciekawsze, okazało się, że najnowsza wersja Flup-a (1.0) nie zawiera już tego middleware, więc gdyby się okazało, że Django bez problemu działa z tą wersją, to może oznaczać tylko głębokie problemy z naszą konfiguracją na styku mod_fastcgi i Django.

Cache obiektów w Django - jak się nie zgubić?

Zdarza się tak, że domyślny sposób buforowania, używany przez Django (buforowanie całych stron, buforowanie całego serwisu) nie wystarcza i trzeba buforować pojedyncze obiekty lub ich listy. Gdy się już zacznie, trudno jest przestać i wtedy okazuje się, że nie sposób dojść do tego, pod jakim kluczem co jest zbuforowane. Sytuacja komplikuje się dodatkowo, gdy używa się memcache, a już całkiem, gdy w buforze trzyma się dane sesji. Najłatwiej byłoby zrestartować memcached, ale przecież polecą sesje użytkowników... Zaczyna się gorączkowe szukanie klucza i strzelanie w ciemno, ale to naprawdę głupiego robota, bo klient memcache zawsze zwraca 1, gdy nie wystąpi żaden krytyczny błąd. Rozwiązaniem byłoby przejrzenie listy kluczy (np. przeiterowanie przez nie), ale czegoś takiego nie ma.

Wpadłem na pomysł prostego rejestru kluczy w postaci zbioru (set). Zdaje się to działać na tym serwisie (który jest trochę poligonem...), a jak działa w naprawdę dużym serwisie, to się dopiero okaże jutro, jak sprawdzę to w pracy. Na razie jestem dobrej myśli, bo idea zdaje się nie mieć słabych punktów... ;)

Są pewne plusy

National Blog Posting Month to idiotyzm, ale dzięki temu James Bennet dzieli się z czytelnikami swoją bezcenną wiedzą na temat Django w tempie jednego postu dziennie. Warto sobie dopisać gdzie trzeba i śledzić.

Wartości domyślne zdają się nie być tym, czym są...

Subtelna różnica, która może okazać się zdradliwa... Gdy definiuje się wartość domyślną dla któregoś z atrybutów modelu w Django, jest ona ustawiana w momencie kompilowania modułu. Niepotrzebna para nawiasów w tym miejscu spowoduje, że zamiast wyniku wywołania jakiejś funkcji w momencie tworzenia egzeplarza klasy, wstawiona zostanie stała wartość, obliczona w momencie kompilacji modułu (czyli najczęściej przy uruchomieniu aplikacji). Warto jest się zastanowić, czy właśnie tego się chce, bo default=datetime.datetime.now daje inny wynik, niż default=datetime.datetime.now().

More power to the people

Będąc code junkie (jak o nas mawiają marketoidy), nie mogę się powstrzymać, żeby czegoś nie ulepszyć (co wcale nie jest zepsute). Przyszło mi do głowy, że mógłbym poćwiczyć z OpenID, więc należy się spodziewać, że wkrótce gdzieś na stronie pojawi się charakterystyczny symbol i posiadacze OpenID będą mogli się wcielić w jakąś specjalną rolę w serwisie. Jeszcze nie wiem jaką, ale będę musiał coś wymyślić, żeby wynagrodzić im wszystkie trudy... ;)

Django-SVN na megiteam.pl

megiteam.pl standardowo udostępnia Django-0.96 jako ramówkę bazową, ale przecież można instalować własne pakiety Pythonowe, a Django nie jest niczym o wiele większym (niektórzy pewnie będą polemizować, ale co mi tam...), więc da się oczywiście użyć nowszej wersji, ze wszystkimi dobrodziejstwami (np. dekorator permalink, unikod, wreszcie działające newforms...). Instrukcja jest dość prosta, a ja nie czuję się natchnionym poetą, więc krótko i rzeczowo.

  • ściągnij wersję z SVN postępując według wskazówek na stronie Django;
  • zainstaluj w normalny (dla megiteam) sposób, podając parametr --prefix=$HOME/.python (czyli pełna komenda będzie wyglądać mniej-więcej tak: python setup.py build && python setup.py install --prefix=$HOME/.python);
  • dodaj do zmiennej PATH katalogi $HOME/.python/bin i $HOME/.python/lib/python2.4/site-packages/django/bin, wpisz to w swoim pliku $HOME/.environment (najlepiej na początku);
  • ciesz się Django-0.97-pre-SVN-unknown, nie zapomnij też zrestartować swoich procesów FastCGI.

Skoro nie widać różnicy, to po co przepłacać (za starą ramówkę)?

Errata (2007-10-13): dekorator permalink występuje już w Django-0.96.

Plug! Szukam współpracowników

Szukam, bo mamy w firmie znaczące niedobory kadrowe. Od razu zaznaczę -- wystarczy pobieżna znajomość Django (w szczególnych przypadkach nawet żadna), podstawy Pythona, wskazane jedynie jest dość dobre obeznanie w sprawach programowania aplikacji webowych (obojętne w czym: Java, .Net, RoR, chodzi o znajomość tematu).

Szczegółowe ogłoszenie wraz z danymi kontaktowymi znajduje się w archiwum Google Groups (po kliknięciu w skrót adresu trzeba wpisać kod z captchy, żeby obejrzeć pełny email kontaktowy). Ogłoszenie jest sprzed miesiąca, ale tym się przejmować nie należy. ;)

4 powody, dla których warto przejść na Django-SVN

W chwili, gdy piszę ten tekst, stabilną wersją Django jest wersja oznaczona numerem 0.96, opublikowana w czerwcu 2007 roku. Biorąc pod uwagę, że jest to spory kawał kodu, trudno nazwać tę wersję starą, a poza tym, działa dość stabilnie (pomijając pewną ilość błędów, poprawionych w ciągu tych kilku miesięcy). Pokusa pozostania przy niej jest silna, szczególnie w przypadku aplikacji o charakterze komercyjnym, które nie powinny podlegać ciągłej pogoni za zmianami, wprowadzanymi w SVN. Jest jednak kilka powodów, dla których warto jest rozważyć przejście na wersję rozwojową.

  • unikod — do tej pory było tak, że część Django była unikodowa (np. ramówka do RSS-ów), a część zwykła. Teraz takich rozjazdów jest znacznie mniej i są one znacznie mniej uciążliwe (zauważyłem np., że klucze i wartości w cookies muszą być zwykłymi stringami, a nie unikodem);
  • poprawiona stabilność — od kilku tygodni nie zauważyłem ani razu słynnego błędu "Can not resolve keyword XXX into field", który był plagą w Django 0.96;
  • dekorator permalink — coś cudownego, odpina całkowicie model od stałego URL-a (przypina go za to do widoku, ale to już chyba lepsze...);
  • działające poprawnie newforms — wreszcie.

I tu się należy słówko wyjaśnienia dla wszystkich tych, którzy wynajdują słabe strony wszystkiego (nazywa się ich malkontentami, to tak na marginesie). Najprawdopodobniej Django posiada jeszcze wiele niedociągnięć i wiele niewykrytych błędów. Prawdopodobnie należy się spodziewać, że wersja rozwojowa zaskoczy nas jeszcze nie raz (i nie raz nie będzie się nadawała do użytku przez jakiś czas). Komu nie odpowiada wersja rozwojowa, ma przez cały czas alternatywę (a nawet kilka): używać wersji 0.96 lub nie używać Django w ogóle. Jest przecież wiele równie fajnych ramówek aplikacyjnych, nie tylko w Pythonie...

Mam archiwum XYZ

Zrobiłem dziś archiwum według miesięcy, przy czym trochę bardziej z ciekawości niż z rzeczywistej potrzeby użyłem pakietu Babel (chciałem mieć ładne nazwy miesięcy itd). Przy okazji znalazłem babola w ostatnim wydaniu, tymczasowo poprawiłem ręcznie żeby w ogóle działało. Poczułem się trochę zawiedziony, że tak zachwalany mi przez kolegów pakiet (i w wersji 0.9.1, a taki numerek powinien już zobowiązywać) nie ma testów, które potrafiłyby wykryć taki problem. Ale co ja psioczę, moje projekty w ogóle nie mają testów... ;)

A na razie cieszę się tym nowym, wspaniałym archiwum i już spoglądam w przyszłość, co następne w kolejce do zrobienia.

Słówkiem wyjaśnienia

Zaczęło się ponownie od tego, że wreszcie z początkiem 2007 roku znalazłem pracę, w której piszę w Pythonie (właściwie to w Django). Nie byłem do tej pory wielkim miłośnikiem programowania internetowego, ale Django mnie przekonało na tyle, że zachciało mi się zrobić coś własnego. Na pierwszy ogień, jak zwykle, poszedł silnik blogowy...

Gdy tylko okazało się, że za skromne pieniądze mogę mieć hostowaną aplikację Django na serwerze firmy megiteam.pl, to długo się nie zastanawiałem. Wykupiłem sobie konto na rok i... voila! Jest. Zaczyna się oczywiście od bloga, ale przecież nie musi się na tym skończyć. Serwer jest, czas może się znajdzie, aplikacja (czy nawet aplikacje) mają szanse dojrzeć.