PyconPL 2009

No, byłem. Co do konferencji jako takiej to wolę się nie wypowiadać, ale wnioskuję, że nie byłem jej targetem — był może jeden wykład, na którym dowiedziałem się czegoś nowego i jeden, który obejrzałem dla przyjemności. Aspekt społeczny w pewnym stopniu został zaspokojony przez bratanie się z kolegami z Allegro.pl. Infrastrukturę (dostęp do sieci, wyżywienie, zakwaterowanie, napoje, przekąski) pominę milczeniem. W końcu też w niedzielę dałem spicz i odniosłem wrażenie, że pomimo pewnych problemów technicznych się podobał. Czyli: pod względem merytorycznym niejaki sukces.

Dla uzupełnienia:

  • prezentacja do obejrzenia/ściągnięcia jest na SlideShare
  • kod w takiej wersji, jaka była prezentowana, jest na BitBucket

Jeżeli czas pozwoli, to skrobnę też jakiś artykuł na podstawie tego, co zostało powiedziane.

Wirtualne pythony na Megiteam.pl

Megiteam.pl w swoich dokumentach pomocy proponuje jeden sposób na oddzielenie swojego środowiska Pythona od systemowego, a także na ustalenie wersji Pythona, z jaką ma być uruchamiana aplikacja. Metodą prób i błędów doszedłem do innego, jak mi się wydaje także lepszego, sposobu na osiągnięcie tego celu.

Virtualenv na pomoc

Virtualenv to obecnie najpopularniejszy sposób na wirtualizację środowiska uruchomieniowego aplikacji. Mocno korciło mnie, by go wykorzystać do oddzielenia środowiska aplikacji od Pythona dostarczanego przez Megiteam.pl i w końcu się udało. Potrzebny będzie do tego plik virtualenv.py z dystrybucji źródłowej virtualenv. Kopiujemy go sobie w jakieś poręczne miejsce, np. do ~/.. Potem już idzie normalnie:

~$ mkdir v
~$ python2.5 virtualenv.py v/chrzczone
New python executable in v/chrzczone/bin/python2.5
Also creating executable in v/chrzczone/bin/python
Installing setuptools................done.
~$ cd v/chrzczone/
~/v/chrzczone$ bin/easy_install -U -Z psycopg2 \
    Django pytz Babel BabelDjango \
    django-registration django-tagging-ng \
    django-pagination

Po kilku minutach mamy już swoje środowisko w katalogu $HOME/v/chrzczone. Pozostaje tylko ustawić aplikację tak, żeby wiedziała kto tu rządzi. Do tego służy plik .environment w katalogu aplikacji. Oto jak on wygląda w mojej przykładowej aplikacji o nazwie "chrzczone":

PATH=$HOME/v/chrzczone/bin:/usr/local/python2.5/bin:$PATH
PYTHONPATH=$HOME/v/chrzczone/lib/python2.5/site-packages:/usr/local/python2.5/lib/python2.5/site-packages

Wszystko w tym momencie powinno działać, ponieważ pliki .environment są dołączane kaskadowo i zmienne zdefiniowane w nich nadpisują zdefiniowane wcześniej. Ale jest również lepsza (mniej podatna na błędy) metoda osiągnięcia tego samego — żeby wszystko zagrało wystarczy, że plik .environment będzie zawierał tylko jedną linijkę:

source $HOME/v/chrzczone/bin/activate

(dzięki, Eluś, masz u mnie piwo w Ustroniu).

Ale po co to wszystko?!

A choćby po to, żeby łatwiej można było tym zarządzać, np. przy robieniu deploymentu przy użyciu Pavera. Virtualenv obecnie staje się standardową metodą zapewniania rozdzielności środowisk uruchomieniowych Pythona, dającym tyle izolacji, ile się chce. Jest wygodne i bardzo upraszcza obsługę takiego zwirtualizowanego środowiska, zwalniając z konieczności ręcznego ustawiania zmiennych środowiskowych — wystarczy zaimportować w powłoce skrypt aktywujący środowisko i wszystko jest na swoim miejscu. A jeżeli używa się virtualenvwrapper to rzeczy proste stają się jeszcze prostsze (workon, mmm...).

Pewna reorganizacja

Zamierzam trochę pozmieniać konfigurację uruchamiania tego serwisu (a przy okazji może dorobię się ciut wygodniejszego deploymentu, do ponownego wykorzystania), więc w ciągu kilku najbliższych dni można się spodziewać niespodziewanych przerw w działaniu.

Expect unexpected, but prepare for better.

Autentykacja, podejście drugie

Ponownie brudzę sobie ręce w aplikacji zrobionej przy użyciu alternative stack (Werkzeug + Jinja2 + SQLAlchemy + WTForms), trochę przy okazji przygotowań do mojego nadchodzącego wystąpienia na PyconPL 2009. I ponownie wszystko szło gładko, dopóki nie musiałem zabrać do autentykacji uwierzytelniana (nie autoryzacji).

Krąży po mieście plotka, że najlepiej do tego użyć AuthKit lub repoze.who. Jak w każdej plotce, w tej także może być ziarno prawdy, więc kilka dni temu postanowiłem to sprawdzić. Już początki nie były zachęcające (każda z tych bibliotek doinstalowuje w zależnościach połowę Pylons), a potem było jeszcze gorzej.

AuthKit okazał się tak mocno związany z Pylons, że nawet nie ma własnej dokumentacji, tylko jakieś rozdziały w Pylons Book. Jedyne, co nadawało się do użycia bez Pylons, to kilka przykładów w repozytorium. Spróbowałem pokonać niechęć i zapoznać się bliżej z tą biblioteką, ale dałem za wygraną po godzinie zastanawiania się, jak do tego bydlaka podejść. Może jakoś niedługo przyjdzie mi do głowy idea, jak mógłbym z tego skorzystać bez Pylons (a może ktoś to opisze...).

Dokumentacja do repoze.who okazała się dużo lepsza, ale tym razem przerosła mnie idea tej biblioteki. Przeczytałem wprowadzenie i zagotowało mi się pod kopułą — jak to w ogóle działa? I najważniejsze, jak to podłączyć do istniejącego kodu? Jestem pewien, że dokumentacja dokładnie opisuje każdy aspekt działania tej biblioteki, ale ja tego zwyczajnie nie zrozumiałem. Może dlatego, że przez cały czas przed oczami miałem mój use case, który obejmuje jedynie mały fragmencik tego, do czego ta biblioteka została stworzona.

Na dzień 10 lipca 2009 roku konkluzja jest taka, że albo przysiądę fałdów i spróbuję użyć jednej z tych dwóch bibliotek, albo pójdę na skróty i użyję RPX (czy czegoś w tym stylu). Co biorąc pod uwagę obecny trend w branży nie wydaje się być takim głupim rozwiązaniem. Niestety, pomijając całkowite niedopasowanie tego rozwiązania do docelowej grupy użytkowników mojej aplikacji...

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. :)

Setuptools to szajs

Wiele było już głosów, że setuptools jest pełne błędów i nie powinno być używane. W mojej praktyce na te błędy nie trafiałem, więc poprzestawałem na niechęci, a drobne wpadki powodowały jedynie wzrost przychylności wobec antagonistów setuptools. Tym razem w ciągu ostatnich dni zostałem doświadczony dwukrotnie przez poważne niedoróbki w setuptools, w tym jedna ma znaczenie krytyczne (nie instaluje się część zawartości pakietu):

Setuptools musi odejść!

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ę.

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ę.

Zrobiło się cicho

A to dlatego, że moje przemyślenia na temat technologii przeniosłem do technologicznej tuby tego serwisu, którą w ramach edukacji implementuję na Google AppEngine. Aplikacja, choć wciąż w trakcie rozwoju, powoli zyskuje kolejne funkcje (a ja przy okazji uczę się nowych rzeczy).

Vendor lock-in (na melodię "do no evil")

Dzisiejsze ogłoszenie zasad płatności za Google AppEngine wywołało burzę w małej szklaneczce, jaką jest środowisko ludzi robiących aplikacje na AppEngine:

  • część (wygląda mi to na większość) cieszy się z tego, że może płacić, w tym także z tego, że za 3 miesiące będzie płacić za to, co do tej pory miała za darmo;
  • część (mniejszość) nazywa to po imieniu: vendor lock-in (najpierw skuś, potem zmień zasady i zmuś do płacenia za to, co do tej pory było za darmo).

Wyliczenia przeprowadzane tu i ówdzie (głównie na liście AppEngine) wskazują, że nowe limity będą odczuwalne przez wszystkie co najmniej przeciętnie popularne serwisy, a koszt może być znaczący.

Wysiłek włożony w zrobienie aplikacji, którą można wyjąć z GAE i włożyć na normalny serwer może się opłacić. A już na pewno można przestać myśleć o przenoszeniu aplikacji z normalnej platformy na GAE — to się po prostu nie opłaci.

Żarte aplikacje na AppEngine

Aplikacja, która jest moją piaskownicą na Google AppEngine z upływem czasu rozrosła się trochę (procesory kontekstu, middleware, takie tam...) i okazało się, że każdy request w przeglądarce logów świeci na żółto, to znaczy że według Google jego obsługa zjadła nadmierną ilość zasobów i powinien zostać w jakiś sposób zoptymalizowany. No i faktycznie, powinien — obsługa każdego z żądań do aplikacji zjadała ~1200 ms CPU (łącznie kod + storage). Coś było ewidentnie nie halo, więc musiałem podjąć pewne kroki zaradcze:

  1. użycie googlowego cache gdzie się tylko da (porzucając tradycyjne pojęcie o tym, gdzie ma to sens);
  2. optymalizacja sposobu dostępu do danych w datastore (wybieranie encji używając klucza/kluczy, a nie budując Query);
  3. jak dla mnie najważniejsze: optymalizacja importów.

Zanim przejdę do omówienia poszczególnych optymalizacji... Warto było nad tym popracować, bo średni czas obsługi żądania spadł do ~120 ms CPU (z grubsza: 10x wzrost wydajności). Pomimo tego, co ja uznałem za najważniejsze, to nie optymalizacja importów dała największy zysk, a zmniejszenie ilości odczytów z datastore (dzięki użyciu memcache i wybierania danych przy użyciu kluczy). Co uważam za dobre w tym wszystkim to to, że AppEngine wymusza przemyślane zaplanowanie aplikacji i optymalizację każdego aspektu działania kodu (i to od samego początku). To nie jest zabawka dla niecierpliwych chłopców-pehapowców. ;)

Memcache gdzie się da

Memcache API było jedną z pierwszych rzeczy, jakie Google dodało do AppEngine po jego uruchomieniu w kwietniu 2008 roku. Jego użycie w aplikacji jest nie tyle optymalizacją, co po prostu koniecznością (szczególnie w świetle zapowiedzianego na koniec maja 2009 obniżenia limitów). O ile w zwykłych aplikacjach w cache umieszcza się rzeczy, które są albo kosztowne do wyliczenia, albo niemal statyczne, o tyle na AppEngine buforować trzeba niemal wszystko, bo pomimo twierdzeń googlarzy, że odczyty z datastore są tanie, to ta taniość jest względna (chyba względem kosztu zapisów) — a koszt obsługi żądania jest liczony jako suma kosztu wykonania kodu jako całości, wraz z kosztem pobrania danych (w limitach te wartości są liczone oddzielnie).

Na co zwrócić uwagę na początku? Na drobne rzeczy: profil użytkownika, listy ostatnio dodanych/popularnych obiektów, to, co pojawia się w kontekście w wyniku działania procesorów lub jest dodawane do obiektu request przez middleware. Po tych rzeczach można zająć się resztą, czyli każdą instancją modelu (i każdą wyliczoną wartością), która pojawia się w aplikacji. Czasem trzeba będzie podjąć decyzję, czy warto aktualizować pokazywane dane w czasie rzeczywistym, czy może da się przełknąć mały poślizg rzędu 15 minut... Bo obiekty wyjęte z cache nie zawsze zachowują się tak, jak byśmy tego oczekiwali (przynajmniej na razie).

