${Pylons} 0.9.7 Przewodnik część 2



Podobne dokumenty
Modele danych walidacja widoki zorientowane na model

Podstawy JavaScript ćwiczenia

Laboratorium 7 Blog: dodawanie i edycja wpisów

ASP.NET MVC. Podstawy. Zaawansowane programowanie internetowe Instrukcja nr 3

Kurs rozszerzony języka Python

Laboratorium 1 Wprowadzenie do PHP

Ajax. 1. Wprowadzenie. 2. Aplikacja serwerowa

Na początku utworzymy formularz w czystym języku HTML i przetestujemy go za pomocą przeglądarki WWW.

Tworzenie kampanii mailowych. Tworzenie kampanii mailowych.

Tworzenie witryn internetowych PHP/Java. (mgr inż. Marek Downar)

APLIKACJA SHAREPOINT

FAQ Systemu EKOS. 1. Jakie są wymagania techniczne dla stanowiska wprowadzania ocen?

Facelets ViewHandler

${Pylons} Przewodnik część 3

Wprowadzenie do Internetu Zajęcia 5

Języki programowania wysokiego poziomu. PHP cz.3. Formularze

Tworzenie stron internetowych z wykorzystaniem HTM5, JavaScript, CSS3 i jquery. Łukasz Bartczuk

Informatyka II. Laboratorium Aplikacja okienkowa

SZYBKI START. Tworzenie nowego połączenia w celu zaszyfrowania/odszyfrowania danych lub tekstu 2. Szyfrowanie/odszyfrowanie danych 4

Baza danych do przechowywania użytkowników

Dokument hipertekstowy

Programowanie w Ruby

Laboratorium 6 Tworzenie bloga w Zend Framework

Dokumentacja smsapi wersja 1.4

IIIIIIIIIIIIIIIMMIMMIII

Podstawy technologii WWW

Miejski System Zarządzania - Katowicka Infrastruktura Informacji Przestrzennej

AKTYWNY SAMORZĄD. Instrukcja użytkownika.

Przewodnik... Budowanie listy Odbiorców

Aplikacje internetowe - laboratorium

Budowanie listy Odbiorców

Programy LeftHand - Obsługa plików JPK. Luty 2017

Programowanie internetowe

Referat z przedmiotu Technologie Internetowe SPIS TREŚCI

Dokumentacja panelu Klienta

Instrukcja importu przesyłek. z Menedżera Sprzedaży do aplikacji Webklient

Instrukcja użytkownika

Instrukcja użytkownika

Dokumentacja panelu Klienta

WINDOWS Instalacja serwera WWW na systemie Windows XP, 7, 8.

Bazy Danych i Usługi Sieciowe

Moduł Notatki Systemu Obsługi Zamówień Publicznych UTP-Bydgoszcz Instrukcja postępowania do 1000 Euro

Przewodnik użytkownika (instrukcja) AutoMagicTest

Aplikacje WWW - laboratorium

Zbieranie kontaktów.

2 Podstawy tworzenia stron internetowych

PHP: bloki kodu, tablice, obiekty i formularze

Panel Administracyjny (wersja beta)

1. Rejestracja 2. Logowanie 3. Zgłaszanie nowego wniosku projektowego

Podstawy technologii WWW

Aplikacje webowe w obliczu ataków internetowych na przykładzie CodeIgniter Framework

Program dla praktyki lekarskiej

Aplikacje WWW - laboratorium


AUDYT DOSTĘPNOŚCI STRONY INTERNETOWEJ

Formularze. Instrukcja MailSolutions Zarządzanie Panelem Administratora Aplikacja zgodna wymogami RODO

Backend Administratora

Publikowanie strony WWW

Aplikacje WWW - laboratorium

Dokumentacja Skryptu Mapy ver.1.1

E-faktura PKP Energetyka

Dane - pobieranie, przekazywanie i przechowywanie. dr Beata Kuźmińska-Sołśnia

Aplikacje internetowe

Zajęcia 4 - Wprowadzenie do Javascript

INSTRUKCJA UŻYTKOWNIKA GENERATORA WNIOSKÓW O DOFINANSOWANIE DLA WNIOSKODAWCÓW

Formularze HTML. dr Radosław Matusik. radmat

INSTRUKCJA zakładania konta w Społecznoś ci CEO

Dokumentacja systemu NTP rekrut. Autor: Sławomir Miller

Instrukcja składania wniosku o dofinansowanie w systemie informatycznym IP na potrzeby konkursu nr 1/1.1.1/2015

