Administrace blogu – pokračujeme ve vývoji s Nette Framework

Napsal dne 28.6.2013 v Nette

Edit: Článek byl aktualizován na verzi 2.2.1

Dostal jsem pár žádostí o rozšíření našeho jednoduchého blogu o administraci, respektive o přidávání nových článků, editaci a mazání těch starších a přihlášení. Našel jsem si opět chvilku čas, a tak se pokusím o jednoduchou administraci, která bude tyto požadavky splňovat. Zároveň nebude obsahovat téměř žádnou grafiku, to už si každý upraví dle libosti, včetně dalších funkcionalit, kreativitě se meze nekladou.

Úvod

Administraci bychom mohli samozřejmě začlenit přímo do front-endové (uživatelské) části, nicméně nebudeme žádní amatéři a vyčleníme si pro administraci (back-end) vlastní sekci, ke které se dostaneme pouze po úspěšném přihlášení. Protože jsem s dalším vývojem blogu nepočítal, tak jsem si předem nevytvořil správnou adresářovou architekturu a strukturu našeho předchozího blogu budeme muset překopat. Šlo by to také naházet všechno do jedné složky a tím si vše zjednodušit, my však půjdeme to složitější cestou abychom si ukázali některé další prvky frameworku Nette a navíc nám pak ulehčí adresářovou orientaci, když budeme chtít něco upravit.

Adresářová struktura

Jak jsem již zmínil, si předem vytvořit vhodnou adresářovou strukturu. Tuto fázi nepodceňujte a před samotným návrhem aplikace si vhodně naplánujte co kam přijde, protože dělat úpravy adresářů v již rozdělaném projektu není nic příjemného a akorát si přiděláte další problémy. Můj příklad tak berte jako špatný vzor, protože jsem s možným rozšířením měl počítat a musím nyní předělávat celou aplikaci.

Pro jednoduchou orientaci si naši aplikaci rozdělíme na dva hlavní moduly – Admin a Front. Ty budou obsahovat vlastní presentery a templaty, naopak zbytek aplikace bude pro oba společná (včetně modelů). Hierarchii struktury si ale také můžete zvolit dle sebe, nic Vám v tom nebrání. Naše struktura bude tedy vypadat takto:

hierarchie

Abyste této struktury dosáhli, máte dvě možnosti. Buď si projekt stáhnete z Githubu a nebudete již nic řešit, anebo pokud vycházíte z našeho předchozího blogu, pak je úprava následovná. Do složky app si vytvořte složku FrontModule, do které zkopírujte stávajícísložky presenters a templates (samozřejmě i s jejich obsahem). Po zkopírování je potřeba, aby jste ve všech presenterech odstranili:

use namespace App\Presenters;

use Nette,
    App\Model;

a místo toho připsali jmenný prostor:

namespace FrontModule;

Taktéž je zároveň třeba aby jste odstranili z config.neon defaultní mapping, takže sekce „application“ bude vypadat ve výsledku pouze takto:

	application:
		errorPresenter: Error

Díky tomu, že jsme použili jmenný prostor, jsme vytvořili náš malý modul. To ale také znamená, že pokud chceme využívat cokoliv z venčí (např. repositáře nebo v budoucnu administraci), tak musíme k tomu přistupovat jako k jinému modulu, resp. k jinému jmennému prostoru. Abychom mohli využívat služby „z venčí“, musíme před všechny volané třídy přidat zpětné lomítko. Upravit tak musíme dědění v BasePresenteru:

abstract class BasePresenter extends \Nette\Application\UI\Presenter

a v HomepagePresenteru:

 \Nette\Application\UI 
    /** @var \PostsRepository */
    private $postsRepository;

    /** @var \CommentsRepository */
    private $commentsRepository;

    function __construct(\CommentsRepository $commentsRepository, \PostsRepository $postsRepository) {
        $this->commentsRepository = $commentsRepository;
        $this->postsRepository = $postsRepository;
    }

Poté si opět ve složce app vytvořte složku AdminModule a do ní vytvořte složky presenters a templates (pouze vytvořte, již nic nekopírujte!). Tím máme adresářovou hierarchii hotovou.

Dodatek: v operačních systémech typu Windows se používá termín složka, naopak v systémech typu Linux se používá termín adresář. Jedná se však o synonyma a v návodu budu používat oba výrazy, tak jen aby nedošlo k nějakému nedorozumění.

Routování