Dostęp do danych

Tym, co bywa najtrudniejsze do przełknięcia przy robieniu aplikacji na AppEngine jest zupełnie inny model storage — nierelacyjny, bez złączeń i nastawiony na zupełnie inne użycie, niż bazy danych ogólnego stosowania, jak MySQL czy PostgreSQL (nie ujmując nic relacyjnym bazom danych). Oczywiście, można udawać, że się tego nie zauważa i próbować symulować relacyjność, ale efektem tego będzie obniżona wydajność. Z tego co zauważyłem w różnych artykułach i podpowiedziach tu i ówdzie, najważniejsze podpowiedzi można streścić w kilku punktach:

  • storage świetnie sprawdza się jako wielka tablica asocjacyjna, dostęp do danych przy użyciu kluczy jest najbardziej wydajny;
  • klucze są jedynymi unikalnymi atrybutami obiektów, można to wykorzystać do kilku celów;
  • przechowywanie listy kluczy (np. w atrybucie typu db.ListProperty) jest równie wygodne jak złączenie wiele-do-wiele, a w większości wypadków wygodniejsze;
  • atrybuty indeksowane (db.StringProperty) są bardziej kosztowne w aktualizacji niż nieindeksowane (db.TextProperty), warto wziąć to pod uwagę przy projektowaniu modelu.

Uwaga na importy

Czym się różni kod uruchamiany na AppEngine od kodu uruchamianego w zwykłym środowisku Pythona? Niczym, oprócz tego, że proces, który obsługuje żądanie żyje dokładnie tyle, ile trwa obsługa żądania. A to oznacza, że wszystkie moduły konieczne do wykonania kodu przy każdym żądaniu muszą zostać zaimportowane, co jest sporym obciążeniem. Aby trochę poprawić sytuację, AppEngine buforuje zaimportowane moduły przez jakiś czas (liczony raczej w sekundach niż w godzinach). Nie są buforowane moduły, które zostały zaimportowane przy użyciu funkcji __import__(), co oznacza tylko jedno: wszystkie wynalazki typu klass = import_string('mypackage.mymodule.MyClass') trzeba odłożyć na półkę. Nie zawsze da się tego całkiem uniknąć, ale w takich przypadkach trzeba przygotować swój kod na to, że może otrzymać obiekt wykonywalny lub ciąg znaków i zminimalizować ilość miejsc, gdzie wykonywany jest niebuforowany import.

Dużo tego, ale nikt nie mówił, że będzie lekko. :)

Koniec darmowych obiadków

Google zapowiedziało, że za trzy miesiące zmniejszy darmowe limity na AppEngine, a od 24 lutego można sobie zwiększyć limity, dopłacając (drobne bo drobne, ale zawsze) parę dolarków. Moje aplikacje są w takim stadium, że tak naprawdę mnie to nie będzie dotyczyło, ale zmniejszenie limitów przy jednoczesnym wprowadzeniu opłat jak dla mnie coś oznacza — Google przygotowuje się na przetrwanie kryzysu w IT ograniczając wydatki. Może to być także oznaką tego, że uznali AppEngine za produkt na tyle dojrzały, że można za niego brać pieniądze (choć według mnie jeszcze sporo do tego brakuje). Redukcja limitów jest spora, bo w przypadku ilości przesłanych danych to jest 90% (z 10GB do 1GB/24h), a w przypadku obciążenia CPU 86% (z 46 godzin do 6.5 godziny/24h). Na blog czy inny maleńki serwis to może i wystarczy, ale nie daj Boże żeby ktoś się tym poważnie zainteresował, bo limit się wyczerpie w pół godziny. Biorąc pod uwagę dodatkowo fakt, że Google nie powiadamia o zbliżającym się wyczerpaniu limitów (np. po przekroczeniu 80% któregokolwiek z nich), można się nagle obudzić z ręką w nocniku. Nie zmienia to oczywiście faktu, że tą platformą nadal warto się interesować i nawet w wersji płatnej wciąż jest ciekawą propozycją do budowania aplikacji, choć już nie tak atrakcyjną.

Tak czy inaczej, chwilowo nie zamierzam się tym przejmować, choć trudno przewidzieć, jak sytuacja będzie się przedstawiała za trzy miesiące. Na razie staram się robić moje aplikacje w ten sposób, żeby przeniesienie ich na normalny hosting nie wymagało przepisywania całości (co kosztuje więcej zachodu, niż mogłoby się wydawać, ale o tym innym razem).

Mam szczęście do szatanów

Zrób swoje własne Django, odc. 2 - procesory kontekstu

Obiecałem, że będzie o procesorach kontekstu jak w Django (a może nawet trochę lepszych), więc proszę.

Procesory kontekstu

Procesor kontekstu wg. Django to kod wykonywalny, który przyjmując obiekt request zwraca słownik, który następnie jest dodawany do kontekstu przed renderowaniem szablonu. W ten sposób można zapewnić sobie dostępność pewnych danych w kontekście bez potrzeby pamiętania o tym, by te dane tam umieścić. W Django procesory kontekstu są wywoływane wtedy, gdy używa się klasy RequestContext. Pół biedy, gdy chodzi o mój kod — już ja o to zadbam, żeby tam był użyty RequestContext, ale problemem może być kod, którego autor o tym nie pomyślał.

Używając Jinja2 i Werkzeug nie możemy liczyć na tak silną integrację systemu szablonów z modelem żądania i odpowiedzi (założenia obydwu komponentów właśnie taką integrację wykluczają). Na szczęście zarówno Jinja2 jak i Werkzeug dostarczają nam wszystkiego czego potrzeba, żeby zaimplementować sobie taką funkcjonalność. Niestety, nie będzie tak prosto jak w przypadku middleware.

Werkzeug posiada coś, co jest nazywane local proxy — jest to wariant thread local storage, specjalnie przystosowany do potrzeb aplikacji webowych. Zazwyczaj umieszcza się w nim kod aplikacji WSGI (klasy lub funkcji), ale nie ma przeszkód, żeby umieścić tam cokolwiek innego, np. obiekt request:

def __call__(self, environ, start_response):
    local.application = self
    local.request = request = Request(environ)

Tak umieszczony obiekt request będzie następnie dostępny w dowolnej innej części aplikacji w trakcie jej czasu życia (czyli przetwarzania żądania i produkowania odpowiedzi). Teraz w okolicy mojego kodu renderującego szablon mogę zrobić taki myk:

def build_globals():
    jinja_globals = {}
    for item in getattr(settings, 'CONTEXT_PROCESSORS', []):
        try:
            processor = import_string(item)
            jinja_globals.update(processor(local.request))
        except (ImportError, AttributeError):
            pass
    return jinja_globals

jinja_env = Environment(loader=FileSystemLoader(settings.TEMPLATE_DIRS))
jinja_env.globals.update(build_globals())

Chodzi o to, że Jinja2 posiada tzw. globalne zasoby — można je zmodyfikować po utworzeniu środowiska a przed wyrenderowaniem pierwszego szablonu. Od teraz w każdym szablonie będzie dostępne to, co znajduje się w moim słowniku jinja_env.globals. A co się tam może znaleźć? Na przykład to:

def messages(request):
    messages = request.environ['beaker.session'].get('messages', [])
    request.environ['beaker.session']['messages'] = []
    request.environ['beaker.session'].save()
    return {
        'MESSAGES': messages,
    }

Czemu jest to lepsze rozwiązanie niż w Django? Ponieważ nie trzeba pamiętać o tym, żeby kontekst był obiektem klasy RequestContext. Dzięki buforowaniu środowiska przez Jinja2, narzut jest minimalny, a prostota rozwiązania kusząca. Czyżby więc było to rozwiązanie idealne? W moim przypadku nie. Trzeba bardzo uważać na to, by zaimportować ten kod w odpowiednim momencie (dla uproszczenia umieściłem go w przestrzeni globalnej). Ale działa i robi dokładnie to, co chciałem.

Ten kod nie powstałby, gdyby nie Ali Afshar (tak, ten od PIDA), który podał rozwiązanie tego problemu na StackOverflow.

Zrób swoje własne Django, odc. 1 - middleware

Żartuję, Django jest na tyle dobre, żeby nie musieć go robić samemu. Chodzi mi raczej o sytuację, kiedy chce się mieć tyle Django, ile trzeba i ani trochę więcej (na przykład dlatego, że z całego oryginalnego Django wykorzystuje się tylko parę komponentów, bo na więcej nie pozwala AppEngine). Z pomocą przychodzi Werkzeug, Beaker i Jinja2 i parę innych bibliotek. Oprócz oczywistego zysku, jakim jest odchudzenie narzędziówki o kilka cennych setek plików, można zyskać bezcenną wiedzę, jak działa Django (im więcej wiem, tym większym podziwem darzę core devs, bo Django okazuje się jeszcze lepsze, niż wygląda na pierwszy rzut oka).

W paru kolejnych odcinkach opiszę rzeczy, które uznawałem za bardzo użyteczne w Django i zaimplementowałem sobie jako uzupełnienie narzędziówki (inna rzecz, że niełatwo jest się wyrzec niektórych przyzwyczajeń).

Middleware

Nie chodzi mi o middleware w rozumieniu WSGI, lecz takie, jak w Django — metody zwykłych klas, które są wywoływane z ustalonymi argumentami w określonych momentach przetwarzania żądania i odpowiedzi. Niespodzianka, Werkzeug ma już coś takiego! Nazywa się to Processor i klasa, która znajduje się w module werkzeug.contrib.kickstart pokazuje, jak powinien wyglądać kompletny interfejs takiej klasy i sygnatury jej metod. Leniwi mogą sobie z tej klasy po prostu odziedziczyć...

Pozostaje jedna rzecz, czyli podłączenie tego cuda do naszej aplikacji WSGI. Poniżej fragment mojej metody __call__() z klasy reprezentującej aplikację WSGI. Podłączam w niej tylko jeden rodzaj middleware (są jeszcze 3 inne możliwe).

for item in getattr(settings, 'MIDDLEWARES', []):
    try:
        klass = import_string(item)
        middleware = klass()
        response = middleware.process_request(request)
    except (ImportError, AttributeError):
        pass
    if response is not None:
        break
if response is None:
    local.url_adapter = adapter = url_map.bind_to_environ(environ)
    try:
        endpoint, values = adapter.match()
        handler = import_string(endpoint)
        response = handler(request, **values)
    except HTTPException, e:
        response = e

Inny sposób na podłączenie do kodu aplikacji WSGI można znaleźć na wiki Werkzeug — jak zwykle pełna wolność wyboru metody (oczywiście, działa dobrze w każdym przypadku).

W następnym odcinku

A w następnym odcinku okaże się, jak zrobić procesory kontekstu (context processors) takie jak w Django, a nawet lepsze. Tym razem w użyciu będzie zarówno Werkzeug jak i Jinja.

Mniej limitów na Google AppEngine

Od dziś na AppEngine zniesiono jeden z najbardziej denerwujących limitów (bo zazwyczaj nie można było go kontrolować) - limit tzw. High CPU requests (czyli pików CPU). Do tej pory limit wynosił 2 na minutę, co łatwo było przekroczyć szczególnie gdy występowały jakieś lokalne błędy, np. z dostępem do datastore. Podobne problemy występują okresowo na platformie Google i jak do tej pory dotyczą szczególnie żądań HTTP przychodzących z Europy.

Z innych udogodnień: dopuszczalny czas odpowiedzi wzrósł z 10 sekund do 30 i dopuszczony został rozmiar odpowiedzi i upload zasobów większych niż dotychczasowe 1MB (obecnie limit wynosi 10MB).

