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

Support ratuje wszystko

Dzisiaj pojawił się problem z pocztą w jednym z moich serwisów na djangohosting.ch. Część serwerów pocztowych odrzucało przesyłki, do części transfer był w dziwny sposób opóźniony, a dla równowagi część serwerów nie zgłaszała najmniejszych problemów. W końcu udało mi się dowiedzieć, że to coś związane z SPF, jaki ma moja domena. I rzeczywiście, po usunięciu rekordu TXT z danymi SPF maile zaczęły docierać do wszystkich. Co prawda w kilku serwisach od razu lądowały w folderze spam, ale lepsze to, niż zwrotka. Zgłosiłem problem wczesnym popołudniem, ale nie biłem piany, bo serwis jest w trakcie wewnętrznych testów, więc jest to właśnie ten czas, kiedy takie rzeczy mają prawo się ujawnić. Właściwie do niczego nie doszliśmy, ale gdy wróciłem do domu i z ciekawości spojrzałem, jak wygląda automatycznie tworzony rekord SPF, to coś mi się nie zgadzało: podobno miał wyglądać inaczej... O 20:29 zgłosiłem nieprawidłowość (jako komentarz do zamkniętego już ticketu, z prośbą o wyjaśnienie, czy te wpisy są ekwiwalentne), a o 20:34 sprawa była załatwiona, instalator domen rzeczywiście dodawał nieprawidłowy wpis SPF.

Fantastyczny support to jest coś, co rekompensuje wszystkie błędy MySQLdb, z którymi muszę się tam zmagać. Gdyby ktoś się zastanawiał, gdzie tanio postawić aplikacyjkę w Django, to polecam djangohosting.ch.

OS X łże jak Windows

To tyle, jeżeli chodzi o tzw. żywotność baterii. Najpierw mówią, że baterii wystarczy na 4:57. Myślisz, oh, yeah, prawie 5 godzin!. Potem się okazuje, że te prawie 5 godzin stopniało do nieco ponad 4 godzin, ale jak MacBook wyłącza się z powodu wyczerpania baterii po 3:40, to już wiesz, że OS X łże jak Windows.

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.

Cron nie odpala zadań z crontaba? Mamy cię!

Jeżeli po aktualizacji Ubuntu do 8.04 (lub po przeniesieniu systemu np. z FreeBSD na 8.04) Twój crond przestał zauważać zadania wpisane w crontabie (po prostu jakby ich tam nie było...), to sprawdź, jak wygląda Twoja tabela zadań i czy ma to coś:

$ crontab -l | grep MAILTO
MAILTO=""

Bez tego nie pojedziesz.

Na wykrycie tego straciłem kilka godzin, więc pomyślałem sobie, że zapiszę tutaj, gdyby kogoś zgnębił podobny problem.

Szatan atakuje ponownie

Od For fun and profit