1. REJESTRACJA W INTERIM24.PL PANEL UŻYTKOWNIKA ZAWARTOŚĆ UZUPEŁNIENIE PROFILU... 9

SYSTEM ZARZĄDZANIA DANYMI OSOBOWYMI - INSTRUKCJA UŻYTKOWNIKA

1. Zaloguj się do systemu UONET+ jako administrator i uruchom moduł Administrowanie.

Komponent Formularz. Rys. 1. Strona programu Joomla - Rys. 2. Instalacja komponentu

REFERAT O PRACY DYPLOMOWEJ

Instrukcja użytkownika

wfirma (Moduł Magento) v Strona 1 z 12

app/ - folder zawiera pliki konfiguracyjne dla całej aplikacji src/ - folder zawiera cały kod PHP aplikacji

Budowa aplikacji ASP.NET współpracującej z bazą dany do obsługi przesyłania wiadomości


Kurier DPD dla Subiekt GT

Instalacja systemu zarządzania treścią (CMS): Joomla

Instrukcja do programu Przypominacz 1.5

Angular, cz. II. Tworzenie serwisów Web 2.0. dr inż. Robert Perliński

MVC w praktyce tworzymy system artykułów. cz. 2

Wyrażenie wewnątrz nawiasów jest atomem (rozpatrujemy je jako całość).

4. Podstawowa konfiguracja

Instrukcja zarządzania kontem jednostki samorządu terytorialnego w serwisie internetowym

KUP KSIĄŻKĘ NA: PRZYKŁADOWY ROZDZIAŁ KOMUNIKATY DLA UŻYTKOWNIKA

Programowanie w Sieci Internet Python - c. d. Kraków, 28 listopada 2014 r. mgr Piotr Rytko Wydział Matematyki i Informatyki

Ciekawym rozwiązaniem służącym do obsługi zdarzeń dla kilku przycisków w ramach jednej aktywności może być następujący kod:

Dokumentacja SMS przez FTP

Katalog książek cz. 3: Web Service

FORMULARZE Formularz ma formę ankiety, którą można wypełnić na stronie. Taki formularz może być np. przesłany pocztą elektroniczną .

Podręcznik Sprzedającego. Portal aukcyjny

CMS Admin instrukcja administratora

Użytkowniku programu FINKA, przekazujemy E-book, który omawia najważniejsze kwestie dotyczące generowania i wysyłania JPK.

Programy LeftHand - Obsługa plików JPK. Wrzesień 2016

Przykładowa integracja systemu Transferuj.pl

Instrukcja obsługi programu Klient SMS v.1.0

Transkrypt:

Tworzymy formularz. ${Pylons} 0.9.7 Przewodnik część 2 Spróbujmy stworzyć w naszym projekcie formularz do rejestracji użytkowników. Będzie on posiadał cztery pola: login, hasło, hasło_potwierdzone oraz e mail. W tym celu dodajmy nową akcję kontrolera users: class UsersController(BaseController): def index(self): # Return a rendered template # return render('/template.mako') # or, Return a response c.name = "d'arc" return render('users/index.xhtml') def register(self): return render('users/register.xhtml') a następnie widok users/register.xhtml: <form action="create" method="post"> <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label><input type="text" name="login" id="loginfield" /></div> <div><label for="emailfield">e mail: </label><input type="text" name="email" id="emailfield" /></div> <div><label for="passwordfield">hasło: </label><input type="password" name="password" id="passwordfield" /></div> <div><label for="password_confirmationfield">potwierdź hasło: </label><input type="password" name="password_confirmation" id="password_confirmationfield" /></div> </fieldset> <div><input type="submit" name="create" value="zarejestruj" /></div> </form> Przyjrzyjmy się kodowi HTML. Pierwszym elementem, na który warto zwrócić uwagę to parametr action. Jest ona po prostu nazwą akcji. Dane z forumlarza zostaną wysłane do metody create. Nie stworzyliśmy jeszcze niczego o tej nazwie, jednak na to przyjdzie czas później. Dane będą przesyłane metodą post, choćby dlatego, aby informacje takie jak hasło nie pojawiły się przypadkiem w pasku adresu. Ale czy Pylons może nam jakoś pomóc przy tworzeniu kodu formularza? Oczywiście :) W skład Jan Koprowski 1