Idzie ku lepszemu, ale jak dla mnie to wciąż mało. :)

Google AppEngine 1.1.9

Od nowej wersji (wydanej właśnie dzisiaj) możliwe jest wreszcie używanie operatorów IN i =! w metodzie filter(), właściwa dokumentacja została zaktualizowana. Co prawda pod spodem wykonywane jest kilka zapytań do datastore (co wpływa na zużycie limitów), ale wygoda jest o wiele większa. Fajnie.

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.

Na rok 2009

Z początkiem roku wiele osób robi sobie plany, czym zamierza się zająć w nadchodzącym okresie, więc i ja sobie usiadłem. Zacząłem się zastanawiać, z czym też warto byłoby się bliżej zapoznać i - biorąc pod uwagę realne możliwości - też zmontowałem sobie taką listę:

Mało? Mało, ale raczej na więcej nie znajdę czasu, który mógłbym określić jako wolny — jedynym rozwiązaniem problemu brakującego czasu wydaje mi się użycie tego w sposób bardziej lub mniej potajemny w pracy, z Werkzeugiem powinno się udać. Jeżeli zostanie mi trochę czasu, to rezerwowym tradycyjnie jest JavaScript w zakresie wykraczającym poza jQuery + pluginy.

wMiastoWzięci.pl na (niejakim) zakręcie

W związku z nadciągającą zimą finansową w IT (wszyscy przepowiadają, że rok 2009 będzie ciężki, tak jak był 2000 — a ja nie mam powodu, żeby nie wierzyć), wMiastoWzięci.pl w najbliższym czasie pozostaną moim prywatnym projektem, częściowo (prawdopodobnie w całkiem sporym zakresie) edukacyjno-eksperymentalnym. Hostowany będzie tak, jak dotychczas, tzn. na najtańszej opcji na djangohosting.ch, więc w wypadku znaczącego pogorszenia wydajności (np. z powodu nagłego zainteresowania serwisem) niczego nie będę dokładał za pieniądze (procesy, pamięć, storage). Lepiej, żeby było to jasne już teraz.

Przez ten rok (co najmniej, bo może być i dłużej) zamierzam zrobić tam kilka rzeczy, ale bez ustalania jakiegoś sztywnego grafiku, bez liczenia wersji i określania terminów:

  • oceny zawartości;
  • personalizowane powiadomienia o zmianach w zawartości;
  • uproszczenie procesu dodawania wydarzeń;
  • ponowne przemyślenie architektury informacji, bo trochę się pogubiłem...;
  • przejście od wydarzeniocentryczności do miejscocentryczności;
  • reorganizacja zawartości wyświetlanej na stronie początkowej serwisu;
  • nowy wygląd, lepiej dopasowany do charakteru serwisu;
  • może jeszcze kilka drobiazgów...

Jak widać, nie ma tego wiele, ale też nie zamierzam spędzać nad tym serwisem wiele czasu — moja córka dorasta w szybkim tempie i wymaga coraz większej uwagi. Na tyle, na ile będę mógł poświęcić na niego czas, będę coś tam dłubał, ale na pewno nie będę urządzał wyścigu do funkcjonalności, ani nie będę śledził działań konkurencji tylko po to, żeby być na czasie, ani tym bardziej o krok dalej. Nowe funkcje się pojawią, błędy będą poprawiane, serwis będzie rósł — w oczekiwaniu na lepsze czasy.

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.

Love it or leave it

Jeff Atwood napisał kolejny tekst, który trafia do mnie, tym razem o ludziach, którzy nie powinni zajmować się programowaniem. Zarzewiem był pewien wątek na pewnym forum, w którym ktoś zastanawiał się nad opuszczeniem branży programistycznej. Trudno mi się z tym artykułem nie zgodzić — w końcu zajmuję się programowaniem od ponad 10 lat. W tej branży 10 lat to jak od epoki brązu do atomu.

Wielokrotnie w ciągu tych 10 lat spotykałem ludzi, którzy programowaniem zajmowali się tylko przejściowo, między studiami a pójściem w managery, na szczęście nie musiałem z nimi współpracować, więc nie napsuli mi wiele krwi. Zawsze jednak dziwiło mnie to, że najpierw poświęcili 5 lat na uczenie się inżynierii oprogramowania na studiach i potem przez 5 lat pisali kod tylko po to, żeby wreszcie wskoczyć na najniższe stanowisko w managemencie IT, mając w dodatku perspektywę, że od najwyższego stanowiska, które będą w stanie osiągnąć na normalnej drodze kariery, dzieli ich tylko jeszcze jedno. Coś mi się tu nie zgadzało: poświęcać 15 lat na to, by osiągnąć szczytowe stanowisko w branży? Przecież można je osiągnąć szybciej, powiedzmy w 10 lat. I co ważniejsze, co dalej? Co potem?

OK, w porządku, nie jestem w stanie myśleć tak, jak myśli 98% ludzi, uważających się za normalną większość — od ciągłego obcowania z komputerami (a pewnie i z powodu szczątków klasycznego wykształcenia) wszystko co robię musi być przemyślane i mieć sens. Artykuł Jeffa Atwooda jest oparty nie tylko na przemyśleniach, sporo w nim emocji, co bynajmniej nie odbiera mu wartości, bo dobrze określa tych, którzy zostają w branży jako ludzi, którzy po prostu kochają pisać kod. Dla niektórych dziwnie wygląda 40-letni programista, dla innych to człowiek, którego w zawodzie trzyma pasja (w ciągu tych 15 lat pracy w zawodzie na pewno trafiają się lepsze propozycje, choćby ze zwykłego rachunku prawdopodobieństwa to wynika).

Ja w każdym razie zostaję. Przez pewien czas próbowałem managerzyć i cieszę się, że już tego nie robię. Moja wątroba ma się lepiej, żona jest mniej nerwowa i jakbym siwiał trochę wolniej.

Polecam: XP-Dev.com

Niedawno po prawej stronie pojawiło się kilka obrazków-linków. O ile dwóch z nich nie ma co opisywać, bo same się doskonale opisują, to link do XP-Dev.com wymaga kilku słów opisu (albo tylko tak mi się wydaje).

Jakiś czas temu chciałem się uniezależnić od mojego dostawcy hostingu w dziedzinie utrzymywania repozytorium z kodem tego serwisu (i paru innych przy okazji też). Przeszkadzało mi to, że współdzielenie dostępu do repozytorium polega na forwardowaniu portu przez SSH, jak dla mnie (i dla znajomych, którym chciałem dać dostęp do repozytorium) to mało wygodne. Zacząłem szukać miejsca, gdzie mógłbym sobie potrzymać mój nie-opensource kod, za darmo lub za niewielką opłatą (ale jak piszę niewielką, to naprawdę niewielką — €10 rocznie to była absolutnie nieprzekraczalna granica). Z serwisów za niewielką opłatą właściwie nie dało się niczego wybrać, zwłaszcza, że oferowały mniej, niż każdy z dwóch darmowych serwisów: Assembla i wspomniany wcześniej XP-Dev. Obejrzałem sobie plany w tych dwóch serwisach i o ile Assembla nie ograniczała ilości repozytoriów, to każde repozytorium miało mniejszy limit wielkości, na XP-Dev.com można było mieć 5 repozytoriów po 300MB każde (na Assembli dowolną ilość 200MB). Założyłem sobie konto na XP-Dev.com, poprosiłem o wgranie dumpów moich repozytoriów i gotowe.

Jak się parę tygodni później okazało, wybór był szczęśliwy — Assembla wprowadziła ograniczenie dla darmowych kont polegające na wymaganiu publicznego dostępu do repozytorium. Parę dni później właściciel XP-Dev.com zapowiedział, że nie zamierza wprowadzać takiego ograniczenia i wręcz zaprasza wygnańców z Assembli. Uch, upiekło mi się...

Od tamtej pory pojawiło się kilka udogodnień, z tych, które zanotowałem (bo mają dla mnie pewną wartość utylitarną):

  • unifikacja url-i serwisu i repozytorium;
  • możliwość samodzielnego wgrywania dumpów repozytorium i dumpowania istniejących repozytoriów;
  • zniesienie limitów rozmiaru poszczególnych repozytoriów i limitu ilości repozytoriów, teraz trzeba się tylko zmieścić w 1.5GB.

Jak dla mnie to co najmniej wystarczająco dużo. Jakby ktoś potrzebował, to są tam też jakieś dodatki typu wiki itp., ale nie ma obowiązku używania. I to mi się podoba.

Memcached to nie jest srebrna kula

Jak ze wszystkimi innymi rzeczami, tak i z memcached nie należy przesadzać. Bynajmniej nie chodzi mi o to, żeby ograniczyć jego używanie — nie ma najmniejszego powodu, żeby sobie odmawiać, to niezły kawałek softu, który dobrze wykonuje swoje zadanie (pewnie mógłby lepiej, ale daruję sobie dygresyjne wędrówki na dzisiaj). Według mnie problem jest natury psychologicznej, a u jego podłoża leżą pospołu lenistwo z ignorancją.

Dla zobrazowania sytuacji przytoczę pewną historię (tak, jak ja ją widziałem, bo nie dotyczyła ona mnie bezpośrednio). Pewien projekt już od swojego założenia miał być ściśle związany z memcache. Wiadomo było od początku, że celem przyspieszenia działania serwisu wiele danych będzie trzymanych na boku w cache, żeby zminimalizować ruch między aplikacją i bazą danych. Założenie rozsądne, ale szybko się okazało, że mocno idealistyczne. Memcache zaczął być traktowany jako panaceum na wszystkie problemy z wydajnością serwisu, obojętnie czy wynikały one ze skomplikowania struktury danych (które skutkuje wysokim obciążeniem bazy danych), czy z marnego algorytmu (nieważne, czy źle zaprojektowanego, czy źle zaimplementowanego). Jeżeli nie dało się czegoś zoptymalizować (czytaj: nikt nie umiał/nikomu się nie chciało/nikomu nie pozwolono marnować na to czasu), to komuś włączała się żaróweczka "o, wiem, wrzucę to do memkesza!", i wrzucał do memkesza. Potem trzeba było opracować metodę usuwania z cache zdezaktualizowanych wpisów. Potem okazywało się, że algorytm do kitu wrzuca do memcache dane do kitu, ale w związku z tym, że aplikacja musi mieć jakiekolwiek dane, obojętne czy prawidłowe czy nieprawidłowe, a w dodatku bez cache aplikacja działać w ogóle nie będzie, to po jakiejkolwiek zmianie algorytmu trzeba było ręcznie usuwać nieprawidłowe dane i trząść się, czy czasem nie usunie się za dużo albo za mało. A i tak do końca nie było pewności, czy po zrestartowaniu aplikacji nagle nie okaże się, że w najmniej oczekiwanym momencie aplikacja nie wysra pięćsetką, a wtedy pozostawał już tylko restart memcached, który, w dużym skrócie, skutkował ogólną apokalipsą (sesje użytkowników w piach, żadnych danych wspólnych przez jakiś czas i ogólnie przerwa w działaniu serwisu idzie w dzisiątki minut).

I tak w kółko, bo jak się okazuje, jest to praktyka powszechna. To znaczy, po tamtym razie widziałem to samo wiele, wiele razy. A jaka jest według mnie recepta? To proste:

  1. zoptymalizuj swój kod
  2. zoptymalizuj bazę danych (słyszałeś o indeksach, prawda?)
  3. to, czego się nie da załatwić optymalizacją, trzymaj w cache

Dokładnie w takiej kolejności.

No future

