strona główna

Archive for the 'Python' Category

Warsztaty IT: nowe kursy

Wednesday, February 11th, 2009

"Warsztaty Django dowodzą, że formuła zajęć się sprawdza, więc co można teraz zrobić? Nowe, ciekawe kursy :)" – właśnie dodałem do stronie Warsztatów IT zapowiedź nowych szkoleń: Ruby on Rails i programowania w Pythonie.

A przy okazji – do najbliższych warsztatów Django już niecałe dwa tygodnie, miejsc systematycznie ubywa. Więcej szczegółów tutaj, a jeśli chcesz mieć pewność że weźmiesz udział – zapisz się już dzisiaj!

Django a martwe drzewa

Friday, January 16th, 2009

Po ostatnich warsztatach dostałem pytanie o książki na temat Django. Odpowiedź może być interesująca dla większej grupy ludzi, więc wklejam ją także tutaj.

Z książkami, niestety, jest marnie. Głównie dlatego, że przed wydaniem wersji 1.0 Django mocno się zmieniało.

Po polsku nie pojawiło się chyba jeszcze nic, chociaż od dłuższego czasu Helion ma "w przygotowaniu" Django. Ćwiczenia praktyczne.

Po angielsku jest The Django Book. Zostało wydane przez Apress, ale w wersji opisującej 0.96, różnic niestety jest dużo i to w ważnych miejscach: szablony, interfejs administracyjny, obsługa formularzy. Wersja dla 1.0 właśnie powstaje (fragment już można przeczytać na stronie) i jest spora szansa, że będzie aktualna dość długo, bo głównym powodem tak dużych zmian w ostatniej chwili była chęć ustabilizowania API. Jeśli czyta to ktoś z jakiegoś wydawnictwa: tak, to jest dobry moment żeby pomyśleć nad tłumaczeniem.

Więc niestety dobrej odpowiedzi nie mam. ALE Django ma naprawdę dobrze napisaną i aktualną dokumentację (bardzo o to dbają – praktycznie każdy commit zmieniający cokolwiek widocznego dla użytkownika zawiera też odpowiednią zmianę w docach), więc na razie RTFM jest najlepszym rozwiązaniem :)

Warsztaty Django: druga edycja

Wednesday, December 10th, 2008

Od kilku dni można zapisywać się na drugą edycję warsztatów "Tworzenie aplikacji w Django". Dla tych, którzy jeszcze zastanawiają się, czy warto, umieściłem na stronie opinie nadesłane przez uczestników pierwszej, listopadowej. Między innymi taką:

Pierwszy raz uczestniczyłem w warsztatach, podczas których uczestnicy nie mieli ochoty wychodzić na przerwy.

- Marcin Raczyński

Kiedy ostatnio byliście na szkoleniu, którego uczestnicy żałowali że już się skończyło i od razu pytali o kontynuację? :)

Najbliższą szansę macie już 13 stycznia. Warto zapisać się już dzisiaj, bo z dwunastu miejsc pierwsze trzy są już zarezerwowane. Dodatkowo, zostało już tylko 10 dni na skorzystanie ze zniżki.

I po warsztatach

Friday, November 28th, 2008

Ogólne podsumowanie umieściłem we wpisie na stronie Warsztatów IT. Tutaj dodam, że:

  • przede wszystkim – warsztaty zdecydowanie nie byłyby tak udane bez pomocy mojej Ani. Prawdopodobnie nawet bym do nich nie dożył :)
  • przygotowanie wszystkiego to dwa miesiące poszukiwań sali, zbierania materiałów i radykalnych zmian zdania co do sposobu przeprowadzenia zajęć,
  • takie warsztaty są świetnym pretekstem do dokładnego poznania prezentowanego materiału; dowiedziałem się paru nowych rzeczy, które będę stosował w moich aplikacjach,
  • byłem zaskoczony, jak wiele firm wynajmujących sale notorycznie olewa klientów – rekordziści do dzisiaj nie przesłali informacji obiecanych przez telefon. Kilkakrotnie. We wrześniu.
  • zajęcia odbyły się w Akademii Linuksa; wszystkim chcącym wynająć salę komputerową mogę tę firmę tylko polecić – ludzie są pomocni i kompetentni, sprzęt sensowny. Vista, Debian i Fedora, nie ma też problemu z dostępem do konta roota (w części firm warunkiem było korzystanie z vmware lub virtual pc). Po całym dniu miałem tylko jedną uwagę: przydałoby się coś zrobić z okablowaniem (mieliśmy drobny incydent z nieszczęśliwie umieszczoną listwą zasilającą osiem komputerów).

Ogólnie – niesamowicie pouczające doświadczenie; ciekawie było przekonać się, jak w praktyce wygląda organizacja takiej imprezy.

A już wkrótce kolejna edycja, jeśli znacie kogoś kto mógłby być zainteresowany – przekażcie, proszę, adres WarsztatyIT.pl :)

Najlepsze serwisy w Django

Thursday, November 6th, 2008