Adresáře jsme vyřešili, nicméně je potřeba trochu upravit routy, aby nám aplikace fungovala správně. Routování je obousměrné překládání mezi URL a akcí presenteru. Obousměrné znamená, že z URL lze odvodit akci presenteru, ale také obráceně k akci vygenerovat odpovídající URL. Díky routování můžeme určovat vzhled URL adresy (pomocí masek) nebo třeba jaké mají být výchozí hodnoty parametrů.

Routy nastavujeme v souboru RouterFactory, který se nachází v adresáři router. Zde si vytvoříme vlastní kolekci routy a tak tohle smažeme:

		$router[] = new Route('index.php', 'Homepage:default', Route::ONE_WAY);
		$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');

Teď vytvoříme routy prvně pro administraci:

        // Admin
        $router[] = new Route('admin/<presenter>/<action>/<id>', array(
            'module' => 'Admin',
            'presenter' => 'Admin',
            'action' => 'default',
            'id' => NULL,
        ));

Asi by to stálo trochu za objasnění. V prvním parametru naší instance uvádíme jak budou cesty v administraci vypadat – tedy prvně bude vždy admin/ a poté bude následovat název presenteru, jeho akce a případné id. V druhém parametru (poli) uvádíme výchozí hodnoty, tedy bude se jednat o modul Admin (Nette samo doplní název *Module, v našem případě tak bude výsledek vypadat jako AdminModule), presenter Admin (opět bude doplněn *Presenter), akce default a id nebudeme zatím potřebovat.

Následuje vytvoření rout pro front-endovou část:

        // Front
        $router[] = new Route('<presenter>/<action>/<id>', array(
            'module' => 'Front',
            'presenter' => 'Homepage',
            'action' => 'default',
            'id' => NULL,
        ));

Myslím, že není třeba vysvětlovat, funguje na stejném principu jako routy pro administraci.

Administrační část

Jako první si ve složce presenters (v AdminModule) vytvoříme soubor BasePresenter, který bude předkem všech ostatních presenterů a který nám bude v budoucnu zajišťovat odhlašování, nyní si však vystačíme s tímto:

namespace AdminModule;

class BasePresenter extends \Nette\Application\UI\Presenter {

}

Z našich rout pak vyplývá, že výchozím presenterem bude soubor AdminPresenter. Vytvoříme si ho tedy a samozřejmě nesmíme stále zapomenout, že se jedná o jmenný prostor AdminModule. Abychom nezaháleli, hned si tam vstříkneme pomocí konstruktoru všechny závislosti a výsledný presenter bude tak vypadat takto:

namespace AdminModule;

class AdminPresenter extends BasePresenter {

    /** @var \PostsRepository */
    private $postsRepository;

    /** @var \CommentsRepository */
    private $commentsRepository;

    function __construct(\PostsRepository $postsRepository, \CommentsRepository $commentsRepository) {
        $this->postsRepository = $postsRepository;
        $this->commentsRepository = $commentsRepository;
    }
}

V routách je dále uvedená jako výchozí akce default, je tak potřeba ji prvně vykreslit. Toho dosáhneme vytvořením metody v AdminPresenteru:

    public function renderDefault() {
        $this->template->posts = $this->postsRepository->fetchAll();
    }

Určitě Vám neunikl řádek, o kterém jsem se předtím nezmínil. Ten nám do šablony (kterou si vytvoříme zanedlouho) vloží proměnnou posts, která bude obsahovat všechny řádky, které jsme získali pomocí metody fetchAll(), nacházející se v repozitáři postsRepository.

Pokud bychom se nyní pokusili aplikaci s adresou /admin spustit, vyhodilo by nám to chybu, že neexistuje akce s název default. Abychom mohli pokračovat dále, je potřeba vytvořit ve složce templates (stále v AdminModule) další složku s název admin, která značí, že se bude jednat o akce presenteru Admin. Do této složky si vytvoříme šablonu default.latte. Nyní by aplikace měla fungovat správně, avšak nic nevykresluje. Proto využijeme naší proměnné $posts a vše si necháme přehledně zobrazit:

{block content}
<div id="posts">
    {if count($posts)}
        <h3>Seznam článků</h3>
        <table border="0">
            {foreach $posts as $post}
                <div class="post">
                    <tr>
                        <td>
                            <a n:href=":Front:Homepage:single $post['id']">{$post['title']}</a> (
                            <small>Přidáno {$post['date']|date}</small>
                            )
                        </td>
                        <td>
                            <a n:href="Admin:single $post->id">Upravit</a> | <a n:href="deleteArticle! $post->id">Smazat</a>
                        </td>
                    </tr>
                </div>
            {/foreach}
        </table>
    {else}
        Zatím nebyl napsán žádný článek.
    {/if}
