Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Facebook Connect na Trop.cz

20.00 - 1. února 2010 | Webdesign

Po delší době přicházíme s dalším buildem Tropu, který je tentokrát ve znamení Facebook Connect. Od dnešního dne se můžete na Trop.cz přihlašovat pomocí vašeho Facebook účtu. Facebook se tak zařadil mezi Seznam, Google, Twitter a další OpenId poskytovatele přihlašování, kteří už na Tropu jsou.

Facebook Connect

Stávající uživatelé

Stávající uživatelé si mohou svůj účet propojit s Facebookem v editaci profilu. Jak na to, se můžete podívat v následujícím krátkém videu: Jak propojit účet s Facebookem.

Můžete si všimnout, že jsem potvrzoval práva na publikování. To proto, že Trop nyní umí posílat vaše recenze na zeď. Pokud při propojování s facebookem potvrdíte publikování, budou se vaše nové recenze posílat i na Facebook. Nemusíte psát, že se vám někde líbilo/nelíbilo dvakrát, stačí to napsat na Trop a vaši přátelé se o tom dozvědí.

Noví uživatelé

Noví, ještě neregistrovaní uživatelé mají nyní další možnost, jak se snadno na trop zaregistrovat a nezatěžovat se s vyplňováním profilu. Vše potřebné se vyplní z Facebooku.

Recenze

Dalších změn se dočkaly recenze. Nyní konečně můžete vaši recenzi zpětně upravit včetně tagů a rozšířených hodnocení. Textové pole pro editaci je nyní větší a úprava je mnohem pohodlnější. Další z novinek je hodnocení recenzí. Pokud se vám něčí recenze líbí, můžete jí posunout nahoru svým hlasem. Samozřejmě můžete poslat dolu i recenze, které nemají žádnou užitnou hodnotu. Každý registrovaný uživatel má právo jednou zahlasovat k recenzi někoho druhého.

Tlačítka pro editaci a hlasování najdete v pravém horním rohu po najetí na recenzi.

Twitter

Ode dneška můžete sledovat, co se o Tropu štěbetá na Twitteru. Na domovské stránce máme nyní widget s Twitterem. Podobně přibyl widget s Twitterem na profilové stránky uživatelů, kteří mají svůj Twitter spojený s Tropem. Můžete tam snadno vidět, čím se tropáci zabývají i mimo psaní zajímavých recenzí. :) Pokud chcete vědět, co štěbetá Trop, můžete ho sledovat také. Najdete zde mimo jiné i seriál tipů na zajímavá místa.

Přání a oprava bugů

Snažili jsme se splnit vaše přání, která nám můžete posílat (a posíláte) na náš uservoice. Některé z nich jsem již zmínil výše, mezi dalšími je oprava hledání, nyní opět můžete hledat i mimo Prahu. Byl vylepšen způsob jakým se posílají chybové stránky, nyní už by měly být jednotné a brzy i o něco použitelnější.

Autor: Aleš Roubíček | Zatím bez komentáře | Delicious | FriendFeed | Facebook | Linkuj!

Doménové dotazy

13.53 - 30. ledna 2010 | ASP.NET 2.0

V každé aplikaci, kde se pracuje s daty, se dostaneme do situace, kdy se nad daty potřebujeme dotazovat. Pokud přijmeme za svůj vzor Repository, můžeme se dostat do situace, podobné té v následující ukázce:

public interface IArticlesRepository {
  Article FindById(long id);
  IEnumerable<Article> FindAll(PagingInfo paging);
  IEnumerable<Article> FindByCategory(Category category, PagingInfo paging);
  IEnumerable<Article> FindByAuthor(User author, PagingInfo paging);
  IEnumerable<Article> FindByTag(string tag, PagingInfo paging);
}

Jak vidno s každým typem dotazu, rozšiřujeme rozhraní naší repository. Což porušuje Open/closed principle – jeden z pilířů Solidní architektury. Ten nám říká, že objekt by měl být otevřený k rozšíření, ale uzavřený k modifikaci. Přidáváním dalších metod ho očividně rozšiřujeme. Jak na to?