W ramach prac nad rozwojem długo oczekiwanego i niedawno otwartego polskiego serwisu Django odbywa się właśnie głosowanie na najlepsze polskie strony napisane z użyciem tej ramówki. Po tygodniu dwa pierwsze miejsca zajmują OiolaEl Monito :)

Lista jest interesująca: o Wydarzysie.netwolnelektury.pl wiedziałem już wcześniej, ale o istnieniu sobotnianoc.pl, szarada.net, miejsce-na-reklame.pl, ocenfotke.pl czy rankingdeweloperow.pl dowiedziałem się dopiero teraz. Widać, że Django jest używane coraz częściej, i to nie tylko przez hobbistów, ale też przez duże serwisy (grono.net, Super Express – ten ostatni przeniesiony z J2EE).

Jeśli jeszcze się zastanawiacie: to jest bardzo dobry moment żeby przyjrzeć się tej ramówce. Jak to zwykle bywa z narzędziami oszczędzającymi olbrzymie ilości czasu, Django szybko zyskuje na popularności. Teraz jeszcze zapewnia wyraźną przewagę konkurencyjną, ale niedługo stosowanie tego typu narzędzi będzie już koniecznością pozwalającą nadążyć.

Warsztaty Django: zapisy otwarte

Tuesday, October 28th, 2008

Od wczoraj można już zapisywać się na pierwszą edycję warsztatów "Tworzenie aplikacji w Django". Liczba uczestników ograniczona do 12 osób, decyduje kolejność zgłoszeń. Pierwsze cztery miejsca już zajęte, więc nie warto zwlekać – jeśli temat Cię interesuje, to zapisz się już teraz.

Szczegóły, jak zwykle, na stronie warsztatów.

Django: powiadomienia

Saturday, October 25th, 2008

Wczoraj przyszło mi do głowy, że powiadomienia o błędach to jednocześnie jeden z bardziej przydatnych i najmniej widocznych elementów Django. To dzięki nim zdarza mi się poprawić błędy i wysłać do użytkownika informację, że jakaś strona działa już poprawnie nawet jeśli nie chciało mu się zgłaszać usterki.

Na przykład:

 
from django.shortcuts import render_to_response
 
def gather_user_data(user):
    # in reality this would be more complex
    return {'email': user.email}
 
def profile_view(request):
    data = {'user': request.user}
    data.update(gather_user_data(request.user))
    return render_to_response('profile_view.html',
                              dictionary = data)
 

Widok profile_view zawiera błąd ujawniający się tylko w niektórych sytuacjach. Jeśli trafi na niego ktoś na serwerze produkcyjnym, zobaczy standardowe 500 Internal server error, a Django automatycznie wyśle do mnie taki raport:

 
Traceback (most recent call last):
 
  File "/home/marcink/checkout/django-trunk/django/core/handlers/base.py",
  line 86, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
 
  File "/home/marcink/checkout/utils/blogowe/errtest/../errtest/errapp/views.py",
  line 8, in profile_view
    data.update(gather_user_data(request.user))
 
  File "/home/marcink/checkout/utils/blogowe/errtest/../errtest/errapp/views.py",
  line 4, in gather_user_data
    return {'email': user.email}
 
AttributeError: 'AnonymousUser' object has no attribute 'email'
 
< wsgirequest GET:< QueryDict: {}>,
POST:< querydict : {}>,
COOKIES:{'django_log_selectedLevel': '0',
 'django_log_showLocation': '1',
 'sessionid': '915922a5710cf210e2f5fae47e0f8900'},