Pylons wchodzi między innymi WebHelpers 1. Jest to zbiór funkcji, które, między innymi, próbują skróci czas jaki poświęcamy na klepanie kodu HTML starając się wygenerować go za nas. Po kolei. Ładujemy helpery. Aby móc korzystać z helperów musimy na starcie je załadować. W tym celu wyedtujmy plik lib/helpers.py i dopiszmy na końcu linijkę: """Helper functions Consists of functions to typically be used within templates, but also available to Controllers. This module is available to both as 'h'. """ # Import helpers as desired, or define your own, ie: # from webhelpers.html.tags import checkbox, password from webhelpers.html.tags import form, text, password, submit, end_form Dzięki temu w naszych szablonach będą dostępne funkcje potrzebne nam do wygenerowania formularzy. Czas na edycję naszego widoku. Zrobimy to krok po kroku. Przechodzimy na helpery. Na starcie wygenerujmy sam tag <form>: ${h.form('/create', method='post')} <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label><input type="text" name="login" id="loginfield" /></div> <div><label for="emailfield">e mail: </label><input type="text" name="email" id="emailfield" /></div> <div><label for="passwordfield">hasło: </label><input type="pasword" name="password" id="passwordfield" /></div> <div><label for="password_confirmationfield">potwierdź hasło: </label><input type="password" name="password_confirmation" id="password_confirmationfield" /></div> </fieldset> <div><input type="submit" name="create" value="zarejestruj" /></div> </form> Powyższa linijka generuje dokładnie taki sam kod HTML jak ten pisany przez nas wcześniej. Dużo krócej prawda? W porządku. Teraz pozamieniajmy pola tekstowe: 1 http://docs.pylonshq.com/thirdparty/webhelpers/index.html Jan Koprowski 2

${h.form('/create', method='post')} <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label>$ {h.text("login", id="loginfield")}</div> <div><label for="emailfield">e mail: </label>$ {h.text("email", id="emailfield")}</div> <div><label for="passwordfield">hasło: </label><input type="password" name="password" id="passwordfield" /></div> <div><label for="password_confirmationfield">potwierdź hasło: </label><input type="password" name="password_confirmation" id="password_confirmationfield" /></div> </fieldset> <div><input type="submit" name="create" value="zarejestruj" /></div> </form> Ilość kodu, jaki musimy napisać aby nasz formularz ukazał się światu systematycznie maleje :) O to chodzi. Teraz czas na pola haseł: ${h.form('/create', method='post')} <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label>$ {h.text("login", id="loginfield")}</div> <div><label for="emailfield">e mail: </label>$ {h.text("email", id="emailfield")}</div> <div><label for="passwordfield">hasło: </label>$ {h.password("password", id="passwordfield")}</div> <div><label for="password_confirmationfield">potwierdź hasło: </label>${h.password("password_confirmation", id="password_confirmationfield")}</div> </fieldset> <div><input type="submit" name="create" value="zarejestruj" /></div> </form> Na koniec wygenerujemy jeszcze przycisk submit oraz znacznik końca formularza. Jan Koprowski 3

${h.form('/create', method='post')} <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label>$ {h.text("login", id="loginfield")}</div> <div><label for="emailfield">e mail: </label>$ {h.text("email", id="emailfield")}</div> <div><label for="passwordfield">hasło: </label>$ {h.password("password", id="passwordfield")}</div> <div><label for="password_confirmationfield">potwierdź hasło: </label>${h.password("password_confirmation", id="password_confirmationfield")}</div> </fieldset> <div>${h.submit("create", "Zarejestruj")}</div> ${h.end_form()} Jak rozumieć istnienie helperów? No właśnie. Przecież to samo możemy napisać ręcznie. Można na to spojrzeć z kilku stron. Po pierwsze: dobrze napisane helpery dają nam pewność, że generowany przez nie kod będzie zgodny ze standardami, na przykład W3C. Jeżeli zależy nam na standardach i chcemy ograniczyć czas poświęcony na sesje spędzone z walidatorem można to postrzegać jako jakiś zysk. Helpery tworzą też swoistą warstwę abstrackji. Jeżeli powiedzmy za kilka lat tworzenie stron internetowych w niczym nie będzie przypominało dzisiejszego kodu HTML. Wtedy wystarczy podmienić w całym Framerowku to co generują helpery, wgrać nową jego wersję i nasza strona spełnia już najnowsze standardy (o ile oczywiście 100% portalu zostało wygenerowane co jest tylko teoretycznie możliwe). Czy więc zawsze należy używać helperów? Nie. Należy ich używać z rozsądkiem. Dobrym przykładem "zbędnego" użycia jest ostatnia linijka naszego formularza. ${h.end_form()} jest znacznie dłuższe niż </form>. Istnieją frameworki, w których nie znajdziesz funkcji odpowiedzialnej za wygenerowanie "nieopłacalnych" z punktu widzenia długości kodu tagów: przykładowo </form>. Wyświetlamy zawartość formularza. Aby wyświetlić zawartość formularza stworzymy w kontrolerze users akcję create a następnie wywołamy szablon, do którego przekażemy wysłane wartości. Do dzieła. Na start wyedytujmy plik controllers/users.py: Jan Koprowski 4

