Dla poddierżenia razgawora

Ostatnio jest cicho, więc dla poddierżenia razgawora kilka drobiazgów.

@classmethod
def import_object(cls, obj):
    return False, None

Zimno. Odkąd wróciłem z Grecji jest mi nieustająco zimno. Niestety, w te wakacje raczej nie uda mi się dostać 10 dni urlopu, więc jeżeli już pojedziemy do Grecji, to na tydzień.

Ustaliliśmy z żoną, że w przyszłym roku Escort idzie do ludzi. Jakby to wyczuł, bo zaczął się sypać: najpierw jakiś wyciek z układu hamulcowego, teraz problemy ze sprzęgłem i skrzynią biegów.

Po raz pierwszy od ponad miesiąca przyjechałem dziś do pracy komunikacją publiczną. Bez stania w korkach i czekania na autobusy (akurat dziś podjeżdżały w mgnieniu oka) zajęło mi to 70 minut (85 od wyjścia z domu). Samochodem w 70 minut dojeżdżam stojąc w korkach (Piłsudskiego/Chełmżyńska i Marsa). Na szczęście to był jednorazowy wybryk, już jutro znowu zamieniam bus na wóz.

Do koncertu Nine Inch Nails w Poznaniu pozostało 20 dni.

Firma zaczyna

Dziś wystawiłem moją pierwszą fakturę. Gdyby nie to, że dziś znowu się wożę (pierwszy raz wiozłem moją żonę, umierała ze strachu...), to pewnie poszedłbym coś chlapnąć na to konto.

Gehenna zdąża ku końcowi

Dziś, 22 kwietnia 2009 dostałem wreszcie mój REGON. Po 3 (trzech!) tygodniach mogę zacząć normalną działalność gospodarczą. W stosunku to gorszej przeszłości jest to zysk na poziomie -14 dni (minus czternastu), co oznacza 14 dni straty.

Lepsze jutro było wczoraj.

Mam szczęście do szatanów

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.

Note: comments closed due to excessive spamming.

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.

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

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

Będzie, czy nie będzie?

Dziś ma zostać wydane Django 1.0.

Będzie?

Nie będzie?

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

Ładowanie...