</div>

Administrace by v této fázi měla vypadat přibližně takto:

admin

Úprava článků

Pěkné, už nám to vypisuje naše články, jenže to by nebyla administrace, kdyby se s nimi nedali nijak pracovat. Nyní bychom chtěli naše články upravovat, takže jdeme na to!

Prvně si vytvoříme metodu v repozitáři PostsRepository, která nám zajistí aktualizaci nových údajů článku:

    public function updateArticle($id, $data) {
        $this->connection->table('posts')
            ->where('id = ?', $id)
            ->update($data);
    }

Tím jsme zatím v repozitáři skončili a můžeme se přesunout do našeho známého AdminPresenteru. Prvně je potřeba naši novou akci nějak vykreslit. Pojmenujeme ji například jako single a vykreslíme si ji:

    public function renderSingle($id) {
    }

Metoda přijímá jako parametr id daného článku. Protože s ní nijak nepotřebujeme pracovat, může zůstat prázdná (je však důležité, aby existovala!). Nyní když pokusíme v naší administraci kliknout na nějaký článek, odkáže nás to na adresu /admin/admin/single/cisloID. Jenže zároveň se objeví chyba, že šablona single neexistuje. To napravíme vytvořením šablony single.latte v souboru templates/admin/ (stejně jako předchozí šablona default). Teď již vše funguje, ale opět se nic nevykresluje.

Tvorba formuláře

Nette framework nabízí jednoduchou možnost vytvoření formuláře a zároveň nemusíme řešit útoky typu XSS nebo CSRF. Abychom mohli využívat formuláře, je potřeba do úvodu (až po namespace) AdminPresenteru vložit

use \Nette\Application\UI\Form;

Poté si vytvoříme samotnou metodu vykreslující formulář společně s dalšími prvky, jako textové pole, text areu nebo tlačítko odeslat:

    protected function createComponentEditArticleForm() {
        $post = $this->postsRepository->fetchSingle($this->getParam('id'));

        $form = new Form();
        $form->addText("title", "Název článku: ")
            ->setDefaultValue($post->title)
            ->setRequired('Vyplňte název článku!');
        $form->addTextArea("body", "Text článku: ")
            ->setDefaultValue($post->body)
            ->setRequired('Vyplňte text článku!');
        $form->addHidden("post_id")
            ->setDefaultValue($this->getParam('id'));
        $form->addSubmit("send", "Odeslat");
        $form->onSuccess[] = $this->editArticleFormSucceeded;

        return $form;
    }

Opět se zde chvilku zastavíme a popíšeme si, co jsme to vlastně napsali. Prvně si do proměnné $post uložíme výsledek dotazu, díky něhož jsme dostali všechny potřebné údaje o článku. Dále si vytvoříme instanci třídy Form a přidáme si všechny elementy, které potřebujeme. Metoda setDefaultValue nám zajišťuje, že se vloží jako defaultní hodnoty které chceme a metoda setRequired se vykoná v případě, že pole nebylo vyplněno a my vyžadujeme, aby nebylo prázdné. Řádek

$form->onSuccess[] = $this->editArticleFormSucceeded;

pak nám zaručuje, že v případě, že vše proběhlo v pořádku, zavolá se metoda editArticleFormSucceeded, kterou si hned vytvoříme:

    public function editArticleFormSucceeded(Form $form) {
        $values = $form->getValues();
        $post = $values->post_id;
        unset($values->post_id);
        $this->postsRepository->updateArticle($post, $values);
        $this->flashMessage('Článek byl aktualizován', 'info');
        $this->redirect("Admin:");
    }

Probereme si teď každý řádek metody jednotlivě. Jako první získáme pomocí metody getValues() všechny hodnoty odeslané formulářem. Poté si do proměnné $post uložíme id článku a následně si ho ve třetím řádku unsetneme z pole, aby nám nevadilo při aktualizaci dat v databázi metodou updateArticle. Nakonec už jenom zobrazíme flashmessage a přesměrujeme na úvodní stránku administrace.

Teď se vrátíme zpět k šabloně single.latte v souboru templates/admin/, kde si vložíme jednoduché automatické vykreslení našeho formuláře:

{block content}
<a n:href="Admin:">&lt;&lt; home </a>

{control editArticleForm}

Výsledek pak bude vypadat takto:
edit

Smazání článku

Úpravu již máme hotovou a teď jdeme na smazání. Jako první si opět vytvoříme metodu v repozitáři PostsRepository, která nám článek smaže:

    public function deleteArticle($id) {
        $this->connection->table('posts')
            ->where('id = ?', $id)
            ->delete();
    }

A poté přidáme metodu do repozitáře CommentsRepository, aby nám smazala veškeré komentáře (aby vám nekřičelo SQL že mažete článek, na který mají „závislost“ jeho komentáře):

    public function deleteComments($id) {
        $this->connection->table('comments')
            ->where('post_id = ?', $id)
            ->delete();
    }

Poté si do AdminPresenteru přidáme metodu, zpracující signál:

    public function handleDeleteArticle($id) {
        $this->commentsRepository->deleteComments($id);
        $this->postsRepository->deleteArticle($id);
        $this->flashMessage('Článek byl smazán', 'info');
        $this->redirect('Admin:');
    }

Tím už jsme hotovi a můžeme nyní články mazat dle libosti.

Přidávání článků

Předposlední částí je přidávání článků. To bude velmi podobné jako úprava již stávajících článků. Jako tradičně, si vytvoříme metodu v repozitáři PostsRepository metodu pro přidání článku:

    public function addArticle($data) {
        $this->connection->table('posts')
            ->insert($data);
    }

Následuje továrnička na tvorbu formuláře a jeho zpracování v AdminPresenteru:

    protected function createComponentAddArticleForm() {
        $form = new Form();

        $form->addText("title", "Název článku: ")
            ->setRequired('Vyplňte název článku!');
        $form->addTextArea("body", "Text článku: ")
            ->setRequired('Vyplňte text článku!');
        $form->addSubmit("send", "Odeslat");
        $form->onSuccess[] = $this->addArticleFormSucceeded;

        return $form;
    }

    public function addArticleFormSucceeded(Form $form) {
        $values = $form->getValues();
        $values->date = new \Nette\DateTime();
        $this->postsRepository->addArticle($values);
        $this->flashMessage('Článek byl přidán', 'info');
        $this->redirect("Admin:");
    }

Formulář máme, jeho zpracování máme, ale je potřeba ještě vytvořit novou šablonu addArticle.latte a nechat si v ní vykreslit formulář:

{block content}
<a n:href="Admin:">&lt;&lt; home </a>

{control addArticleForm}

Nakonec abychom mohli k této šabloně přistupovat, doplníme šablonu default.latte o nový odkaz:

<h5><a n:href="Admin:addArticle">Přidat nový článek</a></h5>

Nyní máme kompletní administraci článků, ale nemůžeme přece kohokoliv nechat, aby si s administrací hrál jak se mu zlíbí. Vrhneme se teď tak na naší poslední část, a to přihlašování.

Přihlašování

Abychom si to zbytečně nekomplikovali, vytvoříme si náš uživatelský účet i s heslem přímo v configu. Otevřeme si ho teda (config/config.neon) a přidáme si novou podsekci Nette:

	nette:
		security:
			users:
				admin: 12345

Jako uživatelské jméno jsem zvolil admin a heslo 12345, zvolit si můžete ale cokoliv jiného. Důležité je za dvojtečkou (resp. před heslem) udělat jednu mezeru, jinak vám bude křičet laděnka. Dále pokud vycházíte ze sandboxu, je třeba smazat službu authenticator (stále v configu, sekce services), kterou nyní nebudeme potřebovat (určitě se ale vyplatí si ji prohlédnout):

- App\Model\UserManager

U tohoto souboru bych se na chvíli pozastavil. Jak jste si určitě všimli, jedná se o soubor typu NEON, který má za úkol konfigurovat parametry v Nette. Velmi důležité je si dát pozor na odsazování. Formát NEON akceptuje odsazení tabulátorem i mezerami, ale v celém souboru musí být použito stejné odsazení. V připraveném config.neon jsou použity tabulátory. Nette se v případě problémů ozve. Problém s odsazením může také nastat v některých IDE, mě se třeba osvědčil klasický poznámkový blog, kdy si provedu potřebné úpravy a pak vše zkopíruji zpět do configu (ale je třeba stále dodržovat stejné odsazení!). I když se může formát NEON na první pohled zdát poněkud chaotický či magický, není třeba v něm hledat nějakou vědu. Více se o něm dozvíte v dokumentaci Nette a se syntaxí si můžete pohrát na stránce ne-on.org.