class UsersController(BaseController): def index(self): # Return a rendered template # return render('/template.mako') # or, Return a response c.name = "d'arc" return render('users/index.xhtml') def register(self): return render('users/register.xhtml') def create(self): c.login = request.post['login'] c.email = request.post['email'] c.password = request.post['password'] c.password_confirmation = request.post['password_confirmation'] return render('users/create.xhtml') Zatrzymajmy się na chwilkę i przeanalizujmy co się tutaj tak właściwie dzieje. Po pierwsze tworzymy cztery zmienne globalne, które za moment wyświetlimy w naszym widoku. Nowym elementem jest niewątpliwie request.post. Obiekt request zawiera między innymi słownik POST, z którego pobieramy dane wysłane wcześniej formularzem. Na koniec renderujemy widok, który za moment stworzymy. Osoby znające troszkę lepiej Pylons mogą zastanawiać się dlaczego nie użyto metody request.params. W naszym formularzu jawnie zarządaliśmy wysyłani danych metodą POST. Dane pobierane za pomocą request.params mogłyby zostać równie dobrze przesłane metodą GET. Warto o tym pamiętać. Mając to na uwadze możemy upewnić się skąd pochodzą informacje, konkretyzując z jakiego źródła je pobieramy. Użycie request.post, jest w tym momencie celowe. Czas na widok. Stwórz plik users/create.xhtml. <strong>login</strong>: ${c.login}<br /> <strong>e mail</strong>: ${c.email}<br /> <strong>hasło</strong>: ${c.password}<br /> <strong>potwierdzenie hasła</strong>: $ {c.password_confirmation} Teraz po wysłaniu formularza zobaczymy wpisane przez nas dane. Jan Koprowski 5

Walidacja formularza. Nigdy nie polegaj wyłącznie na walidacji po stronie klienta opartej o JavaScript czy AJAX. Technologie te mogą znacznie uprzyjemnić i ułatwić wprowadzenie poprawnych danych użytkownikowi, "na bieżąco" informując go o nieprawidłowościach, bez potrzeby wysyłani formularza jednak metody stosowane po stronie klienta w żaden sposób nie gwarantują wysłania formularzem poprawnych wartości. Wniosek? Zawsze sprawdzaj dane po stronie serwera: mechanizmy client side traktuj jako dodatek ale nigdy im nie ufaj. Warto zauważyć, że szalenie użyteczne jest tutaj wykorzystanie zapytań AJAX dla reguł walidacji. Dzięki temu mamy szansę przestrzegać zasady DRY i nie implementować ponownie tych samych reguł walidacji w innym języku programowania, po stronie klienta. Daje nam to automatyczną koniunkcję iż formularz zwalidowany po stronie użytkownika poprawnie przejdzie testy po stronie serwera. Zazwyczaj formularze rejestracyjne wyposażone są w mechanizm walidacji, który wymusza na nas aby hasła były zgodne, login nie był za krótki, e mail posiadał formę e maila i wiele innych. Spróbujemy wyposażyć w takie mechanizmy również naszą stronę. Pylons potrafi korzystać, ze stworzonego specjalnie w tym celu mechanizmu zwanego FormEncode 2. Spróbujmy użyć go w naszym przypadku. Zacznijmy od zdefiniowania kryteriów. Umówmy się, że login nie będzie mógł być krótszy niż cztery znaki i dłuższy niż 25, E mail będzie musiał przypominać e mail, a hasła będą musiały być nie krótsze niż 8 znaków i zgodne ze sobą. Wszystkie pola będą wymagane. Ok. Walidacja naszego formularza będzie polegała na stworzeniu klasy opisującej wcześniej założone przez nas obostrzenia i dodaniu dekoratora do metody mającej odebrać dane z naszego widoku. Skoro wiemy już jak wszystko ma się zachowywać czas na przejście do konkretu. Zaczniemy od stworzenia klasy. W tym celu dodajmy do naszego projektu plik model/form.py: import formencode class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True Powyższy kod to nic innego jak zaimportowanie na starcie formencode, z którego będziemy korzystać i stworzenie klasy. Dziedziczy po klasie Schema. Dodaliśmy na początek dwa atrybuty. Otóż chodzi o to, że w naszym formularzu istnieją pola, które nie powinny być poddane walidacji. Jest to na przykład nasz przycisk submit. Ustawienie wartości allow_extra_fields = True oraz filter_extra_fields = True pozwoli ominąć te, dla których nie ustawimy żadnych restrykcji. Na starcie zacznijmy od minimalnej długości hasła. Ma być nie krótsze niż 8 znaków: 2 http://formencode.org/ Jan Koprowski 6

