Obiecywałem w skrócie przedstawić cuda, jakimi są oba projekty. Jak obiecałem, tak czynię.

Marketing kołem napędowym gospodarki

Oba projekty znałem od dość dawna z prasy, jaką zapewniają im deweloperzy i fani na wszelakich djangowych portalach, planetach i konferencjach. Na siłę używał ich nie będę — pomyślałem — ale może kiedyś się trafi okazja, to zobaczę, czym się tak wszyscy ekscytują. W bebechy nie zaglądałem, wiedziałem tylko, że jeden ma być uniwersalnym szkieletem dla wszelakich serwisów społecznościowych, a drugi klockami do budowy sklepów internetowych.

Bliskie spotkania I stopnia

Okazja się trafiła, choć nie taka, jak planowałem. Najpierw przyszło mi bowiem robić jedynie niewielkie poprawki we wdrożonym już serwisie opartym o Satchmo, później dopisać jakąś prostą aplikację do rozwijanej jeszcze społecznościówki, która do dzisiaj chyba nie dorobiła się ani wdrożenia, ani nawet nazwy.

Działać to działało, pod maskę nie bardzo miałem czas zaglądać. Dalszy kontakt sprowadzał się głównie do czytania samych superlatyw o owych — chciałoby się rzec — przebłyskach geniuszu inżynierii programowania.

Pinax — pierwsza krew

Do czasu. Jeden z kontraktów z Mirumee wymagał wbudowania w serwis podstawowych funkcji społecznościowych. Jednogłośnie zdecydowaliśmy się wypróbować Pinaksa.

Miesiąc później mieliśmy już własny fork, z którego systematycznie usuwaliśmy kawałki takiego kodu, że starczyłoby na doktorat z architektury oprogramowania. Obejścia na workaroundy, zaślepki na hackach. Widoki z pominięciem formularzy rozbierające request na zmienne i bez żadnej walidacji pakujące je do bazy. Widoki pozwalające na edycję cudzych obiektów, jeśli tylko znało się ich id. Aplikacje, które nie działały wcale, albo te działające częściowo, z resztą kodu wykomentowaną i otoczoną FIXME.

Jedno jest pewne, zmarnowaliśmy na to masę czasu i jeśli będę musiał do Pinaksa podejść drugi raz, to tylko po to, żeby ze strzelbą pod pachą wyprowadzić go na tyły domu.

Początek przygody z Satchmo

Całkiem niedawno zostaliśmy z kolei zakontraktowani przez firmę z UK, która specjalizuje się w budowie sklepów internetowych w Satchmo. Niestety programiści odeszli tuż po rozpoczęciu projektu i ktoś musiał zadanie skończyć. Chciałem poznać Satchmo, teraz miałem okazję. A dokładniej dwa tygodnie na przerobienie garści plików PNG z projektami graficznymi na w pełni działające wdrożenie.

Pierwsze dwa dni spędziłem na lekturze dokumentacji i doprowadzeniu kodu do działania na swojej maszynie. Szybko też okazało się, że Satchmo to taki sam zestaw klocków, jak Pinax czy — nie przymierzając — samochód. Oczywiście, można wymontować tylną kanapę i zmienić choinkę zapachową na lusterku, ale próba wyciągnięcia radia szybko pokazuje, że jest trwale przykręcone do kokpitu. Jakiekolwiek próby dopasowania sklepu do potrzeb sprzedawcy albo przypominają próbę ucharakteryzowania poloneza na ciągnik siodłowy z użyciem plasteliny, albo kończą się głęboką ingerencją w kod poszczególnych modułów.

I wtedy zaczęły się schody

Właśnie kontakt z modułami był pierwszym momentem, kiedy krzyknąłem WTF. Kiedy pierwszy raz miałem kontakt z Satchmo, poszczególne moduły i aplikacje mieszkały we wspólnej przestrzeni nazw satchmo. Okazuje się jednak, że ktoś wpadł na genialny pomysł zaoszczędzenia kilku literek i całość by działać musi zostać umieszczona bezpośrednio w ścieżce Pythona. Wielkie brawa za potencjalne konflikty nazw z innymi modułami Pythona oraz za zajęcie co bardziej oczywistych nazw dla lokalnych aplikacji projektu.

To jednak nie koniec, szybko okazało się, że dokumentacja to tylko ciekawostka, a jakiekolwiek pojęcie o działaniu całości daje dopiero lektura kodu. Tam można znaleźć prawdziwe perełki.