Jedním z možných řešení je zavedení dotazovacích objektů, kde využijeme kompozice k docílení lepšího návrhu. A protože jde o obecný mechanismus, můžeme ho pěkně generalizovat. Dále budu předpokládat, že pracujeme vůči datovému zdroji podporujícímu LINQ dotazování, jako je NHibernate.Linq, LINQ2SQL nebo EntityFramework.

public interface IQueryObject<T> {
  int Count(IQueryable<T> table);
  IEnumerable<T> Fetch(IQueryable<T> table);
  T FetchOne(IQueryable<T> table);
}

Dotazovací objekt má tři metody. První spočítá kolik objektů splňuje podmínky dotazu. Druhá vrátí kolekci vybraných objektů a poslední vrací jedinečný záznam. K takovému rozhraní si můžeme vytvořit bázovou třídu, která nám pomůže s rychlejším vytvářením konkrétních dotazů.

public abstract class QueryObjectBase<T> : IQueryObject<T> {

  protected QueryObjectBase<T>(PagingInfo paging) {
    Paging = paging;
  }

  public PagingInfo Paging { get; private set; }

  protected abstract IQueryable<T> CreateQuery(IQueryable<T> table);

  public virtual int Count(IQueryable<T> table) {
    return CreateQuery(table).Count();
  }

  public virtual IEnumerable<T> Fetch(IQueryable<T> table) {
    return CreateQuery(table).
      Skip(Paging.Skip).
      Take(Paging.PageSize).
      ToArray();
  }

  public virtual T FetchOne(IQueryable<T> table) {
    return CreateQuery(table).FirstOrDefault();
  }
}

Konkrétní dotazovací objekt pak ve většině případů implementuje jedinou metodu, která vrací LINQ dotaz. V případě potřeby si však konkrétní implementace může připravené metody změnit k obrazu svému. Máme zde zavedenou i konvenci stránkování, která předchází generování neomezených dotazů do databáze. A teď už nám zbývá jen zabalit to pěkně do nějakého DAO objektu.

public interface IQueryExecutor<T> {
  int Count(IQueryObject<T> query);
  IEnumerable<T> Fetch(IQueryObject<T> query);
  T FetchOne(IQueryObject<T> query);
}

DAO objekt jediný zná konkrétní implementaci datového uložiště, kterou předává do dotazovacího objektu jako IQueryable<T>. Výsledné použití v praxi může pak vypadat následovně:

var articles = _articlesDao.Fetch(new CategoryArticlesQuery(category, pagingInfo));

Kde konkrétní implementace dotazovacího objektu vypadá takto:

public class CategoryArticlesQuery : QueryObjectBase<Article> {

  public CategoryArticlesQuery(Category category, PagingInfo paging)
    : base(paging) {
    Category = category;
  }

  public Category Category { get; private set; }

  protected override IQueryble<Article> CreateQuery(IQueryable<Article> table) {
    return from article in table
           where article.Category == Category
              && article.IsPublished
           orderby article.PublishDate descending
           select article;
  }
}

Výhodou i je, že pokud potřebujeme upravit dotaz, děláme to vždy jen jednou na jednom místě.

Autor: Aleš Roubíček | 5x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Lokalizované URL v ASP.NET MVC

11.26 - 24. ledna 2010 | ASP.NET 2.0

Pokud se dostanete do situace, že budete potřebovat lokalizovat URL adresy vaší webové aplikace, máte hned několik možností, jak to řešit.

Jedno z možných řešení, které se mi ani trochu nelíbí, popsal Augi ve svém článku ASP.NET MVC – lokalizace URL. Já mám raději jednoduchá řešení, která nepotřebují hackovat systém. :)

Když jsme začali psát Trop, byl celý v angličtině. Díky konvencím, které jsem popsal v článku Pojmenované routy v ASP.NET MVC, měla naprostá většina stránek svojí vlastní routu. A protože definice routy není nic jiného než textový řetězec, přesunul jsem jej do resource souboru Routes.resx. To byl první krok.

Když jsme vymýšleli, jakým způsobem poběží jednotlivé jazykové mutace, bylo jasné, že každá mutace bude mít vlastní top-level doménu a bude to samostatná instance aplikace. To je super, protože se pak dá využít konfigurační sekce globalization, kde se nastaví jazyk prostředí konkrétní instance, podle kterého se i vybírají konkrétní resource soubory.