import formencode class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True password = formencode.validators.minlength(8, not_empty=true) Proszę bardzo. Powyższa linjka to wywołanie walidatora MinLength dla pola password. Pierwszym parametrem jest liczba osiem. Dodatkowo dodaliśmy parametr not_empty=true, który wywoła odpowiedni błąd gdy będziemy chcieli zostawić puste pole. Jak łatwo się domyślić skoro istnieje MinLength to istnieje również MaxLength. Mamy więc komplet potrzebny do walidacji pola loginu. Jak jedna połączyć dwa warunki. Służy do tego metoda All: import formencode class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True login = formencode.all(formencode.validators.minlength(4, not_empty=true), formencode.validators.maxlength(25, not_empty=true)) password = formencode.validators.minlength(8, not_empty=true) Co tutaj się wydarzyło? Skorzystaliśmy z metody All aby spiąć klamrą nasze dwa warunki. Pierwszy z nich mówi o tym, że minimalna długość loginu jest równa 4 i pole nie może być puste. Drugi, że maksymalna długość loginu jest równa 25 i pole nie może być puste. Oczywiście dublowanie warunku not_empty=true nie jest konieczne. W porządku. Mamy login, mamy sprawdzanie długości hasła. Brakuje jeszcze sprawdzania poprawności e mail a i sprawdzanie zgodności powtórzonego hasła. Zacznijmy od adresu skrzynki pocztowej: import formencode class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True login = formencode.all(formencode.validators.minlength(4, not_empty=true), formencode.validators.maxlength(25, not_empty=true)) email = formencode.validators.email(not_empty=true) password = formencode.validators.minlength(8, not_empty=true) Tym razem skorzystaliśmy z walidatora Email, i dodaliśmy znanym nam już skądinąd warunek iż Jan Koprowski 7

pole jest wymagane. Teraz czas na potwierdzenie poprawności hasła. Po pierwsze atrybuty, dla których nie stworzyliśmy regułek nie są dostępne w naszej klasie (dwie pierwsze wartości), tak więc nasza klasa nie wie jeszcze nic o polu password_confirmation. Łatwo temu zaradzić tworząc jaką neutralną zasadę. Powiedzmy, że hasło powtórzone będzie musiało być stringiemi: class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True login = formencode.all(formencode.validators.minlength(4, not_empty=true), formencode.validators.maxlength(25, not_empty=true)) email = formencode.validators.email(not_empty=true) password = formencode.validators.minlength(8, not_empty=true) password_confirmation = formencode.validators.string() Fantastycznie. Skoro już zaradziliśmy naszemu "problemowi: i nasza klasa wie już o polu password_confirmation możemy sprawdzić czy zawiera to samo co password. Skorzystamy tutaj z metody w walidacji o nazwie FieldsMatch, który przyjmuje jako parametry nazwy atrybutów, które mają być ze sobą zgodne. Aby jednak porównać pola muszą one przejść wszystkie poprzednie testy. Wskażemy to przypisując regułkę do pola chained_validators. Ale szkoda gadać. Kod będzie mówił sam za siebie: class RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True login = formencode.all(formencode.validators.minlength(4, not_empty=true), formencode.validators.maxlength(25, not_empty=true)) email = formencode.validators.email(not_empty=true) password = formencode.validators.minlength(8, not_empty=true) password_confirmation = formencode.validators.string() chained_validators = [formencode.validators.fieldsmatch('password', 'password_confirmation')] Wiele więcej informacji o możliwych do zastosowania walidatorach oraz ich działaniu znajdziesz w PylonsBook 3 oraz dokumentacji FormEncode 4. 3 http://pylonsbook.com/ 4 http://formencode.org/validator.html Jan Koprowski 8