Często widzę pytania typu jakiego frameworka mam się nauczyć? (albo jaki język ma przyszłość?), kto chce się przekonać, niech spojrzy na Stack Overflow. Zadają je najczęściej newbies, albo jeszcze się uczący albo zaraz po szkole, chcąc się wygodnie ustawić na przewidywane 8-10 lat pracy jako software developer (potem i tak pójdą w managery i będzie im wszystko jedno). Najczęściej dostają kilka-kilkanaście odpowiedzi, sumarycznie dających obraz tego, co jest teraz hot i za czym doświadczeni developerzy sikają w majty: Rails, Merb, Django, Pylons, Werkzeug, erlang, haskell... Trudno nazwać te wszystkie odpowiedzi błędnymi, bo prawdopodobnie w krótkiej perspektywie (2 lata? 3 lata?) to wszystko będzie się sprawdzało. Ale co potem?

A potem wszystkie te odpowiedzi będzie można sobie potłuc o kant zbolałej dupy. Wszystkie te frameworki odejdą w zapomnienie, a języki staną się niszowe (albo nigdy z niszy nie wyjdą). Patrząc na aplikacje, których używają miliony aż dziw, że nikt nie podaje odpowiedzi, która powinna nasująć się sama: "synku, ucz się JavaScript-u". A co to są za aplikacje, te których używają miliony? Nie Word, nie Excel, nawet nie Firefox ani Internet Explorer (paradoks, ale przeglądarka jest taką quasi-aplikacją, jest raczej wirtualnym środowiskiem aplikacyjnym — niektórzy, jak Joel Spolsky, uważają przeglądarki za coś, co zabija tradycyjne desktopy). To GMail, Google Reader, Google Documents, Picasa i pozostałe z koszyczka Google™ (Yahoo!™ nie ma aż takich ambicji, po kupieniu Flickr-a nie rozpoczęło globalnej ekspansji w celu przejęcia władzy nad internetem). Każda z nich jest od dna aż do szczytu napakowana JavaScriptem, co czasem daje efekt taki, że użytkownicy... nie umieją już posługiwać się desktopowymi aplikacjami, które były pierwowzorami aplikacji webowych Google. Uczeń przerósł mistrza używając technologii, która miała stworzyć w internecie namiastkę interaktywności znanej z aplikacji desktopowych. Być może za kilka lat jedynymi płatnymi aplikacjami desktopowymi będą... edytory programistyczne. Bo innych aplikacji w ogóle nie będzie.

Idea webowego desktopu nie jest nowa. Sięgając pamięcią do zamierzchłych lat 90-tych XX wieku niektórzy mogą sobie przypomnieć idee komputerów sieciowych, pozbawionych twardych dysków, będących jedynie stacjami roboczymi z wypasionymi kartami graficznymi (do renderowania stron WWW w oknie przeglądarki). Jak przez mgłę majaczy mi, że nawet poważne firmy, jak np. Sun Microsystems, maczały palce w realizacji tych idei. Dzisiejsze netbooki (z małymi dyskami SSD, procesorem Intel® Atom™, napędzane specjalnie skrojonymi dystrybucjami linuksa) są pokłosiem i ewolucyjnym rozwinięciem tej idei — to nie maszynka desktopowa ma dysponować przerażającą mocą obliczeniową, tylko maszyna, na której jest uruchomiona aplikacja. Stacji roboczej wystarczy tyle mocy, żeby wyrenderowała stronę w przeglądarce.

Za 5 lat obecne ramówki aplikacyjne i języki będą zjawiskiem zanikającym, a tym, co będzie napędzało aplikacje będzie ogromne morze JavaScriptu. I o ile wiem, nie jestem odosobniony w tym poglądzie...

Pokemony w natarciu

Do tej pory pracowałem przy poważnych aplikacjach dla poważnych ludzi. Aplikacje te sprawiały poważne problemy i walka z nimi była naznaczona ich powagą. No i kiedyś musiała nadejść ta chwila, że zająłem się aplikacją dla pokemonów — robię wyszukiwarkę dla jednego z serwisów przeznaczonych dla publiczności w wieku gimnazjalno-licealnym (tytułowe pokemony).

Byłem przygotowany na koszmarny, niczego nie przypominający język. Byłem przygotowany na wszelkie zbrodnie przeciwko ortografii, gramatyce i interpunkcji. Spodziewałem się dziwactw, które są w stanie wywalić w kosmos aplikację niezabezpieczoną przed wstrzykiwaniem SQL. Przeglądając dane z forum tego serwisu natknąłem się jednak na coś, co przesunęło moje granice percepcji.

Podczas próby indeksowania silnik indeksowy updarcie odmawiał przyjęcia pewnego zbioru danych twierdząc, że znajduje się w nich znak kontrolny Unicode o numerze 14, czyli Shift Out. WTF?! Skąd tam się wziął niedrukowalny (i niewprowadzalny z klawiatury!) znak kontrolny? Nie da się go wpisać, nie da się go wkleić w okienko do wprowadzania tekstu (tak mi się przynajmniej wydaje), bo nie można go też skopiować. Prawdę mówiąc zupełnie nie interesuje mnie, jak tamta aplikacja wpuściła ten znak do swojej bazy (to jest nazwa użytkownika, więc jest używana w wielu miejscach), za to interesuje mnie, jak ktoś go przeprowadził przez formularz HTML, żądanie HTTP POST i bibliotekę kliencką bazy danych (znak ten zapisany był w zwykłym polu znakowym typu varchar, nie powinien był tam się znaleźć bez odpowiedniego wyeskejpowania).

Wiele lat pracy z poważnymi aplikacjami przyzwyczaiło mnie do tego, że aplikacja jest linią frontu, na której muszą zostać powstrzymane śmiecie — powstrzymane lub zneutralizowane. W wyniku konsekwentnego chronienia rodowych sreber (dane) przed zalewem gówna, można mieć pełne zaufanie do danych aplikacji. No i okazało się, że są aplikacje, którym tak naprawdę jest wszystko jedno, którą częścią ciała szczekają psy...

Mam pomysł na spotkanie towarzyskie

Chętnie spotkałbym się z tymi programistami Delphi, którzy robią (zawodowo lub OMC zawodowo) w Pythonie. Właśnie nie dawno zobaczyłem posta Fiedzi na django-users, parę innych nazwisk też mi się wydaje znajomych...

Markdown dla grzecznych

Markdown to fajny wynalazek, ale zaskakująco niebezpieczny i to w miejscu, którego zwykle się nie spodziewamy... Wspomniał o tym jakiś czas temu Jeff Atwood przy okazji jakichś innych wynurzeń. Utkwiło mi to bardzo mocno w pamięci, pewnie dlatego, że kiedyś zdarzyło mi się popełnić aplikację podatną na XSS. Tak, nieumiejętnie (nazwałbym to bezmyślnie, ale nie jest moim zamiarem itd.) zastosowany markdown wystawi na niebezpieczeństwo także i Twój serwis. Zastanawiam się nad tym, jak zastosować do zmyślnie, bo chciałbym użytkownikom wMiastoWzięci.pl dać do ręki coś, co im się przyda, a jednocześnie nie pozwoli im na wyrządzenie żadnych szkód (obojętne, umyślnie czy nieumyślnie).

Pewnym rozwiązaniem mogłoby być oczyszczenie tekstu źródłowego przed zapuszczeniem na nim filtra markdown — ale to trochę przypomina wylewanie dziecka z kąpielą, bo wyłącza część użyteczności markdown. Z drugiej jednak strony, rozwiązanie Jeffa A. (czyszczenie tylko części HTML) nie wydaje mi się rozsądne, przede wszystkim z powodu ogromnego skomplikowania zagadnienia. Czyżby była to sytuacja bez wyjścia?

Why Python?

Dlaczego właśnie Python? Często sam zadaję sobie to pytanie i próbuję dać autorytatywną odpowiedź, biorąc pod uwagę mnogość języków, które znam i których używałem (zawodowo i hobbistycznie) w ciągu moich ponad 10 lat praktyki. W ostatnich dniach pojawiło się kilka głosów, które dają pewne sugestie co do możliwej odpowiedzi:

Jestem coraz bliżej odpowiedzi na moje pytanie.

Lighty na ratunek ortografii

Zdarzyło się tak, że zrobiłem literówkę w urlconfie i stało się, Google zaindeksowało mi stronę pod błędnym url-em. O ja nieszczęsna, ale przecież zdarza się w najlepszej rodzinie. Trzeba było zmontować jakiegoś redirecta. Co prawda Django ma jakąś swoją aplikację do redirectów (django.contrib.redirects), ale przecież nie o to chodzi, żeby Django robiło wszystko, skoro to taka drobnostka — przecież każdy serwer http ma coś do robienia redirectów, Lighttpd nie jest żadnym wyjątkiem. Trochę nad tym posiedziałem (bo orłem z wyrażeń regularnych to ja nie jestem...) i działa jak złoto:

$HTTP["host"] =~ "(^|\.)wmiastowzieci\.pl$" {
    url.redirect = (
        "/miajsca/$" => "/miejsca/",
    )
}

Żeby było ciekawiej (choć to babol i w nowszym lighty będzie zmienione), lighty w tym momencie wysyła 301 (czyli permanent redirect) co akurat w moim przypadku robi dobrze, bo nie zamierzam więcej popełniać tej literówki. :)

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. ;)

It's 25, baby!

Turbo Pascal jest dziś starszy, niż spora część programistycznego światka. Wspominam go z sentymentem, bo to był mój pierwszy język programowania, w który wsiąkłem na dobre (i na wiele lat). Nauczyłem się przy jego użyciu (a potem Delphi, poczynając od wersji 2) wielu rzeczy, na nim wyrosłem na dojrzałego programistę i w nim napisałem moje pierwsze aplikacje, używane przez innych ludzi. Na dobre skończyłem pisać w Delphi/Pascalu dopiero z końcem 2006 roku, więc pamięć jest jeszcze żywa.

Jak się ma 25 lat, to już można sobie golnąć. Zdrowie młodzieńca!

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?

Zagadka rozwiązana

Gra, która zostanie ożywiona (stąd "Revival", i trochę z mojego upodobania do pewnego utworu The Soulsavers), to Gwiezdny Kupiec, wydany dawno temu przez nieistniejącą już firmę Encore. Ktoś kiedyś zrobił wersję interwebową tej gry, ale serwis już dawno zniknął z sieci — gdyby nie ten fakt nawet nie próbowałbym myśleć o odtworzeniu tej gry. Gratuluję wszystkim, którzy odgadli, a tych, co nie odgadli pocieszam, że to dość niszowe zagadnienie. ;)

Co do współpracy... Kilka osób się odezwało, dziękuję za zainteresowanie. Wybrańcem losu został Franek Goliński, który użył jokera, bo pracuje ze mną i mamy bezpośredni kontakt przez 8 godzin dziennie (siedzimy może 3 metry od siebie...).

Szczerze mówiąc, nie spodziewałem się w ogóle odzewu, a tu proszę... Zaskoczenie.

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).

Hacknot zniknął z sieci

Z ogromną przykrością i z prawdziwym żalem odkryłem, że Hacknot.info (nie linkuję, bo domenę przejęli już squatterzy) jeden z najciekawszych serwisów z esejami dotyczącymi produkcji oprogramowania zniknął z sieci. Zniknęła masa fantastycznych artukułów (często bardzo kontrowersyjnych, ale zawsze ciekawych i pozwalających przemyśleć poważnie różne sprawy). Pozostała książka (do ściągnięcia za darmo w postaci PDF lub do kupienia za parę €), ale zawiera ona tylko wybór esejów Eda Johnsona.

Szkoda, naprawdę wielka szkoda. Z drugiej strony, nie mogę uwierzyć w to, że cała zawartość tego serwisu zniknęła bez śladu (przecież była na CC!), więc przez cały czas można mieć jakąś nadzieję, że gdzieś uchowała się lokalna kopia...

IDE dla developera

