čarabaňa.cz - ofca v síti

Web na zelené louce II.

19. září 2012  

V okamžiku, kdy se mi v Dropboxu uvelebily kompletní HTML šablony nového webu, bylo potřeba přistoupit k činu a vytvořit engine, který bude stránky pohánět. Ze studijních důvodů jsem nechtěl použít žádný existující CMS, vždyť to by pak nebyla žádná sranda.

Žádné extra požadavky při výběru jsem si nekladl, nicméně vytvořil jsem si soukromý seznam toho, co (ne)připadá v úvahu:

  • žádné nepřehledné bastlení v PHP, Perlu apod.
  • žádné exotické řešení bez dokumentace a supportu
  • žádný overkill typu Java
  • žádné speciální nároky na web hosting

Do užšího výběru se nakonec dostalo několik frameworků založených na Ruby a Pythonu - zejména proto, že jsou tyto jazyky přehledné a čitelné, zkrátka se mi převelice zamlouvají. Jako nejsympatičtější a nejvhodnější pro moje záměry mi nakonec přišlo Django.

Zakládáme a tvoříme

Vytvoření projektu a aplikace typu blog je popsané v kdejakém Django tutoriálu, nicméně stojí za to zastavit se u některých praktických záležitostí, na něž člověk během vývoje narazí.

Standardní sekvence příkazů:

django-admin startproject carabana
cd carabana
django-admin startapp blog

vytvoří základní kostru budoucího webu, ve které je aplikace úzce svázaná s konkrétním nastavením serveru (umístění, databáze atd). Proto některé zdroje doporučují umístit všechny aplikace někam bokem, čímž se stanou na projektu nezávislé.

O znovupoužití blogu někde jinde jsem neuvažoval, nicméně pro všechny případy jsem jej přesunul do podadresáře apps, kam budu moci v budoucnu přidávat další aplikace. Aby o tom projekt věděl, přidal jsem do settings.py následující:

import os
import sys

PROJECT_ROOT = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(PROJECT_ROOT, "apps"))

Dále jsem celý projekt carabana umístil do adresáře s ostatními pythoními projekty. Interpretr je najde díky upravené proměnné PYTHONPATH v .profile:

export PYTHONPATH=/home/martin/devel/python:$PYTHONPATH
export DJANGO_SETTINGS_MODULE=carabana.settings

Druhý řádek říká utilitě django-admin, které nastavení se má použít při práci s projektem: settings.py v adresáři carabana.

Po tomto naštelování už jsem mohl odkudkoli spustit vlastní testovací server pomocí django-admin runserver, otevřít v prohlížeči localhost:8000 a radovat se. Nutno ale poznamenat, že fixní nastavení DJANGO_SETTINGS_MODULE se hodí pro případ práce s jediným projektem. Jinak je lepší specifikovat jej individuálně:

unset DJANGO_SETTINGS_MODULE
django-admin runserver --settings=carabana.settings

Agáta je na data

Napsat první verzi modelů bylo jednoduché; příkaz django-admin syncdb pak zajistil vytvoření SQLite databáze v adresáři projektu, založení všech potřebných tabulek a naplnění daty. Horší už to bylo se změnami. Stačilo upravit datové typy nebo cizí klíče a synchronizace databáze už neprošla. Několikrát jsem se pokusil upravit schéma ručně v  Sqlitemanovi, případně databázi obnovit pomocí django-admin reset. Výrazně ale stoupla moje spotřeba Studentské pečeti, proto jsem toho brzy zanechal.

Na rozdíl například od Rails nemá Django žádné vestavěné řešení pro migrace DB, za neoficiální standard je ovšem považovaná knihovna South.

Nainstaloval jsem tedy south, přidal jej do INSTALLED_APPS a spustil následující:

django-admin schemamigration carabana --initial
django-admin migrate carabana

V adresáři blogu se objevil nový podadresář migrations se zápisem první migrace, která se hned také provedla. Další změny modelů a následné migrace databáze již probíhaly bez problémů:

django-admin schemamigration carabana --auto
django-admin migrate carabana

S databází souvisí ještě jedna zásadní věc: v settings.py je nastaveno výchozí připojení k lokální SQLite databázi, na produkčním serveru ale pravděpodobně budeme chtít něco jiného a zapisovat detaily připojení včetně hesla přímo sem není moc dobrý nápad (obzvlášť používáme-li pro deployment nějaký veřejný GIT server). Proto se hodí následující trik, jenž načte potřebné nastavení z jiného souboru umístěného pouze na produkčním serveru:

import socket
PRODUCTION = (socket.gethostname() == 'carabana.cz')

if PRODUCTION:
  import settings_production
else:
  DATABASES = {
    # sqlite connection for test ...
  }

Možností je spousta, kupříkladu mohou existovat samostatné settings.py určené pro produkční server. V každém případě se ale oplatí i takovéto server-only nastavení někam zálohovat.

Umísťujeme

Je dobře, že po dřívějších zmatcích se statickými soubory Django rozlišuje dva odlišné typy dat:

  • static - statický obsah webu; například ikonky, pozadí, styly nebo JavaScript
  • media - uživatelský obsah, typicky nahraný přes administraci webu; například obrázky, dokumenty nebo archivy ke stažení