Podczas pisania kursu próbowałem zastosować wersję walidowania e maili z włączonym sprawdzaniem domeny w DNS, parametrem resolve_domain=true. Pierwszym błędem jaki zaczął zwracać Pylons był rzekomy brak moduły DNS pochodzącego z projektu pydns 5 (oczywiście w konsoli Pythona importowanie modułu było bezproblemowe). Próba odszukania plików modułu skończyła się fiaskiem: katalog, na który wskazywała dokumentacja był traktowany przez system jako plik binarny. Ostatecznie "pomogło" ściągnięcie najnowszych źródeł ze strony projektu i zainstalowanie ich z użycie pliku setup.py (sudo python setup.py install) jednak i tym razem biblioteka okazała się wadliwa pojawiały się problem z kodowanie znaków, domyślam się zawartych w adresie e mail. Wiadomości z bugiem wysłana do developerów jest odrzucana przez serwer pocztowy. Walidacja w akcji. Czas na nasz kontroler tutaj zabiegi będą czysto kosmetyczne. Na początek przygotujmy naszemu kontrolerowi warsztat i zaimportujmy niezbędne moduły. import logging from pylons import request, response, session, tmpl_context as c from pylons.controllers.util import abort, redirect_to from pylons.decorators import validate from darc.lib.base import BaseController, render #from darc import model from darc.model.form import RegisterForm log = logging.getlogger( name ) class UsersController(BaseController): def index(self): # Return a rendered template # return render('/template.mako') # or, Return a response c.name = "d'arc" return render('users/index.xhtml') def register(self): return render('users/register.xhtml') def create(self): c.login = request.post['login'] c.email = request.post['email'] 5 http://pydns.sf.net/ Jan Koprowski 9

Z kwestii formalnych: na starcie zaimportowaliśmy dekorator validate, a następnie naszą klasę, w której zapisaliśmy informacje o wymaganiach jakie chcemy aby spełniały dane wysyłane przez nasz formularz. To jednak ciut za mało aby wszystko zaczęło współgrać zaczęła działać. Czas na magię: class UsersController(BaseController): def index(self): # Return a rendered template # return render('/template.mako') # or, Return a response c.name = "d'arc" return render('users/index.xhtml') def register(self): return render('users/register.xhtml') @validate(schema=registerform(), form="register") def create(self): c.login = request.post['login'] c.email = request.post['email'] I tylko tyle? Zapytacie. Tak. Tylko tyle. Użyliśmy składni dekoratora. Pierwszym parametrem jest szablon, do którego mają pasować dane wysłane przez nasz formularz. Tworzyliśmy go w poprzednim rozdziale. Drugi to nazwa akcji kontrolera, do której należy wrócić kiedy coś się nie powiedzie. Inaczej: nazwa akcji zawierającej formularz. Możesz już przetestować swoje dzieło. Składnia dekoratorów została wprowadzona w języku Python dopiero po wersji 2.3. Jeżeli chcesz używasz wersji Pythona nie obsługującej dekoratorów zamiast linijki @validate wpisz validate(schema=registerform(), form="register")(create) po deklaracji metody create. Zauważ, że podczas nieudanej walidacji, któregoś z pól, dane w formularzu pozostają na swoim miejscu. Psujemy dalej czyli XSS w natarciu. No to się narobiliśmy! Mysz się nie prześliźnie mogłoby się wydawać sielanka, fajrant! Domyślnie w wersji 0.9.7 włączona jest nawet ochrona przed atakami XSS. Winowajca nazywa się default_filters=['escape'] i znajdziemy go w pliku config/environment.py. Zobaczmy co się stanie jeżeli wyłączymy escapowanie. W tym celu wyedtujemy plik widoku users/ create.xhtml Jan Koprowski 10