Piotr Maliński przemyśliwał nad IDE, więc miałem okazję porównać moje potrzeby w tym samym względzie. Są większe. :)

Do jego listy dopisuję rozszerzalność w postaci definiowania akcji zdarzeń — niech takie IDE ma zdefiniowane zdarzenia typu file-open, file-save i niech mi da możliwość (choćby ograniczoną) podpinania swoich akcji na te wydarzenia. To, co dla mnie było największym hitem PIDA to ciągłe sprawdzanie poprawności syntaktycznej modułu po każdym zapisie (i dodatkowo kilka innych rzeczy, jak np. aktualizacja drzewa definicji w kodzie). Wszystkie (no, prawie...) edytory, z którymi pracowałem, mają możliwość uruchamiania zewnętrznych narzędzi, niektóre nawet pozwalają przypiąć do nich skróty klawiszowe, ale żaden nie dawał możliwości automatycznego uruchamiania tych narzędzi (Komodo Edit pozwala uruchomić makro w odpowiedzi na pewne zdarzenia, ale to nie to samo...). Oczywiście, nauczyłem się bez tego żyć, wyrobiłem sobie też nawyk częstego pociskania Shift-Ctrl-V w TextMate, ale to namiastka.

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.

Pomoc z niespodziewajki

Jakkolwiek dziwne może się wydać to stwierdzenie, Google jest świetnym narzędziem do testowania aplikacji webowych. A właściwie to jego bot. Nic się przed nim nie ukryje, spróbuje pobrać dokument spod każdego URL-a, na jaki tylko trafi, posłusznie raportując każdą 500 i 404 w GWT. A przy okazji mając włączone raportowanie mailem można szybko zareagować na problem. Nie policzę, ile błędów wykrył Googlowy bot w ciągu kilku ostatnich dni, odkąd moja aplikacja jest na etapie testów wewnętrznych.

Dziękujemy ci, Google. :)

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. :)

I hate this feeling

Nienawidzę tego uczucia zazdrości, kiedy mając w 50% zaawansowaną aplikację widzę ogłoszenie, że ktoś zrobił dokładnie to, nad czym właśnie ślęczę. Jeszcze bardziej nienawidzę tego uczucia zazdrości, gdy widzę, że moja aplikacja nie jest równie dobra, jak tamta. Wszystko w niej wtedy wydaje się lepsze — przepływ sterowania, architektura informacji, wygląd, dosłownie wszystko.

10 minut i jednego papierosa później przeklinam bardzo brzydko, siadam z kartką papieru i zaczynam kreślić plan pokonania konkurencji. Wygram, k**wa, wygram!

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.

Framework to nie hop-siup...

Przyglądając się WebOb i dołączonym do niego przykładom można wreszcie zobaczyć, jak dużą rzeczą są ramówki webowe. O ile WebOb dostarcza podstawy do zbudowania własnej ramówki, to porównanie tej podstawy z gotowym frameworkiem, np. Django, uświadamia, jak wiele rzeczy trzeba napisać, żeby wszystko razem zagrało. Oczywiście, zazwyczaj używa się tylko pewnej części ramówki i wydaje się, że napisanie własnej spowoduje, że będzie szybsza, lżejsza i w ogóle lepsza, bo przecież pozbawiona kodu (a więc i funkcji), którego się i tak nie używa. Ale to, co pozostaje, to i tak ogromna ilość kodu i ogromna ilość funkcji, które trzeba zaimplementować. I to zaimplementować poprawnie, przewidując sytuacje niezwykłe (dziwnego klienta, któremu się coś pieprzy z HTTP i czegoś-tam zapomina wysłać albo nie rozumie) i awaryjne.

Ja w każdym razie dziękuję, zostanę przy cudzej robocie. :)

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. :)

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ć? ;)

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!

TextMate jest niedorobione!

Kto używał Vima ten pewnie zna ten skrót: znak "gwiazdki" (*) wyszukuje słowo pod kursorem w dokumencie. I co powiecie? W TextMate, tak zajebistym skądinąd edytorze, nie ma czegoś takiego, jak "Find word at cursor".

Żenada.

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!

Mac i Python, Python i Mac

Jak wiedzą wszyscy (albo wszyscy w mieście), Apple dostarcza jakiegoś Pythona ze swoim OSX. Mogłoby to wskazywać, że Python jest first class citizen, jeżeli chodzi o języki programowania na OSX, podobnie jak jest to w przypadku Ubuntu. A figę (chcialem napisać bardziej dosadnie, ale podobno nieletni czytają)!

Robienie aplikacji na Maka w Pythonie to droga przez mękę. Serio. PyObjC obecnie oficjalnie jest w wersji, która ledwo wspiera poprzednią wersję OSX (10.4), zresztą pisanie w tym czymś przypomina programowanie aplikacji na Win32 używając PyWin i MFC. A chyba nawet gorzej. Z normalnych ramówek GUI są oczywiście wszystkie, ale w takim przypadku bez Py2App nie ma co się do tego zabierać. Normalnie jak na Windows.

Która to konkluzja doprowadza mnie do szerzej ujmującej makowo-osxową rzeczywistość konstatacji — ogólnie Mac jest jak Windows, tylko trochę bardziej. Odczuwam taki sam brak wolności (w sensie free speech) i podobne podejście ludzi, którzy robią oprogramowanie na Maki — byle tylko zarobić i nic nie pokazać. Na linuksie może nie ma aż tak wyczesanych efektów wizualnych, może też nie wszystkie ficzery tak ładnie działają (zamknij klapę laptoka, a się uśpi, podnieś, a się obudzi — i WiFi przeżyje), ale pod względem aplikacji skierowanych na produktywność po prostu nie ma porównania. Instalujesz Ubuntu i masz dosłownie wszystko, czego potrzebujesz, a to, czego jeszcze nie masz, jest na odpalenie apt-get install.

Co doprowadza mnie do kolejnej konkluzji, że moim kolejnym prywatnym komputerem nie będzie Mac, tylko jakiś fajny normalny laptop z matrycą 13'4. Jak na przykład twoja Toshiba, Smoku...

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.

Miód, miód!

While comments are neither inherently good or bad, they are frequently used as a crutch. You should always write your code as if comments didn't exist. This forces you to write your code in the simplest, plainest, most self-documenting way you can humanly come up with. (#, podkr. oryg.)

Od zawsze staram się pisać kod tak, by nie trzeba było go komentować. Od zawsze staram się komentować kod tam, gdzie jego działanie jest nieoczywiste (tzn. nie jest jasne, dlaczego ten kod działa). To samo tyczy się testów — nie ma rzeczywistej potrzeby pisania testów do kodu, który:

  1. jest oczywisty
  2. jest kolejną (liczoną w tysiącach) implementacją algorytmu, o którym wiadomo, że działa

Potrzeba osiągnięcia coverage na poziomie 85% nie jest rzeczywistą potrzebą uzasadniającą pisanie testów. Podobnie jest z docstringami — to, że jakiś kod został wydzielony do funkcji/metody nie oznacza, że trzeba go dokumentować.

I na zakończenie jeszcze jeden cytat, odnoszący się do zwyczaju komentowania kodu (wyjątek z listy popularnych usprawiedliwień do dawania komentarzy w kodzie).

The code too complex to understand without comments. I used to think this case was a lot more common than it really is. But truthfully, it is extremely rare. Your code is probably just bad, and hard to understand. Re-write it so that's no longer the case. (#)

Brakujące ogniwo

W nowej pracy dostałem nowe zadanie do zrobienia — zdeployować gotową aplikację w Javie na kilka maszyn w kilku odmianach. Jako że do tej pory takimi rzeczami zajmował się dla mnie dział deploymentu (słodkie czasy), było to dla mnie coś zupełnie nowego. Do zrobienia była dość prosta rzecz:

  • skopiować kilka szablonów plików z jednego drzewa katalogów do drugiego,
  • podmienić odpowiednie ciągi znaków w plikach szablonów,
  • spakować wszystko do tar.gz,
  • wrzucić na zdalną maszynę przez scp,
  • rozpakować na zdalnej maszynie,
  • uruchomić aplikację pod screenem.

Zacząłem od poszukiwania narzędzia, które by mi to ułatwiło.

Skrypty w shellu odpadły na samym początku. Nie umiem ich pisać, poza paroma najbardziej podstawowymi zastosowaniami.

W pracy używamy Pavera, więc naturalną koleją rzeczy zasiadłem do klepania pliku pavement.py. Szybko okazało się, że to potwornie żmudna robota, gdy aplikacja nie jest napisana w Pythonie i wszystkie ułatwienia Pavera można sobie wsadzić głęboko.

Przypomniałem sobie wtedy, że parę lat temu robiłem podobne rzeczy przy użyciu anta, więc napisałem sobie odpowiedni plik build.xml. Było z tym trochę roboty, ale działa. Wreszcie. Ale niestety, to jest Java i XML, więc piętno zostało wybite.

Zacząłem się rozglądać za innym narzędziem i wtedy wpadł mi w oko Fabric. Z opisu wynika, że może być tym, co zastąpi mi anta... ale może też wcale tak nie być. Projekt jest w bardzo wczesnej fazie rozwoju, ale będę miał na niego oko, gdyby ant za bardzo mi dopiekł.

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.

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.

KomodoEdit i Pyflakes

Ponieważ PIDA zaczęła mnie wkurzać (co chwilę a to coś działa, a to nie działa), postanowiłem dać szansę KomodoEdit, mocno okrojonej, ale darmowej wersji niezłego Komodo IDE. Ponieważ mój nowy lapek ma 2GB RAM na pokładzie i mocny, dwurdzeniowy procesor, żerność Komodo nie była aż tak wielkim problemem jak dawniej. Obejrzałem dokładnie program i zacząłem zastanawiać się, jak do niego dołączyć to, do czego przyzwyczaiła mnie PIDA, to znaczy integrację z Pyflakes. Dla niezorientowanych: Pyflakes to program do statycznej analizy poprawności kodu Pythona, nieco mniej zaawansowany niż Pylint, jednak jednocześnie nie aż tak inwazyjny (oczywiście wiem, że Pylinta można tak skonfigurować, żeby nie sprawiał wrażenia faszystowskiego narzędzia).

Jak się szybko okazało, nie ma możliwości podpięcia Pyflakes tak, by uruchamiało się przy każdym zapisie bufora, jak dzieje się to w PIDA, jednak dla mnie ważniejsze było to, żeby działało, niż to, żeby działało identycznie. Postanowiłem przypiąć Pyflakes jako narzędzie zewnętrzne w Toolboksie Komodo. Oto skrócona recepta, jak to zrobić, żeby działało ładnie i produkowało fajne wyniki.

  1. Toolbox -> Add -> New Command
  2. Command (U): /usr/bin/pyflakes %F
  3. Run in: "Command Output Tab"
  4. [*] Parse output with: ^(?P<file>.+?):(?P<line>\d+): (?P<content>.*)$
  5. [*] Show parsed output as a list

A teraz opis słowno-muzyczny. Z menu Toolbox wybierz Add i następnie z podmenu New Command. Na zakładce Command w nowo otwartym okienku w pole Command wpisz polecenie uruchomienia Pyflakes ze ścieżką bieżącego bufora (u mnie: /usr/bin/pyflakes %F). Z listy Run in wybierz Command Output Tab. Zaznacz pole Parse output with i w pole polecenia parsującego wpisz wyrażenie regularne ^(?P<file>.+?):(?P<line>\d+): (?P<content>.*)$, które podzieli wyjście Pyflakes na 3 części. Zaznacz pole Show parsed output as a list, a Komodo wyświetli wyniki w postaci ładnej listy z 3 kolumnami — dwukrotne kliknięcie na każdej pozycji listy przeniesie kursor do odpowiedniej linii w edytorze.

Po tym wszystkim pozostaje jedynie przypisanie do tego jakiegoś wygodnego skrótu klawiszowego w tym samym okienku na zakładce Key Binding.

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. ;)