Rozdíl i použití jsou pěkně popsané v samostatném tutorialu. Pro nastavení příslušných adresářů v settings.py jsem opět využil proměnnou PROJECT_ROOT:

MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media')
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
STATIC_URL = '/static/'

Kdo je tady šéf?

Django umí pro každou aplikaci automaticky vytvořit administrační rozhraní a není důvodu se tomu bránit. Po spuštění testovacího serveru je administrace dostupná na adrese localhost:8000/admin a k přihlášení slouží údaje, které jsme zadali při vytváření databáze. Editovat lze data všech modelů, které jsou zaregistrované v admin.py.

Moje aplikace obsahuje tři základní modely pro uchování článků, štítků a obrázků:

class Tag(models.Model):
  # name, slug, ...

class Picture(models.Model):
  picture = models.ImageField(upload_to = settings.MEDIA_ROOT + '/picture')
  article = models.ForeignKey('blog.Article')
  # height, width, ...

class Article(models.Model):
  # title, slug, perex, body, tags, ...

Tyto modely jsem si zaregistroval v admin.py následovně:

class TagAdmin(admin.ModelAdmin):
  ...

class PictureInline(admin.TabularInline):
  model = Picture

class ArticleAdmin(admin.ModelAdmin):
  inlines = [PictureInline,]
  ...

class PictureAdmin(admin.ModelAdmin):
  list_display = ['id', 'title', 'thumbnail', 'url', 'size', 'dimensions', 'article']

admin.site.register(Tag, TagAdmin)
admin.site.register(Article, ArticleAdmin)
admin.site.register(Picture, PictureAdmin)

Kód napovídá, že v seznamu obrázků se zobrazují náhledy (thumbnails). Kde je ale vezmeme?

Spolu s vytvořeným článkem se ukládají do databáze i přiložené obrázky, přičemž se volá metoda save(). Zde jsem dopsal kód, který s pomocí Python Imaging Library každý obrázek zmenší na šířku 300 pixelů a uloží pod novým názvem.

from PIL import Image, ImageEnhance
import PIL.ExifTags

class Picture(models.Model):
  def save(self):
    # check if a picture is assigned
    if not self.picture:
      return
    super(Picture, self).save()

    # open image and fetch info about it
    filename = self.picture.url
    basename, ext = os.path.splitext(filename)
    thumb = Image.open(filename)
    width = 300 # thumbnail width
    size = (width, int(width * ratio))
    self.width, self.height = size
    filename = '%s_%d%s' % (basename, width, ext)

    # resize, sharpen and save under different name
    thumb = thumb.resize(size, Image.ANTIALIAS)
    sharpener = ImageEnhance.Sharpness(thumb)
    thumb = sharpener.enhance(1.4)
    thumb.save(filename)

Pokud se originální obrázek jmenuje například obraz.jpg, jeho náhled se uloží do stejného adresáře jako obraz_300.jpg.

Ještě zbývá funkce, která zajistí zobrazení náhledu v administraci:

def url(self, width):
  ...
def thumbnail(self):
  return u'<img src="%s" />' % self.url()

Základ blogu je nyní hotový a příště nás čeká jeho zpřístupnění světu. Stay tuned.


comments powered by Disqus

/ Podobné články /

Groovy: řekni prostě, co chceš

My vývojáři už jsme si na nějakou tu nesrozumitelnost v kódu za ty roky zvykli. Víme, co dělá ta která funkce, a taky nám dlouho trvalo, než jsme jim vymysleli ta dlouhá a zcela nepochopitelná jména. Rovněž jsme dřív psali mnohastránkové elaboráty s popisem rozhraní, ale teď jedeme agilně, a tak pro jistotu nepíšeme vůbec nic.

Arabské klikyháky snadno a rychle

Dostal jsem poměrně exotický úkol: naučit naše automaty mluvit arabsky a hebrejsky. To by nemuselo být zas tak těžké, myslel jsem si, jenže jde o embedded zařízení optimalizovaná pro velmi nízkou spotřebu a to s sebou nese spoustu omezení.

WebExpo, kedlubny a flanelové košile

Z letošního WebExpa jsem si opět odnesl pár nových poznatků, spoustu inspirace a díky veselé hře s QR kódy a povedené sobotní party i několik nových kontaktů. Nebudu ale zdlouhavě přežvýkávat obsah jednotlivých přednášek, to už učinili mnozí jiní. Jde mi totiž o něco z mého pohledu mnohem podstatnějšího. O přetékání.

Vysílač Fredymant

Přišla doba, kdy na kopcích začaly růst stožáry a ty stožáry vysílaly a přijímaly všecko to, co si lidé na dálku vzkazovali. Stačilo se na chvilku beze slova zastavit a uslyšeli byste, jak se vzduchem nese tichounké brebentění. A v něm všechna řeč světa, jazyky domácí i cizokrajné, smích i pláč, líbezný zpěv i hudrání, hlásky jako med i velitelské staccato.