Česká instance pak má ve web.configu následující řádek:

<globalization culture="cs-CZ" />

Díky tomu se použijí routovací pravidla z Routes.cs.resx a URL adresy se generují česky, aniž bych musel v kódu cokoli změnit. Samozřejmě nejsou přeložená všechna routovací pravidla, spousta akcí, které jsou pouze POST, překlad nepotřebuje, protože nejsou součástí prezentační vrstvy.

Nakonec veškeré úsilí, které bylo k implementaci lokalizovaných URL potřeba, bylo v tom, embedovat definované routy do resource souboru, s čímž mi ochotně pomohl Refactor! Pro. :) Zbytek je věc konfigurace v deployovacím scriptu. Ale to už je na jiné povídání.

Autor: Aleš Roubíček | 1x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Začal jsem cvičit TDD katu

10.02 - 27. prosince 2009 | Webdesign

Co je to za blbost, říkáte si.

Každý cvičí

Když se podíváte do jakékoli oblasti lidského působení, zjistíte, že všichni mistři pravidelně cvičí, aby se zdokonalovali nebo jen udržovali formu. Proč by programátoři měli být něco extra? I programátoři musí cvičit! Jen cvičením dosáhnete požadované formy.

Katy

Pro cvičení programátorů dnes vznikají různé katy (znáte z karate nebo juda), které jsou v podobě zadání, které byste měli zvládnout naimplementovat pomocí TDD za určitý čas. Cvičení takové katy by vám mělo přejít do krve, každý stisk klávesy by měl být zcela automatický a intuitivní, podobně jako se cvičí katy v karate. Abyste to vše stihli, musíte minimalizovat saháni na myš na úplné minimum. Musíte se naučit ovládat vaše vývojové prostředí a dostat z něj maximum. Musíte se naučit dělat věci co nejjednodušeji, ale výsledek musí být uspokojivě kvalitní. Čistý kód.

Hudba

U katy byste měli mít správný rytmus, aby vše odsejpalo jak má. Proto je důležitý výběr správné hudby, která udává rytmus a podporuje správné flow vašeho cvičení. Mnozí si vybírají nějakou klasiku, třeba Rachmaninova. Já si vybral taneční klasiku Pryda – Aftermath (Original Extended Mix), která má zajímavé progresivní pasáže a navíc trvá celých 15 minut. Na jedno 30minutové cvičení mi tedy vyjde poslech přesně 2×. Můj cíl, je dát celou katu na jeden jediný poslech.

Výzva

Když si vytyčíte nějaké cíle, musíte jim něco obětovat, sáhnout si na dno svých možností. Pojďte do toho také! Pojďte cvičit a staňte se mistry! Někteří budou nuceni doinstalovat lepší nástroje, které zvýší jejich produktivitu, jiní přijdou na to, že jazyk, ve kterém píší, je zdržuje a budou muset switchnout na produktivnější. :) Pojďte se poměřovat, každé ráno po cvičení hoďte svůj výsledek na štěbeták s tagem #tddkatacz.

Co cvičit

Já osobně začínám cvičit StringCalculator katu od Roye Osherova, ale je jich víc. Jsou tu katy na počítání prvočísel, výpočet výsledku bowlingu, převod římských čísel na arabská apod. Pokud se chcete inspirovat doporučuji navštívit stránky http://katas.softwarecraftsmanship.org/.

Autor: Aleš Roubíček | 5x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Ošetření chybových stavů v ASP.NET MVC

12.50 - 22. prosince 2009 | ASP.NET 2.0

Každá aplikace se může dostat do stavu, kdy není vše, jak má být, a nastane chyba. Tyto stavy bychom neměli ignorovat, ale pečlivě ošetřovat. Jak na to se podíváme v tomto spotu.

ASP.NET obsahuje poměrně sofistikovaný mechanismus na zpracování chyb. Ukazuje celkem podrobné chybové výpisy, které jsou nám vývojářům velice užitečné. Nicméně nejsou užitečné naším uživatelům a ještě mohou leccos prozradit našim útočníkům. Proto by v produkci měly být tyto hlášky zakázané.

Konfigurace