Miłe złego początki

Na początku było śmiesznie:

{% for product in products %} 
    {% if forloop.first %} <ul>  {% endif %}
        <li>{% thumbnail product.main_image.picture 85x85 as image %}
        <a href="{{ product.get_absolute_url }}"><img src="{{ image }}" width="{{ image.width }}" height="{{ image.height }}" /></a>
        <a href="{{ product.get_absolute_url }}">{{ product.translated_name }}</a></li>
    {% if forloop.last %} </ul> {% endif %}
{% endfor %}

Oczywiście, zdarza się, że pierwszy i ostatni element listy występują gdzieś w środku, dlatego bezpieczniej sprawdzić wszystkie.

Powrót do przyszłości

Wkrótce okazało się, że system płatności dla niektórych operatorów wymaga dodatkowo podania daty wystawienia karty. Z tym, że data wystawienia i ważności oferują ten sam zestaw opcji: od stycznia bieżącego roku, do grudnia za cztery lata. Cóż, widać karty starsze niż trzy miesiące nie są zbyt popularne, za do zdarzają się w obrocie karty wystawione za dwa lata.

Działa doskonale, działa bez przerwy

A co powiecie na nieskończoną pętlę rekurencji właśnie w module płatności?

def confirm_info(request, template='shop/checkout/protx/confirm.html', extra_context={}):
    payment_module = config_get_group('PAYMENT_PROTX')
    controller = confirm.ConfirmController(request, payment_module)
    # ...
    controller.onForm = secure3d_form_handler
    controller.confirm()
    return controller.response

# ...

def secure3d_form_handler(controller):
    """At the confirmation step, protx may ask for a secure3d authentication.  This method
    catches that, and if so, sends to that step, otherwise the form as normal"""
    
    if controller.processorReasonCode == '3DAUTH':
        # ...
        return http.HttpResponseRedirect(redirectUrl)
    
    return controller.onForm(controller)

Ładnie, prawda?

Telekonkurs dla naszych widzów

Na koniec zagadka. Problem: podczas testów użytkownik opłacił za towar, ale na stronie z potwierdzeniem zamówienia pojawiła się informacja o nieudanej płatności. Rozwiązanie: okazuje się, że właściciel sklepu źle wypełnił pole na własny adres e-mail. Czy ktoś z was potrafi bez zaglądania w kod znaleźć logiczne powiązanie?

Otóż pole na e-mail obsługi sklepu nie jest typu EmailField i jest mu generalnie obojętne, jaką wartość się poda. System płatności zaś na koniec transakcji wysyła e-mail z potwierdzeniem na adres sklepu. W bloku o takiej konstrukcji:

try:
    self.response = dict([row.split('=', 1) for row in result.splitlines()])

    # ...

    return ProcessorResult(self.key, success, detail, payment=payment)

except Exception, e:
    self.log.info('Error submitting payment: %s', e)
    payment = self.record_failure(order=order, amount=amount,
        transaction_id="", reason_code="error",
        details='Invalid response from payment gateway')

To oczywiście nie koniec. W kodzie można znaleźć takie wynalazki, jak:

  • obiekt NOTSET, pełniący funkcję None;
  • aplikacja livesettings, umożliwiająca softcoding rzeczy, które powinny być w kodzie (niektóre z nich wymagają do działania restartu usługi, to się dopiero nazywa ironia);
  • bezmyślne zapychanie logów gdzie i czym popadnie;
  • i wiele, wiele innych.

Odpręż się, to nie będzie bolało

Nie mam pojęcia, jak oba projekty wyrobiły sobie jakąkolwiek renomę. Pinax jest zwyczajnie niegroźną kupą kompostu, którą należy omijać z daleka albo mieć potem pretensję do samego siebie. Satchmo z kolei operuje cudzymi pieniędzmi.

Czy nie byłoby lepiej zamiast chwalić się udanymi wdrożeniami (chyba, że jest to chwalenie się na zasadzie: nie wiem jakim cudem, ale się udało), zająć się testowaniem własnego kodu? Już nie mówię nawet o poprawianiu go, żeby przypominał coś napisanego w Pythonie, wystarczy, żeby działała więcej niż jedna kombinacja, zaś zepsute i nietestowane od miesięcy moduły zostały w stosowny sposób oznaczone.

Mówiąc w skrócie, jestem na nie.