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

Web na zelené louce III.

3. října 2012  

Cílem dnešním dílu je dostat naše dílko na server a šířit slovo mezi prostý lid. Tak jednoduché to ale nebude. Ještě zbývá několik věcí dotáhnout do konce a ověřit si, že to všecko bude fungovat.

Blog může velice dobře posloužit jako propojovací článek digitálních ostrůvků, které si postupně zakládáme. Na jednom ostrůvku máme alba s fotkami, jinde oblíbené záložky, příspěvky nebo třeba online životopis. Předpokladem je samozřejmě to, že chceme ostatním tato propojení našich osobních dat umožnit.

Propojujeme

Bylo by však naivní si myslet, že na příchozí návštěvníky vychrlíme padesát ikonek Facebooku, Twitteru a všech dalších služeb, které používáme, a oni je nadšeně všechny navštíví a budou ostošest lajkovat. Raději si zodpovězme následující otázky:

  • Zabývá se blog určitým tématem, kterému se věnujeme i jinde? V tom případě nechejme návštěvníky v článcích namlsat ukázkami (například vložené úryvky, obrázky, videa nebo dokumenty) a pak je přesměrujme tam, kde najdou zbytek obsahu - typicky na Flickr, Vimeo, DevianART, Slideshare, GitHub nebo třeba na odborný portál, kam pravidelně přispíváme.
  • Je záměrem blogu posílit naši osobní značku, například s cílem prosadit se na trhu práce? Pokud ano, nezbývá než odhalit naši identitu, profesní background, případně kontaktní informace. Zde se nabízí propojení se službami typu LinkedIn nebo XING. A obráceně, online vizitka na about.me nebo flavors.me může odkazovat na náš blog.
  • Fungují stránky jako rozcestník do našich sociálních profilů? Pak se hodí zobrazit několik posledních příspěvků, aby se návštěvníci mohli rozhodnout, chtějí-li vidět celý profil - například na Facebooku, Twitteru, Google+, Pinterestu, Last.FM nebo Foursquare.

Toliko teorie. Podívejme se, jak tato propojení vyřešit technicky.

Na vložení cizího obsahu do článku (tzv. embedding) stačí v mnoha případech trocha HTML kódu. Podstatné je znát adresu nebo ID konkrétního obrázku, videa apod. Tu nejspíše získáme na domovské stránce obsahu.

Flickr:

<a href="http://www.flickr.com/photos/user/PICTURE_ID/" title="Picture title"><img src="PICTURE_URL" alt="Picture title"></a>

Youtube:

<iframe src="http://www.youtube.com/embed/VIDEO_ID?rel=0" allowfullscreen></iframe>

Slideshare:

<iframe src="http://www.slideshare.net/slideshow/embed_code/DOCUMENT_ID?rel=0" scrolling="no" allowfullscreen></iframe>

Sliderocket:

<iframe src="http://app.sliderocket.com:80/app/fullplayer.aspx?id=DOCUMENT_ID" scrolling="no" allowfullscreen></iframe>

Abych nemusel do článků celé tyhle sekvence znovu a znovu odněkud kopírovat, nadefinoval jsem si pro jednotlivé typy obsahu zkrácený zápis, který je během ukládání automaticky převeden na konečný HTML kód.

Tento zápis v Pythonu:

text = re.sub('<p>\[youtube (?P<id>([\w_]+))\]<\/p>', '<iframe src="http://www.youtube.com/embed/\g<id>?rel=0" allowfullscreen></iframe>', text)

převede zkrácený zápis [youtube L9R_WEN0uYU] v článku na následující kód:

<iframe src="http://www.youtube.com/embed/L9R_WEN0uYU?rel=0" allowfullscreen></iframe>

Výše uvedené příklady mají jedno společné: každé takové vložení je manuální operace, která stojí čas. Musíme otevřít stránku se zdrojovým obsahem, najít potřebné ID, upravit článek... Co kdybychom chtěli zobrazit například celý seznam naposledy přidaných obrázků nebo příspěvků?

Pro Django naštěstí existuje celá řada rozšíření, která si rozumí s veřejným API mnoha populárních služeb a umí potřebná data získávat automaticky.

Na tomto blogu naleznete dva automaticky přebírané feedy: poslední příspěvky z Twitteru a obrázky z Flickru. Data jsou získávána prostřednictvím rozšíření python-twitter a flickrapi.

Tato rozšíření fungují jako kontextové procesory, je tedy potřeba je zaregistrovat a nakonfigurovat v settings.py:

TEMPLATE_CONTEXT_PROCESSORS += (
  "blog.processors.latest_tweets",
  "blog.processors.latest_photos",
)

# fetching latest tweets from twitter
TWITTER_USER = '...'
TWITTER_TIMEOUT = 3600 # cache set to 1 hour
TWITTER_ITEMCOUNT = 3 # show recent 3 statuses

# fetching latest photos from flickr
FLICKR_USER = '...' # user NSID
FLICKR_KEY = '...' # application key
FLICKR_TIMEOUT = 3600 * 4 # cache set to 4 hours
FLICKR_ITEMCOUNT = 4 # show recent 4 photos

Flickr navíc vyžaduje NSID (unikátní user ID) a aplikační klíč. První údaj se mi podařilo zjistit až s pomocí služby Fusr, klíč jsem získal přímo na stránkách Flickru.

Po tomto nastavení jsem vytvořil v adresáři aplikace soubor processors.py pro načtení externích dat:

import twitter
import flickrapi

# get latest tweets from preconfigured twitter account (cached)
def latest_tweets(request):
tweets = cache.get('tweets')

  if settings.DEBUG:
    return { "tweets" : () } # disable this time-consuming functionality (useful for testing)

  if tweets:
    return {"tweets": tweets}

  # lazy fetch via twitter API
  tweets = twitter.Api().GetUserTimeline(settings.TWITTER_USER)[:settings.TWITTER_ITEMCOUNT]
  for t in tweets:
    t.date = datetime.strptime(t.created_at, "%a %b %d %H:%M:%S +0000 %Y")

  cache.set('tweets', tweets, settings.TWITTER_TIMEOUT)

  return {"tweets": tweets}

# get latest public photos from preconfigured flickr account (cached)
def latest_photos(request):
  photos = cache.get('photos')

  if settings.DEBUG:
    return { "photos" : () } # disable this time-consuming functionality (useful for testing)

  if photos:
    return {"photos": photos}

  # lazy fetch via flicker API
  photos = []
  flickr = flickrapi.FlickrAPI(settings.FLICKR_KEY)
  photowalk = flickr.walk(user_id = settings.FLICKR_USER, per_page = settings.FLICKR_ITEMCOUNT)

  for i in range(settings.FLICKR_ITEMCOUNT):
    photo = photowalk.next()
      if photo is not None:
        id = photo.attrib['id']
        secret = photo.attrib['secret']
        server_id = photo.attrib['server']
        farm_id = photo.attrib['farm']
        user_id = photo.attrib['owner']
        title = photo.attrib['title']

        # calculate the thumbnail URL and photo URL
        # http://www.flickr.com/services/api/misc.urls.html
        thumbnail_url = "http://farm%s.static.flickr.com/%s/%s_%s_q.jpg" % (farm_id, server_id, id, secret)
        photo_url = "http://www.flickr.com/photos/%s/%s"  % (user_id, id)
        t = thumbnail_url, photo_url, title
        photos.append(t)

  cache.set('photos', photos, settings.FLICKR_TIMEOUT)

  return {"photos": photos}

Proměnné photos a tweets jsou poté k dispozici ve všech šablonách.

Za povšimnutí stojí ještě použití cache a vrácení prázdného seznamu při nastavené proměnné DEBUG. Počáteční autorizace a stažení dat totiž může trvat celkem dlouho, a to při ladění webu zdržuje. Navíc většina služeb si dobře hlídá, aby jim externí aplikace nadmíru nezatěžovaly systém.

Všecko v cajku?

Stejně jako lidi brzy přestanou kupovat nedopečené rohlíky, přestanou i brzy chodit na polofunkční web s blikajícím titulkem under construction. Nezbývá proto než jednat - intenzivně testovat a co nejrychleji všechny nedodělky odstranit.

Django s tímto naštěstí počítá, k základnímu otestování webu mi proto stačilo toliko:

  • vytvořit soubor tests.py obsahující třídu založenou na TestCase
  • napsat testovací funkce
  • spustit test příkazem django-admin test

Následující příklad zkouší, zda jsou jednotlivé stránky webu dostupné a vytvořené s pomocí správné šablony. Víceméně jde o verifikaci urlpatterns.

from django.test import TestCase
from blog.models import *