Každá aplikace v produkci by měla mít nastavené pěkné chybové stránky, bez podrobností, co se stalo, ale s informacemi, kam dál uživatel může pokračovat. Popřípadě mu pomoci nalézt to, co hledal. K tomu slouží konfigurační element customErrors, kde můžeme nastavit režim zobrazování a jednotlivé stránky, které se mají zobrazit na určitý chybový kód.

Bohužel, systém je navržen zcela kokotsky a pokud chcete v konfiguraci nastavit pro 404 a 500 jiné stránky, jsou vraceny s kódem 200. Proto využijeme jen následující konfiguraci. Zbylá je celkem k ničemu.

<customErrors mode="RemoteOnly" />

Tím docílíme toho, že všechny requesty, které nejsou z tohoto serveru obdrží pěkné chybové stránky. Dotaz z daného serveru dostane podrobný chybový výpis, což se může hodit.

Zpracování chyb

ASP.NET MVC má v sobě základní mechanismus pro ošetřování chyb. Je jím ActionFilter HandleError, který renderuje pohled Error.aspx, pokud dojde k probublání neošetřené výjimky až do akce řadiče.

[HandleError]
public class MyController : Controller {
}

Procento chyb, takto zachytitelných, jistě není malé, ovšem není 100%. Nemusíme chodit pro příklad daleko a stačí udělat chybku v šabloně pohledu. Hned tu máme nepěknou chybovou stránku.

Takovouto chybu odchytíme až v Global.asax:

protected void Application_Error() {
#if DEBUG
  return;
#endif

  Exception exception = Server.GetLastError();

  Response.Clear();

  RouteData routeData = new RouteData();
  routeData.Values.Add("controller", "Error");

  if (IsPageNotFoundException(exception as HttpException)) {
    routeData.Values.Add("action", "NotFound");
  }
  else {
    routeData.Values.Add("action", "Error");
  }

  Server.ClearError();

  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}

Pokud máme aplikaci v debug modu (typicky u vývojáře) je vhodné nechat chyby probublat až k němu. Pokud jsme ale v releasu musíme chybu zpracovat. Na výpis chybové stránky nám poslouží ErrorController, který má dvě akce: NotFound pro 404 a Error pro 500.

public class ErrorController : Controller {

  [StatusCode(HttpStatusCode.NotFound)]
  public ActionResult NotFound() {
    return View();
  }

  [StatusCode(HttpStatusCode.InternalServerError)]
  public ActionResult Error() {
    return View();
  }
}

Díky způsobu hledání šablon v ASPX View enginu, můžeme na Error využít stejnou Error.aspx ve složce Shared jako pro chyby zachycené už na řadiči.

Neplatná referenční identita

Teď se ještě dostaneme k tomu, proč tu máme zpracování HttpException s kódem 404. Kde se vezme?

Typicky, když máme nějakou detail page, snažíme se pomocí nějakého jedinečného identifikátoru natáhnout zobrazovanou entitu. Pokud žádnou nenajdeme je vhodné vyhodit výjimku a protože jsme na úrovni webové aplikace, měla by to být HttpException s kódem 404, která říká, že hledaná stránka nebyla nalezena.

Její zpracování už máme výše vyřešeno. A to je snad pro dnešek vše. :)

Autor: Aleš Roubíček | Zatím bez komentáře | Delicious | FriendFeed | Facebook | Linkuj!

Spustili jsme Trop blog

16.22 - 17. prosince 2009 | Gryphoon

Už je tomu tak, každá správná sociální služba se má šířit všema kanálama a dnes přibyl další.

Dneska najdete Trop skoro všude:

Původně jsem chtěl blog nasadit na některý z existujících redakčních systémů. Výbíral jsem z Wordpressu, Oxite, AtomSite a nakonec nevybral ani jeden. :) Instalovat na server další databázový stroj (MySQL) jen kvůli blogu mi přišlo zbytečné – Wordpress padl. Oxite se zdá být zajímavým, protože na něm jsou postaveny prezentační weby Microsoftích konferencí a navíc je to napsaný v ASP.NET MVC (stejně jako Trop). Bohužel je to celé dost nepoužitelné a navíc chybí podpora pro správu uživatelů. Což je velký problém, kterým trpí i jinak zajímavě vypadající AtomSite.

