You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

612 lines
44 KiB
TeX

\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}
\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}
\usepackage{graphicx}
\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
\begin{figure}[h!]
\hspace{0.05\linewidth}
% \includegraphics[width=0.9\linewidth]{images/Zadani1.png}
\end{figure}
\pagebreak
\begin{figure}[h!]
\hspace{0.05\linewidth}
% \includegraphics[width=0.9\linewidth]{images/Zadani2.png}
\end{figure}
\pagebreak
\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
\pagenumbering{arabic}
\setcounter{page}{8}
\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}
Projekt má 3-úrovňovou, resp. 4-úrovňovou administraci. Jsou zde normální uživatelé, kteří jen pasivně konzumují obsah a\,\,nemají proto přístup nikam, kromě nastavení vlastního profilu. Pak zde jsou tvůrci -- uploadeři, kteří už nějak přispěli do databáze a\,\,mají tak veřejný status přispěvatele, ale navíc se jim uvolní pouze správa vlastních příspěvků v management prostředí. Dále zde jsou už zvolení moderátoři, kteří mají práva měnit příspěvky, přidávat, upravovat nebo mazat tagy a\,\,moderovat komentáře. Nemají však možnost komentáře definitivně mazat, takže uživatel má vždy možnost obsah upravit, aby komentář zase moderátor odblokoval. Admin má pak absolutní práva. Jediné jeho restrikce jsou neoprávněné změny uživatelských nastavení včetně jeho uživatelského jména.
\begin{figure}[h!]
\includegraphics[width=\linewidth]{usecase.png}
\caption{Use-case diagram}
\label{usecasediagram}
\end{figure}
\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, aby nebylo zapotřebí tyto parametry generovat během zpracování dotazu. Jako vyžadovaná informace, sloužící pro ověření pravosti a\,\,věrohodnosti během moderace příspěvku, je zde pak zdroj -- odkaz na stránku autora, nebo jinou stránku z níž mohl uživatel čerpat.
\begin{figure}[h!]
\includegraphics[width=\linewidth]{erdiagram.png}
\caption{ER diagram}
\label{erdiagram}
\end{figure}
Jsou zde definované 3 základní kategorie přístupnosti. ,,Safe`` jako bezpečný pro všechny věkové kategorie, ,,Questionable``, pro obrázky které nevypadají úplně akceptovatelně, ale zase nepatří na 100\% do kategorie 18+ a\,\,pak ,,Explicit``, který není v žádném případě vhodný pro nezletilé osoby. Kategorie se rozlišuje subjektivně podle daného kontextu, ale vždy by se mělo dosáhnout podobného výsledku.
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í. Tagových kategorií je přesně 6. Vyjadřují typ tagu ve vztahu s obsahem příspěvku tak, aby to v budoucnu umožnilo jednodušší parsování databáze, ale i vizuálního rozlišení.
% 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("<desktop") {
...
}
\end{minted}
\caption{Příklad využití knihovny include-media}
\end{listing}
Rezponzivita je rozdělena do tří rozměrů, kde minimální šířka pro desktop je 900px a\,\,pro tablet 560px. Při přechodu z desktopového rozvržení na tabletové se obsah pravého postranního panelu přesune nad hlavní obsah a\,\,při dalším zmenšování na mobilní rozvržení se kompletně skryjí odkazy v horním navigačním panelu a\,\,jsou dostupné přes menu tlačítko vpravo nahoře. Naopak při nadměrném zvětšování je tu limit pro šířku hlavního wrapperu 1300px, takže bude obsah vždy vycentrován na očích uživatele.
Stránky jsou designované v tmavém šedivém barevném schématu, ne jen kvůli tomu, že se s tmavými prvky lépe pracuje, ale i proto, že méně svítící obrazovka také méně unavuje oči uživatele. Původní design byl postaven okolo tmavě červené, ale od toho se upustilo kvůli nevhodnosti kombinace s dalšími barevnými prvky. Barvy, včetně pár dalších parametrů, lze však kdykoli jednoduše přizpůsobit přenastavením globálních proměnných v scss souboru \verb|assets/css/main.scss|.
\begin{listing}[h]
\begin{minted}[breaklines=true,fontsize=\footnotesize]{scss}
$bg-color: #222;
$text-color: #fff;
$a-color: #bbb;
$ahover-color: #909090;
$notif-bg-color: #000c;
$notif-bg-error: #500c;
$main-wrap-padding: 8px;
$sidepanel-width: 18rem;//14rem;
$max-content-width: 1300px;
\end{minted}
\cprotect\caption{Definice proměnných v \verb|main.scss|}
\end{listing}
Hlavní indexová stránka obsahuje dynamicky se přizpůsobující výpis náhledů posledních nahraných obrázků, využívající speciálně postavený blok s\,\,\mintinline{css}|display: flex;|\break a\,\,\mintinline{css}|flex-flow: row wrap;|. Každý náhledový obrázek pak má speciální za běhu generovaný atribut \mintinline{css}|flex: * 1 *px;|\label{flexatribut} (kde \verb|*| identifikuje pixelovou šířku v poměru s minimální definovanou výškou), který zajišťuje, že se normalizuje jeho pixelová výška a\,\,budou se zobrazovat a\,\,zalamovat bez zbytečných mezer, tak jak mají.
Přes veškerou snahu strávenou na tvorbě tohoto responzivního řešení, ve snaze napodobit web DeviantArt (str. \pageref{obdobne:3}), ale s využitím čistého CSS v kontrastu s jejich JavaScript řešením, tento způsob není dokonalý. Například obrázkům, postaveným na výšku, nedává vždy tolik zobrazovacího prostoru, takže jsou efektivně mnohem menší. Z toho je také odvozené, že pokud uživatel na vstupu nahraje obrázek s příliš exotickým poměrem stran, je možné, že se layout výpisu bude chovat nepředvídatelně.
% SCREENSHOT - NÁHLED INDEX LAYOUTU
Aby se náhledy v posledním řádku na stránce neroztáhly na celou šířku, je tu pro jistotu přidaný pseudoelement \mintinline{css}|::after| zajišťující smrštění náhledů na levý bok.
\begin{listing}[h]
\begin{minted}[breaklines=true,fontsize=\footnotesize]{scss}
section.post-list {
@include media("<tablet") {
margin-left: -#{$main-wrap-padding};
margin-right: -#{$main-wrap-padding};
}
.posts {
$figure-margin: 8px;
@include media("<desktop") {
margin-left: -#{$figure-margin};
margin-right: -#{$figure-margin};
}
&::after {
content: "";
flex: 10000 0 350px;
}
> 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("<desktop") {
display: contents;
}
}
\end{minted}
\caption{Řešení responzivity na stránce příspěvku}
\end{listing}
Původní design tohoto přeskládání byl postaven poněkud složitějším způsobem. Vlastní náhled a\,\,sidebar byl ve svém flexboxu a\,\,postranní panel měl \mintinline{css}|height: 0;|, kde jeho obsah přetíkal ven. Když se nastavil patřičný margin na levé straně, tak to na desktopu umožňovalo umístit komentáře rovnou pod náhled a\,\,v mobilním rozvržení naopak dát náhledu \mintinline{css}|order: -1;| a\,\,připíchnout ho tak na vrch stránky. Nevýhoda tohoto řešení spočívala v překrývání levého panelu s patičkou stránky, takže se od tohoto designu upustilo.
Levý panel zde obsahuje kromě výpisu tagů, které definují daný příspěvek, s jejich globálním počtem výskytů i samotný popis příspěvku a\,\,malý panel pro editaci příspěvku.
Popis obsahuje výpis prakticky veškerých parametrů příspěvku, které se vyskytují v tabulce \verb|post| (str. \pageref{database}), včetně lidsky formátovaného relativního času nahrání a\,\,jmen uploadera a\,\,moderátora, který přispěvek schválil (popř. statusu schválenosti). Může se zde vyskytovat odkaz na ,,parent`` příspěvek, pokud současný nějaký má, nebo naopak jeho děti. Pod položkami jsou odkazy na originální zdrojový soubor beze změny, popřípadě i JPEG verzi, pokud byl originál ve formátu PNG.
Komentářová sekce má jednoduchý vzhled. Hlavička komentáře obsahuje uživatelské jméno autora a\,\,na pravou stranu odsazený control tooltip, kterým lze spravovat daný komentář. Pokud jste autor daného komentáře, po kliknutí na \verb|edit| odkaz se obsah komentáře nahradí za textové pole pro úpravu a\,\,v tooltipu přibydou možnosti pro aktualizaci, nebo zrušení změn. Jako moderátor se zde objeví odkazy pro zablokování/odblokování komentáře.
Pokud bude mít uživatel vypnutý JavaScript, panel úprav se zobrazí automaticky při najetí kurzorem nad blok komentáře.
Pod hlavičkou je samotný obsah komentáře. V případě zablokování komentáře moderátorem, se jeho obsah nahradí červenou poznámkou o jeho stavu zablokování. To však neznemožní jeho správu, jelikož pro autora i moderátora bude nadále obsah zprávy viditelný.
% SCREENSHOT - KOMENTÁŘ WITHOUT/WITH EDIT
Každý uživatel má svou stránku profilu. Jsou zde veřejně dostupné statistiky o jeho užívání služby, blacklist tagů a\,\,seznam posledních komentářů. Jako přihlášený uživatel lze najít svůj profil kliknutím na položku \verb|Profile| v padacím menu, dostupném po najetí kurzorem na jméno uživatele v horním panelu. Jako registrovaný uživatel máte také přístup do stránek administrace. Na to je tu položka \verb|Settings|.
Jako normální uživatel můžete manipulovat s nastavením svého profilu. Jsou zde formuláře pro úpravu vlastní biografie, změnu hesla nebo emailové adresy, nastavení preferované maximální přístupové kategorie a\,\,blacklistu tagů příspěvků zobrazovaných na hlavní stránce a\,\,samozřejmě možnost smazat veškerá svá uživatelská data.
Moderátorům v levém navigačním panelu přibydou položky s odkazy na stránky pro hromadnou správu detailů příspěvků a\,\,tagů. Pokud uživatel už přispěl nějakým obrázkem do databáze, může se přes levý panel dostat do správy příspěvků také. Zobrazí se však pouze příspěvky které sám přidal. Rozhraní pro správu je ve formě základní tabulky parametrů. Na konci každého řádku ve sloupci \verb|Manage| kliknutím na ikonku pro editování nebo jednoduše poklikáním na položku, kterou chcete upravit, lze zobrazit formulář pro daný řádek s ikonkami pro zahození a\,\,odeslání změn. Vždy je na konci také ikonka koše pro vymazání daného záznamu. Stránka správy tagů navíc umožňuje vytváření nových tagů. Responzivita u tabulky není bez použití přebytečných knihoven elegantní na implementaci, proto jsem se zdržel přílišných složitostí a\,\,aplikoval horizontální scrollování, aby alespoň tak bylo možné na mobilních platformách provádět modifikace.
Administrátorům dále přibyde možnost pro správu uživatelů.
\pagebreak
\section{Popis projektu}
Ke stavbě svého projektu jsem využil mikroframework Flask, který běží na jazyku Python. S mými dřívějšími zkušenostmi s jazykem Python a\,\,frameworkem Flask, jsem se rozhodl, že využiji příležitosti a\,\,prohloubím své znalosti na větším projektu. Flask není plnohodnotný framework typu Django nebo Laravel v případě PHP, ale umožňuje jednoduché rozšíření svých funkcí pomocí spousty pomocných balíčků vytvořených komunitou.
Jako databázový engine používám PostgreSQL. Moje volba byla postavena na vyšší jednoduchosti obsluhy a\,\,nižší velikosti/výkonové náročnosti, a\,\,tím samozřejmě odvozené vyšší bezpečnosti celého engine ve srovnání např. s MySQL/MariaDB SQL DBMS implementací.
\subsection{Frontend}
Flask má vestavěný šablonový engine Jinja2, pomocí něhož jsem schopen efektivně třídit a\,\,skládat pohledy s přidanou vrstvou bezpečnosti, jako ochrana před XSS escapováním znaků. Tímto jsem schopen vytvořit základní kostru stránky, ze které lze dále dědit formou bloků.
\begin{listing}[h]
\begin{minted}[breaklines=true,fontsize=\footnotesize]{html}
{% extends 'layout/base.html' %}
{% block content %}
<section class="sidepanel">
{% block sidebar %}{% endblock %}
</section>
<div class="important-subwrap">
{% block main_content %}{% endblock %}
</div>
{% 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|<Enter>| zvýrazněný tag přidat do vybraných. Na hlavní stránce se opakovaným stisknutím \verb|<Enter>| 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|<Tab>| a\,\,\verb|<Shift-Tab>|. 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 %}
<td>
<span>{{ caller() }}</span>
</td>
{% else %}
<td>
<span class="notedit">{{ caller() }}</span>
<span class="edit">{{ formfield() }}</span>
</td>
{% 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\break\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\break\mintinline{python}|render_pagination()| z \path{_includes.html}, které využije poskytnutého objektu\break\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\break\mintinline{python}|render_tag_input()| vezme jako argument 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 \path|config.py| v instančním adresáři repozitáře. 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í\break 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\break\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}
Příklad základní konfigurace je uložen ve skriptu \path{config.def.py}. Před nasazením projektu je však nutné tento soubor přesunout do instančního adresáře v kořenu repozitáře pod jménem \path{config.py}, jinak nebude aplikace operovat správně. V konfiguračním souboru lze najít základní parametry, jako je URI pro přístup do databáze obsahující přihlašovací údaje, adresu a\,\,název databáze nebo náhodný tajný klíč, určený pro generování bezpečných tokenů a\,\,hesel napříč aplikací. Lze zde také najít vlastní konfigurační proměnné měnící např. počet zobrazovaných náhledů na hlavní stránce, položek na stránkách managementu nebo možnost pro změnu názvu instance, který se projeví na mnoha místech aplikace.
\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<min_width*min_height or width<min_size or height<min_size:
raise ValidationError('Image has too low resolution.')
\end{minted}
\caption{Část validace nahrávaného souboru}
\end{listing}
Po úspěšné validaci se objekt \verb|Post| inicializuje, originál obrázku se uloží do složky v adresáři instance a\,\,zavoláním \mintinline{python}|Post.generate_image_files()| se opět pomocí balíčku \verb|Pillow| vygenerují všechny jeho varianty pro pozdější užití.
\subsubsection{Management}
Blueprint pro management v \path{bp/manage.py} má poněkud jinou strukturu než autorizační koncové body nebo endpoint pro upload. Pro zpřehlednění a\,\,zjednodušení manipulace jsou zde oddělené cesty pro zobrazení spravovacích tabulek dotazy GET a\,\,ty pro formuláře dotazem POST. I ve \path{forms.py} si lze povšimnout abstraktní třídy \verb|EditForm|, která definuje základní způsob manipulace s daty a\,\,z níž jednotlivé typy dědí.
\begin{listing}[h]
\begin{minted}[breaklines=true,fontsize=\footnotesize]{python}
class EditForm(CSRFForm):
id = HiddenField('ID')
create = SubmitField('Create')
edit = SubmitField('Modify')
delete = SubmitField('Delete')
def validate_id(form, field):
# if (form.edit.data or form.delete.data) and not field.data:
if not form.create.data and not field.data:
raise ValidationError('ID must be defined to be able to modify.')
\end{minted}
\cprotect\caption{Abstraktní třída formuláře \verb|EditForm|}
\end{listing}
V endpointech pro zpracování poskytnutých dat tak lze podle jména odeslaného pole typu \verb|submit| rozhodovat, která operace se provede. Kromě užitých dekorátorů\break\mintinline{python}|@loginrequired| jsou i zde použity i dekorátory \mintinline{python}|@moderatorrequired|\break a\,\,\mintinline{python}|@adminrequired|, implementované v modulu \path{models.py}. Je zde kromě bodů pro modifikaci příspěvků, tagů a\,\,uživatelů umístěný i endpoint pro moderaci komentářů, která je však zatím dostupná pouze ze stránky příslušného příspěvku.
% \subsection{Deployment}
\section{Závěr}
Tento projekt vznikl jako proof-of-concept, že lze za pomoci velice málého množství použitých frontendových knihoven napsat poměrně moderně vypadající, rychlé a spolehlivé rozhraní. Jako původní myšlenka sloužil fakt, že web v dnešní době je nezkutečně pomalý a pomalu nelze nalézt stránku, která by se zobrazila správně bez zapnutého JavaScriptu. Jako hrdý člen open-source komunity si cením svého internetového soukromí a ocením každou službu, kterou si mohu ,,zahostovat`` na vlastním hardware bez závislosti na externích poskytovatelích.
Také musím zmínit, že jsem se mnohé na tomto projektu naučil. Jde zatím o nejrozsáhlejší aplikaci, na níž jsem kdy pracoval, takže párkrát jsem poznal svou nezkušenost s Flaskem, ale i obecnou strukturizací kódu. Hodně mi dalo zabrat, než jsem se naučil převádět regulérní SQL dotazy do SQLAlchemy notace, ale nakonec jsem stejně dosáhl akceptovatelných řešení. Program pořád není dokonalý a na některých místech může být poměrně neefektivní, i přesto, že jsem se snažil o nejvyšší úroveň optimalizace.
Tyto stránky asi do budoucna už neplánuji rozšiřovat, ale díky nabraným poznatkům jsem schopen se do příště vyvarovat chybám, které jsem na designu tohoto software udělal.
\pagebreak
\addcontentsline{toc}{section}{Seznam přístupových údajů}
\section*{Seznam přístupových údajů}
% \vspace{2cm}
\begin{center}
{\renewcommand{\arraystretch}{2}%
\begin{tabular}{ |c|c|c| }
\hline
Oprávnění & Přihlašovací jméno & Heslo \\ [5pt]
\hline
Administrátor & admin & latexsucks \\
\hline
\end{tabular}}
\end{center}
\pagebreak
\addcontentsline{toc}{section}{Seznam ukázek kódu}
\renewcommand*\listoflistings{\listof{listing}{Seznam ukázek kódu}}
\listoflistings
\pagebreak
\addcontentsline{toc}{section}{Seznam obrázků}
\section*{Seznam obrázků}
\renewcommand\listfigurename{}
\vspace{-1cm}
\listoffigures
\pagebreak
\addcontentsline{toc}{section}{Seznam použité literatury}
\section*{Seznam použité literatury}
\begin{enumerate}
\item MySQL. In: Wikipedia: the free encyclopedia [online]. San Francisco (CA): Wikimedia Foundation, 2001- [cit. 2020-03-30]. Dostupné z: \url{https://cs.wikipedia.org/wiki/MySQL}
\end{enumerate}
\pagebreak
\addcontentsline{toc}{section}{Přílohy}
\section*{Přílohy}
Příloha 1 - Zdrojový kód webové stránky na CD/DVD disku
\end{document}