META:{'COLORTERM': 'gnome-terminal',
 'CONTENT_LENGTH': '',
 'CONTENT_TYPE': 'text/plain',
 'DBUS_SESSION_BUS_ADDRESS': 'unix:abstract=/tmp/dbus-BibuFgosrz,guid=c50cabac45d33af5b6936d0049024c87',
 'DESKTOP_SESSION': 'default',
 'DISPLAY': ':0.0',
 'DJANGO_DIR': '/home/marcink/checkout/django-trunk',
 'DJANGO_SETTINGS_MODULE': 'errtest.settings',
 'FCGI_PORT': '9014',
 'GATEWAY_INTERFACE': 'CGI/1.1',
 'GDMSESSION': 'default',
 'GDM_LANG': 'en_US.UTF-8',
 'GDM_XSERVER_LOCATION': 'local',
 'GNOME_DESKTOP_SESSION_ID': 'Default',
 'GNOME_KEYRING_PID': '7904',
 'GNOME_KEYRING_SOCKET': '/tmp/keyring-glnuCS/socket',
 'GTK_RC_FILES': '/etc/gtk/gtkrc:/home/marcink/.gtkrc-1.2-gnome2',
 'HISTCONTROL': 'ignoreboth',
 'HOME': '/home/marcink',
 'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
 'HTTP_ACCEPT_LANGUAGE': 'en-us,en;q=0.5',
 'HTTP_CACHE_CONTROL': 'max-age=0',
 'HTTP_CONNECTION': 'keep-alive',
 'HTTP_COOKIE': 'sessionid=915922a5710cf210e2f5fae47e0f8900; django_log_showLocation=1; django_log_selectedLevel=0',
 'HTTP_HOST': '127.0.0.1:5003',
 'HTTP_KEEP_ALIVE': '300',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.17) Gecko/20080922 Ubuntu/7.10 (gutsy) Firefox/2.0.0.17',
 'JAVA_HOME': '/home/marcink/devel/jre1.6.0/',
 'LANG': 'en_US.UTF-8',
 'LD_LIBRARY_PATH': '/home/marcink/checkout/ldev_comicspot/comicspot/../other',
 'LESSCLOSE': '/usr/bin/lesspipe %s %s',
 'LESSOPEN': '| /usr/bin/lesspipe %s',
 'LOGNAME': 'marcink',
 'LS_COLORS': 'no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.flac=01;35:*.mp3=01;35:*.mpc=01;35:*.ogg=01;35:*.wav=01;35:',
 'OLDPWD': '/home/marcink',
 'OTHER_DIR': '/home/marcink/checkout/ldev_comicspot/comicspot/../other',
 'OUTER_DIR': '/home/marcink/checkout/ldev_comicspot/comicspot/..',
 'PATH': '/home/marcink/bin:/home/marcink/local/bin:/home/marcink/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games',
 'PATH_INFO': u'/profile/',
 'PROJECT_BASE_DIR': '/home/marcink/checkout/ldev_comicspot/comicspot',
 'PROJECT_DIR': '/home/marcink/checkout/ldev_comicspot/comicspot/comicspot',
 'PROJECT_NAME': 'comicspot',
 'PWD': '/home/marcink/checkout/utils/blogowe/errtest',
 'PYTHONPATH': '/home/marcink/checkout/django-trunk',
 'QUERY_STRING': '',
 'REMOTE_ADDR': '127.0.0.1',
 'REMOTE_HOST': '',
 'REQUEST_METHOD': 'GET',
 'RUN_MAIN': 'true',
 'SCRIPT_NAME': u'',
 'SERVER_NAME': 'raven.loc',
 'SERVER_PORT': '5003',
 'SERVER_PROTOCOL': 'HTTP/1.1',
 'SERVER_SOFTWARE': 'WSGIServer/0.1 Python/2.5.1',
 'SESSION_MANAGER': 'local/raven:/tmp/.ICE-unix/7907',
 'SHELL': '/bin/bash',
 'SHLVL': '1',
 'SSH_AGENT_PID': '7947',
 'SSH_AUTH_SOCK': '/tmp/ssh-lBRzQZ7907/agent.7907',
 'TERM': 'xterm',
 'TZ': 'America/Chicago',
 'USER': 'marcink',
 'USERNAME': 'marcink',
 'WINDOWID': '39852701',
 'WINDOWPATH': '7',
 'XAUTHORITY': '/tmp/.gdmELMXJU',
 'XDG_DATA_DIRS': '/usr/local/share/:/usr/share/:/usr/share/gdm/',
 'XDG_SESSION_COOKIE': '90de2c2a7bf31a95a76c4900471ce700-1224887428.976645-1927173936',
 '_': '/usr/bin/python',
 'wsgi.errors': < open file '< stderr>', mode 'w' at 0xb7dc30b0>,
 'wsgi.file_wrapper': < class 'django.core.servers.basehttp.FileWrapper'>,
 'wsgi.input': < socket ._fileobject object at 0x854a72c>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': True,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)}>
 

Po pierwsze – dowiaduję się o problemie bez żadnej akcji ze strony użytkownika. Po drugie – dostaję mnóstwo informacji o błędzie: opis wyjątku (tutaj – brak atrybutu email w AnonymousUser), dokładnie wskazane miejsce wystąpienia razem z pełnym stosem wywołań funkcji, zawartość parametrów przekazanych przez GET i POST i zawartość zmiennych środowiskowych. W praktyce zwykle wystarcza to do złożenia testu pozwalającego na powtórzenie i usunięcie błędu.

„Django - 3 lata pracy i wszystko, co mamy, to marne 1.0″

Monday, October 13th, 2008

Z okazji wydania Django 1.0 na ostatnim Bootstrapie opowiedziałem o najważniejszych zmianach, jakie zaszły w tej ramówce podczas tych trzech lat, które upłynęły od momentu otwarcia kodu. Slajdy z mojej prezentacji:

Reklama: już wkrótce odbędzie się pierwsza edycja warsztatów Django – najszybszy sposób na poznanie możliwości tego środowiska i rozpoczęcie pracy.

Django na Bootstrapie

Saturday, October 4th, 2008

Już w przyszłym tygodniu Bootstrap 8.10. Andy Budd opowie o testach użyteczności (“Guerilla Usability Testing with Silverback”), a ja ponarzekam na Django ("Django - 3 lata prac i wszystko, co mamy, to marne 1.0").

Serdecznie zapraszamy.

Skróty: render_to_response

Wednesday, October 1st, 2008

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

Wygląda to tak:

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

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

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

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

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

Działa tak:

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

Przykłady:

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

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

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

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

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

I trochę testów:

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

A sam dekorator wygląda tak:

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

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

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