commit 1fdae19f0a7ddb4571bb9e02b586fbd816ca60db Author: Jan Kužílek Date: Tue Mar 31 14:25:48 2020 +0200 Initial diff --git a/dokumentace.pdf b/dokumentace.pdf new file mode 100644 index 0000000..54e55ef Binary files /dev/null and b/dokumentace.pdf differ diff --git a/dokumentace.tex b/dokumentace.tex new file mode 100644 index 0000000..b7f211a --- /dev/null +++ b/dokumentace.tex @@ -0,0 +1,529 @@ +\documentclass[12pt,a4paper]{article} +\usepackage[czech]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} + +%\usepackage[margin=2cm]{geometry} +\usepackage[top=25mm,left=35mm,right=20mm,bottom=25mm]{geometry} +\usepackage{indentfirst} +\usepackage{parskip} +\usepackage{blindtext} +\usepackage{listings} +% \renewcommand\listingscaption{Blok kódu} +\usepackage{cprotect} + +\usepackage{minted} +% \usemintedstyle{borland} +% \usemintedstyle{murphy} +\usemintedstyle{tango} + +\usepackage{titlesec} +% \setcounter{secnumdepth}{4} +\titleformat{\paragraph}{\normalfont\normalsize\bfseries}{\theparagraph}{1em}{} +\titlespacing*{\paragraph}{0pt}{2ex plus .2ex}{0ex plus .2ex} + +\usepackage{url} + +\author{Jan Kužílek} +\title{Střední průmyslová škola elektrotechnická\\ a Vyšší odborná škola Pardubice} + +\renewcommand*\contentsname{Obsah} + +\begin{document} +\pagenumbering{gobble} + +\begin{titlepage} + \begin{center} + + \large{Střední průmyslová škola elektrotechnická\\ a Vyšší odborná škola Pardubice} + + \vspace{2cm} + \large\textbf{STŘEDNÍ PRŮMYSLOVÁ ŠKOLA ELEKTROTECHNICKÁ} + + \vfill + \Large\textbf{MATURITNÍ PRÁCE - WEBOVÉ STRÁNKY} + + \vspace{.5cm} + \LARGE{YADC - Yet Another Danbooru Clone} + \end{center} + + \vfill + \large{březen 2020{\hfill}Jan Kužílek} +\end{titlepage} +\pagebreak + +\vspace*{\fill} +\textit{ + ,,Prohlašuji, že jsem maturitní práci vypracoval(a) samostatně a použil(a) jsem literárních pramenů, informací a obrázků, které cituji a uvádím v seznamu použité literatury a zdrojů informací a v seznamu použitých obrázků a neporušil jsem autorská práva. +} + +\textit{ + Souhlasím s umístěním kompletní maturitní práce nebo její části na školní internetové stránky a s použitím jejich ukázek pro výuku.`` +} + +\vspace{1cm} +\noindent +V pardubicích dne {\dotfill}{\hfill}Podpis: {\dotfill} +\pagebreak + +\pagenumbering{arabic} + +\section*{Anotace} +YaDc - Moderní ImageBoard pro moderní lidi. Sdílej a stahuj moderované obrázky a tapety s Anime/Manga tématikou. + +Klíčová slova: imageboard, anime, manga, tapety, obrázky +\pagebreak + +\section*{Anotation} +YaDc - Modern ImageBoard for modern people. Share and download curated Anime/Manga themed images and wallpapers. + +Keywords: imageboard, anime, manga, wallpapers, pictures +\pagebreak + +\tableofcontents +\pagebreak + +\addcontentsline{toc}{section}{Úvod} +\section*{Úvod} +Projekt umožňuje uživatelům sdílet obrázky ve formě příspěvků, které budou mít přiřazené kategoricky roztříděné tagy, věkovou přístupnost a další parametry. Před oficiálním akceptováním příspěvku bude každý obrázek patřičně zkontrolován moderátory a označen jako validní. Každý obrázek je volně přístupný ke stažení v původním formátu i zmenšené verzi formátu JPEG. Kromě toho může každý uživatel u příspěvku napsat vlastní komentář. + +Cílem bylo vytvořit modulární moderovaný imageboard systém s filtrovatelným obsahem jednoho zaměření, které se dá dle instance přizpůsobit, s použitelným API, které umožní stavět aplikační klienty pro propojení s ostatními sociálními službami. Backend projektu je postavený na mikroframeworku Flask, běžícím v Pythonu, čímž je možné dosáhnout vyšší modulárnosti s možností v budoucnu implementovat další užitečné funkce. + +Motivací byla převážně absence moderně vypadajícího, self-hostovatelného systému, zaměřeného na sdílení a třídění obrázků a pozadí plochy. + +Frontend je moderní, přesto minimalistický, umožňující rychlé prohlížení a stahování obrázků. Je postavený s myšlenkou jednoduchosti a nezávislosti na přebytečných knihovnách jako je Bootstrap nebo jQuery. +% FIX BOOTSTRAP +\pagebreak + +\section{Analýza obdobných webových stránek} +\subsection{Konachan.net} \label{obdobne:1} +\begin{itemize} + \item[Adresa] \url{https://konachan.net} +\end{itemize} +Konachan.net běží na projektu Moebooru\footnote{\url{https://github.com/moebooru/moebooru}}, hluboce modifikovaném forku Danbooru\footnote{\url{https://github.com/r888888888/danbooru}}. Je napsán v ne-úplně standardním frameworku Ruby on Rails, který ve výsledku jenom zhoršuje rozšiřitelnost projektu. Nabízí moderovaný systém obrazových příspěvků, filtrovatelný pomocí tagů, s komentáři u každého příspěvku, rozsáhlým API a spoustou dalších funkcí. Frontend je jednoduchý, nabízející docela pohodlné prohlížení na desktopu, avšak neresponzivní, takže na mobilních zařízeních takřka nepoužitelný. +\paragraph{Kladné stránky} +\begin{itemize} + \item klasický vzhled imageboard, bez zbytečností, které by jinak zpomalovaly stránku + \item standardizované API + \item poměrně kompletní ekosystém (vestavěná wiki, různé ,,pooly`` odebírané uživateli) + \item open-source +\end{itemize} +\paragraph{Záporné stránky} +\begin{itemize} + \item Ruby on Rails - špatná rozšiřitelnost + \item neresponzivní vzhled, tudíž nevhodný pro prohlížení na mobilních zařízeních + \item nepřehledné pro průměrného potencionálního uživatele, bez integrace se sociálními sítěmi +\end{itemize} + +\subsection{Pixiv.net} \label{obdobne:2} +\begin{itemize} + \item[Adresa] \url{https://www.pixiv.net} +\end{itemize} +Pixiv je kompletní, (převážně v Japonsku) populární platforma/sociální síť zaměřená na tvůrce ilustrací, mangy a knižních novel. Obsahuje žebříčky nejlepších, přizpůsobuje zobrazovaný obsah podle zájmů uživatele. +\paragraph{Kladné stránky} +\begin{itemize} + \item moderní, responzivní vzhled + \item pokročilé funkce moderních sociálních sítí (např. streamování tvorby v reálném čase) +\end{itemize} +\paragraph{Záporné stránky} +\begin{itemize} + \item nepřehledné rozhraní + \item bez registrace nepřístupné + \item proprietární +\end{itemize} + +\subsection{Deviantart.com} \label{obdobne:3} +\begin{itemize} + \item[Adresa] \url{https://www.deviantart.com/} +\end{itemize} +Deviantart je centralizovaná platforma pro tvůrce pro publikování své umělecké obrazové tvorby. Autor má možnost svůj obsah limitovat na určité skupiny lidí anebo ho monetizovat. +\paragraph{Kladné stránky} +\begin{itemize} + \item moderní, responzivní vzhled + \item elegantní prezentace tvorby +\end{itemize} +\paragraph{Záporné stránky} +\begin{itemize} + \item přílišná závislost na javascriptových knihovnách, které způsobují stránku nepoužitelnou v pomalejších prostředích + % \item responzivita postavená na javascriptu, nízký výkon během manipulace se stránkou + \item vysoká míra komercializace + \item proprietární +\end{itemize} + +\pagebreak +\section{Návrh projektu} + +\subsection{Cílové skupiny} +Projekt je primárně cílen na anime fanoušky a ostatní znalce moderní Japonské tvorby, kteří ocení open-source moderovanou platformu pro obrazovou tvorbu v nejvyšší/původní kvalitě. Primární využití by mělo být uchovávání/archivování tvorby do budoucna s transparentním přístupem pro kohokoli. +% Za zmínku stojí i možnost přístupu přes Danbooru API. +%Stránky jsou napsány + +\subsection{Administrace webu} + +\subsection{Databáze} \label{database} +Databáze se skládá ze 4 hlavních tabulek. Je tu tabulka \verb|user| v níž jsou uchovávány veškeré uživatelské informace, jako uživatelské jméno, email, hash hesla, jeho status oprávnění, status účtu a samozřejmě informace o jeho profilu s preferencí nejvyšší kategorie přístupnosti. Za pozornost stojí relace s tabulkou \verb|tags|, identifikující blacklist všech tagů, které uživateli nevyhovují a nechce je ve výpisu zobrazovat. + +Jako druhý nejhlavnější prvek tvoří tabulka \verb|post|, která má také navázáno nemalé množství relací. Obsahuje prakticky veškeré informace o nahraném obrázku jako např. md5 hash pro zajištění unikátnosti souboru, datový typ souboru, rozměry a velikost souboru jako užitečná informace pro uživatele, kategorii přístupnosti, tagy a zdroj/odkaz na stránku autora. Každý příspěvek má svého uploadera v podobě instance typu \verb|user| a popř. approvera - moderátora, který zodpovídá za stav příspěvku. Další relace odkazuje na ,,parent`` \verb|post|, který by měl být nějakým způsobem/kompozicí příbuzný tomuto. Tímto se dostáváme k \verb|m:n| relaci (využívající vázací tabulku \verb|post_tags|) s tabulkou \verb|tags|, která definuje každý tag, který by měl daný obrázek vystihovat. + +Tabulka \verb|tags| je jednoduchá. Je definována unikátním názvem tagu a jeho kategorií. +% Typy/kategorie tagů + +Kromě tagů je k tabulce \verb|post| i \verb|user| připojená tabulka \verb|comment|, implementující uživatelské komentáře u příspěvku. Obsahuje vlastní obsah zprávy a vlastnost deleted, pokud byl komentář zablokován moderátorem. + +\subsection{Design a responzivita} +Jedna z původních myšlenek bylo zachovat klasický imageboard formát, ale přinést trochu vylepšení v podobě responzivity a moderních designových prvků. Veškeré styly jsou zde řešené pomocí CSS, resp. SCSS stylovacího jazyka a miniaturní knihovny \verb|include-media|\footnote{\url{https://eduardoboucas.github.io/include-media/}}, starající se o elegantnější řešení rezponzivity za pomoci CSS Media Queries. + +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{scss} +$breakpoints: (tablet: 560px, desktop: 900px); +@include media(">=desktop") { + ... +} +@include media(" figure { + margin: $figure-margin; + ... + } + } +} +\end{minted} +\caption{Zjednodušený pohled do designu výpisu náhledů} +\end{listing} +V tabletovém a dále mobilním rozvržení se pomocí záporného marginu odeberou okraje u náhledů pro lepší uživatelský experience. +Výpis dále využívá \verb|srcset| HTML atribut pro poskytnutí náhledu v rozlišení adekvátním pro dané zařízení. + +V levém panelu se nachází speciální prvek pro přehled a výběr/filtrování pomocí tagů. Výpis, který po najetí myší umožní následný výběr pro filtraci, indikuje tagy, které se v tento moment vyskytují v příspěvcích na přehledové části vpravo a jejich současný unikátní počet. V horní části je vyhledávací pole, kterým lze vyhledávat a vybírat tagy, které nejsou na stránce. + +Tento prvek s tagy je přizpůsoben, aby byl znovuužitelný i na jiných místech webu (např. na stránce uploadu nebo úpravy příspěvku) jako náhrada za multiselect. Prvek je použitelný i v prostředích s vypnutým JavaScriptem, kde se zobrazí jen jako textový input obsahující mezerou oddělené hodnoty. Tagy nabízené na hlavní stránce se v tomto prostředí chovají jako jednoduché odkazy, takže se výpis při kliknutí automaticky aktualizuje. Očividně však není možné provádět hledání s nápovídáním. + +Stránka zobrazení příspěvku je neméně zajímavá - kromě postranního panelu obsahuje flexový wrapper pro JPEG sample obrázku ve vyšším rozlišení a komentářovou sekci. Při přechodu na tabletové rozvržení je díky funkci \mintinline{css}|display: contents;|, který předá své položky bloku o třídu výše, možné vložit boční panel mezi náhled obrázku a komentářovou sekci. +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{scss} +.important-subwrap { + display: flex; + flex-flow: column nowrap; + flex-grow: 1; + + @include media(" + {% block sidebar %}{% endblock %} + +
+ {% block main_content %}{% endblock %} +
+{% endblock content %} +\end{minted} +\caption{Příklad jednoduché šablony, použité pro postranní panel} +\end{listing} + +Základní šablona se nachází v souboru \path{templates/layout/base.html}. Obsahuje základní HTML strukturu včetně hlavičky, v níž se importují zabundlované (viz. Backend) CSS soubory a \verb|Font-Awesome| icon pack, který využívám na mnoha místech projektu. Tělo stránky obsahuje základní strukturu hlavního navigačního panelu, cyklus pro výpis pop-up notifikací a patičku stránky s importy opět zabundlovaných JS skriptů. Jelikož z této šablony dědí všechny stránky projektu, budou tyto prvky dostupné všude. + +V mobilním rozvržení se v hlavičce aktivuje skript \path{assets/js/base.js}, který se postará o zobrazení přetékajícího menu a o zmrazení scrollování stránky. + +Lze si také všimnout, že zde, stejně jako na mnoha stránkách frontendu, využívám pomocné funkce z modulu \path{utils.py} (str. \pageref{utilspy}), které jsou do tzv. (frontendového) kontextu naimportované v \path{__init__.py}. + +Většina stránek dědí i ze \path{layout/base_sidebar.html}, který přidává postranní panel, ale zdaleka zajímavější je \path{layout/base_sidebar_tags.html}. Je zde vidět základní struktura panelu s tagy. Většina interaktivních prvků je však definována až ve skriptu v \path{assets/js/taginput.js}. Tento skript se aplikuje na každý prvek se vstupem pro tagy. Po zadání příslušného slova do textového inputu lze kliknutím na nabízený tag v popupu nebo stisknutím klávesy \verb|| zvýrazněný tag přidat do vybraných. Na hlavní stránce se opakovaným stisknutím \verb|| potvrdí vyhledávání a po pravé straně se aktualizuje výpis náhledů příspěvků. V popupu nabízených tagů během vyhledávání se lze také pohybovat pomocí klávesových šipek nebo kombinací \verb|| a \verb||. Vyhledávání probíhá pomocí AJAX dotazů v pozadí využívající \verb|Fetch| API. Dotaz se provede vždy v určitém časovém intervalu poté, co uživatel přestane psát. + +Template \path{layout/management.html} obsahuje univerzální znovuužitelnou strukturu pro tvorbu tabulek pro management, který masivně využívá schopností Jinja2 engine. Je tu předdefinovaná kostra tabulky s formuláři pro každý řádek, které se pomocí \verb|for| cyklu generují. Dědící šablony z adresáře \path{manage/} pak mají díky block definicím možnost vložit vlastní obsah hlavičky a polí do těla tabulky. Pole jsou generována pomocí makra, \verb|genfield()|, kterému lze pomocí \verb|call| statementu vložit vlastní payload pro lepší přizpůsobení zobrazovaného pole, když není editováno (např. jako odkaz na příslušnou stránku příspěvku). +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{html} +{% macro genfield(formfield=None) %} +{% if not formfield %} + + {{ caller() }} + +{% else %} + + {{ caller() }} + {{ formfield() }} + +{% endif %} +{% endmacro %} +\end{minted} +\cprotect\caption{Makro \verb|genfield()| v \path{management.html}} +\end{listing} + +% MAIN INDEX +Hlavní template pro indexovou stránku v \path{post/index.html} obsahuje pouze jednoduchý výpis náhledů příspěvků. Adresa na zdroj obrázku je generována metodou \mintinline{python}|Post.url()|, která vždy vrátí správný formát podle \mintinline{python}|IMAGE_STORE| enumerátoru uvedeného jako argument. + +Výpis je v backendu limitován, proto je zde také import pro makro \mintinline{python}|render_pagination()| z \path{_includes.html}, které využije poskytnutého objektu \verb|Pagination| z dotazu na straně backendu. Implementovaný stránkovací mechanizmus umožňuje kompletní volnost v pohybu vpřed a vzad, poskytující i odkazy na sousední strany. + +Stránka pro zobrazení jednoho příspěvku kromě postranního výpisu jeho parametrů a jednoduchého formuláře pro úpravu, obsahuje i komentářový výpis. Opět se zde v cyklu pro každý poskytnutý komentář volá makro ze soboru \path{_includes.html}, které tentokrát vrátí kompletní blok komentáře. + +Stránka s formulářem pro upload příspěvků je zajímavá integrací dříve využitého tagového inputu, opět definovaného uvnitř \path{_includes.html}. Toto makro \mintinline{python}|render_tag_input()| jako argument vezme jednoduché \verb|wtforms| textové pole a přetvoří ho na přizpůsobený prvek, ovládaný skriptem \path{taginput.js}. + +\subsection{Backend} + +Základem celého projektu je tzv. app-factory v souboru \path|__init__.py| v kořenové složce projektu, kde se definuje chod celé Flask aplikace. + +Nejprve se zde načtou globální konfigurační proměnné ze souboru, jehož šablona \path|config.def.py| se po nasazení aplikace do provozu musí umístit do kořene instančního adresáře pod názvem \path|config.py|. Uvnitř jsou definované hodnoty jako je název instance aplikace, tajný klíč pro generování unikátních tokenů nebo URI pro přístup do zvoleného DBMS. Konfigurace je pak dostupná pod objektem \verb|config| v instanci Flasku. + +Dále se zde inicializují balíčky jako např. SQLAlchemy\footnote{\url{https://www.sqlalchemy.org/}} ORM (nebo spíše jeho adaptace pro Flask -- \verb|Flask-SQLAlchemy|\footnote{\url{https://flask-sqlalchemy.palletsprojects.com/en/2.x/}}), který využívám pro operace s databází nebo \verb|Flask-Assets|\footnote{\url{https://flask-assets.readthedocs.io/en/latest/}}, který se postará o kompilaci a minimalizaci SCSS a zabalení do jednotných souborů, a to i v případě JS. + +\subsubsection{Struktura aplikace} +\paragraph{Směrování} +Flask obsahuje vlastní směrovací systém s podporou tzv. blueprintů, jimiž jsem schopen přehledně oddělit specifické části projektu do zvláštních souborů a přidělit jim vlastní prefix v URL cestě. Uvnitř blueprintů je pak možné pomocí tzv. funkčních dekorátorů označit koncové funkce cestami nebo jinými podmínkami. + +Program je rozdělen do pěti blueprintů. + +Z uživatelské perspektivy je nejpodstatnější částí blueprint \path|bp/post.py|, kde jsou definované endpointy pro zobrazení indexu a jednotlivých příspěvků, ale i formulář pro nahrávání příspěvků, koncový bod pro uživatelské komentáře, jednoduché API pro automatické napovídání během výběru tagů k filtraci a velice jednoduché API pro aplikaci třetí strany, aby mohla přistupovat k obrázkové databázi. +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +from yadc.bp import main, post, auth, manage, user +app.register_blueprint(main.bp) +app.register_blueprint(post.bp, url_prefix='/post') +app.register_blueprint(auth.bp, url_prefix='/auth') +app.register_blueprint(manage.bp, url_prefix='/manage') +app.register_blueprint(user.bp, url_prefix='/user') +\end{minted} +\cprotect\caption{Inicializace blueprintů v \path|__init__.py|} +\end{listing} + +Neméně důležitý je pak \path|bp/main.py|, který má na starost zpracovávat dotazy na obrazová data uložená ve složce instance. Jsou zde koncové body pro pět různých formátů/velikostí každého nahraného obrázku s dynamicky parsovanou URL cestou. + +V souboru \path|bp/manage.py| jsou endpointy pro stránky managementu a jejich formuláře a \path|bp/user.py| obsahuje koncové body pro uživatelský profil s jeho stránkou nastavení. + +Blueprint \verb|bp/auth.py| se nakonec stará o přihlášení a registrace. + + +\paragraph{Formuláře} +Pro zelegantnění práce s formuláři napříč projektem využívám Python balíček \verb|wtforms|. Ten umožňuje snadnou definici univerzálních formulářů s možností validace polí a případné reportování nalezených chyb. Má také částečně integrovanou ochranu před CSRF útoky pomocí skrytého formulářového pole. +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +class LoginForm(CSRFForm): + username = StringField('Username', validators=[DataRequired()], render_kw=dict(placeholder="Username")) + password = PasswordField('Password', validators=[DataRequired()], render_kw=dict(placeholder="Password")) + remember_me = BooleanField('Remember me') + submit = SubmitField('Log In') +\end{minted} +\caption{Implementace přihlašovacího formuláře s použitými validátory} +\end{listing} + +Definici všech užitých formulářů lze nalézt v souboru \path|forms.py| + +\paragraph{Přístup do databáze} +Do databáze přistupuji pomocí SQLAlchemy Object Relation Mapperu. Mohu si tak definovat tabulky přímo jako třídy a manipulovat s nimi pomocí třídy SQLAlchemy. Pro jednoduchý SELECT všech záznamů modelu \verb|Post| mi postačí zavolat \mintinline{python}|Post.query.all()| (zkratka pro \mintinline{python}|db.session.query(Post).all()|) a vrátí se mi list instancí objektu \verb|Post|, s nímiž mohu dále manipulovat. + +Definice pomocí modelů mi umožňuje definovat vlastní metody na daném objektu pro provádění užitečných operací. Výborným příkladem je funkce generující \verb|flex| CSS atribut pro náhledy ve výpisu na hlavní stránce (na str. \pageref{flexatribut}). +% \begin{listing}[h] +% \begin{minted}[breaklines=true]{python} +% @cached_property +% def flex(self): +% return "flex: {0:.2f} 1 {0:.2f}px;".format( +% self.width/self.height*current_app.config.get( 'POST_LIST_THUMB_HEIGHT', 240)) +% \end{minted} +% \cprotect\caption{Příklad metody na modelu \verb|Post|} +% \end{listing} + +V souboru \path|models.py| se kromě modelů vyskytují i enumerace obecných hodnot jako je datový formát nahraných obrázků, úrovně oprávnění uživatelů nebo kategorie tagů. Se všemi dokáže SQLAlchemy operovat. + +\paragraph{Utility} \label{utilspy} +Pro uchování užitečných funkcí, které však nepatří na žádné specifické místo a je potřeba k nim přistupovat v globálním měřítku, slouží soubor \path{utils.py}. Jsou zde funkce napomáhající s manipulací s URL adresami jako např. nahrazování parametrů, tvorba adres pro tagy nebo jednoduché \textit{,,flashování``} errorů z formulářových endpointů, aby se poté mohly vypsat uživateli. Je zde také definovaná vypůjčená funkce \mintinline{python}|sizeof_fmt()| \footnote{\url{https://web.archive.org/web/20111010015624/http://blogmag.net/blog/read/38/Print_human_readable_file_size}}, která převádí jednotky velikosti souboru. + +\paragraph{Konfigurace} + + +\subsubsection{Hlavní stránka} +Ačkoli se to nemusí zdát, struktura endpointu pro vypisování příspěvků není jednoduchá. +Zpracovávají se zde vstupní parametry URL v podobě tagů a vynucené hodnoty věkové přístupnosti. +Pokud byl věkový rating vynucen v URL, instantně se převede na enumerátor \mintinline{python}|RATING| a vloží se pro příští návštěvy do \verb|session|, aby byl při příštích návštěvách indexu dostupný. Pokud není vynucen, tak se jako default nastaví preference přihlášeného uživatele. Pokud nejsou splněny podmínky výše, zobrazí se jen obsah s bezpečnou tématikou. Věková přístupnost se pomocí funkce \mintinline{python}|matched| na instanci enumerátoru počítá sestupně, takže není možné vypínat jednotlivé kategorie. + +Poté co se provede implicitní SELECT tagů, které má uživatel v blacklistu, se dynamicky vytvoří dotaz a pomocí speciální \mintinline{python}|paginate()| funkce se dotaz provede a převede na objekt \verb|Pagination|, v němž se dá později stránkovat a současně je efektivnější než \verb|OFFSET| a \verb|LIMIT| atributy. + +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +posts_query = Post.query +if f_tags or blacklist_tags: + posts_query = posts_query.join(Post.tags).group_by(Post.id) + if blacklist_tags: + subquery = db.session.query(Post.id) + .join(Tag.posts).filter(Tag.content.in_(blacklist_tags)).subquery() + posts_query = posts_query.filter(~Post.id.in_(subquery)) + if f_tags: + posts_query = posts_query.filter(Tag.content.in_(f_tags)) + .having(func.count(Post.id)==len(f_tags)) +posts_query = posts_query.filter(Post.rating.in_(m_ratings)) + .order_by(Post.created.desc()) +posts = posts_query.paginate(page, current_app.config.get('POSTS_PER_PAGE')) +\end{minted} +\caption{Dynamická tvorba databázového dotazu v závislosti na vstupu} +\end{listing} + +\subsubsection{Přihlášení a registrace} +Mezi jeden z nejvíce užitečných rozšíření pro Flask je \verb|Flask-Login|\footnote{\url{https://flask-login.readthedocs.io/en/latest/}}. Zajistí integraci s modelem uživatele a postará se i o session proměnné. Na mě byla jen integrace s modelem v podobě tvorby a srovnávání hesel funkcemi \mintinline{python}|Post.create_password()| a \mintinline{python}|Post.check_password()| v podobě \verb|PBKDF2| hashů. +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +def create_password(self, password): + self.pass_hash = generate_password_hash(password, salt_length=16) + +def check_password(self, password): + if self.pass_hash is None: + return False + return check_password_hash(self.pass_hash, password) + +def login(self, remember): + login_user(self, remember=remember) + self.last_login = utcnow() +\end{minted} +\cprotect\caption{Metody na třídě \verb|User| pro práci s hesly a operaci s \verb|Flask-Login|} +\end{listing} + +Jak již bylo zmíněno, endpointy pro přihlašování a registrace jsou v blueprintu \path{bp/auth.py}. Ve většině případů jde o velice podobné zpracování dotazů, kde se při \verb|POST| requestu zvaliduje odeslaný formulář a provede odpovídající akce, nebo jen vrátí zpracovaný template, v němž se nová instance formuláře vyrenderuje. + +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +@bp.route('/login', methods=['GET', 'POST']) +def login(): + if fl.current_user.is_authenticated: + return redirect(url_for('main.index')) + + form = LoginForm(request.form) + if request.method == 'POST' and form.validate(): + user = User.query.filter_by(username=form.username.data).first() + if user is None or not user.check_password(form.password.data): + flash('Invalid username or password.', category='error') + return redirect(url_for('.login')) + user.login(remember=form.remember_me.data) + db.session.commit() + + flash(f'Logged in as {user.username}.') + return redirect(nextpage()) + + flasherrors(form) + return render_template('auth/login.html', form=form) +\end{minted} +\cprotect\caption{Endpoint pro login uživatele} +\end{listing} + +Je zde implementována registrace, přihlášení, odhlášení a reset hesla. + +\subsubsection{Nahrávání příspěvků} +Upload příspěvku je, jak již je zřejmé, dělen do několika částí. Nejprve se za pomoci balíčku \verb|Flask-Login| a jeho dekorátoru \mintinline{python}|@loginrequired| zajistí, že je uživatel přihlášen, jinak bude přesměrován na stránku s přihlášením. V případě, že uživatel pomocí POST dotazu odesílá obsah formuláře, proběhne validace na straně formuláře, a při jakémkoli erroru ho přesměruje na původní stránku a notifikací ho upozorní na chybnost odeslaných dat. + +Kromě základní validace textových vstupů se zde kontroluje i stav nahraného obrázku. Nejprve se za pomoci balíčku \verb|Magic| srovná reálný mimetype souboru obrázku s podporovanými formáty, posléze se pomocí knihovny \verb|Pillow| zkontroluje integrita souboru. +Poskytnutím souboru statické metodě \mintinline{python}|Post.fileinfo()| se dále obstarají metadata o daném obrázku, na kterých se dále kontroluje unikátnost a minimální rozlišení nahrávaného příspěvku. +\begin{listing}[h] +\begin{minted}[breaklines=true,fontsize=\footnotesize]{python} +fileinfo = Post.fileinfo(file) +post = Post.query.filter_by(md5=fileinfo['md5']).first() +if post is not None: + raise ValidationError('Image with same hash already exists. Reposting is not allowed.') + +width, height = fileinfo['resolution'] +if width*height