Aplikacja browaru się rozwija
Raz, że trochę poładniała, a dwa — można już komentować wpisy warek. Jeszcze trochę, a przejmę władzę nad światem. ;)
Im mniej mam do czynienia z Django w pracy, tym większą mam przyjemność z drobnych robótek na swoim.
Raz, że trochę poładniała, a dwa — można już komentować wpisy warek. Jeszcze trochę, a przejmę władzę nad światem. ;)
Im mniej mam do czynienia z Django w pracy, tym większą mam przyjemność z drobnych robótek na swoim.
Właśnie przestawiłem ten serwis na Django 1.2.1. Migracja i dostosowanie kodu zajęły nie więcej niż pół godziny, pomimo sporych zmian jakie były wprowadzone w wersji 1.2. Jak zwykle jestem pod wrażeniem dbałości o zgodność wsteczną i wzorowej dokumentacji projektu.
I niestety tradycyjnie już memory footprint aplikacji wzrósł — nieznacznie, ale od 1.0.x w sumie jest to już ok. 50% większe zapotrzebowanie na pamięć. Z wersji na wersję Django pożera wciąż więcej i więcej.
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. :)
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ę.
Nie nie chodzi o żadne feedy, tylko o tzw. resident set aplikacji w Django. Obserwuję tę wartość od dłuższego czasu dla tej małej aplikacyjki i z pewnym niepokojem obserwuję jak rośnie, od ~15MB pod koniec 2007 roku przez ~17MB w okolicach wydania 1.0 do ~19MB z wersją 1.1-beta1 (przy wręcz zmniejszającym się feature secie aplikacji). A mój niepokój bierze się z mojej zadziwiająco dobrej pamięci, jak na mój podeszły wiek.
Kiedyś, w zamierzchłych czasach (okolice Slackware 9.0, czyli początek 2003 roku), była sobie fajna, mała przeglądarka WWW, która nazywała się Phoenix. Wyrosła z potrzeby istnienia po prostu przeglądarki i w swoich bebechach była Mozillą (tak się kiedyś nazywała przeglądarka wyrosła z Netscape Communicatora) bez wszystkiego tego, co nie służyło przeglądaniu stron WWW. Była szybka, miała małe wymagania i wszyscy ją pokochali od razu. Pokochali ją tak bardzo, że Mozilla Foundation postanowiła skupić na niej swoje wysiłki developerskie. W szybkim tempie (od wersji 0.4 Oceano z listopada 2002 do wersji 0.8 Royal Oak z lutego 2004) program zbliżył się apetytem na zasoby do swojego rodzica i przestał być postrzegany jako lekki i szybki. Po 7 latach istnienia Mozilla Firefox wciąż bohatersko zwalcza bloat, który był głównym powodem jego wypączkowania z projektu Mozilla Suite.
Na ile ostatnie develpmenta w Django przypominają to, co stało się z Phoeniksem? Na tyle, że nagle wszyscy pokochali Django i zaczęło ono obrastać w rzeczy, które już nie mieszczą się w legendarnych 80% (i django.contrib.gis to naprawdę mały pikuś w tym zestawie). Wygląda na to, że zadowalanie coraz większej rzeszy użytkowników daje w wyniku coraz większe zapotrzebowanie na zasoby...
Rekapitulując, ja też uważam, że martwię się na zapas. Sytuacja sama w sobie nie wygląda jeszcze na niepokojącą, niepokojący jedynie może być ten trend. Czy doprowadzi do tego, że Django przejdzie na ciemną stronę mocy, to się dopiero okaże.
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:
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... :)
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ę.
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.
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?
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.
Nie używałem zanadto, ale miałem sentyment, bo serwis był zrobiony w Django. A teraz przejęło ich Six Apart i zabawa się skończyła. To chyba normalne w interwebie, ale żal jakiś jest...
Strona 1 z 9 następna