Zdrada? To się dopiero okaże...

No i będę miał w pracy MacBooka. Niby zwykłego, ale tak nie do końca, bo czarnego. Nigdy nie miałem Maca, nigdy też go nie używałem, więc zacząłem od czytania, co też mnie czeka po przesiadce z linuksa. Nie zapowiada się różowo, głównie z powodu problemów z bibliotekami. Ale może jakoś to przeżyję...

Cierpię

Po raz kolejny cierpię, jak za każdym razem, gdy muszę zmontować jakiś layout dla serwisu. W tej dziedzinie mam dwie lewe ręce i zezowate oko, więc staram się (przynajmniej na początku) znaleźć coś gotowego. I ciągle mam ten sam problem — szablony które udaje mi się znaleźć mają w większości ustaloną szerokość circa about 800px, czasem pomijalnie większą. To jest rzecz, która dyga mnie za każdym razem, jak potrzebuję wygrzebać jakiś gotowy szablon: przytłaczająca większość szablonów jest robiona na fixed width, przez co masa przestrzeni się marnuje. Nie wspominając o tym, że wszystkie wyglądają jak odbite z jednej matrycy z niewielkimi zmianami.

Nie chce mi się już robić we webie.

Przede mną tydzień urlopu

Po miesięcznym zapieprzu związanym z zakończeniem kontraktu na aplikację, wziąłem tydzień wolnego od firmy. Zamierzam nie myśleć o pracy i zajmować się swoimi sprawami, w swoim tempie. Nie wiem jeszcze, co będę robił, niczego nie planuję. Jest wiele rzeczy, które mam ochotę zrobić — chcę sobie trochę poczytać, przećwiczyć parę sztuczek z javascriptem, może napisać trochę kodu na własne potrzeby.

Patch applied

W pracy używamy kilku różnych bibliotek third-party. W ogniu walki często okazuje się, że mają one jakieś mankamenty, albo brakujące funkcjonalności, od których zależy działanie naszej aplikacji. Czasem kończy się to zgłoszeniem błędu, ale częściej razem ze zgłoszeniem dostarczamy poprawkę. Czuję się szczególnie doceniony, gdy moja poprawka trafia do upstream — mam wtedy megalomańskie uczucie, że dzięki mnie świat staje się lepszy. Chociaż w niewielkim zakresie... ;)

jQuery mi się podoba

Po raz kolejny zastrzegam: to nie jest miłość (bo miłość może być tylko jedna). Ale jQuery mi się podoba.

Dzisiaj miałem zrobić taki pokręcony formularz z trzema select-ami, gdzie środkowy przyjmuje elementy z dwóch naokoło niego na kliknięcie. Biorąc pod uwagę moją niechęć (i co tu dużo ukrywać, nieznajomość też) do JavaScriptu, planowałem sobie to na co najmniej dzień roboty, a najprawdopodobniej półtora. Tymczasem usiadłem do roboty około 10, a o 12 miałem już to zrobione. 35 linijek kodu i żadnego znużenia. Więcej takich ułatwiaczy poproszę.

To dziwne czasy

Gdy kilkanaście lat temu zaczynałem pisać programy o nieco większym stopniu skomplikowania, obowiązującą mantrą było: przerzuć obliczenia na serwer bazy danych, on ma więcej mocy niż aplikacja. Stąd wzięły się wszystkie moje umiejętności optymalizacyjne, które dobrze robiły przez wiele lat. A teraz dowiedziałem się od naszego bazodanowca, że muszę chronić serwer przed przegrzaniem i jak najwięcej robić na serwerze aplikacyjnym, bo... serwer z bazą nie wyrabia.

Mam dysonans poznawczy. Wolałbym, żeby okazało się, że z powodów oszczędnościowych firma postawiła bazę na jakimś Celeronie 1.7GHz z 256MB RAM, niż że moje umiejętności optymalizacyjne nadają się do lamusa... ;)

Pasanie owieczek

Po ponad roku pracy z młodzieżą zebrało mi się na kilka refleksji dotyczących całej branży robienia softu. Raczej przygnębiających.

Po pierwsze, i najważniejsze, większość z programistów przed 30-tką, jakich spotkałem w ciągu tego roku, nie ma pojęcia o projektowaniu aplikacji. Umieją napisać kod, który robi to, co założyli, ale zazwyczaj cała aplikacja nie robi tego, co miała robić. Ich horyzont widzenia kodu zamyka się w jednym, najwyżej w dwóch modułach, więc każda większa zmiana skutkuje zwykle załamaniem się innego fragmentu aplikacji. A o czymś takim, jak zastanawianie się nad konsekwencjami zmian w kodzie zwykle sobie nie pozwalają. Powiedziałbym ogólnie, że myślenie nie jest ich najmocniejszą stroną, albo inaczej — są świetni w rozwiązywaniu problemów, które sami sobie stworzyli (by sparafrazować Kisiela). I to jest ta przygnębiająca konkluzja.

Przyczyna tego stanu rzeczy wydaje się leżeć głębiej, a jest ona chyba jeszcze bardziej przygnębiająca. Otóż, według mnie, głównym czynnikiem, który sprawia, że młodzi programiści nie dojrzewają do projektowania aplikacji, jest nacisk managementu na to, żeby jak najszybciej dostarczyć działający kod, byle by był. Management żyje w świecie złudzeń (podtrzymywanych przez samych programistów), jakoby zawsze można było poprawić źle działający kod później, ale z drugiej strony, żeby się z tych złudzeń wyleczyć, to trzeba napisać sporo kodu, którego nikt nigdy nie poprawił z powodu braku czasu. Autor nie poprawi, bo nie ma kiedy (zajmuje się przecież czymś innym, również na już), a jego następca też nie poprawi, bo kodu nie rozumie (autor mu nie wytłumaczy, bo nie ma czasu, zresztą prawdopodobnie sam już nie pamięta).

I co teraz? Założę się, że nic, będzie tak, jak poprzednio.

JavaScript może czuć się przeproszony

Nigdy nie lubiłem JavaScriptu. Syntaktycznie mi się ten język nie podobał, jego idea wydawała mi się poroniona (co najmniej), a różnice w implementacjach przez różne platformy zwyczajnie dyskredytujące. Tym niemniej zapoznałem się z nim przynajmniej na tyle, żeby znać go z widzenia. Ogólnie konieczność pisania kodu w JavaScripcie napawała mnie obrzydzeniem.

Do niedawna. Bo niedawno wypatrzyłem jQuery i całe moje podejście do JavaScriptu zmienniło się diametralnie. Miłością nie zapałałem (bo miłość może być tylko jedna), ale polubiłem JavaScript. Dziękujemy Ci, jQuery!

PIDA mnie rozczarowuje

Większość mojego kodu w Pythonie (a właściwie kodu w ogóle, włączając w to HTML i skrypty SQL) piszę w PIDA. Jak dla mnie to środowisko pozostawia mi ultrawygodnego Vima, dodając to, czego potrzebuję podczas pracy: zarządzanie grupą plików jako projektem, kilka podstawowych poleceń systemu kontroli wersji, przeglądarkę klas i integrację z pyflakes. Niestety, PIDA ma wciąż sporo błędów i — to jest prawdziwe niestety — nie są one w ogóle poprawiane (prawdopodobnie dlatego, że nie ma kto tego zrobić, i nie, ja też tego nie dam rady zrobić). Bardzo to wkurzające, przede wszystkim zważywszy fakt, że żaden z wolnodostępnych edytorów nie oferuje tego, co PIDA...

Tak łatwo to jeszcze nie było

Znalazłem kilka dni temu samouczek pisania własnych ramówek webowych w Pythonie w oparciu o WSGI i bibliotekę WebOb. Tak łatwo to chyba jeszcze nie było...

Na szczęście minęły czasy pączkujących ramówek (w tempie dwóch tygodniowo, strach było otworzyć lodówkę), ale może takie samouczki pokażą pretendentom, co ich czeka od strony kodu. Bo tego, co ich czeka od strony użytkowników to się nie da opisać żadnymi słowami. ;)

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.

Walki z SuSE (open) ciąg dalszy

Do pisania aplikacji w Django ten system się raczej nie nadaje — nie ma w nim całej masy bibliotek, a przede wszystkim nie ma psycopg.

Kicha.

Topre, topre

Some people would argue that not all developers are morons or assholes, but they are mistaken. For example, some people posit the existence of what I will call the “angel” developer. “Angels” read specs closely, write code, and then thoroughly test it against the accompanying test suite before shipping their product. Angels do not actually exist, but they are a useful fiction to make spec writers to feel better about themselves

(za diveintomark)

Po raz kolejny porażka

Nie pamiętam już ile razy przysiadałem (w celach czysto edukacyjnych, aby zrozumieć jak to działa) do zrobienia sobie wielokolumnowego układu dokumentu HTML, z kilkoma wymaganiami, które zdają się obecnie wykluczać:

  • kolumna główna o szerokości względnej, dopasowującej się do wielkości okna przeglądarki;
  • kolumny boczne o stałej szerokości, wyrażonej w jednostkach em;
  • kolumna główna zdefiniowana w kodzie HTML jako pierwsza;
  • bez gremlinów w typie poziomego paska przewijania w IE6;
  • i jak najmniej boilerplate (niestrukturalnych div-ów).

Nie ma tego wiele. Co jakiś czas przeglądam to, co znajduje mi Google, ale wciąż powtarzają się te same triki: a to kolumny boczne trzeba definiować jako pierwsze, a to nie da się ustalić ich szerokości, a to znowu coś innego. A jeżeli znajduję gdzieś gotowy, działający szablon, to jego kod jest tak pogmatwany, że tylko łapię się za głowę.

Jak dla mnie, dowodzi to tylko jednego: CSS, choćby nie wiem jak był użyteczny w innych dziedzinach, w obecnej postaci do tworzenia układów stron się nie nadaje. Gdy tylko chce się wyjść poza ustawianie wielkości czcionki, obramowania elementu czy koloru tła, okazuje się, że używanie do tego celu CSS przypomina dokręcanie śrubek przy użyciu łyżki. Ktoś mógłby argumentować, że web to nie gazeta i kolumny są wbrew naturze WWW, ale w takim razie oznaczałoby to tylko jedno: że WWW nie nadaje się do użycia sterowanego zapotrzebowaniem. Bo ewidentnie potrzeba dzielenia treści na kolumny istnieje, a wręcz ze zwiększaniem się rozdzielczości monitorów ta potrzeba staje się coraz bardziej naoczna.

Więc co? Flash? Dopiero Flash to nie jest web (gdzie tu dokument, połączony z innymi dokumentami systemem dowiązań?). Czekam od kilku lat na rozwiązanie tego problemu w sposób kompleksowy, pewnie będę czekał jeszcze długo, i wszystko wskazuje na to, że się nie doczekam.

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ę...

Nowości z frontu walki z Akismet

Coś wygląda na nieźle spieprzone w usłudze sprawdzania komentarzy przez Akismet — podanie w komentarzu linku do strony na Grupach Google (obojętne, dyskusje czy pliki), zamiast spodziewanego "true" lub "false" zwraca "" (pusty ciąg znaków). Na razie nikt nie zwrócił uwagi na takie zachowanie Akismet, ale ja się zacząłem rozglądać za czymś innym. Na razie plany rozszerzenia CommentModerator obejmują LinkSleeve i Defensio.

Jak na razie wygląda na to, że nie da się zamieścić komentarza, który zawiera link do Google Groups. Wolałbym nie rezygnować z automatycznego moderowania komentarzy, ale jeżeli nie da się tego uniknąć, to będę moderował komentarze ręcznie. Oh, my...

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ć.

Po WARPY #4

Zmieściłem się w czasie... prawie. ;)