class BlogTests(TestCase):

  def setUp(self):
    self.tag1 = Tag.objects.create(name="tag1", slug="tag1")
    self.article1 = Article.objects.create(title="Test article", slug="test-article", status = Article.ARTICLE_STATUS_PUBLIC)
    self.article1.tags.add(self.tag1)

  def test_view(self):
    # check that index page exists
    response = self.client.get('/')
    self.assertEqual(response.status_code, 200)
    self.assertTemplateUsed(response, 'index.html')

    # check that predefined blog page exists
    response = self.client.get('/blog/test-article')
    self.assertEqual(response.status_code, 200)
    self.assertTemplateUsed(response, 'view_article.html')

    # check that another blog page does not exist
    response = self.client.get('/blog/other-article')
        self.assertEqual(response.status_code, 404)

Připravit všechny potřebné objekty ve funkci setUp() mi ovšem přišlo zdlouhavé, vytvořil jsem si proto testovací data přímo v administraci webu a vyexportoval ve formě fixtures:

manage.py dumpdata blog > testdata.json

Získaný soubor jsem přesunul do podadresáře fixtures aplikace blog a upravil testovací třídu BlogTests, aby data našla:

class BlogTests(TestCase):
  fixtures = ['testdata.json']
  ...

Nasazujeme

Pokud nemáme vlastní server, bude se hodit nějaký hosting podporující Django (viz seznam na DjangoFriendly).

Vytvořený kód taky musíme na server nějak dostat. FTP už je dnes naštěstí out, zatímco GIT, Mercurial a podobné verzovací systémy jsou v kurzu, proto se zaměřím na ně.

Pro svůj web jsem si konkrétně vybral GitHub, který se mezi vývojáři těší velké oblibě a kromě veřejného GIT repozitáře zdarma nabízí i jednoduché a přehledné webové rozhraní.

Vytvořil jsem si tedy účet a nainstaloval GIT:

sudo aptitude install git
git config --global user.name "76house"
git config --global user.email "můj email"

Pomocí souborů .gitignore jsem v každém adresáři označil soubory, které nikam synchronizovat nechci - dočasné soubory, zálohy, soubory s privátními daty a podobné:

*.log
*.pot
*.pyc
*.*~
*~
*.sqlite
local_settings.py
.sass-cache

Poté jsem přidal celý projekt do repozitáře a provedl synchronizaci:

git init
git add .
git commit -m "first commit"
ssh-add -l
git remote add origin git@github.com:76house/carabana.git
git pull origin master
git push

Zde pozor na jeden chyták, který zpravidla končí hláškou Permission denied (publickey). To se stane, pokud jako origin zadáte něco jiného než git@github.com/.... Potom nezbude než nově vytvořený repozitář smazat (rm -r .git) a vytvořit znovu.

Nuže, jakmile data byla na GitHubu, přihlásil jsem se na hosting přes SSH a stáhl obsah repozitáře tam:

git clone git://github.com/76house/carabana.git
git pull

Toto čarování je naštěstí potřeba jen na začátku. Pro další commity stačí příkazy add, commit, push a pull. I to jde ale zjednodušit - s pomocí nástroje Fabric.

V adresáři projektu jsem vytvořil skript fabfile.py:

from fabric.api import *
env.hosts = ['uživatel@hosting_server']
def deploy():
  local('git push')
  run('cd ~/carabana; git pull')
  run(' ~/init/carabana restart')

Vytvořil jsem commit a příkazem fab deploy jsem skript spustil. Vše další už se provedlo automaticky, včetně přenosu změn na hosting a restartu aplikace. K dokonalosti ještě chybí poštelovat SSH agenta tak, abych pokaždé nemusel zadávat heslo.

Ještě zmíním některé chytáky, se kterými jsem se při deploymentu potýkal.

Jedním z nich je instalace knihoven a rozšíření pro Django na serveru. Nástroje jako easyinstall nebo pip jsou zvyklé instalovat do systémových adresářů, což na běžném hostingu není možné.

Protože se mi nechtělo pouštět do žádných harakiri s virtualenv, využil jsem možnosti specifikovat místo pro instalaci v domovském adresáři:

easy_install --install-dir=~/.local/lib/python2.6/site-packages flickrapi

Další věcí, na kterou člověk lehce zapomene, je nakopírovat statické soubory (nové i změněné) do STATIC_ROOT příkazem django-admin collectstatic. Jen tak je webserver najde.

Kafépauza

Čarabaňa na Githubu:

To chci vidět

Web nám tedy běží, a co dál?

Teď přijde to úplně nejdůležitější - obsah. Bez něj by nám byly sebelepší stránky k ničemu. Na možnosti tvorby obsahu, sledování návštěvnosti a trochu SEO magie se podíváme příště.


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.