Poprvé v tomto návodu se budeme zabývat front-endem (uživatelskou částí). Již v sandboxu existuje SignPresenter a adresář Sign se šablonou in.latte. Toho využijeme a proto do šablony @layout.latte, která je předkem všech dalších šablon, přidáme odkaz pro přihlášení (nejlépe pod vypsáním flashmassage a nad samotným obsahem):

<a n:href="Sign:in">Přihlásit se do administrace</a><br>

Přejdeme do SignPresenteru a klidně můžeme celý obsah smazat a udělat si to po svém. Protože budeme přidávat formulář, je třeba pod namespace FrontModule opět přidat potřebný „formulářový“ modul:

use \Nette\Application\UI;

A poté si opět vytvoříme další formulář pro přihlášení:

	protected function createComponentSignInForm()
	{
		$form = new UI\Form;
		$form->addText('username', 'Jméno:')
			->setRequired('Vyplňte Vaší přezdívku.');
		$form->addPassword('password', 'Heslo:')
			->setRequired('Vyplňte Vaše heslo.');
		$form->addSubmit('send', 'Přihlásit se');

		// call method signInFormSucceeded() on success
		$form->onSuccess[] = $this->signInFormSucceeded;
		return $form;
	}

Poté následuje metoda signInFormSucceeded, která nám zajistí buď správné přihlášení a přesměrování do administrace nebo vyhodí chybu:

    public function signInFormSucceeded($form) {
        $values = $form->getValues();

        try {
            $this->getUser()->login($values->username, $values->password);
            $this->flashMessage('Byl jste přihlášen', 'info');
            $this->redirect(':Admin:Admin:');

        } catch (\Nette\Security\AuthenticationException $e) {
            $form->addError($e->getMessage());
        }
    }

Tak a hotovo, nyní se můžeme již přihlásit a vše funguje správně. Jednoduché co? Tak teď ještě jenom vyřešit odhlašování.

Odhlášení

Teď se přesuneme opět do administrace. Abychom měli možnost odhlásit se kdekoliv nezávisle na aktuální šabloně, využijeme stejně jako ve front-endu společného předka, kterého si vytvoříme s názvem například @layoutAdmin.latte (zavináč na začátku je důležitý). Do něj můžeme umístit to samé, jako je v @layout.latte, ale hlavně náš signální požadavek na odhlášení:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="description" content="">
    <meta name="robots" content="{$robots}" n:ifset="$robots">

    <title>{block title|striptags|upper}Nette Application Skeleton{/block}</title>

    <link rel="stylesheet" media="screen,projection,tv" href="{$basePath}/css/screen.css">
    <link rel="stylesheet" media="print" href="{$basePath}/css/print.css">
    <link rel="shortcut icon" href="{$basePath}/favicon.ico">
    {block head}{/block}
</head>

<body>
<script> document.documentElement.className+=' js' </script>

<div n:foreach="$flashes as $flash" class="flash {$flash->type}">{$flash->message}</div>