<strong>login</strong>: ${c.login}<br /> <strong>e mail</strong>: ${c.email}<br /> <strong>hasło</strong>: ${c.password n}<br /> <strong>potwierdzenie hasła</strong>: $ {c.password_confirmation} Opcja n powoduje wyłączenie domyślnego filtru. Teraz wpisz w pole hasła <script>alert('akuku')</script>. Nasz tekst zostanie wstawiony bez zamiany znaczników < i > na encje HTML przez co przeglądarka potraktuje go jak kod JavaScript. Pojawi się więc okienko z informacją "akuku". Pozostawmy opcję n i dodajmy do niej przykładowo h: <strong>login</strong>: ${c.login}<br /> <strong>e mail</strong>: ${c.email}<br /> <strong>hasło</strong>: ${c.password n,h}<br /> <strong>potwierdzenie hasła</strong>: $ {c.password_confirmation} Opcja h to włączenie opcji esacpowania HTML. Uzyskaliśmy więc taki sam efekt jak na początku. Użycie literki u spowoduje bezpieczne wyświetlanie charakterystyczne dla adresów URL, zaś literka x przyda nam się gdy będzie potrzebowali filtrować kod XML. Inne przydatne opcje to trim, które wycina spacje z początku i końca stringu czy unicode, które zwróci obiekt unicode Pythona. Użyteczną opcją może okazać się również decode.<jakieś kodowanie>, które zwracając string użyje wskazanego kodowania. Zawsze escapuj dane. Jeżeli masz wątpliwości w którym miejscu, rób to najbliżej wyjścia (w widoku gdy wyświetlasz dane). Tworzymy layout. Zazwyczaj strona posiada jakiś powtarzający się na każdej stronie element. Menu, czy chociażby nagłówki, które generalnie dla każdej strony są takie same. Spróbujmy więc stworzyć przykładowy layout. Niech będzie to plik templates/layout.xhtml. <?xml version="1.0" encoding="utf 8"?> <!DOCTYPE html PUBLIC " //W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/tr/xhtml1/dtd/xhtml1 frameset.dtd"> <html> <head> <title>d'arc</title> <meta http equiv="content Type" content="application/xhtml+xml; charset=utf 8" /> <meta name="author" content="imi ę Nazwisko" /> </head> <body> ${next.body()} </body> </html> Jan Koprowski 11

Poza kodem XHTML, który widać na pierwszy rzut oka wkradł się ${next.body()}. ${next.body()} to nic innego jak informacja dla Mako, iż życzymy sobie tutaj wstawić treść naszego następnego dokumentu. Już pokazuję jak to działa. Aby to sprawdzić musimy powiedzieć naszym widokom,z którego szablonu mają skorzystać. W tym celu edytujemy kolejno pliki users/create.xhtml, users/index.xhtml oraz users/register.xhtml: <%inherit file="/layout.xhtml"/> <strong>login</strong>: ${c.login}<br /> <strong>e mail</strong>: ${c.email}<br /> <strong>hasło</strong>: ${c.password n,entity}<br /> <strong>potwierdzenie hasła</strong>: $ {c.password_confirmation} <%inherit file="/layout.xhtml"/> <h1>indeks uzytkowników</h1> <ul> <li>${c.name}</li> </ul> <%inherit file="/layout.xhtml"/> ${h.form('create', method='post')} <fieldset> <legend>formularz rejestracyjny</legend> <div><label for="loginfield">login: </label>$ {h.text("login", id="loginfield")}</div> <div><label for="emailfield">e mail: </label>$ {h.text("email", id="emailfield")}</div> <div><label for="passwordfield">hasło: </label>$ {h.password("password", id="passwordfield")}</div> <div><label for="password_confirmationfield">potwierdź hasło: </label>${h.password("password_confirmation", id="password_confirmationfield")}</div> </fieldset> <div>${h.submit("create", "Zarejestruj")}</div> ${h.end_form()} Proszę bardzo. Znacznik inherit na początku każdego z plików poinformuje Mako iż chcemy zawrzeć ten plik wewnątrz wskazanego w parametrze. Aby udowodnić, że wszystko gra podejrzyj źródła swojej strony :) Jan Koprowski 12

Piszemy testy. Testowanie aplikacji często bywa traktowane po macoszemu. Być może dlatego, że zostawiane są zawsze na koniec. I rzeczywiście w małych projektach, pisanych adhoc, w których kod pisze się tylko raz i mają działać pomysł testowania może okazać się stratą czasu. W dużych przedsięwzięciach okazuje się przydatny. Wyobraź sobie, że masz całkiem rozbudowany system (ze 40 kontrolerów każdy po 7 13 akcji) i wprowadzasz w losowej ich części jakąś nową funkcjonalność. Perspektywa odwiedzania 40 * 7+13/2 stron i sprawdzanie czy wszystko działa... jest strasznie czasochłonna. Lepiej napisać testy i je "odpalić" co trwa dużo szybciej. Do sprawdzania poprawności dostajemy ponownie Paste 6. Nasze przykłady oprzemy o jedną metodę, polegającą na sprawdzeniu czy to co zwróciła strona internetowa zawiera jakieś wyrażenie. Czyli czy kod strony posiada jakiś, zadany przez nas wcześniej, fragment. Otwieramy plik tests/functional/test_users.py. Kod ten został utworzony przez generator gdy tworzyliśmy kontroler. Czas wyjaśnić co się w nim znajduje: from darc.tests import * class TestUsersController(TestController): def test_index(self): response = self.app.get(url(controller='users', action='index')) # Test response... Widzimy tutaj klasę testującą TestUsersController. Została wygenerowana automatycznie metoda wywołująca akcję index. Nazewnictwo jest proste: test_nazwaakcji, którą testujemy. Fragment response = self.app.get(url(controller='users', action='index')) "odwiedza" stronę http://127.0.0.1:5000/users/index i kod, który został zwrócony do przeglądarki zapisuje w zmiennej response. Mamy więc tam źródło witryny. Najprostszą metodą sprawdzenia czy dostaliśmy to czego oczekujemy jest wykonanie sprawdzenia obecności w źródle dobrze nam znanego fragmentu. W tym przypadku spodziewamy się <h1>indeks. from darc.tests import * class TestUsersController(TestController): def test_index(self): response = self.app.get(url(controller='users', action='index')) assert '<h1>indeks' in response # Test response... Proste prawda? Nasza nowa linijka sprawdzi czy w zmiennej response znajduje się ciąg <h1>indeks. W podobny sposób potraktujmy kolejne elementy naszego serwisu. Najprościej bo w analogiczny sposób możemy napisać metodę dla register: 6 http://pythonpaste.org/testing applications.html http://docs.pylonshq.com/testing.html Jan Koprowski 13