Velké ostřílené distribuce jako SubText, BlogEngine.net nebo dasBlog se mi už zkoušet nechtělo a sáhl jsem po tom s čím dokážu dělat snadno a rychle. Ano, po stařičkém Gryphoonovi, který pohání i tento blog. Chtělo to jen pár drobných úprav a frontend, který byl jen a pouze na tomto blogu, už je nasaditelný i na jiné domény než rarouš.net. :) Možná ho o prázdninách dám nějak do kupky a vydám bastl hybrid Gryphoon 1.90. :)

Stay tuned.

Autor: Aleš Roubíček | 3x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Jak jsem potkal Twitter

05.59 - 25. listopadu 2009 | Webdesign

Znáte Twitter? Nejspíš ano, kdo by ho dnes neznal. Ale nebylo tomu vždycky tak.

Budou tomu dva roky, co @mravenec přemýšlel jak vylepšit @weblogy. „Chtělo by to nějakej chat,“ povídá. A já si myslel, k čemu, nějakej jabber gadget? Pak úplně nesouvisle k tomu, jsem ve Flocku nastartoval Twitter. Tehdy jsem to používal na broadcast. Ukázal jsem to pár kolegům, napsal jsem „jdeme vařit“ a za chvíli se zvedl @mravenec s hrnkem, následován @fromen a @macus a šli jsme navštívit naši Atlasí kuchyňku. V tomhle fungoval twitter skvěle. :)

Pak se najednou nenápadně objevil takový malý box pod zprávičkama na W3B. Bylo tam tehdy asi šest účtů. Ono jich tehdy na českém štěbetáku taky o moc víc nebylo. Ale začaly přibývat. :) A to celkem rychle. Ten box si pomalu našel cestu nahoru. Možná se to spoustě lidí nelíbilo a křičeli, ale nebyli vyslyšeni. Tehdy se objevila beta IE8 a @hassmanm absolutně vytapetoval weblogy svou on-line reportáží z její instalace a prvních dojmů. Myslím, že byl za to na nějaký čas odfollowován… :)

No a dneska je situace zcela jiná. Twitter už zná skoro každý, dokonce se o něm pořádají přednášky, jak na něj správně psát a jak na něm dělat marketing. Škoda, že jsme ty znalosti neměli už tehdy. Dneska jsme mohli bejt strašně bohatý. ;)

Autor: Aleš Roubíček | 1x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Tropněte si o sud piva

15.12 - 20. listopadu 2009 | Webdesign

Na Tropu už máme přes šestset recenzí a to díky vám! Za poslední týden se počet recenzí takřka zdvojnásobil. To mě velice těší, protože Trop tak začíná mít smysl. Líbí se mi, že začali psát i další lidé a hlavně kvalitní recenze. Moc se mi líbí recenze od Dera, jsou snad ještě lepší, než jeho titulky na HIMYM. ;) Za zmínku stojí také recenze slovodaloslovo, což je profesionální copywriterka. :) Napsala o nás taky jeden spot. Děkujeme.

Každý má svůj osobitý styl a to je super. Jen tak dál! :)

Proč psát na Trop?

Možná si říkáte, „proč bych na Trop měl psát?“ Jedním z důvodů může být to, že se procvičíte v psaní jako takovém. Navíc tim můžete někomu pomoci se rozhodnout, zda má cenu neznámé místo navštívit. A v neposlední řadě probíhá soutěž! Máte ještě deset dní na to se umístit na čelních pozicích a vyhrát některou z cen. Osobně pochybuju o tom, že ještě někdo dokáže dohnat voytu, který žebříček táhne a jde si pro svůj sud piva, ale nemožné to je! Třeba mu ten sud ještě někdo vyfoukne. ;)

Takže Tropujte!

Související

Autor: Aleš Roubíček | 2x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Mikroformáty jako datový model stránky

12.47 - 18. listopadu 2009 | Webdesign

Dříve, když jsem psal webové aplikace, kde bylo potřeba hodně JavaScriptu, aby se dosáhlo pohodlné práce, duplikoval jsem data. Ano, je to tak a stydím se za to. Většina dat, které se na stránce vypisovaly, byla duplicitní. Jednou byly v HTML a podruhé v JSON, aby se s nimi dalo snadno pracovat v JavaScriptu. Jenže všichni víme, že duplikování je zlo. :)