<a n:href="logOut!">Odhlásit se!</a>
{include #content}

{block scripts}
    <script src="{$basePath}/js/jquery.js"></script>
    <script src="{$basePath}/js/netteForms.js"></script>
    <script src="{$basePath}/js/main.js"></script>
{/block}
</body>
</html>

Poté využijeme BasePresenter, který jsme vytvořili hned na začátku naší administrace. Jako první oznámíme v metoda BeforeRender(), že chceme jako výchozí layout používat náš @layoutAdmin:

    public function beforeRender() {
        $this->setLayout('layoutAdmin');
    }

A nakonec metodu pro odhlášení a přesměrování zpět do front-endu:

    public function handleLogOut() {
        $this->user->logout();
        $this->flashMessage('Byl jste odhlášen', 'info');
        $this->redirect(':Front:Homepage:default');

    }

Závěr

Uf, bylo to vyčerpávající sepsat to v co nejsrozumitelnější podobě, nicméně je možné, že jsem někde udělal nějakou chybu či nepopsal něco dostatečně srozumitelně. Pokud by jste teda měli nějaké námitky, návrhy či jiné dotazy, klidně napište do komentáře a já se pokusím vše vyřešit.

Nakonec opět nabízím možnost celého blogu co jsme dnes tvořili si buď prohlédnout na Githubu, nebo stáhnout zde.

Štítky:
  • Pingback: Návod jak vytvořit blog v Nette 2.0.10 | Knowledge Is Free()

  • Edmund

    Ahoj, zkusil jsem si i druhou část a zdárně se dostal k funkčnímu blogu. Místy to ale byl boj. Doporučil bych doplnit/opravit toto:
    – Na začátku v textu o úpravách struktury z minulého dílu a o potřebě doplnit namespace chybí zmínka o potřebě doplnit zpětná lomítka do:
    > class BasePresenter extends \Nette\Application\UI\Presenter
    > public function inject(\PostsRepository $postsRepository, \CommentsRepository $commentsRepository)
    Na tom jsem se zasekl asi nejdéle :-(
    – chybí, co má být v single.latte
    – při zadávání admin: 123456 do config.neon jsem zapomněl na mezeru za dvojtečkou a to byl další hodinový zásek :-) Možná by nebylo od věci na to upozornit nebo odkázat na popis syntaxe souboru config.neon

    • Jerry Klimčík

      Děkuji za Vaše připomínky a omlouvám se, že jste v tomhle ohledu musel být ten první pionýr. Bohužel jsem některé věci bral až příliš za automatiku a něco jsem opomenul. Vše jsem již upravil a doplnil o odkazy týkající se konfigurace a syntaxe.

  • Jirka

    Moc mi to pomohlo, díky.

  • Terka

    Ahoj, díky, pomohlo mi to.

  • NEXT

    Tyto 2 články mi pomohli dostat se do Nette, moc děkuji.

  • Jakub

    Zdravím,
    zkoušel jsem vytvořit aplikaci podle návodu, ale v Nette 2.1.2 (aktuální verze). Bohužel jsem se zasekl u definice routy. Zřejmě tam mezi verzemi budou nějaké změny. Hledal jsem v dokumentaci, ale nic jsem nenašel.

    Zápis mám přesně jako v tutoriálu, ale dostávám hlášku:

    Cannot load presenter ‚Front:Homepage‘, class ‚App\FrontModule\Presenters\HomepagePresenter‘ was not found in ‚E:\+++Software+++\EasyPHP\data\localweb\projects\Nette\vitani_prvaku\app/FrontModule/presenters/HomepagePresenter.php‘

    Přitom tam ten presenter je a Nette který je v tomto tutoriálu funguje.

    Dokáže někdo poradit?

    • Jerry Klimčík

      Zdravím, bohužel jsem si všiml že Nette prodělal několik velkých změn. Budu tedy návod postupně aktualizovat pro novou verzi, každopádně toho času teď moc není, takže pokud opravdu spěcháte a chcete využít tento návod, doporučuji si stáhnout starší verzi Nette zde, popřípadě se zeptat na fóru Nette.

      • Jakub

        Díky, už jsem na to přišel. V RouterFactory stačí smazat default mapping, případně si ho přepsat na vlastní.

  • Jakub

    Ještě jsem objevil, že tam vůbec není ošetřen přístup na ostatní stránky. Tzn. dá se dostat do administrace, pokud znám odkaz a nejsem přihlášen. Chtělo by to něco jako if(!$this->user->isLogged) a redirect :).

    • Jerry Klimčík

      Ano, přesně takhle by to šlo vyřešit. Jedná se pouze o velmi jednoduchý návod, tudíž další ošetření už nechávám na ostatních aby si vše přizpůsobili k obrazu svému :-)

  • Michal Biel

    Díky za pěkný návod, jenom mě v něm pořád bije do očí jedna věc – aby jste 😀 píšeme abyste

  • ydenda

    Ahoj,
    moc pěkný návod. Použil jsem ho pro rozdělení vlastní aplikace na front a backend.
    Co mě teď aktuálně trápí je přihlašování. Používám Nette 2.3 a chtěl jsem využít SignFormFactory ze sandboxu, nicméně když si v Sign presenteru vyrobím komponentu z továrny, vyhlásí mě laděnka, že žádný FrontModule\SignFormFactory nevidí. Zadal jsem tedy absolutní cestu \Nette\App\Forms\SignFormFactory, ale bez úspěchu. Mohl bys mě prosím postrčit, co dělám špatně?

    Respektive důvod proč chci použít tu továrnu je, že chci mít přihlašování do Front i Admin modulu a rád bych se vyhnul psání jednoho formuláře dvakrát.

    Díky za odpověď,
    Ydenda