from darc.tests import * class TestUsersController(TestController): def test_index(self): response = self.app.get(url(controller='users', action='index')) assert '<h1>indeks' in response # Test response... def test_register(self): response = self.app.get(url(controller='users', action='register')) assert '<legend>formularz rejestracyjny</legend>' in response Oczywiście możesz wybrać inny charakterystyczny dla danej witryny fragment. To było banalnie proste. Sprawa zaczyna się komplikować przy chęci przetestowania metody create. Po pierwsze, jak pamiętamy działa ona wyłącznie dla danych przesłanych z użyciem POST poza tym jakoś te dane trzeba przesłać. Pierwszy warunek możemy szybko spełnić, jak łatwo się domyśleć, zamieniając self.app.get na self.app.post. Do przesłania przykładowych danych wykorzystamy argument params. from darc.tests import * class TestUsersController(TestController): def test_index(self): response = self.app.get(url(controller='users', action='index')) assert '<h1>indeks' in response # Test response... def test_register(self): response = self.app.get(url(controller='users', action='register')) assert '<legend>formularz rejestracyjny</legend>' in response def test_create(self): response = self.app.post(url(controller='users', action='create'), params={'login': 'johny', 'email': 'jan.koprowski@gmail.com', 'password': 'haslo1234', 'password_confirmation': 'haslo1234'}) assert '<strong>login</strong>' in response Tym razem wywołaliśmy naszą akcję z użyciem metody POST, dodatkowo przekazując z użyciem Jan Koprowski 14

słownika, odpowiednie wartości. Przypomnę, że test powinien dać wynik pozytywny. Tak więc przesłane przez nas parametry muszą być "poprawne". Walidacja nadal działa i dla błędnych wartości zwróci nam ponownie formularz test się nie powiedzie. Jeżeli chciałbyś testować czy coś się nie powiedzie (np. podając złe dane sprawdzić czy wywołał się formularz) możesz to zrobić stosując słówko not np. assert '<strong>login</strong>' not in response. Oczywiście istnieje inne metody będące dokładniejsze i mniej podatne na zmiany źródeł witryny. Zauważ, że w testach nie wykorzystujemy polskich liter. Niestety, podczas próby ich użycia nawet z nagłówkiem # * encoding: utf 8 * otrzymywałem błędy kodera. Jak widać można sobie jednak dać bez nich radę. Koniec części drugiej. Na "dziś" to już wszystko. Nauczyłeś się naprawdę sporo. Czas na własne eksperymenty i oswojenie się z nabytą wiedzą. Warto poszerzyć informacje o metodzie pozyskiwania danych użytecznych przy prowadzeniu testów. Linki podałem w stopce na odpowiedniej stronie. Powodzenia! Licencja http://creativecommons.org/licenses/by nc nd/2.5/pl/ Jan Koprowski 15

Spis treści Tworzymy formularz...1 Ładujemy helpery...2 Przechodzimy na helpery...2 Jak rozumieć istnienie helperów?...4 Wyświetlamy zawartość formularza...4 Walidacja formularza...6 Walidacja w akcji...9 Psujemy dalej czyli XSS w natarciu...10 Tworzymy layout...11 Piszemy testy...13 Koniec części drugiej...15 Licencja...15 Jan Koprowski 16