Category: Django
Salasanojen replikoiminen Djangoon
Django tallentaa normaalisti salasanat SHA1-hasheina, joissa on mukana myös 5-merkkinen salt. Mutta jos salasanoja replikoi jostain toisesta systeemistä Djangoon, ne voi tallentaa myös MD5-hasheina tai ilman salttia. Tämä tarkoittaa, että melkein mistä tahansa tyypillisestä web-pohjaisesta käyttäjärekisteristä on suhteellisen helppoa replikoida käyttäjätunnukset ja salasanat Djangoon.
Djangon itse luoma salasanakenttä näyttää tietokannassa suurin piirtein tältä:
sha1$abcde$9f228544172d611cbfb5b8c7111f51aac0a166ba
Kentässä on siis peräkkäin algoritmi (sha1), salt (abcde) ja itse hashi (9f228544172d611cbfb5b8c7111f51aac0a166ba) dollarimerkeillä eroteltuna.
Jos oletetaan, että legacy-järjestelmässä on käytetty pelkkää MD5-hashia ilman saltteja, niin siellä tietokantakenttään on tallennettu luultavasti jotain tällaista:
5f4dcc3b5aa765d61d8327deb882cf99
Tämä voidaan replikoida suoraan Djangoon kirjoittamalla salasanakenttä tietokantaan tässä muodossa:
md5$$5f4dcc3b5aa765d61d8327deb882cf99
Vanha salasana toimii nyt normaalisti sisäänkirjautumiseen. Kun salasanaa sitten joskus vaihdetaan, se korvautuu uudella SHA1-hashilla, jossa on salt mukana.
PS. Papukaijamerkki kaikille hakkereille, jotka osaavat päätellä tuosta, mitä salasanaa käytin esimerkeissä. ;-)
Markdown-tuki Djangon kommenteissa
Otin käyttöön blogissani Djangon markdown-tuen kommenteille. Testailen tässä yhteydessä niiden toimivuutta.
Linkkien esikatselu Djangossa
Leikin hieman python-webkit2png:llä ja tein kotisivujeni Shared Links -osioon esikatselun. Niistä linkeistä, joille esikatselukuva on generoitu, pitäisi kyseisen kuvan tulla näkyviin, kun vie hiiren linkin päälle.
Python-webkit2png käyttää Qt-kirjastoa ja WebKit-moottoria esikatselukuvan renderöintiin. Omat asetukseni generoivat kuvia 800x600 pikselin kokoisella virtuaaliselaimella, jossa ei ole Flashia eikä JavaScriptiä. Sen jälkeen kuvat pienennetään kokoon 400x300, mikä tekee niistä tällä hetkellä hieman kökön näköisiä.
Djangossa tämä operaatio ajetaan cronissa management commandina. Se käy läpi ne linkit, joilla ei vielä ole esikatselukuvaa, ja tallentaa uudet kuvat kunkin linkin ImageField-kenttään. Joillekin linkeille tämä epäonnistuu, koska asetin renderöinnin maksimiajaksi 60 sekuntia, ja maailmalta löytyy edelleen kovin raskaita saitteja, jotka eivät siinä ajassa kerkeä latautua kokonaan.
SQL-kyselyiden eliminoiminen Djangosta
Django-sovellus on suhteellisen helppoa rakentaa siten, että se lataa kaiken tarvittavan välimuistista, eikä tee ollenkaan SQL-tietokantahakuja. Omien datamallien lisäksi on kuitenkin huomioitava muutama Djangon sisäänrakennettu komponentti, jotka yrittävät väkisin tehdä SQL-hakuja. Ne voi eliminoida seuraavasti.
1. Välimuistittava authentication backend
Jos käyttäjä on kirjautunut sisään, Django kutsuu jokaisella hakupyynnöllä authentication backendin get_user()-funktiota. Normaalisti tämä lataa käyttäjän tiedot SQL-tietokannasta. Tietokantahaun voi välttää toteuttamalla oman backendin, joka osaa pitää tiedot välimuistissa esimerkiksi omaa kustomoitua User-mallia käyttäen.
#auth_backends.py from django.contrib.auth.backends import ModelBackend from myapp.profile.models import User class MyBackend(ModelBackend): def get_user(self, user_id): try: return User.objects.get_by_id(user_id) # uses cache except User.DoesNotExist: return None
Oma backend otetaan käyttöön asettamalla settings.py:ssä muuttuja AUTHENTICATION_BACKENDS = ('myapp.auth_backends.MyBackend',).
2. Oman User-mallin get_and_delete_messages()
Django tekee jokaisella hakupyynnöllä kirjautuneille käyttäjille vielä toisenkin SQL-haun tarkistaakseen onko auth_messages-taulussa uusia viestejä. Jos viestejä ei kaipaa omassa sovelluksessa, on helpointa toteuttaa omaan User-malliin tyhjä get_and_delete_messages()-metodi:
#models.py from django.contrib.auth.models import User as DjangoUser class User(DjangoUser): def get_and_delete_messages(self): return []
3. Site.objects.current_site() monkey-patch
Mikäli käyttää Djangon multi-site-ominaisuutta, tarvitsee usein oletussaittia, joka saadaan metodilla Site.objects.current_site(). Django pitää saittia muistissa Pythonin VM:ssä, mutta jos käytössä on useita palvelinprosesseja, kukin niistä joutuu erikseen hakemaan saitin tiedot kannasta. Tämän voi eliminoida pienellä monkey-patchilla, joka tallentaa saitin oikeaan välimuistiin:
from django.contrib.sites.models import Site from django.conf import settings from django.core.cache import cache _original_get_current = Site._default_manager.__class__.get_current def _caching_get_current(self): site = cache.get('Site:' + str(settings.SITE_ID)) if site is None: site = _original_get_current(self) cache.set('Site:' + str(settings.SITE_ID), site) return site Site._default_manager.__class__.get_current = _caching_get_current
Näillä muutoksilla Django ei enää tee lainkaan SQL-hakuja, ja koko HTTP-hakupyyntö voidaan palvella suoraan välimuistista.
Uusien kommenttien sähköposti-ilmoitukset
Koodasin tähän Django-pohjaiseen blogisysteemiini tuen sähköposti-ilmoituksille uusista kommenteista. Testailen niitä tämän artikkelin kommenteissa.
Jinja2 sucks with Django i18n
Until now, Jinja2 was my favorite template engine over Django 1.1's default templates. It provides more flexibility and performs much better.
However, if the website requires language translation support, Jinja2 sucks. Django has a nice automatic feature to collect all translations for a project by simply running:
django-admin.py makemessages -a
Then it's easy to edit the translation files of each language in locale/(language)/LC_MESSAGES/django.po and finally compile them with one command:
django-admin.py compilemessages
This is very simple and automatic, and everything is centralized in one file per language. But with Jinja2, it doesn't work any more, because Django's makemessages doesn't detect the Jinja2 templates. Apparently it's caused by Jinja2 choosing to use different template tags for translations than Django does.
There are some other problems with Jinja2 templates, too. They could pretty easily support most of Django's template tags, but for some reason choose not to. So you have to change {% blocktrans %} to {% trans %} and {% url proj.home.views.index %} into some custom tag you have to implement yourself, and so on. Close, but no cigar.
Jinja2 would be much more interesting if they were (even just 99%) backwards compatible with Django. That would allow an upgrade path from projects initially developed with Django templates and later needing a performance boost from Jinja2.
Valinnan paikka: Python & Django vs. Ruby on Rails
Olen käyttänyt viime aikoina paljon energiaa sen pohtimiseen, kumpi on parempi alusta kehittää web-sovelluksia: Python-pohjainen Django vai Ruby on Rails.
Ruby on Rails
Kypsä ja elegantti, mutta kärsii suorituskyvyn rajallisuudesta. Ruby on Railsilla on vaivatonta tehdä siistejä sovelluksia, joissa kaikilla komponenteilla on vakioitu paikkansa. Toisen kehittäjän tai alihankkijan on helppo omaksua projektin rakenne. Rubyssä jopa koodin sisennys on vakioitu aina 2 spaceen.
Python & Django
"Hakkerihenkinen", helposti muokattava, erittäin suorituskykyinen. Ongelmana jokaisen projektin erilaisuus, kun sovellusten komponentteja toteutetaan eri tavoilla ja sijoitellaan sinne tänne. Ei esimerkiksi vakioitua paikkaa layout-tason templateille, omille apukirjastoille, cronjob-skripteille ja vastaaville osioille. Yrityksen pitää itse määritellä policyt näille.
Kumpi tärkeämpää, eleganttius vai suorituskyky?
Tähän se valinta kilpistyy. Pythonilla on isoja valtteja reaalimaailmassa, kuten esimerkiksi Googlen App Engine -tuki sekä hiljattain ilmestynyt FriendFeedin/Facebookin Tornado-webbipalvelin. Ruby on Rails puolestaan tuntuu olevan vähän pienempien pelurien alusta, mutta toisaalta kuitenkin Twitter käyttää sitä.Kumman sinä valitsisit?