Przeciągnąłem o 5 minut, ale obskoczyłem wszystko, pomimo problemów z warstwą prezentacyjną. Niestety, publiczność nie dopisała i mieliśmy pustki na sali.

Materiały są dostępne na stronie listy mailowej.

WARPY #4

Kolejne, 4 spotkanie w ramach WARPY odbędzie się tam, gdzie zawsze, o 19:15. Poopowiadam trochę o tłumaczeniu i lokalizacji programów w Pythonie. Będzie o modułach gettext i locale, a dodatkowo pokażę parę tricków z biblioteką Babel.

Nauczony poprzednią wpadką postaram się ograniczyć moje pływanie w dygresjach. ;)

Gdybym mógł wybierać...

Jest kilka firm, w których chciałbym pracować. Na poniżej liście nie ma mojego obecnego pracodawcy, bynajmniej nie dlatego, że nie chcę już pracować w Sensisofcie, ale dlatego, po pierwsze jest to lista marzeń, a nie planów, a po drugie, gdybym tam nie chciał pracować, to już bym nie pracował.

  1. Google. To oczywiste. Każdy programista czy projektant chciałby pracować w Google, a ja nie jestem w tej dziedzinie wyjątkiem. Ta firma robi rzeczy wielkie, coś na miarę projektu Manhattan, ale w skali oprogramowania. Chciałbym (chociaż przez pewien czas) być jednym z nich.
  2. Xerox. Dziwnym może się wydać umieszczenie firmy Xerox już na drugim miejscu, bo dla większości ludzi oblatanych w IT kojarzy się ona z kopiarkami, potwornie drogimi drukarkami, a dla niektórych także z niezwykle innowacyjną historią Palo Alto Research Center (Xerox PARC). Jest jednak coś innowacyjnego w tej firmie także i dziś, ale niewiele osób o tym wie, bo jej najbardziej innowacyjne produkty mają wąskie grono odbiorców, jak np. VIPP. Poznałem VIPP dość dobrze i doceniłem jego klasę.
  3. Canonical. Robienie na linuksie jest dla mnie teraz tak oczywiste, że trudno mi sobie wyobrazić, że mógłbym w pracy używać innego systemu operacyjnego, a w dzisiejszych czasach coraz częściej jest tak, że mówisz linux, myślisz Ubuntu.
  4. IBM, Sun i Microsoft, ex aequo. Każda z tych firm robi ciekawe rzeczy, choć te, które mnie naprawdę interesują, stanowią margines ich działalności. Gdyby udało mi się popracować przy tych ciekawych rzeczach, to czemu nie?

Jak widać, nie ma tego szczególnie wiele. Jedyną nieoczywistą pozycją w tym zestawieniu wydaje się być Xerox, więc może jednak zostanę w Sensisofcie... ;)

Nowe na megiteam.pl

megiteam.pl (gdzie hostowany jest ten skromny serwis) wprowadziło w ramach nowości rozliczanie za używaną pamięć, zamiast rozliczania za procesy. Lepiej? Gorzej? Nie wiem, jak dla mnie to chyba drożej, bo drugą aplikację uruchomić będzie mi trudno (w sumie zostało mi ze 20MB pamięci, więc nawet na małego memkesza mi nie wystarczy), musiałbym sobie dodać jeszcze ze 20MB pamięci, a te aplikacje, które teraz powstają mają trochę większe oczekiwania, więc raczej pozastanawiam się jeszcze trochę nad dedykiem w hetzner.de.

Swoją drogą, ciekawe jest orientacyjne zestawienie, ile która ramówka potrzebuje dla aplikacji...

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...

Dziwne potrzeby, oczywiste rozwiązania: polib

Ktoś (imienia nie wymienię) wysłał klientowi teksty do przetłumaczenia... w Excelu. Klient zadowolony, że nie musi używać żadnych hackerskich narzędzi typu poEdit, oczywiście przetłumaczył teksty w tymże Excelu, a nam przyszło załamać ręce. Ale tylko na chwilę.

Dzięki bibliotece polib załatwiliśmy sprawę w pół godziny. A żeby zaoszczędzić stękań na przyszłość, następne partie tłumaczeń też będziemy wysyłać w Excelu. A co!

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.

Czego warto zazdrościć Javie

Na pewno nie średników i klamerek. ;)

Java jako język może nadawać się najwyżej do programowania pralek. Jako platforma może być przegniła do korzenia. Ale ma coś, czego co jakiś czas brak odczuwam bardzo mocno programując w Pythonie — zestaw specyfikacji do J2EE. Specyfikacja jak to specyfikacja, to suchy papier, może być bez sensu, ale złe są dopiero implementacje. Spośród tych wszystkich większość jest dość specyficzna dla Javy ("stwarzamy problemy by bohatersko je pokonywać", czy jakoś tak to było), ale są dwie, które ja uważam za perełki. I to są te dwie, których brakuje mi najbardziej:

  • JNDI, czyli uniwersalne usługi katalogowe;
  • JMS, czyli integracja komponentów aplikacji przez komunikaty.

Cała reszta może się schować, ale tych dwóch naprawdę mi brakuje. Nie to, żeby Python potrzebował takich specyfikacji, ja potrzebuję standardowych implementacji. O, jakby mi ułatwił życie taki serwer JMS z interfejsem w Pythonie (nie, ActiveMQ + Stomp to jeszcze nie jest to!)...

Kolory się mienią

Sprawdzanie schematów kolorów do Vima bywa męczące. Czasem nawet bardzo. Dlatego strona, na której można zobaczyć kilkaset schematów w działaniu na raz, może być zbawieniem. Szkoda tylko, że nie można obejrzeć tam kodu w Pythonie, to by mi najbardziej ułatwiło życie...

Trick do Vima, który zmienia życie

Długo szukałem, aż wreszcie znalazłem. Jak zmieniać colorscheme w zależności od typu pliku i to tak, żeby działało to również przy przełączaniu między buforami z plikami różnego typu?

"colorschemes based on file type
augroup ColorSchemeOnFileType
    autocmd!
    autocmd BufEnter * if(&ft == 'htmldjango') | colorscheme oceandeep | else | colorscheme django | endif
augroup END

Udało się wreszcie... A już zaczynałem być zdesperowany. ;)

Mam kolejnego gadżeta

Tym razem zupełnie niechcący. Kończyła mi się umowa u komórkowców, więc ją sobie przedłużyłem, przy okazji wymieniając telefon na nowy. Dopiero po powrocie do domu, przeglądając instrukcję obsługi, znalazłem logo Symbiana i coś na temat S60... Czyli mam fona z S60! :D

Pierwszą rzeczą, którą sobie zainstalowałem był (a jakże inaczej) PyS60...

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ć.

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...

WARPY #3

Tym razem będę się produkował ja. :)

Opowiem trochę o programowaniu aplikacji GUI przy użyciu PyGTK, raczej od podstaw, niż dla bardziej zaawansowanych.

Próbowałem, poddałem się (na razie)

Chciałem podłączyć moją usługę (webservice zrobione na podstawie twisted.web) do serwera Zeroconf i okazało się, że to całkowicie niemożliwe w obecnej sytuacji. Jedyna dobra biblioteka do tego, czyli avahi, jest tak mocno zintegrowana z DBus, że wymaga... głównej pętli programu zrobionej na GObject, żeby móc działać asynchronicznie. Porażka. Znalazłem wstęp do implementacji usług związanych z mDNS przy użyciu Twisted, ale oczywiście niepełną.

Chyba trzeba będzie sobie wreszcie ubrudzić ręce trochę poważniejszym kodem...

Już zapomniałem jak to jest...

Family going to bed at 10 PM is so much worse than jet lag.

Kiedyś było to dla mnie dość normalne... Czyżby dopadała mnie nostalgia za kawalerskimi czasami? ;)

Wygląd tego wpisu przekonuje mnie, że czym prędzej muszę się rozejrzeć za jeszcze szerszym szablonem, bo mi sie stripy z XKCD nie mieszczą...

Trafiony, zatopiony

Pół dnia spędziłem na szukaniu błędu, który ukrył się w poniższym fragmencie kodu:

try:
    contactsIndex = self.names.index('Contact')
    contacts = items[contactsIndex]
    mail = email_re.findall(contacts)[0]
    if mail:
        logger_main.debug('Email %s found within ad data' % mail)
        report_counters['ads_with_emails'] = report_counters['ads_with_emails'] + 1
except:
    mail = False

Wszystko wydawało się w porządku, ale nie było. Klauzula except bez wyspecyfikowania wyjątku zachowuje się tak, że wyłapuje wszystko. Dopiero zapisanie jej w postaci except Exception, e: i zalogowanie wyjątku pokazało, gdzie tak naprawdę ten błąd był. Zupełnie nie tam, gdzie się spodziewałem — klucz słownika report_counters wcale nie nazywał się 'ads_with_emails' (mniejsza o to, jak się nazywał).

Po prostu się nie spodziewałem. Perfidia.

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...

WARPY #2

Dziś na PW kolejny wykład w ramach WARPY. Z powodów osobistych się nie wybiorę, niestety.

Następny wykład będzie 10 stycznia 2008, o ile nie zajdą jakieś nieprzewidziane okoliczności. I będę się starał wygłosić prelekcję o PyGTK.

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!

Rzeczy proste, rzeczy skomplikowane

Kiedyś o tym ktoś już napisał (może Joel Spolsky, a może ktoś inny, nie mogę sobie przypomnieć). A teraz dotknęło to mnie osobiście.

Są rzeczy proste i są rzeczy skomplikowane. Naturą rzeczy prostych jest to, że łatwo jest ich używać (my to nazywamy, że mają prosty interfejs). Są jednak także rzeczy, które są skomplikowane same z siebie. A jak jest z ich używaniem?

Podobnie jak z rzeczami prostymi, rzeczy skomplikowane są... skomplikowane w użyciu. Rzeczy skomplikowane mają skomplikowany interfejs właśnie dlatego, że ze swej natury są skomplikowane i ich funkcji nie da się wyrazić w sposób uproszczony. Można to porównać do dwóch narzędzi, które służą do wyciągania gwoździ: obcęgów i tzw. łapki. Obcęgi są dość skomplikowanym narzędziem, które spełnia kilka funkcji (na kilka sposobów), więc jego interfejs jest nieco bardziej skomplikowany, niż łapki. Oczekiwanie, że obcęgi będą miały prostszy interfejs, doprowadzi do degeneracji obcęgów do poziomu łapki. Podobnie, nikomu nie wpadnie do głowy, by żądać od 50-tonowej lokomotywy, by miała interfejs Hondy Civic.

A teraz zdążam na skróty ku poincie. Dużo ludzi uważa, że zadaniem nas, czyli developerów, jest zapewnianie prostego interfejsu. Abstrahując od tego, że jest to piramidalną bzdurą, jest to także niemożliwe. Rzeczy skomplikowane nie mogą mieć prostego interfejsu, dopóki są skomplikowane. I nie będą miały prostszego interfejsu, dopóki będą realizować skomplikowane funkcje w skomplikowany sposób. A takie działanie jest chyba immanentne dla aplikacji webowych...

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ć.

WAR-PY

Albo WARPY, albo może WarPy... Nie wiem, jaka jest oficjalna pisownia tego dziwnego skrótowca, nawet pomimo tego, że w jego powstawaniu brałem udział kilka tygodni temu w pubie Wetlina. Dość, że robocza nazwa staje się powoli nazwą oficjalną.

We czwartek 29 listopada o 19.00 na Politechnice Warszawskiej w sali AL odbędzie się pierwsza prelekcja, którą wspólnymi siłami (Grono.net, Sensisoft, WARLUG i WO@PW) urządzamy celem popularyzacji Pythona. Zacznie Marek Pułczyński z Grona, od standardów pisania kodu w Pythonie. Z agendy wnoszę, że będzie głównie opowiadał o trzymaniu się konwencji, dokumentowaniu kodu przy użyciu docstrings i budowaniu pakietów, ale temat może odpłynąć, bo zagadnienie jest... dość nośne. Kolejne spotkania będą się odbywać co dwa tygodnie.