Tropu jsem se tomuto nešvaru snažil vyhnout. A tak je většina dat ve stránkách označena mikroformáty. Výhodou mikroformátů je, že jsou snadno strojově zpracovatené. Tudíž i JavaScriptem. Pokud se třeba podíváte na detail místa (např. The Pub na Staroměstské) uvidíte mapku, kde je bublina s kontaktními údaji místa. Tato data nejsou v žádném JSON ve stránce, ani se nestahují AJAXovým dotazem. Script jednoduše vezme data z mikroformátu hCard včetně geo souřadnic, kde se má bublina vykreslit.

Podobný model je aplikován na různých místech Tropu.

Mikroformáty vs. RDFa

Myslím, že v tomto případě se i ukazuje, že mikroformáty jsou lepší než RDFa. Protože parsovat mikroformáty pomocí jQuery je mnohem snažší a přehlednější. RDFa je možná flexibilnější v tom, kolik gramatik dokáže pojmout, ale to je i jeho zásadní slabinou. Mikroformáty definují jednoduché formáty, které staví na jednoduchém modelu HTML. Existují nástroje, díky nimž lze s nimi snadno pracovat.

Mikroformáty a ASP.NET MVC

Úžasnou novinkou ASP.NET MVC 2 bude extenze Html.DisplayFor, kde se vytvoří jediná šablona třeba s vizitkou uživatele a pak jí mužu používat všude, aniž bych někde na něco zapomněl nebo něco nabořil. Dnes musím mít několik extenzí pro různé typy dat, kde mám navíc kód přímo v extenzi. Sice je jednoduchý, ale čím méně HTML v C# kódu, tím lépe. (On to tedy není přímo HTML kód, ale jeho objektová reprezentace.)

Práce s mikroformáty tak bude mnohem jednodušší a ucelenější. :)

Autor: Aleš Roubíček | 2x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Odpalování akcí měnících data odkazem

09.29 - 16. listopadu 2009 | Webdesign

V RESTové architektuře webu byste neměli odpalovat akce, které mění data, pomocí odkazů. Měli byste tak činit pomocí sloves HTTP protokolu (PUT, POST, DELETE). Přejdu fakt, že dnešní prohlížeče znají jen sloveso POST a ostatní musíte simulovat pomocí XHR. Stejně se dostanete do situací, kdy potřebujete nějakou akci měnící data odpalít odkazem.

Abych pro příklad nemusel chodit daleko, vezmu si hlasování k akcím na Tropu. U každé akce se můžete vyjádřit k tomu, zda na akci půjdete, možná se ukážete nebo nepůjdete. Pokud je uživatel nepřihlášený, vidí pod názvem akce tři ikonky s počtem hlasů. Po přihlášení se stanou aktivními a lze s nimi hlasovat. Původně byla tato „tlačítka“ pouhými odkazy na akci, která hlasování zpracovávala. Jenže po tom, co jsem pouštěl některé spidery, začaly přibývat hlasy.

Tím se dostáváme k praktickému důsledku porušení doporučení z prvního odstavce. Problém jsem nakonec vyřešil následujícím vzorem: Do stránky jsem přidal formuláře, které bezpečným způsobem odpalují akce měnící data. Každý formulář má své id. Odkazy, které původně vedly na akce měnící data, nyní odkazují na id jednotlivých formulářů a mají třídu form-submit. Jednoduchý jQuery kód pak projde tyto odkazy a na click jim naváže submit odkazovaného formuláře a ten pak schová.

Stránka vypadá pořád stejně, chová se stejně, ale dělá to jinak. Pokud nemá uživatel povolený JavaScript, tak se po kliknutí na ikonku dostane na správný formulář, který musí odeslat stiskem tlačítka submit. Funkčnost tak zůstává přístupná i bez JavaScriptu. Zároveň nehrozí, že by došlo k nechtěné aktivaci akce nějakým spiderem, který si ošahává odkazy na stránce.

Snad se bude hodit i vám.

Autor: Aleš Roubíček | 5x komentováno | Delicious | FriendFeed | Facebook | Linkuj!