Na prelekcję za dwa tygodnie ostrzy sobie zęby Rafał Zawadzki, więc ja się wstrzelę pewnie dopiero za 6 tygodni. Na razie planuję opowiedzieć coś o PyGTK, ale przecież wszystko może się odmienić...

Practicality beats purity

Każdy, kto programuje w Pythonie dłużej niż kilka godzin, na pewno kiedyś zetknął się z tym:

Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Często zdarza mi się zapomnieć o jednej z tych zasad: że practicality beats purity. Mamy teraz taki drobiazg do zrobienia, przydałoby się wyjąć z jednego modelu wszystko, co jest związane z jego wyświetlaniem. Oczywiście, nie tylko dlatego, że to łamie podstawową zasadę MVC i miesza model z jego wyświetlaniem — z tego powodu jest to tylko brzydkie, a poza tym to jest też dość niezręczne w zarządzaniu. Wiele już myślałem nad tym, jak wyjąć konfigurację wyświetlania tego modelu żeby wyglądało to ładnie no i chyba właśnie wygrało to practicality — zrobimy oddzielny model na DisplayProperties.

Platforma dla programisty

Używam linuksa (jak to się teraz modnie mawia: lennoxa) od wielu lat. Od wielu lat słyszę, jakoby system był nieprzyjazny, nieintuicyjny i wiele innych nie na jego temat. Nie będę się teraz rozpisywał, jak to z gruntu fałszywe są to opinie (bo mam jeden dowód na to w domu w postaci żony i drugi w bloku naprzeciwko w postaci mojej matki). Są jednak tacy ludzie, dla których linux jest jedną z najbardziej przyjaznych platform.

Programiści.

Niezliczone ilości narzędzi (darmowych!), biblioteki do prawie każdego zadania i języka, jakie tylko można sobie zamarzyć. Kod źródłowy, który może służyć za przykład, nie zawsze najwyższej jakości, ale przecież jest. Żyć nie umierać, tylko pisać kod. Nie powiem, pisałem programy na własne potrzeby i dla własnej przyjemności także i wtedy, gdy używałem najpopularniejszego systemu operacyjnego, ale zawsze było to trochę utrudnione — a to nie działało to czy tamto, a to jakiejś biblioteki nie było sportowanej, a zdarzały się dziwne zachowania programu wynikające z idiosynkrazji tego innego systemu operacyjnego.

A najgorsze było to, że Vim działał jakoś dziwnie na tym całym Windows®...

Übersatan

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.

Budzi się pragnienie

Powoli budzi się we mnie pragnienie (a może i potrzeba) wymiany laptoka na nową sztukę. Mój obecny HP nx6110 z najniższej półki wciąż jeszcze mi dobrze służy (chyba przede wszystkim dlatego, że mam niewielkie wymagania...), ale zaczynam już odczuwać potrzebę poprawienia mojej sytuacji życiowej. Lepsze jest wrogiem dobrego, a już na pewno jest wrogiem słabego.

  • 15' matryca mojego HP jest chyba jedną z najgorszych, jakie były produkowane dwa lata temu; wiem, że są gorsze (mają je np. najtańsze Acery z tamtego okresu), ale wcale nie zmienia to faktu, że kolory odwzorowuje tragicznie, szczególnie na bitmapach;
  • procesor Celeron-M360J (1.4GHz) nie jest demonem prędkości i nie ma co się nad tym rozwodzić;
  • dysk 40GB dawno już został zapełniony i muszę się posiłkować dodatkowym dyskiem podłączanym przez USB;
  • 3 kilogramy na ramieniu potrafią przygiąć do ziemi każdego.

Nie za bardzo mam ochotę kupować sobie MacBooka, wolałbym jakieś fajne HP z matrycą 13.1'... Ale takich nie ma. Przynajmniej na moją kieszeń.

W każdym razie, i tak muszę poczekać do przyszłego roku. Obiecałem, że w tym roku nie kupię sobie komputera...

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().

Web3.0? Nie, dziękuję.

Spotkałem się w niedzielę z pewnym człowiekiem... Zaproponował mi wzięcie udziału w dużym, celującym wysoko międzynarodowym projekcie serwisu, oczywiście nie za darmo. Sama propozycja bardzo mile podłechtała moją próżność (podobnie jak kilka miesięcy wcześniejsza propozycja wzięcia udziału w rekrutacji do Google), więc bardzo się nie opierałem przed osobistym spotkaniem, pomimo tego, że miało ono nastąpić w sobotę lub w niedzielę. Katalizatorem dobrego nastawienia do propozycji było to, że człowiek ten przyjechał specjalnie w niedzielę z Krakowa, żeby spotkać się ze mną i z jeszcze jedną osobą z nieodległej firmy (technologicznie i geograficznie), którą też chciał w ten projekt wciągnąć. Umówiliśmy się na 17:00 w Złotych Tarasach, żeby omówić projekt i kilka wstępnych ustaleń.

Człowiek okazał się bardzo miłym młodzieńcem, naprawdę zapalonym do swojego pomysłu. Przedstawił swoją wizję dość klarownie, jednak próba pokazania mi działającego dema zakończyła się katastrofą i nie zobaczyłem nic, oprócz strony z debugiem błędu w Django. Pierwsze skojarzenie, jakie mi się nasunęło, to katastrofa, której doznaliśmy w lipcu, gdy próbowaliśmy pokazać klientowi aplikację na Węgrzech — wtedy przez 5 minut było OK, ale potem wszystko zaczęło się walić. Tutaj nie było nawet tych pięciu minut, co przy założeniu, że aplikacja ma być gotowa za miesiąc, oznaczać może tylko jedno — stan aplikacji jest bardziej niż niepokojący. Z innych rzeczy pozaproduktowych, kwestię finansową przemilczę, bo moja matka twierdzi, że przewróciło mi się w głowie i straciłem kontakt z rzeczywistością. Wystarczy że napiszę, że nie miałbym lepiej, niż mam teraz.

A teraz o samym produkcie. Nie poruszyła mnie jego idea. Być może jestem za stary na takie gwizdki, być może przez cały czas myślę jak człowiek z trzeciego świata, ale faktem jest, że nie poruszyła mnie wizja milionów ludzi płacących po $10 rocznie za coś, co tylko podbudowuje ich ego. Bardzo możliwe, że w ten sposób działają nastolatki, bardzo też możliwe, że postępujące otumanienie społeczeństw konsumpcyjnych idzie w parze z zatrzymaniem rozwoju mentalnego na poziomie nastolatków. Wiem, że można na tym robić pieniądze (vide przykład MySpace czy Facebook) i to nawet ogromne, jednak nie przekonuje mnie to. Wydaje mi się, że na długi czas zatrzymałem się na poziomie Web1.0 i z dużym trudem doszlusowałem do poziomu Web2.0, nie połykając go w całości jak leci, a jedynie przyswajając sobie kluczowe idee. Z każdą kolejną iteracją główna idea internetu wydaje mi się odchodzić w kierunku coraz głębszego autyzmu, a projekt przedstawiony przez tego człowieka wydaje mi się tak autystyczny, że bardziej już nie można. Więcej o filozoficznych podstawach mojego poglądu na rozwój internetu pewnie jeszcze kiedyś napiszę.

Konkudując w skrócie, mam jeszcze kilka godzin na zastanowienie się, ale nie przypuszczam, żeby cokolwiek było w stanie zmienić moją decyzję. Nie wchodzę w to.

Duma i niezadowolenie

Zawsze mnie zastanawiało, skąd się biorą badziewne serwisy WWW, zrobione na zasadzie prawie jak..., po których natychmiast widać, że są marną podróbką czegoś-tam. Wydawało mi się, że pomimo różnic w przymiotach intelektualnych (nie, nie jesteśmy sobie równi pod tym względem), istnieje pewien minimalny poziom dobrego smaku połączony z minimalnym poziomem rozsądku, którym posługuje się przytłaczająca większość ludzkości i takie wpadki nie są wynikiem celowego działania, bo przecież nikt specjalnie nie strzelałby sobie w stopę. No i właśnie niedawno doznałem zderzenia z rzeczywistością. Nie ma czegoś takiego. Zasada wydaje się prosta — jeżeli jest jakaś głupota, której klient może sobie zażyczyć, to sobie zażyczy. Można próbować mu wyperswadować taki pomysł, można próbować podsuwać mu rozsądne rozwiązania, które zmniejszą poziom głupoty jego propozycji, ale trzeba założyć, że skutek tego będzie żaden. W dodaku, raz wszedłszy na ścieżkę produkcji g*wna, nie da się z niej zejść tak łatwo i dodawanie kolejnych ficzerów powoduje tylko narastanie tej sterty. W takim przypadku trzeba się cofnąć o te kilka kroków, naprawić popełnione wcześniej błędy (najczęściej przez usunięcie tego, co kontrowersyjne) i dopiero wtedy można mówić o uwolnieniu się.

Jestem rozgoryczony, to prawda. Niewiele brakowało, a byłby serwis WWW, którym mógłbym się pochwalić znajomym i być z niego dumny na zasadzie jam ci to sprawił. I g*wno. Z powodu idiotycznych żądań klienta musieliśmy zrobić z niego koszmarek i nie dało się tego wyperswadować, nie dało się też przekonać klienta do naszych propozycji. Patrząc od strony biznesowej wszystko niby jest w porządku, pieniądze przepłynęły z jednego konta na drugie, klient jest zadowolony, firma jest zadowolona — ale ja nie jestem. Przez cały czas mam wrażenie, że przyłożyłem rękę do produkcji szajsu. którego jeszcze długo będę się wstydził, bo przecież nikogo tak naprawdę nie interesuje, ile włożyłem w to zaangażowania i pracy, przecież tak naprawdę liczy się efekt końcowy. W tym przypadku — opłakany i jest to oczywiste dla każdego, kto w internecie spędza trochę więcej czasu, niż trzeba na przejrzenie newsów na BBC, CNN czy Onecie. Pół roku pracy bez efektu, z którego można być zadowolonym już raz doprowadziło do tego, że zmieniłem pracę (a warunki na rynku pracy wtedy były znacznie gorsze niż teraz). Niestety, po około 10 latach pracy w branży nie spodziewam się, by gdziekolwiek sytuacja przedstawiała się lepiej. No, może tylko w Google, ale też pewien nie jestem...

A co gorsza, utknąłem w tym szajsie na dobre, trochę przez swoje wcześniej wspomniane zaangażowanie, a trochę z powodu braku zasobów. Pociesza mnie tylko to, że to wciąż Python.

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... ;)

Trzy kolory

Jeśli edytor, to wiadomo: Vim. A jeśli pisanie w nim kodu, to wiadomo, co jest jedną z podstaw: podświetlanie składni tak, żeby wszystko było dobrze widoczne, a jednocześnie w takim zestawie kolorów, który nie męczy oczu, nawet wtedy, gdy trzeba patrzeć w ekran po kilka godzin. W takich przypadkach wygrywają zestawy raczej mało kontrastowe i raczej te z ciemnym tłem. Oto trzej moi faworyci, w kolejności niekoniecznie przypadkowej:

  • oceandeep, utrzymany w zielono-niebieskiej tonacji, prawie bez mankamentów;
  • desert, brązowo-beżowy, cokolwiek nudny, ale bardzo spokojny;
  • zenburn, faworyt wielu, dla mnie trochę więcej niż akceptowalny, jakoś nie przekonuje mnie aż tak niski kontrast, ale czasem (szczególnie późno w nocy) jest ukojeniem dla oczu.