Chapter 7: Formularze i walidacja
Formularze i walidatory
W web2py istnieją cztery różne sposoby budowania formularzy:
- Helper
FORM
zapewnia implementację niskiego poziomu w zakresie helperów HTML. ObiektFORM
może być serializowany do kodu HTML i jest mu znana zawartość pól. Wie, jak sprawdzać zgłoszone formularze. - Helper
SQLFORM
dostarcza API wysokiego poziomu do budowania funkcjonalności tworzenia, aktualizowania i usuwania formularzy powiązanych z istniejącymi tabelami bazy danych. - Metoda
SQLFORM.factory
jest abstrakcyjną warstwą na szczycieSQLFORM
, wykorzystującą funkcjonalności generowania formularzy helperaSQLFORM
, nawet jeśli nie jest dostępna baza danych. Generowanie formularzy jest tu bardzo podobne doSQLFORM
, z opisem tabeli, ale bez potrzeby tworzenia tabeli bazy danych. - Metody obiektu
CRUD
są funkcjonalnym równoważnikiem SQLFORM i są oparte na SQLFORM, ale dostarczają bardziej zwartą notację.
Wszystkie te formularze są na tyle inteligentne, że jeśli dane wejściowe nie przejdą walidacji, mogą zostać zmienione przez formularz i moga zostać dodane komunikaty błędów. Formularze można wypytywać o zmienne walidacyjne i o komunikaty błędów, generowanych podczas walidacji.
Dowolny kod HTML może być wstawiany do lub ekstrahowany z formularza przy użyciu helperów FORM
i SQLFORM
. Helperami tymi można manipulować w podobny sposób jak helperem DIV
. Na przykład można ustawić styl formularza:
form = SQLFORM(..)
form['_style']='border:1px solid black'
FORM
Rozważmy dla przykładu aplikację test z następującym kontrolerem "default.py":
def display_form():
return dict()
i związananym widokiem "default/display_form.html":
{{extend 'layout.html'}}
<h2>Formularz wejściowy</h2>
<form enctype="multipart/form-data"
action="{{=URL()}}" method="post">
Twoja nazwa:
<input name="name" />
<input type="submit" />
</form>
<h2>Zgłoszone zmienne</h2>
{{=BEAUTIFY(request.vars)}}
Jest to zwykły formularz HTML, który pyta o nazwę użytkownika. Gdy wypełni się ten formularz i kliknie przycisk zatwierdzający, nastąpi samozgłoszenie formularza i zmienna request.vars.name
wraz z wartością zostanie wyświetlona na przycisku.
Można wygenerować taki sam formularz używając helperów. Można to zrobić w widoku lub w akcji. Ponieważ web2py przetwarza formularz w akcji, lepiej jest też zdefiniować taki formularz w akcji.
Oto nowy kontroler:
def display_form():
form=FORM('Your name:', INPUT(_name='name'), INPUT(_type='submit'))
return dict(form=form)
i związany z nim widok "default/display_form.html":
{{extend 'layout.html'}}
<h2>Formularz wejściowy</h2>
{{=form}}
<h2>Zgłoszone zmienne</h2>
{{=BEAUTIFY(request.vars)}}
Kod ten, jak do tej pory, jest równoważny poprzedniemu kodowi, ale formularz jest generowany przez wyrażenie {{=form}}
, które serializuje obiekt FORM
.
Teraz dodamy jeden poziom złożoności, dodając walidację formularza i przetwarzanie.
Zmieńmy kontroler następujaco:
def display_form():
form=FORM('Your name:',
INPUT(_name='name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if form.accepts(request,session):
response.flash = 'form accepted'
elif form.errors:
response.flash = 'form has errors'
else:
response.flash = 'please fill the form'
return dict(form=form)
oraz związany widok "default/display_form.html":
{{extend 'layout.html'}}
<h2>Formularz wejściowy</h2>
{{=form}}
<h2>Zgłoszone zmienne</h2>
{{=BEAUTIFY(request.vars)}}
<h2>Zaakceptowane zmienne</h2>
{{=BEAUTIFY(form.vars)}}
<h2>Błędy w formularzu</h2>
{{=BEAUTIFY(form.errors)}}
Proszę zauważyć, że:
- W akcji dodaliśmy walidator
requires=IS_NOT_EMPTY()
dla pola input "name". - W akcji dodaliśmy wywołanie
form.accepts(..)
- W widoku drukujemy
form.vars
iform.errors
jak również formularz irequest.vars
.
Cała praca wykonywana jest przez metodę accepts
obiektu form
. Filtruje ona request.vars
zgodnie z deklarowanymi wymaganiami (wyrażonymi przez walidatory). Metoda accepts
przechowuje zmienne, które przechodzą walidację, w form.vars
. Jeśli wartość pola nie spełnia wymagań, wykorzystywany walidator zwraca błąd, który zostaje przechowany w form.errors
. Zarówno form.vars
jak i form.errors
są obiektami gluon.storage.Storage
podobnymi do request.vars
. Pierwszy z nich zawiera wartości, które przeszły walidację, na przykład:
form.vars.name = "Max"
Ten drugi obiekt zawiera błędy, na przykład:
form.errors.name = "Cannot be empty!"
Pełna sygnatura metody accepts
jest następująca:
form.accepts(vars, session=None, formname='default',
keepvalues=False, onvalidation=None,
dbio=True, hideerror=False):
Znaczenie parametrów opcjonalnych jest wyjaśnione w następnych podrozdziałach.
Pierwszym argumentem może być request.vars
lub request.get_vars
lub request.post_vars
lub po prostu request
. Ten ostatni parametr jest równoznaczny z zaakceptowaniem jako wejścia zmiennej request.post_vars
.
Funkcja accepts
zwraca True
jeśli formularz został zaakceptowany a w przeciwnym razie False
. Formularz nie zostanie zaakceptowany, jeśli ma błędy lub gdy nie został zgłoszony (na przykład, za pierwszym razem jest pokazywany).
Oto jak wygląda ta strona, gdy formularz jest wyświetlany za pierwszym razem:
To jest wygląd formularza, po nieprawidłowym zgłoszeniu:
Po prawidłowym zgłoszeniu nasz formularz będzie wyglądać tak:
Metody process
i validate
process
: inxx
Skrótem dla
form.accepts(request.post_vars,session,...)
jest
form.process(...).accepted
Skrót nie potrzebuje argumentów request
i session
(choć ewentualnie można je określić). Różni się on również tym od accepts
, że sam zwraca formularz. Wewnętrznie process
akceptuje i przekazuje do niego swoje argumenty. Wartości zwracane przez accepts
są zapisywane w zmiennej form.accepted
.
Funkcja process
pobiera kilka dodatkowych argumentów, który nie pobiera accepts
:
message_onsuccess
;onsuccess
: jeśli równeflash
(domyślnie) i formularz został zaakceptowany, to pojawi się o tym komunikatmessage_onsuccess
;message_onfailure
;onfailure
: jeśli równe 'flash' (domyślnie) i walidacja formularza nie powiodła się, to pojawi się o tym komunikatmessage_onfailure
;next
wskazuje gdzie przekierować użytkownika po zaakceptowaniu formularza.
Argumenty onsuccess
i onfailure
mogą być funkcjami takimi jak lambda form: do_something(form)
.
Skrót
form.validate(...)
równoważny jest wyrażeniu
form.process(...,dbio=False).accepted
Pola warunkowe
pola warunkowe`:inxx
Czasem zachodzi potrzeba pokazania pola, jeśli spełniony jest jakiś warunek.
Rozważmy następujący model:
db.define_table('purchase', Field('have_coupon','boolean'), Field('coupon_code'))
Przyjmijmy, że chcemy wyświetlić pole
coupon_code, wtedy i tylko wtedy, gdy
zostało zaznaczone pole
have_coupon.
Można to wykonać w JavaScript. W web2py, odpowiedni kod JavaScript może zostać
wygenerowany automatycznie. Wystarczy zadeklarować, że to pole jest zależne od
wyrażenia. Wykorzystując atrybut
show_if pola:
def index(): db.purchase.coupon_code.show_if = (db.purchase.have_coupon==True) form = SQLFORM(db.purchase).process() return dict(form = form)
Wartość
show_if jest zapytaniem i wykorzystuje tą samą składnię DAL, którą
używa się w zapytaniach do bazy danych. Różnica polega na tym, że to zapytanie
nie jest wysyłane do bazy danych, ale jest konwertowane do kodu JavaScript
i wysyłane do przeglądarki, gdzie kod ten jest wykonywany, gdy użytkownik edytuje
formularz.
#### Pola ukryte
pola ukryte:inxx
Gdy serializowany jest powyższy obiekt formularza przez
{{=form}} i ponieważ
wywołana została metodę
accepts, to kod HTML formularza będzie wyglądać tak:
<form enctype="multipart/form-data" action="" method="post"> your name: <input name="name" /> <input type="submit" /> <input value="783531473471" type="hidden" name="_formkey" /> <input value="default" type="hidden" name="_formname" /> </form> :code
Proszę zwrócić uwagę na obecność dwóch ukrytych pól: "_formkey" i "_formname".
Ich obecność jest wyzwalana przez wywołanie
accepts. Odgrywają one dwie ważne
role:
- Ukryte pole o nazwie "_formkey" jest jednorazowym tokenem, który jest używany
do zabezpieczenia przed podwójnym złożeniem formularza. Wartość tego klucza jest
generowana podczas serializowania formularza i przechowywana w
session.
Gdy formularz zostaje złożony, wartość ta musi pasować, w przeciwnym przypadku
metoda
accepts zwraca
False bez błędów, tak jakby formularz w ogóle nie
został złożony. Dzieje się tak dlatego, że web2py nie może ustalić, czy formularz
został złożony poprawnie.
- Ukryte pole o nazwie "_formname" jest generowane przez web2py jako nazwa
dla formularza, ale nazwa ta może zostać nadpisana. Pole to jest niezbędne,
aby dać możliwość tworzenia stron, które zawierają i przetwarzają wiele formularzy.
W web2py identyfikuje się i rozróżnia złożone formularze po ich nazwach.
- Opcjonalnie, ukryte pola można zdefiniować przez
FORM(..,hidden=dict(...)).
Rola tych ukrytych pól i ich wykorzystanie w niestandardowych formularzach i stronach
z wieloma formularzami jest omówione szczegółowo w dalszej części rozdziału.
Jeśli rozpatrywany formularz został złożony z pustym polem "name", nie przejdzie
walidacji. Gdy formularz jest ponownie serializowany, to pojawi się jako:
<form enctype="multipart/form-data" action="" method="post"> your name: <input value="" name="name" /> <div class="error">cannot be empty!</div> <input type="submit" /> <input value="783531473471" type="hidden" name="_formkey" /> <input value="default" type="hidden" name="_formname" /> </form> :code
Proszę zwrócić uwagę na występowanie DIV klasy "error" w serializowanym formularzu.
Kod web2py wstawia ten komunikat błędu w formularzu, aby powiadomić odwiedzającego
o polu, które nie przeszło walidacji. Po złożeniu formularza, metoda
acceptsokreśla, ze formularz został złożony, sprawdza czy pole "name" jest puste i czy
wartość tego pola jest obowiązkowa i ewentualnie umieszcza w formularzu komunikat
błędu z walidatora.
Bazowy widok "layout.html" przewiduje obsługę bloków DIV z klasą "error". Domyślny
szablon układu wykorzystuje efekty jQuery do pokazywania komunikatów błędów z efektem
zsuwania na czerwonym tle. Proszę zapoznać się ze szczegółami w rozdziale 11.
#### Argument
keepvalues
keepvalues:inxx
Opcjonalny argument
keepvalues powiadamia web2py, co zrobić gdy formularz został
zaakceptowany i nie ma przekierowania, czego skutkiem jest to, że ten sam formularz
będzie wyświetlony ponownie. Domyślnie w takim przypadku formularz jest czyszczony.
Jeśli
keepvalues jest ustawione na
True, formularz jest wstępnie wypełniany
poprzednio wprowadzonymi danymi. Jest to pomocne, gdy ten sam formularz ma być
używany wielokrotnie do wstawiania podobnych rekordów. Jeśli argument
dbiojest ustawiony na
False, web2py nie będzie wykonywał jakichkolwiek operacji
wstawiania lub aktualizowania rekordów w bazie danych po zaakceptowaniu formularza.
Jeśli
hideerror zostanie ustawiony na
True a formularz zawiera błędy, nie
będą one wyświetlane podczas renderowania formularza (rolą programisty będzie
wyświetlenie jakoś tych błędów ze zmiennej
form.errors).
Argument
onvalidation jest omówiony poniżej.
#### Argument
onvalidation
onvalidation:inxx
Wartością argumentu
onvalidation może być
None lub funkcja, która pobiera
formularz i zwraca
nothing. Taka funkcja może być wywołana i przekazana do formularza,
niezwłocznie po pozytywnej walidacji i przed czymkolwiek co się stanie po tej walidacji.
Funkcja ta ma wiele zastosowań. Na przykład, wykonanie dodatkowego sprawdzenia formularza
i ewentualnie dodanie do formularza błędów lub obliczenie wartości jakichś pól na
podstawie wartości innych pól lub wyzwolenie jakiejś akcji (jak na przykład wysłanie
wiadomości email) przed utworzeniem lub zaktualizowaniem rekordu.
Oto przykład:
db.define_table('numbers', Field('a', 'integer'), Field('b', 'integer'), Field('c', 'integer', readable=False, writable=False))def my_form_processing(form): c = form.vars.a * form.vars.b if c < 0: form.errors.b = 'a*b cannot be negative' else: form.vars.c = c
def insert_numbers(): form = SQLFORM(db.numbers) if form.process(onvalidation=my_form_processing).accepted: session.flash = 'record inserted' redirect(URL()) return dict(form=form)
:code
#### Wykrywanie zmian w rekordzie
Podczas wypełniania formularza podczas edycji istnieje pewne prawdopodobieństwo,
że inny użytkownik może jednocześnie edytować ten sam rekord. Więc w czasie zapisywania
rekordu dobrze jest sprawdzić, czy nie uległ on w międzyczasie zmianie, w celu uniknięcia
konfliktów. Można to zrobić tak:
db.define_table('dog',Field('name'))
def edit_dog(): dog = db.dog(request.args(0)) or redirect(URL('error')) form=SQLFORM(db.dog,dog) form.process(detect_record_change=True) if form.record_changed:
do something elif form.accepted:
do something else else:
do nothing return dict(form=form)
:code
#### Formularze a przekierowania
przekierowania:inxx
Najczęstszym sposobem wykorzystania formularzy jest tzw. samozgłoszenie, co oznacza,
że zgłoszone zmienne pól są przetwarzane w tej samej akcji, w której wygenerowano
formularz. Po zaakceptowaniu formularza, niezwykła rzeczą jest jego ponowne
wyświetlenie na bieżącej stronie. Bardziej rozpowszechnione jest przekierowanie
odwiedzającego do "następnej" strony.
Oto nowy przykład kontrolera:
def display_form(): form = FORM('Your name:', INPUT(_name='name', requires=IS_NOT_EMPTY()), INPUT(_type='submit')) if form.process().accepted: session.flash = 'form accepted' redirect(URL('next')) elif form.errors: response.flash = 'form has errors' else: response.flash = 'please fill the form' return dict(form=form)
def next(): return dict() :code
W celu ustawienia komunikatu fleszowego na następnej stronie, a nie na bieżącej,
trzeba zastosować
session.flash zamiast
response.flash. Spowoduje to przeniesie
wykonania pierwszego wyrażenia na następną stronę, po przekierowaniu. Trzeba mieć
na uwadze, że używanie
session.flash wymaga niestosowania
session.forget().
#### Wiele formularzy na jednej stronie
Treść tego rozdziału dotyczy zarówno klasy
FORM jak i
SQLFORM.
Jest możliwe posiadanie wielu formularzy na stronie, ale trzeba umożliwić ich
rozróżnianie przez web2py. Jeśli formularze są uzyskiwane z
SQLFORM z różnych
tabel, to web2py automatycznie przydziela im różne nazwy. W przeciwnym razie zachodzi
potrzeba jawnego określenia nazw dla poszczególnych formularzy. Oto przykład:
def two_forms(): form1 = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()), INPUT(_type='submit')) form2 = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()), INPUT(_type='submit')) if form1.process(formname='form_one').accepted: response.flash = 'form one accepted' if form2.process(formname='form_two').accepted: response.flash = 'form two accepted' return dict(form1=form1, form2=form2) :code
a to jest wyprodukowane wyjście:
[[image http://www.web2py.com/books/default/image/42/pl6100.png center 300px]]
Gdy użytkownik zgłasza pusty form1, to tylko w form1 wyświetlany jest błąd, jeśli
odwiedzający zgłosi pusty form2, to błąd będzie wyświetlony tylko w form2.
#### Współdzielenie formularzy
Treść tego rozdziału dotyczy klasy
FORM oraz
SQLFORM. To co tu omówimy
jest możliwe, ale nie zalecane, ponieważ zawsze jest dobrą praktyką, aby tworzyć
formularz samozgłaszający się. Czasami jednak nie ma się wyboru, gdy akcja wysyłająca
formularz i akcja odbierająca należą do różnych aplikacji.
Jest możliwe wygenerowanie formularza, który zgłaszany jest do różnych akcji.
Realizuje się to przez określenie adresu URL akcji przetwarzającej w atrybucie
obiektów
FORM lub
SQLFORM. Na przykład:
form = FORM(INPUT(_name='name', requires=IS_NOT_EMPTY()), INPUT(_type='submit'), _action=URL('page_two'))
def page_one(): return dict(form=form)
def page_two(): if form.process(session=None, formname=None).accepted: response.flash = 'form accepted' else: response.flash = 'there was an error in the form' return dict() :code
Proszę zauważyć, że ponieważ "page_one" oraz "page_two" używają ten sam obiekt
form, zdefiniowaliśmy go tylko raz umieszczając na zewnątrz wszystkich akcji,
aby się nie powtarzać.
Wspólna część kodu na początku kontrolera zostaje wykonana
za każdym razem przed przekazaniem sterowania do wywoływanej akcji.
Ponieważ "page_one" nie wywołuje
process (ani
accepts), formularz nie ma
nazwy ani klucza, więc trzeba przekazać
session=None i ustawić
formname=Nonew
process, inaczej formularz nie będzie walidowany, gdy otrzyma go "page_two".
#### Dodawanie przycisków do obiektów FORM
Zazwyczaj formularz zawiera jeden przycisk zgłaszający „Prześlij”. Często chce
się dodać przycisk "Wstecz" zamiast przycisku „Prześlij”, przekierowujący odwiedzającego
do innej strony.
add_button:inxx
Można to zrobić przy wykorzystując metodę
add_button:
form.add_button('Wstecz', URL('other_page')) :code
Do formularza można dodawać więcej niż jeden przycisk. Argumentami metody
add_button są wartość przycisku (jego tekst) i adres url przekierowania.
Proszę zapoznać się też z argumentem buttons dla SQLFORM, który dostarcza bardziej
zaawansowane rozwiązanie.
#### Więcej o manipulowaniu obiektami FORM
Tak jak opisano to w rozdziale poświęconemu widokom, klasa FORM jest helperem HTML.
Helpery mogą być manipulowane jak listy Pythona i jak słowniki, które można tworzyć
i manipulować nimi w czasie wykonania.
###
SQLFORM
SQLFORM:inxx
Przejdźmy teraz do następnego poziomu i stwórzmy aplikację z plikiem takiego oto
modelu:
db = DAL('sqlite://storage.sqlite') db.define_table('person', Field('name', requires=IS_NOT_EMPTY())):code
Zmodyfikujmy kontroler w ten sposób:
def display_form(): form = SQLFORM(db.person) if form.process().accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' else: response.flash = 'please fill out the form' return dict(form=form) :code
Widoku nie potrzebujemy zmieniać.
W nowym kontrolerze nie potrzebujemy budowania
FORM, ponieważ konstruktor
SQLFORM zbudował jeden formularz z danymi z tabeli
db.person, zdefiniowanej
w modelu. Jest to nowy formularz, który po zserializowaniu do kodu HTML będzie wyglądał
następująco:
<form enctype="multipart/form-data" action="" method="post"> <table> <tr id="person_name__row"> <td><label id="person_name__label" for="person_name">Your name: </label></td> <td><input type="text" class="string" name="name" value="" id="person_name" /></td> <td></td> </tr> <tr id="submit_record__row"> <td></td> <td><input value="Submit" type="submit" /></td> <td></td> </tr> </table> <input value="9038845529" type="hidden" name="_formkey" /> <input value="person" type="hidden" name="_formname" /> </form> :code
Ten automatycznie wygenerowany formularz jest bardziej złożony niż poprzedni
formularz niskiego poziomu. Przede wszystkim zawiera wiersze tabeli a każdy wiersz
posiada trzy kolumny. Pierwsza kolumna zawiera etykiety pól (tak jak określono to
w
db.person), druga kolumna zawiera pola wejściowe (input) i ewentualnie
komunikaty błędów, a trzecia kolumna jest opcjonalna i dlatego pusta (może zostać
wypełniona z pól w konstruktorze
SQLFORM).
Wszystkie znaczniki w formularzu mają nazwy pochodzące z tabeli i nazwy pól. Pozwala
to na łatwe dostosowanie formularza przy użyciu CSS i JavaScript. Możliwości te są
szczegółowiej opisane w rozdziale 11.
Bardziej istotne jest to, że teraz nowa metoda
accepts ma dużo więcej pracy.
Podobnie jak w poprzednim przypadku wykonuje walidację danych wejściowych, ale
dodatkowo, jeśli dane wejściowe przechodzą walidację, to również wykonuję wstawienie
nowych rekordów i zapisanie w
form.vars.id unikalnego "id" nowego rekordu.
Obiekt
SQLFORM również automatycznie zajmuje się polami "upload" dokonując zapisu
przesyłanych plików w folderze "uploads" (po zmianie nazwy pliku w celach uniknięcia
konfliktów i zabezpieczenia przed atakami „directory traversal”) i przechowując ich
nazwy (nowe nazwy) w odpowiednim polu w bazie danych. Nowa nazwa pliku zostaje
udostępniona, po przetworzeniu formularza, w
form.vars.fieldname (czyli zastępuje
obiekt
cgi.FieldStorage w
request.vars.fieldname), więc można łatwo odwoływać
się do nowej nazwy.
-----
UWAGA:
Domyślna długość pola wynosi 512 znaków. Gdy jakiś system plików nie obsługuje
tak długich nazw plików, może być to powodem błędów związanych z generowaniem
nieprawidłowych nazw. Rozwiązaniem może być ustawienie
Field(...,length=...)na pożądaną wartość. Proszę mieć też na uwadze, że może to powodować skrócenie
kodowania oryginalnej nazwy pliku i uniemożliwić odzyskanie pliku po jego pobraniu
lub przesłaniu.
-----
Helper
SQLFORM wyświetla wartości "logiczne" w polach wyboru (checkbox), wartości
"tekstowe" w obszarach tekstowych (textarea), natomiast wartości, które muszą się
znajdować w określonym zestawie lub bazie danych w listach rozwijanych (drop-downs)
a pola "przesyłanych danych" w odnośnikach umożliwiających użytkownikom pobieranie
plików. Ukrywa on pola "blob", ponieważ muszą być obsługiwane inaczej, co omówione
jest dalej.
Dla przykładu przeanalizujmy następujący model:
db.define_table('person', Field('name', requires=IS_NOT_EMPTY()), Field('married', 'boolean'), Field('gender', requires=IS_IN_SET(['Male', 'Female', 'Other'])), Field('profile', 'text'), Field('image', 'upload')) :code
W tym przypadku,
SQLFORM(db.person) wygeneruje następujący formularz:
[[image http://www.web2py.com/books/default/image/42/pl6200.png center 300px]]
Konstruktor
SQLFORM umożliwia różnorakie przystosowywania formularza, takie
jak wyświetlanie tylko podzestawu pól, zmianę etykiet, dodawanie wartości do
opcjonalnej trzeciej kolumny lub tworzenie formularzy UPDATE i DELETE, a nie
standardowych formularzy INSERT.
SQLFORM jest obiektem, który na tle innych
obiektów web2py, potrafi najbardziej oszczędzić czas programisty.
Klasa
SQLFORM została zdefiniowana w "gluon/sqlhtml.py". Można ją łatwo
rozszerzyć nadpisując jej metodę
xml serializującą obiekty, tak aby zmienić
jej wyjście.
fields:inxx
labels:inxx
Sygnatura konstruktora
SQLFORM jest następująca:
SQLFORM(table, record = None, deletable = False, linkto = None, upload = None, fields = None, labels = None, col3 = {}, submit_button = 'Submit', delete_label = 'Check to delete:', showid = True, readonly = False, comments = True, keepopts = [], ignore_rw = False, record_id = None, formstyle = 'table3cols', buttons = ['submit'], separator = ': ', attributes)
:code
- Opcjonalny drugi argument zamienia formularz INSERT na formularz UPDATE dla
określonego rekordu (patrz następny podrozdział).
showid:inxx
delete_label:inxx
id_label:inxx
submit_button:inxx
- Jeśli
deletable jest ustawiony na
True, formularz UPDATE wyświetla pole
wyboru "Zaznacz aby skasować". Wartość etykiety tego pola jest ustawiana w argumencie
delete_label.
-
submit_button wartość przycisku zatwierdzania.
-
id_label ustawia etykietę "id" rekordu.
- "Id" rekordu nie jest pokazywany, jeśli argument
showid jest ustawiony na
False.
-
fields jest opcjonalną listą nazw pól, które chce się wyświetlić.
Jeśli lista zostaje dostarczona, to wyświetlane będą tylko pola z tej listy.
Na przykład:
fields = ['name'] :code
-
labels jest słownikiem etykiet pól. Kluczami słownika są nazwy pól a wartościami,
to co ma być wyświetlone jako etykieta pola. Jeśli etykieta nie jest określona,
web2py wywodzi etykietę z nazwy pola (nazwa pola jest kapitalizowana a znaki
podkreślenia są zamieniane na spacje). Na przykład:
labels = {'name':'Your Full Name:'} :code
-
col3 jest słownikiem wartości dla trzeciej kolumny. Na przykład:
col3 = {'name':A('what is this?', _href='http://www.google.com/search?q=define:name')} :code
-
linkto i
upload są opcjonalnymi ścieżkami URL do kontrolerów zdefiniowanych
przez użytkownika, które umożliwiają, aby formularz radził sobie z polami referencyjnymi.
Jest to omówione w dalszej części rozdziału.
-
readonly. Jeśli ustawiony na True, formularz jest wyświetlany jako tylko do odczytu.
-
comments. Jeśli ustawiony na False, nie będą wyświetlane komentarze w col3.
-
ignore_rw. Zwykle, w formularzu tworzącym i aktualizującym wyświetlane są
tylko pola oznaczone jako
writable=True a w formularzu tylko do odczytu pola
oznaczone jako
readable=True. Ustawienie
ignore_rw=True spowoduje, że
ograniczenia te będą ignorowane i wyświetlone zostaną wszystkie pola. Jest to
stosowane głównie w interfejsie appadmin do wyświetlenia wszystkich pól każdego
tabeli, przesłaniając to co wskazuje model.
-
formstyle:inxx
formstyle określa styl jaki będzie użyty podczas serializowania
formularza do kodu HTML. Może to być "table3cols" (domyślnie), "table2cols"
(jeden wiersz dla etykiety i komentarza i jeden wiersz dla pola wejściowego),
"ul" (wytwarza nieuporządkowana listę pól wejściowych), "divs" (reprezentuje formularz
wykorzystujący przyjazne bloki div CSS, dla dowolnego dostosowania), "bootstrap"
wykorzystujący klasę bootstrap formularza o wartości "form-horizontal". Argument
formstyle może również być funkcją generującą wszystko wewnątrz znacznika FORM.
Można przekazać do konstruktora funkcję z dwoma argumentami, form i fields. Wskazówki
można znaleźć w pliku kodu źródłowego sqlhtml.py (proszę zobaczyć do funkcji
o nazwie formstyle_).
-
buttons:inxx
buttons jest listą helperów
INPUT lub
TAG.button
(choć teoretycznie może być dowolną kombinacją helperów), które mają być dodane
do DIV w którym znajduje się przycisk zatwierdzający.
Na przykład, dodanie przycisku powrotnego opartego na URL (dla formularza
wielostronicowego) i zmiana nazwy przycisku zatwierdzającego:
-----------------
buttons = [TAG.button('Back',_type="button",_onClick = "parent.location='%s' " % URL(...), TAG.button('Next',_type="submit")]:code
lub przycisku, który łączy do innej strony:
buttons = [..., A("Go to another page",_class='btn',_href=URL("default","anotherpage"))] :code
-----------------
-
separator:inxx
separator ustawia ciąg znakowy, który oddziela etykiety
formularza od pól wejściowych.
- Opcjonalnie
attributes są argumentami rozpoczynającymi się od znaku podkreślenia,
które przekazuje się do znacznika
FORM w celu odpowiedniego renderowania obiektu
SQLFORM. Oto przykład:
_action = '.' _method = 'POST' :code
Istnieje specjalny atrybut
hidden. Gdy słownik jest przekazywany jako
hidden,
jego elementy są tłumaczone na ukryte pole INPUT (zobacz przykład helpera
FORM
w rozdziale 5):
form = SQLFORM(...,hidden=...) :code
co powoduje, że ukryte pola są przekazywane w zgłoszeniu, nic więcej, nic mniej.
Argumenty
form.accepts(...) nie są przeznaczone do odczytywania odebranych
ukrytych pól i trzeba przenieść je do
form.vars. Powodem tego jest bezpieczeństwo.
Ukryte pola mogą być naruszane, więc trzeba jawnie przenieść ukryte pola z żądania
do formularza:
form.vars.a = request.vars.a form = SQLFORM(..., hidden=dict(a='b')) :code
#### Metoda
processW SQLFORM dostępna jest metoda
process (podobnie jak
forms).
Jeśli w SQLFORM chce się wykorzystać argument
keepvalues, trzeba przekazać go
w metodzie
process:
if form.process(keepvalues=True).accepted::code
####
SQLFORM a
insert,
update i
delete
Gdy formularz jest akceptowany,
SQLFORM tworzy nowy rekord. Zakładając, że
form=SQLFORM(db.test), to identyfikator ostatnio utworzonego rekordu będzie
dostępny w
myform.vars.id.
usuwanie rekordu:inxx
Jeśli przekaże się rekord, jako drugi argument konstruktora
SQLFORM, formularz
stanie się formularzem UPDATE dla tego rekordu. Oznacza to, że podczas składania
formularza aktualizowany jest istniejący rekord a nowy rekord jest wstawiany.
Jeśli ustawi się
deletable=True, formularz UPDATE wyświetla pole wyboru
"zaznacz aby usunąć". Jeśli zaznaczysz, rekord zostanie usunięty.
------
Przy składaniu formularza, jeśli zaznaczone jest pole wyboru usuwania, atrybut
form.deleted zostaje ustawiany na
True.
------
Zmodyfikujmy kontroler z poprzedniego przykładu, tak aby przekazywał
dodatkowy argumentu całkowitoliczbowy w ścieżce URL, na przykład:
/test/default/display_form/2 :code
i jeśli istnieje rekord z odpowiednim identyfikatorem, to
SQLFORM generuje
dla tego rekordu formularz UPDATE/DELETE:
def display_form(): record = db.person(request.args(0)) or redirect(URL('index')) form = SQLFORM(db.person, record) if form.process().accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' return dict(form=form) :code
Linia 2 odnajduje rekord a linia 3 wykonuje formularz UPDATE/DELETE. Linia 4
realizuje całe przetwarzanie odpowiedniego formularza.
------
Formularz aktualizujący jest bardzo podobny do utworzonego formularza z tym, że
jest on wstępnie wypełniony danymi bieżącego rekordu oraz ma podgląd obrazów.
Mamy domyślnie ustawione
deletable = True co oznacza, że formularz aktualizujący
będzie wyświetlał opcję "delete record".
------
Formularze edycyjne zawierają również ukryte pole INPUT z
name="id", które
jest używane do identyfikacji rekordu. Identyfikator ten jest również przechowywany
po stronie serwera dla dodatkowego bezpieczeństwa i jeśli odwiedzający będzie coś
"majstrował" z wartością tego pola, UPDATE nie zostanie wykonane a web2py zgłosi
SyntaxError, "user is tampering with form".
Gdy pole jest oznaczone z
writable=False, nie będzie pokazywane
w tworzonym formularzu a w formularzach aktualizujących będzie wyświetlane jako pole
tylko do odczytu. Jeśli pole jest oznaczone jako
writable=False i
readable=False,
to nie będzie wyświetlane w ogóle, nawet w formularzach aktualizujących.
Formularze tworzone przez
form = SQLFORM(...,ignore_rw=True) :code
ignorują atrybuty
readable i
writable oraz zawsze pokazują wszystkie pola.
Formularze w
appadmin ignorują domyślnie to zachowanie.
Formularze tworzone przez
form = SQLFORM(table,record_id,readonly=True) :code
wyświetlają wszystkie pola w trybie tylko do odczytu i nie mogą być akceptowane.
Oznakowanie pola przez
writable=False zapobiega włączeniu tego pola w strukturę
formularza i powoduje przetwarzanie formularza z pominięciem wartości
request.vars.field.
Jednakże, jeśli przypisze się wartość
form.vars.field, to ta wartość ''stanie ''
się częścią polecenia wstawiającego lub aktualizującego podczas przetwarzania formularza.
Umożliwia to zmianę wartości pól, które z jakichś powodów nie chcemy umieszczać
w formularzu.
####
SQLFORM w HTML
Czasem zachodzi potrzeba zastosowania
SQLFORM, tak aby wykorzystać jego generator
formularzy i przetwarzanie, ale z możliwoością uzyskania większego poziomu dostosowania
kodu HTML formularza, który nie można osiągnąć z parametrami obiektu
SQLFORM,
co wymusza konieczność zaprojektowania formularza przy użyciu HTML.
Edytujmy teraz poprzedni kontroler i dodajmy nową akcję:
def display_manual_form(): form = SQLFORM(db.person) if form.process(session=None, formname='test').accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' else: response.flash = 'please fill the form'Note: no form instance is passed to the view return dict()
:code
oraz wstawmy formularz związany z widokiem "default/display_manual_form.html":
{{extend 'layout.html'}} <form action="#" enctype="multipart/form-data" method="post"> <ul> <li>Your name is <input name="name" /></li> </ul> <input type="submit" /> <input type="hidden" name="_formname" value="test" /> </form> :code
Proszę zauważyć, że ta akcja nie zwraca formularza, ponieważ nie trzeba przekazywać
go do widoku. Widok zawiera formularz utworzony ręcznie w HTML. Formularz zawiera
ukryte pole "_formname", którego wartość musi być taka sama jak wartość argumentu
formname określonego w akcji. Nazwa ta jest wykorzystywana w web2py,
gdy istnieje wiele formularzy na tej samej stronie i trzeba ustalić, który z nich
został zgłoszony. Jeśli strona zawiera tylko pojedynczy formularz, można ustawić
formname=None i pominąć w widoku ukryte pole.
Metoda
form.accepts będzie wyszukiwać wewnątrz
response.vars dane
odpowiadające polom w tabeli
db.person. Pola te są deklarowane w formacie
HTML
<input name="field_name_goes_here" /> :code
Proszę zauważyć, że w podanym przykładzie, zmienne formularza będą przekazywane
jako argumenty w adresie URL. Jeśli nie jest to pożądane, musi się określić
protokół
POST. Proszę ponadto mieć na uwadze, że jeśli określi się pola ''upload'',
formularz będzie musiał być ustawiony na zezwolenie tych pól. Tutaj są pokazane
obie opcje:
<form enctype="multipart/form-data" method="post"> :code
####
SQLFORM a pola plikowe
Pola typu ''upload'' (pola plikowe) są specjalne. Są renderowane jako pola INPUT
type="file".
Jeśli nie określi się tego inaczej, przesyłany na serwer plik jest strumieniowany
do używanej pamięci podręcznej i przechowywany w folderze "uploads" aplikacji
z użyciem nowej bezpiecznej nazwy, przypisywanej automatycznie. Nazwa tego pliku
jest następnie zapisywana do pól typu ''upload''.
Dla przykładu rozważmy następujący model:
db.define_table('person', Field('name', requires=IS_NOT_EMPTY()), Field('image', 'upload'))
:code
Użyjemy tej samej akcji kontrolera, "display_form", pokazanej powyżej.
Po wstawieniu nowego rekrdu, formularz pozwoli na przeglądanie pliku.
Wybierz, na przykład, obraz jpg. Plik ten zostanie pobrany i zapisany jako:
applications/test/uploads/person.image.XXXXX.jpg :code
"XXXXXX" jest losowym identyfikatorem przypisywanym plikowi przez web2py.
content-disposition:inxx
-------
Domyślnie, oryginalna nazwa przesłanego pliku, to
b16encoded
i jest ona użyta do budowania nowej nazwy pliku. Nazwa ta jest pobierana przez
domyślną akcję "download" i używana jest do ustawienia zawartości nagłówka
disposition na oryginalną nazwę pliku.
-------
Tylko rozszerzenie pliku jest zachowywane. Jest to wymóg bezpieczeństwa, ponieważ
nazwa pliku może zawierać znaki specjalne, które mogą posłużyć odwiedzającym do
przeprowdzenie ataków z przeglądaniem katalogów lub do wykonania innych szkodliwych
operacje.
Nowa nazwa pliku zostaje zapisana w
form.vars.image.
Podczas edytowania rekordu przy użyciu formularza UPDATE, pożądane jest udostępnienie
odnośnika do przesłanego pliku i web2py oferuje na to sposób.
Jeśli przekaże się adres URL do konstruktora
SQLFORM poprzez argument
upload,
web2py użyje akcji określonej w tym adresie URL do pobrania pliku. Przyjrzyjmi się
następujacej akcji:
def display_form(): record = db.person(request.args(0)) form = SQLFORM(db.person, record, deletable=True, upload=URL('download')) if form.process().accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' return dict(form=form)def download(): return response.download(request, db)
:code
Wstaw teraz nowy rekord do adresu URL:
http://127.0.0.1:8000/test/default/display_form
:code
Prześlij zdjęcie, zatwierdź formularz i następnie edytuj nowo utworzony rekord,
odwiedzając:
http://127.0.0.1:8000/test/default/display_form/3
:code
(zakładamy tu, że ostatani rekord ma id=3). Formularz wyświetli podgląd obrazu,
tak jak pokazano niżej:
[[image http://www.web2py.com/books/default/image/42/pl6300.png center 300px]]
Formularz ten, po serializacji, wygeneruje następujący kod HTML:
<td><label id="person_image__label" for="person_image">Image: </label></td> <td><div><input type="file" id="person_image" class="upload" name="image" />[<a href="/test/default/download/person.image.0246683463831.jpg">file</a>| <input type="checkbox" name="image__delete" />delete]</div></td><td></td></tr> <tr id="delete_record__row"><td><label id="delete_record__label" for="delete_record" >Check to delete:</label></td><td><input type="checkbox" id="delete_record" class="delete" name="delete_this_record" /></td>
:code
Zawiera on odnośnik umożliwiający pobieranie załadowanego pliku z serwera oraz
pole wyboru do usuwania pliku z rekordu bazy danych (i tym samym przechowywaniu
NULL w polu "image").
Dlaczego ten mechanizm jest udostępniany? Dlaczego trzeba napisać samemu funkcję
pobierania? Bo można wymusić jakiś machanizm autoryzacji w funkcji pobierającej.
Przykład znajduje się w rozdziale 9.
Normalnie przesyłane pliki są przechowywane a katalogu "app/uploads", ale można
określić inną lokalizację:
Field('image', 'upload', uploadfolder='...')
W większości systemów operacyjnych, gdy w folderze znajduje się wiele plików,
dostęp do systemu plików może być powolny. Gdy planuje się przesyłanie więcej niż
1000 plików, można w web2py wymusić przesyłanie plików do podfolderów:
Field('image', 'upload', uploadseparate=True)
#### Przechowywanie oryginalnych nazw plików
W web2py automatycznie zapsisuje się oryginalną nazwę pliku wewnątrz nowej nazwy
UUID pliku i udostępnia się ją, gdy plik jest pobierany z serwera. Po pobraniu
pliku z serwera, oryginalna nazwa pliku jest zapisywana w nagłówku ''content-disposition''
odpowiedzi HTTP. Dzieje się to w sposób przejrzysty, bez konieczności programowania.
Czasami zachodzi konieczność zapisania oryginalnej nazwy pliku w bazie danych.
W takim przypadku trzeba zmodyfikować model i dodać pole do przechowywania tej nazwy:
db.define_table('person', Field('name', requires=IS_NOT_EMPTY()), Field('image_filename'), Field('image', 'upload'))
:code
Potem trzeba zmodyfikować kontroler, tak aby to obsługiwał:
def display_form(): record = db.person(request.args(0)) or redirect(URL('index')) url = URL('download') form = SQLFORM(db.person, record, deletable=True, upload=url, fields=['name', 'image']) if request.vars.image!=None: form.vars.image_filename = request.vars.image.filename if form.process().accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' return dict(form=form) :code
------
SQLFORM nie wyświetla pola "image_filename".
Akcja "display_form" przenosi nazwę pliku
request.vars.image do
form.vars.image_filename, aby można ją było przetworzyć przez
acceptsi zapisać w bazie danych. Zanim funkcja pobierajaca prześle plik, sprawdzi w
bazie danych oryginalną nazwę pliku i użyje ją w nagłówku ''content-disposition''.
------
#### Atrybut
autodelete
autodelete:inxx
SQLFORM, przy usuwaniu rekordu, nie usuwa fizycznie plików przesłanych na
serwer do których odnosi się ten rekord. Jest to spowodowane tym, że web2py nie
wie czy ten sam plik nie jest używany (zlinkowany) z innym tabelami lub używany
w innych celach. Jeśli wie się, że dany plik można bezpiecznie usunąć podczas
usuwania związanego z nim rekordu, można wykonać co następuje:
db.define_table('image', Field('name', requires=IS_NOT_EMPTY()), Field('source','upload',autodelete=True)) :code
Atrybut
autodelete jest ustawiany domyślnie na
False. Gdy ustawi się go
na
True, plik zostanie usunięty przy usuwaniu rekordu.
#### Odnośniki do rekordów
Rozważmy teraz przypadek dwóch tabel połączonych przez referencje:
db.define_table('person', Field('name', requires=IS_NOT_EMPTY())) db.define_table('dog', Field('owner', 'reference person'), Field('name', requires=IS_NOT_EMPTY())) db.dog.owner.requires = IS_IN_DB(db,db.person.id,'%(name)s') :code
Osoba ma psy a każdy pies należy do jakiegoś właściciela, który jest osobą.
Dla każdego właściciela psa odnosimy własciwe pole
db.person.idprzez nazwę
'%(name)s'.
Wykorzystajmy interfejs **appadmin** naszej aplikacji do dodania kilku osób i ich
psów.
Podczas edytowania istniejącej osoby, formularz UPDATE **appadmin** pokazuje
odnośnik do strony z wykazem psów należących do tej osoby. Zachowanie takie może
być replikowane przy pomocy argumentu
linkto.
Argument
linkto musi wskazywać na lokalizator URL nowej akcji, która otrzymuje
ciąg zapytania z
SQLFORM oraz listy odpowiednich rekordów.
Oto przykład:
def display_form(): record = db.person(request.args(0)) or redirect(URL('index')) url = URL('download') link = URL('list_records', args='db') form = SQLFORM(db.person, record, deletable=True, upload=url, linkto=link) if form.process().accepted: response.flash = 'form accepted' elif form.errors: response.flash = 'form has errors' return dict(form=form) :code
a oto strona:
[[image http://www.web2py.com/books/default/image/42/pl6400.png center 300px]]
Jest tu odnośnik "dog.owner". Nazwa tego odnośnika może być zmieniona poprzez
argument
labels, na przykład:
labels = {'dog.owner':"This person's dogs"}:code
Kliknięcie na ten odnośnik spowoduje przekierowanie na:
/test/default/list_records/dog?query=db.dog.owner%3D%3D5 :code
"list_records" jest określoną akcją, gdzie
request.args(0) ustawia nazwę
przywoływanej tabeli a
request.vars.query ustawia ciąg zapytania SQL.
Ciag zapytania zawiera w lokalizatorze URL odpowiednio zakodowaną wartość
"dog.owner=5" (web2py rozkodowuje to automatycznie podczas parsowania URL).
Można łatwo zaimplementować bardzo ogólną akcję "list_records" w następujacy
sposób:
def list_records(): import re REGEX = re.compile('^(\w+).(\w+).(\w+)\=\=(\d+)$') match = REGEX.match(request.vars.query) if not match: redirect(URL('error')) table, field, id = match.group(2), match.group(3), match.group(4) records = db(db[table][field]==id).select() return dict(records=records)
:code
ze związanym widokiem "default/list_records.html":
{{extend 'layout.html'}} {{=records}}
:code
Gdy zwracany jest przez ''select'' zestaw rekordów i serializowany w widoku,
to najpierw jest on konwertowany do obiektu SQLTABLE (nie tego samego co Table)
i następnie jest serializowany do tabeli HTML, gdzie każde pole odpowiada kolumnie
tabeli.
#### Wstępne wypełnianie formularzy
Można zawsze wstępnie wypełnić formularz posługujac się składnią:
form.vars.name = 'fieldvalue' :code
Wyrażenia podobne do powyższego muszą być wstawiane po deklaracji formularza, ale
przed zakceptowaniem tego formularza, niezależnie od tego, czy pole ("name"
w naszym przykładzie) jest jawnie wizualizowane w formularzu, czy nie.
#### Dodawanie dodatkowych elementów formularza do
SQLFORM
Czasem po utworzeniu formularza zachodzi potrzeba dodania do formularza dodatkowego
elementu. Na przykład, można chcieć dodać pole wyboru z potwierdzeniem, że użytkownik
akceptuje regulamin strony internetowej:
form = SQLFORM(db.yourtable) my_extra_element = TR(LABEL('I agree to the terms and conditions'), INPUT(_name='agree',value=True,_type='checkbox')) form[0].insert(-1,my_extra_element) :code
Zmienna
my_extra_element powinna być dostosowana do *formstyle*. W naszym
przykładzie domyślnie przyjęto
formstyle='table3cols'.
Po złożeniu formularza
form.vars.agree będzie zawierał stan pola wyboru, co
może być wykorzystane, na przykład, w funkcji
onvalidation.
####
SQLFORM bez I/O bazy danych
Czasem zachodzi potrzeba wygenerowania formularza z tabeli bazy danych przy użyciu
SQLFORM i odpowiedniego sprawdzenia zgłoszonego formularza, ale bez jakichkolwiek
automatycznych działań INSERT/UPDATE/DELETE na bazie danych. Jest to przypadek
występujacy, na przykład, gdy trzeba przetworzyć pola z uwzględnieniem wartości
innych pól wejściowych. Przypadek ten występuje również, gdy potrzeba wykonać
dodatkową walidację wprowadzonych danych, czego nie można wykonać stosując standardowe
walidatory.
Można to łatwo zrobić poprzez rozbicie:
form = SQLFORM(db.person) if form.process().accepted: response.flash = 'record inserted':code
na:
form = SQLFORM(db.person) if form.validate():
transakcja z jawnym przesłaniem pliku form.vars.id = db.person.insert(dict(form.vars)) response.flash = 'record inserted'
:code
To samo może być zrealizowane dla formularzy UPDATE/DELETE przez rozbicie:
form = SQLFORM(db.person,record) if form.process().accepted: response.flash = 'record updated'
:code
na:
form = SQLFORM(db.person,record) if form.validate(): if form.deleted: db(db.person.id==record.id).delete() else: record.update_record(**dict(form.vars)) response.flash = 'record updated' :code
W przypadku tabeli zawierającej pole typu "upload" ("fieldname"), zarówno
process(dbio=False) jak i
validate() domyślnie dotyczą przechowywania
przesyłanego pliku, jak w przypadku
process(dbio=True).
Nazwę przypisaną przez web2py do przesyłanego pliku można znaleźć w:
form.vars.fieldname :code
### Inne typy formularzy
####
SQLFORM.factory
SQLFORM.factory:inxx
Istnieją przypadki, gdy potrzeba wygenerować fomularz, który nie jest skojarzony
z tabelą bazy danych, ale posiadający pozostałe możliwosci formularza generowanego
przez
SQLFORM, takie jak ładny wygląd oparty na CSS oraz przesyłanie plików
na serwer i zmianianie ich nazwy.
Można do zrobić przez
form_factory. Oto przykład, w którym generowany jest
formularz, wykonywana jest walidacja, przesyłany jest plik i wszystkie dane zostają
zapisane w
session :
def form_from_factory(): form = SQLFORM.factory( Field('your_name', requires=IS_NOT_EMPTY()), Field('your_image', 'upload')) if form.process().accepted: response.flash = 'form accepted' session.your_name = form.vars.your_name session.your_image = form.vars.your_image elif form.errors: response.flash = 'form has errors' return dict(form=form) :code
Obiekt
Field w konstruktorze
SQLFORM.factory() jest w pełni udokumentowany
w [[rozdziale DAL ../06#field_constructor]].
Technika konstruowania w czasie rzeczywistym w SQLFORM.factory() jest następująca:
fields = [] fields.append(Field(...)) form=SQLFORM.factory(*fields):code
Oto widok "default/form_from_factory.html":
{{extend 'layout.html'}} {{=form}} :code
W etykietach pola trzeba używać znaku pokreślenia zamiast spacji lub jawnie przekazać
słownik
labels do
form_factory, tak samo jak się to robi w
SQLFORM.
Domyślnie
SQLFORM.factory generuje formularz, wykorzystując atrybut "id" HTML,
wygenerowany tak jakby formularz został wygenerowany z tabeli o nazwie "no_table".
Zmiane nazwy tej atrapy tabeli można wykonać stosując atrybut
table_namew metodzie
factory:
form = SQLFORM.factory(...,table_name='other_dummy_name') :code
Zmienna
table_name jest niezbędna, jeśli chce się umieścić dwie "fabryki"
generujące formularze w tej samej tabeli i chce się uniknąć konfliktów CSS.
##### Przesyłanie plików w SQLFORM.factory
[TODO]
#### Jeden formularz dla wielu tabel
Zdarza się często, że mając dwie tabele (na przykład 'client' i 'address'), które
są połączone z sobą przez odniesienie, chce się utworzyć pojedynczy formularz, który
umożliwia wstawianie do obu tych tabel (czyli o kliencie i jego adresach). Oto
jak to zrobić:
model:
db.define_table('client', Field('name')) db.define_table('address', Field('client','reference client', writable=False,readable=False), Field('street'),Field('city'))
:code
kontroler:
def register(): form=SQLFORM.factory(db.client,db.address) if form.process().accepted: id = db.client.insert(db.client._filter_fields(form.vars)) form.vars.client=id id = db.address.insert(db.address._filter_fields(form.vars)) response.flash='Thanks for filling the form' return dict(form=form) :code
SQLFORM.factory wykonuje wspólny formularz przy użyciu pól
publicznych z obu tabel i dziedziczy też ich walidatory.
Wspólny formularz akceptuje dwie metody wstawiające, kilka danych z pierwszej
tabeli i kilka danych z drugiej.
-------
Działa to tylko wtedy, gdy tabele nie mają wspólnych pól.
-------
#### Formularze potwierdzające
confirm:inxx
formularze potwierdzające:inxx
Często potrzebny jest formularz potwierdzający wybór. Akceptowanie tego formularza
jest ostatecznym potwierdzeniem wyboru. Formularz może mieć dodatkowe opcje, które
łaczą z innymi stronami internetowymi. web2py dostarcza prosty sposób na wykonanie
tego:
form = FORM.confirm('Are you sure?') if form.accepted: do_what_needs_to_be_done() :code
Formularz potwierdzający nie musi i nie może wywoływać
.accepts lub
.process,
ponieważ jest to wykonywane wewnętrznie. Można dodać przycisk z odnośnikami do
formularza potwierdzającego w formularzu słownika
{'value':'link'}:
form = FORM.confirm('Are you sure?',{'Back':URL('other_page')}) if form.accepted: do_what_needs_to_be_done()
:code
#### Formularz do edytowania słownika
Wyobraźmy sobie system, który przechowuje opcje konfiguracyjne w słowniku:
config = dict(color='black', language='English')
:code
i potrzebny jest formularz umożliwiający odwiedzającym modyfikowanie tego słownika.
Można to zrealizować tak:
form = SQLFORM.dictform(config) if form.process().accepted: config.update(form.vars) :code
Formularz będzie wyświetlał pole INPUT dla każdej pozycji słownika. Wykorzystuje
on klucze słownika jako nazwy i etykiety pól INPUT i bieżące wartości do wnioskowania
typów (string, int, double, date, datetime, boolean).
Dział to doskonale, ale pozostawiamy Tobie wykonanie logiki realizującej utrwalenie
konfiguracji słownika. Na przykład można przechowywać
config w sesji:
session.config or dict(color='black', language='English') form = SQLFORM.dictform(session.config) if form.process().accepted: session.config.update(form.vars) :code
### CRUD
CRUD:inxx
crud.create:inxx
crud.update:inxx
crud.select:inxx
crud.search:inxx
crud.tables:inxx
crud.delete:inxx
Jednym z najnowszych dodatków do web2py jest API CRUD (Create/Read/Update/Delete)
stworzony na szczycie SQLFORM.
CRUD tworzy SQLFORM, ale upraszcza kodowanie, ponieważ obejmuje tworzenie formularza,
przetwarzanie formularza, powiadomienie i przekierowanie - wszystko w jednej funkcji.
Na samym początku trzeba podkreślić, że CRUD różni się od innych interfejsów
API web2py, jakie używaliśmy do tej pory tym, że nie jest on domyślnie udostępniany.
Jest to ważne. Trzeba go też połączyć z określoną bazą danych. Na przykład:
from gluon.tools import Crud crud = Crud(db) :code
Zdefiniowany powyżej obiekt
crud dostarcza następujące API:
crud.tables:inxx
crud.create:inxx
crud.read:inxx
crud.update:inxx
crud.delete:inxx
crud.select:inxx .
-
crud.tables() zwraca listę tabel zdefiniowanych w bazie danych.
-
crud.create(db.tablename) zwraca formularz tworzący rekord w tabeli
tablename.
-
crud.read(db.tablename, id) zwraca formularz odczytujący rekord
id w tabeli
tablename i.
-
crud.update(db.tablename, id) zwraca formularz aktualizujący rekordu
id w
tablename.
-
crud.delete(db.tablename, id) usuwa rekord.
-
crud.select(db.tablename, query) zwraca listę rekordów wybranych z tabeli.
-
crud.search(db.tablename) zwraca krotkę
(form, records) gdzie
form jest formularzem wyszukiwania a
records jest listą rekordów wybranych na
podstawie zgłoszonego formularza wybierającego.
-
crud() zwraca jeden z powyższych wyników w oparciu o
request.args().
Na przykład, następujaca akcja:
def data(): return dict(form=crud())
:code
udostępniać będzie następujace adresy URL:
http://.../[app]/[controller]/data/tables http://.../[app]/[controller]/data/create/[tablename] http://.../[app]/[controller]/data/read/[tablename]/[id] http://.../[app]/[controller]/data/update/[tablename]/[id] http://.../[app]/[controller]/data/delete/[tablename]/[id] http://.../[app]/[controller]/data/select/[tablename] http://.../[app]/[controller]/data/search/[tablename]
:code
Jednakże akcja:
def create_tablename(): return dict(form=crud.create(db.tablename)) :code
udostępniać będzie tylko metodę
create:
http://.../[app]/[controller]/create_tablename
:code
Natomiast akcja:
def update_tablename(): return dict(form=crud.update(db.tablename, request.args(0))) :code
będzie udostępniać tylko metodę
update:
http://.../[app]/[controller]/update_tablename/[id] :code
i tak dalej.
Zachowanie CRUD można dostosować na dwa sposoby: przez ustawienie kilku atrybutów
obiektu
crud lub przez przekazanie dotakowych parametrów do każdej jego metody.
#### Ustawienia
Oto kompletny wykaz aktualnych atrybutów CRUD, ich wartości domyślnych i znaczeń:
- aby wymusić uwierzytelnianie we wszystkich formularzach CRUD:
crud.settings.auth = auth :code
Użycie jest wyjaśnione w rozdziale 9.
- aby określić kontroler definiujący funkcję
data, która zwraca obiekt
crud:
crud.settings.controller = 'default'
:code
- aby określić adres URL przekierowania po pomyślnym utworzoniu rekordu:
crud.settings.create_next = URL('index')
:code
- aby określić adres URL przekierowania po pomyślnym zaktualizowaniu rekordu:
crud.settings.update_next = URL('index')
:code
- aby określić adres URL przekierowania po pomyślnym usunięciu rekordu:
crud.settings.delete_next = URL('index')
:code
- aby określić adres URL, który ma być użyty do zlinkowania przesłanych plików:
crud.settings.download_url = URL('download') :code
- aby określic dodatkowe funkcje, które mają być wykonane po standardowych procedurach
walidacyjnych dla formularzy
crud.create:
crud.settings.create_onvalidation = StorageList() :code
StorageList jest taki sam jak obiekt
Storage, obydwa są zdefiniowane
w pliku "gluon/storage.py", ale
StorageList ma wartość domyślną
[],
gdy wartością domyślną
Storage jest
None. Ma następującą składnię:
crud.settings.create_onvalidation.mytablename.append(lambda form:....) :code
- aby określić dodatkowe funkcje, które mają być wykonane po standardowych
procedurach dla formularzy
crud.update:
crud.settings.update_onvalidation = StorageList() :code
- aby określić dodatkowe funkcje, które mają być wykonane po zakończeniu
działania formularzy
crud.create:
crud.settings.create_onaccept = StorageList() :code
- aby określić dodatkowe funkcje, które mają być wykonane po zakończeniu działania
formularzy
crud.update:
crud.settings.update_onaccept = StorageList() :code
- aby określić dodatkowe funkcje, które mają być wykonane po zakończeniu działania
formularzy
crud.update, jeśli rekord został usunięty:
crud.settings.update_ondelete = StorageList() :code
- aby określić dodatkowe funkcje, które mają być wykonane po zakończeniu
crud.delete:
crud.settings.delete_onaccept = StorageList()
:code
- aby określic, czy formularze "update" powinny mieć przycisk "delete":
crud.settings.update_deletable = True
:code
- aby określić, czy formularze "update" powinny pokazywać identyfikator edytowanego
rekordu:
crud.settings.showid = False
:code
- aby określić, czy po pomyślnym złożeniu formularzy należy utrzymywać poprzednio
wprowadzone wartości, czy też zresetować formularz do wartości domyślnych:
crud.settings.keepvalues = False :code
-
crud zawsze wykrywa, czy rekord był edytowany przez osoby postronne w czasie
pomiędzy momentem wyświetlania formularza a czasem zgłoszenia. Zachowanie to jest
równoważne z:
form.process(detect_record_change=True)
i jest ustawiane w:
crud.settings.detect_record_change = True :code
i może być zmienione (wyłączone) przez ustawienie zmiennej na
False.
- styl formularza można zmienić przez:
crud.settings.formstyle = 'table3cols' or 'table2cols' or 'divs' or 'ul':code
- można ustawić separator we wszystkich formularzach CRUD:
crud.settings.label_separator = ':'
:code
#### CAPTCHA
Do formularzy można dodać CAPTCHA, stosując tą sama konwencję wyjaśnioną
w rozdziale 9, tak:
crud.settings.create_captcha = None crud.settings.update_captcha = None crud.settings.captcha = None :code
#### Komunikaty
Oto wykaz możliwych do dostosowania komunikatów:
-
crud.messages.submit_button = 'Submit':code
ustawia tekst przycisku "submit" zarówno dla formularzach :create", jak i "update";
-
crud.messages.delete_label = 'Check to delete:':code
ustawia etykietę przycisku "delete" w formularzach "update";
-
crud.messages.record_created = 'Record Created':code
ustawia komunikat fleszowy przy pomyślnym utworzeniu rekordu;
-
crud.messages.record_updated = 'Record Updated':code
ustawia komunikat fleszowy po pomyślnym zaktualizowaniu rekordu;
-
crud.messages.record_deleted = 'Record Deleted':code
ustawia komunikat fleszowy po pomyślnym usunięciu rekordu;
-
crud.messages.update_log = 'Record %(id)s updated':code
ustawia komunikat dziennika po pomyślnym zaktualizowaniu rekordu;
-
crud.messages.create_log = 'Record %(id)s created':code
ustawia komunikat dziennika po pomyślnym utworzeniu rekordu;
-
crud.messages.read_log = 'Record %(id)s read':code
ustawia komunikat dziennika po pomyślnym dostępie do odczytu rekordu;
-
crud.messages.delete_log = 'Record %(id)s deleted':code
ustawia komunikat dziennika po pomyślnym usunięciu rekordu;
------
Obiekty
crud.messages należy do klasy
gluon.storage.Message,
która jest podobna do
gluon.storage.Storage, ale automatycznie tłumaczą swoje
wartości, bez potrzeby używania operatora
T.
------
Komunikaty dziennika są używane tylko i wyłącznie, gdy CRUD jest połączony
z uwierzytelnianiem, tak jak omówiono to w rozdziale 9. Zdarzenia są rejestrowane
w tabeli uwierzytelniania "auth_events".
#### Metody
Zachowanie metod CRUD można również dostosować dla każdego poszczególnego wywołania.
Oto sygnatury metod:
crud.tables() crud.create(table, next, onvalidation, onaccept, log, message) crud.read(table, record) crud.update(table, record, next, onvalidation, onaccept, ondelete, log, message, deletable) crud.delete(table, record_id, next, message) crud.select(table, query, fields, orderby, limitby, headers, **attr) crud.search(table, query, queries, query_labels, fields, field_labels, zero, showall, chkall) :code
-
table jest tabelą DAL lub
tablename przedmiotowej metody.
-
record i
record_id są identyfikatorami rekordu przedmiotowej metody.
-
next jest adresem URL przekierowania po pomyślnym wykonania metody.
Jeśli adres URL zawiera podłańcuch "[id]", to zostanie on zastąpiony przez identyfikator
aktualnie utworzonego lub zaktualizowanego rekordu.
-
onvalidation ma tą samą funkcję, co SQLFORM(..., onvalidation)
-
onaccept jest funkcją, która ma być wywołana po rozpatrzeniu i zaakceptowaniu
złożenego formularza, ale przed przekierowaniem.
-
log jest komunikatem dziennikowym. Komunikat dziennika w CRUD widzi zmienne
w słowniku
form.vars, takie jak "%(id)s".
-
message jest to komunikat fleszowy po zaakceptowaniu formularza.
-
ondelete jest wywoływane w miejsce
onaccept, gdy rekod zostaje usunięty w formularzu "update".
-
deletable określa, czy formularz "update" powinien mieć opcje usuwania.
-
query jest zapytaniem, które ma być zastosowane do wyboru rekordów.
-
fields jest to lista pól do wyboru.
-
orderby określa, w jakiej kolejności powinny być zestawiane wybierane rekordy (zobacz [[DAL rozdział ../06#orderby]]).
-
limitby określa zakres wybranych rekordów, które mają być wyświeltlone (zobacz rozdział 6).
-
headers - słownik z nazwami nagłówka tabeli.
-
queries- lista taka jak
['equals', 'not equal', 'contains'] zawierająca metody dopuszczalne w formularzu wyszukiwania.
-
query_labels - słownik taki jak
query_labels=dict(equals='Equals') podajacy nazwy metod wyszykiwania.
-
fields - lista listą pól, które maja byc wylistowane w widżecie wyszzukiwania.
-
field_labels - słownik mapujący nazwy pól na etyliety.
-
zero - domyślnie "choose one", jest używane jako domyślna opcja dla pól rozwijanych w widżecie wyszukiwania.
-
showall - ustawienie na
True powoduje zwracanie wierszy jak dla każdego
zapytania w pierwszym wywołaniu (dodano w wersjach po 1.98.2).
-
chkall - ustawienie na
True powoduje włączenie wszystkich pól wyboru
w formularzu wyszukiwania (dodano w wersjach po 1.98.2).
Oto przykład zastosowania w pojedynczej funkcji kontrolera:
assuming db.define_table('person', Field('name'))
def people(): form = crud.create(db.person, next=URL('index'), message=T("record created")) persons = crud.select(db.person, fields=['name'], headers={'person.name': 'Name'}) return dict(form=form, persons=persons) :code
Oto następna bardzo ogólna funkcja kontrolera, która pozwala wyszukiwać, tworzyć
i edytować każdy rekord z dowolnej tabeli, gdzie nazwa tabeli przekazuje
request.args(0):
def manage(): table=db[request.args(0)] form = crud.update(table,request.args(1)) table.id.represent = lambda id, row: A('edit:',id,_href=URL(args=(request.args(0),id))) search, rows = crud.search(table) return dict(form=form,search=search,rows=rows) :code
Proszę zwrócić uwagę na linię
table.id.represent=..., która powiadamia web2py,
aby zmienił reprezentację pola
id i zamiast tego wyświetlił odnośnik do samej
strony i przekazał identyfikator jako
request.args(1), co włącza utworzoną
stronę do strony aktualizacyjnej.
#### Wersjonowanie rekordów
Zarówno SQLFORM jak i CRUD dostarczają narzędzie dla wersjonowania rekordów bazy danych:
Jeśli chce się mieć tabelę (db.mytable), która wymaga pełnej historii zmian, można
to zrobić następująco:
form = SQLFORM(db.mytable, myrecord).process(onsuccess=auth.archive)
:code
form = crud.update(db.mytable, myrecord, onaccept=auth.archive) :code
Metoda
auth.archive definuje nową tabelę o nazwie **db.mytable_archive**
(nazwa pochodzi od nazwy tabeli do której się odwołuje) i aktualizuje rekord,
przechowując kopie rekordu (w postaci z przed aktualizacji) w utworzonej tabeli
archiwalnej, w tym odniesienie do bieżącego rekordu.
Ponieważ rekord jest w rzeczywistości aktualizowany (archiwizowany jest tylko
jego poprzedni stan), odniesienia nie są nigdy zrywane.
Wszystko dzieje się "pod maską". Jeśli chce się uzyskać dostęp do tabeli archiwalnej,
trzeba zdefiniować to w modelu:
db.define_table('mytable_archive', Field('current_record', 'reference mytable'), db.mytable) :code
Proszę zauważyć, że tabela ta rozszerza
db.mytable (w tym wszystkie jej pola)
i dodaje odniesienia do
current_record.
Metoda
auth.archive nie wykonuje znacznika czasu zapisywanego rekordu, chyba
że oryginalna tabela ma pola znacznika czasu, na przykład:
db.define_table('mytable', Field('created_on', 'datetime', default=request.now, update=request.now, writable=False), Field('created_by', 'reference auth_user', default=auth.user_id, update=auth.user_id, writable=False),
:code
Nie ma nic szczególnego w tych polach i można nadawać im dowolną nazwę. Zostają
wypełnione przed archiwizacją rekordu i są archiwizowane w każdej kopii rekordu.
Nazwa tabeli archiwalnej i ewentualnie nazwa odnoszonego pola mogą być zmieniane
w ten sposób:
db.define_table('myhistory', Field('parent_record', 'reference mytable'), db.mytable)
...
form = SQLFORM(db.mytable,myrecord) form.process(onsuccess = lambda form:auth.archive(form, archive_table=db.myhistory, current_record='parent_record'))
:code
### Niestandardowe formularze
Jeśli formularz jest tworzony z SQLFORM, SQLFORM.factory lub CRUD, to istnieje
wiele sposobów na osadzenie formularzy w widoku umożliwiającym różnorakie dostosowanie.
Rozważmy dla przykładu następujacy model:
db.define_table('image', Field('name', requires=IS_NOT_EMPTY()), Field('source', 'upload'))
:code
i akcję aktualizującą:
def upload_image(): return dict(form=SQLFORM(db.image).process()) :code
Najprostrzym sposobem osadzenia formularza w widoku dla
upload_image jest:
{{=form}}
:code
Prowadzi to do standardowego układu tabeli. Jeśli chce się użyć innego układu,
można podzielić formularz na elementy:
{{=form.custom.begin}} Name: <div>{{=form.custom.widget.name}}</div> File: <div>{{=form.custom.widget.source}}</div> {{=form.custom.submit}} {{=form.custom.end}} :code
gdzie
form.custom.widget[fieldname] zostanie zserializowane na widżet właściwy
dla pola. Jeśli formularz zostanie zgłoszony i wypełniony błędami, są one jak
zwykle dołączane poniżej widżetów.
Powyższy formularz pokazuje ta ilustracja:
[[image http://www.web2py.com/books/default/image/42/pl6500.png center 300px]]
Podobny efekt można by uzyskać bez stosowania własnego formularza:
SQLFORM(...,formstyle='table2cols')
:code
lub w przypadku formularzy CRUD przez zastosowanie następujacych parametrów:
crud.settings.formstyle='table2cols' :code
Inne możliwości dla
formstyle, to "table3cols" (domyślnie), "divs" i "ul".
Jeśli nie chce się stosować widżetów serializowanych przez web2py, można zamienić
je na kod HTML. Istnieje kilka zmiennych, które można do tego wykorzystać:
-
form.custom.label[fieldname] zawiera etykietę dla pola.
-
form.custom.comment[fieldname] zawiera komentarz dla pola.
-
form.custom.dspval[fieldname] form-type i field-type zależne od reprezentacji
wyświetlania pola.
-
form.custom.inpval[fieldname] form-type i field-type zalezne od wartości,
jakie maja być uzyte w kodzie pola.
Jeśli formularz ma
deletable=True, to powinno się również wstawić:
{{=form.custom.delete}}
:code
dla wyświetlania pól wyboru usuwania.
Ważne jest, aby postępować z niżej opisaną konwencją.
#### Konwencje CSS
Zanaczniki generowane w formularzach przez SQLFORM, SQLFORM.factory i CRUD
ściśle spełniają konwencję nazewniczą CSS, która może być (a właściwie powinna
być) wykorzystana przy dalszym dostosowywaniu formularzy.
Tabela "mytable" i pole "myfield" typu "string", renderowane domyślnie
przez:
SQLFORM.widgets.string.widget
:code
będą wyglądać tak:
<input type="text" name="myfield" id="mytable_myfield" class="string" /> :code
Proszę zauważyć, że:
- klasa znacznika INPUT jest taka sama, jak typ pola. Jest to bardzo ważne dla
działania kodu jQuery w "web2py_ajax.html". Daje to pewność, że można mieć tylko
liczby w polach typu "integer" i "double" oraz, że pola "time", "date" i "datetime"
wyświetlają okienko "calendar/datepicker";
- id jest nazwą klasy plus nazwa pola, połączone znakiem podkreślenia. Zapewnia
to unikatowość odniesienia do pola poprzez, na przykład,
jQuery('#mytable_myfield') i manipulowanie arkuszem stylów pola lub powiązanie akcji ze zdarzeniami pola
(focus, blur, keyup itd.);
-
name to, jak można się spodziewać, nazwa pola.
#### Ukrywanie komunikatów o błędach
hideerror:inxx
Czasem zachodzi potrzeba wyłaczenia automatycznego umieszczania komunikatów o błędach
i wyświetlania tych komunikatów w innym miejscu niż domyślnie. Można to łatwo
zrobić.
- W przypadku FORM lub SQLFORM, przekaż
hideerror=True do metody
accepts.
- W przypadku CRUD, ustaw
crud.settings.hideerror=True
Można również zmodyfikować widoki, aby wyświetlać komunikaty błędów (ponieważ
nie są one już wyświetlane automatycznie).
Oto przykład, w którym komunikaty błędów są wyswietlane powyżej formularza
a nie w formularzu.
{{if form.errors:}} Your submitted form contains the following errors: <ul> {{for fieldname in form.errors:}} <li>{{=fieldname}} error: {{=form.errors[fieldname]}}</li> {{pass}} </ul> {{form.errors.clear()}} {{pass}} {{=form}} :code
Ilustruje to ten obrazek:
[[image http://www.web2py.com/books/default/image/42/pl6600.png center 300px]]
Mechanizm ten działa również w niestandardowych formularzach.
### Walidatory
walidatory:inxx
Walidatory, to klasy stosowane do sprawdzania pól wejściowych (w tym formularzy
generowanych z tabel bazy danych).
W zaawansowanych formularzach pochodzących z SQLFORM, walidatory tworzą widżety
takie jak rozwijane menu i podglądy innych tabel.
Oto przykład używania walidatora w
FORM:
INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10)):code
Tu natomiast jest przykład zastosowania walidatora dla pola tabeli:
db.define_table('person', Field('name')) db.person.name.requires = IS_NOT_EMPTY() :code
Walidatory zawsze są przypisywane przy zastosowaniu atrybutu
requires pola.
Pole może mieć pojedynczy walidator lub wiele walidatorów. Wiele walidatorów jest
przypisywanych w postaci listy:
db.person.name.requires = [IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.name')] :code
Walidatory są normalnie wywoływane automatycznie przez funkcję
accepts i
processw
FORM lub inny pomocniczy obiekt HTML zawierający formularz. Wywoływane są
one w kolejności, w jakiej są listowane.
Można również wywołać walidatory jawnie w polu:
db.person.name.validate(value)
co zwraca krotkę
(value,error), w której
error ma wartość
None
w przypadku braku wartości walidacyjnej.
Wbudowane walidatory mają konstruktory pobierające opcjonalny argument:
IS_NOT_EMPTY(error_message='cannot be empty') :code
error_message pozwala zastąpić domyślny komunikat błędu dla każdego walidatora.
Oto przykład walidatora na tabeli bazy danych:
db.person.name.requires = IS_NOT_EMPTY(error_message='fill this!') :code
gdzie mamy dostępny operator translacji
T, aby umożliwić umiędzynarodowienie.
Proszę zauważyć, że domyślny komunikat błędów nie jest tłumaczony.
Trzeba pamiętać, że walidatory, które mogą być użyte z polami typu
list:, to:
-
IS_IN_DB(...,multiple=True)-
IS_IN_SET(...,multiple=True)-
IS_NOT_EMPTY()-
IS_LIST_OF(...)
Ten ostatni może być używany do zastosowania dowolnego walidatora do poszczególnych
elementów w liście.
multiple=(1,1000) wymaga wyboru elementów pomiędzy 1 a 1000.
Wymusza to wybór co najmniej jednego elementu.
#### Walidatory formatu tekstowego
#####
IS_ALPHANUMERIC
IS_ALPHANUMERIC:inxx
Walidator ten sprawdza, czy pole zawiera znaki alfa-numeryczne w zakresie a-z, A-Z lub 0-9.
requires = IS_ALPHANUMERIC(error_message='must be alphanumeric!') :code
#####
IS_LOWER
IS_LOWER:inxx
Ten walidator nie zwraca błędu. Po prostu konwertuje wartość pola na małe litery.
requires = IS_LOWER() :code
#####
IS_UPPER
IS_UPPER:inxx
Ten walidator nie zwraca błędu. Po prostu konwertuje wartość pola na duże litery.
requires = IS_UPPER() :code
#####
IS_EMAIL
IS_EMAIL:inxx
Sprawdza, czy wartość pola wygląda jak adres e-mail. Nie próbuje w celu sprawdzenia
wysyłać wiadomości e-mail.
requires = IS_EMAIL(error_message='invalid email!') :code
#####
IS_MATCH
IS_MATCH:inxx
Walidator ten dopasowuje wartość pola do wyrażenia regularnego i zwraca błąd,
jeśli dopasowanie nie jest zgodne.
Oto przykład zastosowania przy sprawdzeniu poprawności z amerykańskim kodem pocztowym:
requires = IS_MATCH('^\d{5}(-\d{4})?$', error_message='not a zip code'):code
Oto przykład użycia tego walidatora do sprawdzenia adresu IPv4 address (uwaga:
bardziej właściwszym walidatorem do tego celu jest IS_IPV4):
requires = IS_MATCH('^\d{1,3}(.\d{1,3}){3}$', error_message='not an IP address')
:code
W tym przykładzie sprawdzamy poprawność amerykańskiego numeru telefonicznego:
requires = IS_MATCH('^1?((-)\d{3}-?|\(\d{3}\))\d{3}-?\d{4}$', error_message='not a phone number') :code
Więcej informacji o wyrażeniach regularnych Pythona można znaleźć w oficjalnej
dokumentacji Pythona.
IS_MATCH akceptuje opcjonalny argument
strict, z domyślną wartością
False.
Gdy ustawi się go na
True, to porównywany bedzie tylko początek łańcucha:
>>> IS_MATCH('ab', strict=False)('abc') ('abc', None) >>> IS_MATCH('ab', strict=True)('abc') ('abc', 'Invalid expression')
IS_MATCH ma też opcjonalny argument
search, którego domyślna wartość,
to
False. Gdy ustawi się go na
True, to do sprawdzenia wartości będzie
stosowana metoda
search zamiast metody
match.
IS_MATCH('...', extract=True) filtruje i wydziela tylko pierwsze dopasowanie
podłańcucha a nie oryginalną wartość.
#####
IS_LENGTH
IS_LENGTH:inxx
Sprawdza, czy długość łańcucha wartości pola mieści się w określonym zakresie.
Działa zarówno dla wejściowych pól tekstowych i plikowych.
Jego argumentami są:
- maxsize: maksymalna długość (wielkość) (domyślnie = 255)
- minsize: minimalna dopuszczalna długość (wielkośc).
Przykłady:
Sprawdzenie, czy łańcuch tekstowy jest krótszy niż 33 znaki:
INPUT(_type='text', _name='name', requires=IS_LENGTH(32)):code
Sprawdzenie, czy hasło jest dłuższe od 5 znaków:
INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
:code
Sprawdzenie, czy przesyłany plik ma wielkość pomiędzy 1KB a 1MB:
INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) :code
Dla wszystkich typów pól z wyjątkiem plikowych, walidator ten sprawdza długość
łańcucha wartości pola. W przypadku plików, wartością jest
cookie.FieldStorage,
tak więc sprawdzana jest długość danych w pliku, czyli jest to zachowanie, które
można się intuicyjnie spodziewać.
#####
IS_URL
IS_URL:inxx
Odrzuca łańcuch URL, jeśli wystąpi któryś z poniższych warunków:
- łańcuch jest pusty lub None;
- w łańcuchu znajdują się znaki niedopuszczalne w adresach URL;
- łańcuch łamie jakiekolwiek reguły składniowe HTTP;
- schemat określa (jeśli jest określony) coś innego niż 'http' lub 'https';
- nie istnieje domena najwyższego poziomu (jeśli jest określona nazwa hosta).
Powyższe zasady oparte są na standarcie RFC 2616
RFC2616:cite.
Funkcja ta sprawdza tylko składnię adresów URL. Na przykład, nie sprawdza
wskazań URL do rzeczywistego dokumentu lub tego, czy lokalizator URL ma inny
sematyczny sens. Dokleja ona automatycznie na początku adresu schemat 'http://'
w przypadku skróconego adresu URL (np. 'google.ca').
Gdy zastosowany zostanie parametr
mode='generic', to funkcja zmienia zachowanie.
Będzie odrzucać łańcuch URL, jeśli wystąpi któryś z poniższych warunków:
- łańcuch jest pusty lub None;
- w łańcuchu znajdują się znaki niedopuszczalne w adresach URL;
- schemat URL jest nieprawidłowy (jeśli został określony).
Powyższe zasady są oparte na standardzie RFC 2396
RFC2396:cite.
Lista dozwolonych schematów jest dostosowalna przy użyciu parametru allowed_schemes.
Jesłi z listy wykluczy się
None, to odrzucane będą skrócone adresy URL (bez
schematu takiego jak 'http').
Domyślnie schemat adresu URL jest dostosowalny za pomocą parametru
prepend_scheme.
Jeśli
prepend_scheme ustawi się na
None, to poprzedzanie adresu URL schematem
zostanie wyłączone. Adresy URL wymagające przy parsowaniu dodania schematu nadal
będą akceptowane, ale zwracana wartość nie będzie zmodyfikowana.
IS_URL jest kompatybilny ze standardem Internationalized Domain Name (IDN) określonym
w dokumencie RFC 3490
RFC3490:cite. W rezultacie, adresami URL mogą być zwykłe
łańcuchy znaków ASCII jak i łańcuchy UNICODE.
Jeśli element domenowy adresu URL (np. google.ca) zawiera litery ze zbioru
innegi niż US-ASCII, to domena będzie konwertowana do Punycode (określonego w RFC
3492
RFC3492:cite ). IS_URL idzie troche dalej i pozwala, aby w ścieżce
i zapytaniu URL występowały znaki z poza zbioru US-ASCII. Znaki z poza US-ASCII
będa kodowane. Na przykład, spacja będzie kodowana jako '%20'. Znak UNICODE 0x4e86
zostanie zakodowany jako '%4e%86'.
Przykłady:
requires = IS_URL()) requires = IS_URL(mode='generic') requires = IS_URL(allowed_schemes=['https']) requires = IS_URL(prepend_scheme='https') requires = IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https') :code
#####
IS_SLUG
IS_SLUG:inxx
requires = IS_SLUG(maxlen=80, check=False, error_message='must be slug') :code
Jeśli
check jest ustawione na
True, to sprawdzane jest, czy badana
wartość jest nazwą skróconą (''ang. slug''), dopuszczając tylko znaki alfa-numeryczne
i nie powtarzające się myślniki.
Jeśi
check jest ustawione na
False (domyślnie) to badana wartość jest
konwertorowana do nazwy krótkiej.
#### Walidatory daty i czasu
#####
IS_TIME
IS_TIME:inxx
Walidator ten sprawdza, czy pole zawiera wartość będącą czasem w określonym
formacie.
requires = IS_TIME(error_message='must be HH:MM:SS!') :code
#####
IS_DATE
IS_DATE:inxx
Walidator ten sparwdza, czy pole zawiera wartość będącą datą w określonym
formacie. Dobrą praktyka jest stosowanie operatora translacji w celu obsługi
różnych lokalizacji.
requires = IS_DATE(format=T('%Y-%m-%d'), error_message='must be YYYY-MM-DD!') :code
Pełny opis dyrektyw
% znajduje się w opsie walidatora IS_DATETIME.
#####
IS_DATETIME
IS_DATETIME:inxx
Walidator ten sprawdza, czy pole zawiera wartość typu ''datetime'' w określonym
formacie. Dobrą praktyka jest stosowanie operatora translacji w celu obsługi
różnych lokalizacji.
requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'), error_message='must be YYYY-MM-DD HH:MM:SS!'):code
Poniższe symbole można wykorzystać do formatowania łańcucha ''datetime''
(pokazano tu symbol i przykładowy łańcuch):
%Y '1963' %y '63' %d '28' %m '08' %b 'Aug' %b 'August' %H '14' %I '02' %p 'PM' %M '30' %S '59' :code
#####
IS_DATE_IN_RANGE
IS_DATE_IN_RANGE:inxx
Działa bardzo podobnie do poprzedniego walidatora, ale pozwala określić zakres:
requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'), minimum=datetime.date(2008,1,1), maximum=datetime.date(2009,12,31), error_message='must be YYYY-MM-DD!') :code
Pełny opis dyrektyw % znajduje się w opsie walidatora IS_DATETIME.
#### Walidatory zakresu, zbioru i równości
#####
IS_EQUAL_TO
IS_EQUEL_TO:inxx
Sprawdza, czy badana wartość jest równa określonej wartości (która może być zmienną):
requires = IS_EQUAL_TO(request.vars.password, error_message='passwords do not match') :code
#####
IS_NOT_EMPTY
IS_NOT_EMPTY:inxx
Sprawdza, czy zawartość pola nie jest pustym łańcuchem.
requires = IS_NOT_EMPTY(error_message='cannot be empty!') :code
#####
IS_NULL_OR
IS_NULL_OR:inxx
Zdeprecjonowany alias
IS_EMPTY_OR opisany poniżej.
#####
IS_EMPTY_OR
IS_EMPTY_OR:inxx
Czasem zachodzi potrzeba dopuszczenia pustych wartości w polu razem z innymi
wymaganiami. Na przykład pole ,oze być datą, ale może być również puste.
Walidator
IS_EMPTY_OR to umożliwia:
requires = IS_EMPTY_OR(IS_DATE()) :code
#####
IS_EXPR
IS_EXPR:inxx
Jego pierwszym argumentem jest łańcuch zawierający wyrażenie logiczne w kontekście
wartosci zmiennej. Sprawdza on wartość pola, wyrażenie ewaluuje do
True.
Na przykład:
requires = IS_EXPR('int(value)%3==0', error_message='not divisible by 3'):code
Należy najpierw sprawdzić, czy wartość jest liczbą całkowitą tak, aby nie był
zgłaszany wyjątek.
requires = [IS_INT_IN_RANGE(0, 100), IS_EXPR('value%3==0')] :code
#####
IS_DECIMAL_IN_RANGE
IS_DECIMAL_IN_RANGE:inxx
INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot=".")) :code
Konwertuje dane wejściowe do typu decimal języka Python lub generuje błąd, jeśli
liczba dziesiętna się nie mieści (włącznie) w określonym zakresie. Porówanie jest
wykonywane w arytmetyce dziesiętnej języka Python.
Minimalnymi i maksymalnymi ograniczeniami może być odpowiednio None, nie mniej
lub nie więcej.
Argument
dot jest opcjonalny i pozwala na umiędzynarodowienie symbolu używanego
do separacji dziesiętnej.
#####
IS_FLOAT_IN_RANGE
IS_FLOAT_IN_RANGE:inxx
Sprawdza, czy wartość pola jest liczbą zmienno-przecinkową w zdefiniowanym zakresie,
0 <= value <= 100 w poniższym przykładzie:
requires = IS_FLOAT_IN_RANGE(0, 100, dot=".", error_message='too small or too large!') :code
Argument
dot jest opcjonalny i pozwala na umiędzynarodowienie symbolu używanego
do separacji dziesiętnej.
#####
IS_INT_IN_RANGE
IS_INT_IN_RANGE:inxx
Sprawdza, czy wartość pola jest liczbą całkowitą w zdefiniowanym zakresie,
0 <= value < 100 w poniższym przykładzie:
requires = IS_INT_IN_RANGE(0, 100, error_message='too small or too large!') :code
#####
IS_IN_SET
IS_IN_SET:inxx
multiple:inxx
W SQLFORM (i siatkach) walidator ten automatycznie ustawia pole formularza na pole
z opcjami (czyli na pole rozwijanego menu).
IS_IN_SET sprawdza, czy wartości pól są w zbiorze:
requires = IS_IN_SET(['a', 'b', 'c'],zero=T('choose one'), error_message='must be a or b or c') :code
Argument
zero jest opcjonalny i określa tekst domyślnie wybieranej opcji,
która nie została zaakceptowana przez sam walidator
IS_IN_SET. Jeśli nie
chcesz opcji "choose one", ustaw
zero=None.
Elementy walidatora zbioru można łączyć z walidatorem numerycznym, tak długo, jak
IS_IN_SET jest pierwszym elementem listy. Spowoduje to wymuszenie konwersji
przez walidator do typu numerycznego. Tak więc, IS_IN_SET może poprzedzać
IS_INT_IN_RANGE (który konwertuje wartość do liczby całkowitej) lub
IS_FLOAT_IN_RANGE (który konwertuje wartość do liczby zmienno-przecinkowej).
Na przykład:
requires = [ IS_IN_SET([2, 3, 5, 7],IS_INT_IN_RANGE(0, 8), error_message='must be prime and less than 10')]:code
[[checkbox_validation]]
###### Sprawdzanie pól wyboru
Do wymuszenia wypełnienia pola wyboru w formularzu (jak na przykład przy potwierdzaniu
regulaminu), można użyć tego:
requires=IS_IN_SET(['on'])
:code
###### Słowniki i krotki z IS_IN_SET
Można również uzyć słownika lub listy krotek do wykonania bardziej opisowej listy
rozwijanej:
Przykład użycia słownika: requires = IS_IN_SET({'A':'Apple','B':'Banana','C':'Cherry'},zero=None)
Przykład użycia listy krotek: requires = IS_IN_SET([('A','Apple'),('B','Banana'),('C','Cherry')]) :code
#####
IS_IN_SET a tagowanie
Walidator
IS_IN_SET ma opcjonalny atrybut
multiple=False. Jeśli ustawi
się go na
True, to w polu będzie można przechowywać wiele wartości. Pole
powinno być typu
list:integer lub
list:string. Referencje
multiplesa obsługiwane automatycznie przy tworzeniu i aktualizowaniu formularzy, ale
są niewidoczne dla DAL. Sugerujemy korzystanie z wtyczki ''multiselect'' jQuery
do renderowania wielowartosciowych pól.
------
Proszę zauważyć, że gdy
multiple=True, walidator
IS_IN_SET będzie
akceptował
zero lub więcej wartości, czyli będzie akceptował pole, gdy nic nie
będzie wybrane. Opcja
multiple może również być krotką krptka w postacji
(a,b) gdzie
a i
b to minimaln i maksymalna (z wyłączeniem) liczba
elementów, które mogą być odpowiednio wybrane.
------
#### Walidatory złożoności i bezpieczeństwa
#####
IS_STRONG
IS_STRONG:inxx
Wymusza pewną złożoność łańcucha w polu (zazwyczaj polu hasła)
Przykład:
requires = IS_STRONG(min=10, special=2, upper=2) :code
gdzie
-
min jest minimalna długością łańcucha;
-
special jest minimalną liczbą znaków specjalnych w łańcuchu, takich jak
!@#$%^&*(){}[]-+;
-
upper jest minimalna liczbą znaków dużej litery.
#####
CRYPT
CRYPT:inxx
Jest to też filtr. Wykonuje bezpieczny skrót z danych wejściowych i jest używany
do zabezpieczenia haseł w czasie przekazywania ich do bazy danych.
requires = CRYPT() :code
Domyślnie
CRYPT używa 1000 iteracji algorytmu pbkdf2 w połączeniu z SHA512 do
wytworzenia 20-bajtowego skrótu. Starsze wersje web2py używały "md5" lub HMAC+SHA512
w zależności od tego, czy klucz został określony czy nie.
Jeśli określi się klucz,
CRYPT wykorzysta algorytm HMAC. Klucz może zawierać
przedrostek określający algorytm jaki trzeba użyć w HMAC, na przykład SHA512:
requires = CRYPT(key='sha512:thisisthekey') :code
Jest to składnia zalecana. Klucz musi być unikalnym łańcuchem związanym z bazą
danych. Klucza nie można nigdy zmienić. Jeśli zgubi się klucz, poprzednie wartości
skrótów stają się bezużyteczne.
Domyślnie
CRYPT stosuje losową sól, tak więc każdy wynik jest inny. W celu użycia
stałej wartości soli trzeba określić jej wartość:
requires = CRYPT(salt='mysaltvalue'):code
lub nie używać soli:
requires = CRYPT(salt=False) :code
Walidator
CRYPT miesza swoje wejście, co sprawia że jest dość wyjątkowy. Jeśli
zachodzi potrzeba sprawdzenia pola hasła, zanim zostanie zakodowane, można użyć
CRYPT w liscie walidatorów, ale trzeba walidator ten umieścić na końcu listy,
tak aby został wywołany ostatni. Na przykład:
requires = [IS_STRONG(),CRYPT(key='sha512:thisisthekey')] :code
CRYPT pobiera również argument
min_length, z wartoscią domyślną 0.
Wynikowy skrót ma postać
alg$salt$hash, gdzie
alg jest użytym algorytmem
mieszania,
salt jest łańcuchem soli (który może być pusty) a
hash jest
wyjściem algorytmu. W konsekwencji, skrót jest samoidentyfikujący się, dzięki
czemu algorytm może być zmieniany bez unieważniania poprzednich skrótów.
Jednak klucz musi pozostać ten sam.
#### Walidatory specjalne
#####
IS_LIST_OF
IS_LIST_OF:inxx
To właściwie nie jest walidator. Jego przeznaczeniem jest umożliwienie sprawdzania
pól, które zwracają wiele wartości. Jest on wykorzystywany w tych rzadkich przypadkach,
gdy formularz zawiera wiele pól o tej samej nazwie lub pole wielokrotnego wyboru.
Jego jedynym argumentem jest inny walidator a wszystko co robi, to zastosowanie
innego walidatora dla każdego elementu listy. Na przykład, poniższe wyrażenie sprawdza,
czy każdy element listy jest liczbą całkowitą w przedziale 0-10:
requires = IS_LIST_OF(IS_INT_IN_RANGE(0, 10)) :code
Nigdy nie zwraca błędu i nie zawiera komunikatu o błędzie. Błędy generuje wewnętrzny
walidator.
#####
IS_IMAGE
IS_IMAGE:inxx
Walidator ten sprawdza, czy przesyłany poprzez wejście plikowe plik został zapisany
w jednym z wybranych formatów graficznych i czy ma rozmiary (szerokość i wysokość)
w określonych granicach.
Nie sprawdza on maksymalnego rozmiaru pliku (uzyj w tym celu IS_LENGTH). Ddy żadne
dane nie zostaną przesłane walidacja kończy się niepowodzeniem. Obsługuje formaty BMP,
GIF, JPEG, PNG i nie wymaga biblioteki Python Imaging Library.
Kod pochodzi z ref.
source1:cite
Walidator pobiera następujace argumenty:
-
extensions: iterowalna wartość zawierająca dopuszczalne rozszerzenia pliku
graficznego, pisane małymi literami;
-
maxsize: iterowalna wartość maksymalna szerokość i wysokość obrazu;
-
minsize: iterowalna wartość zawierająca minimalna szerokość i wysokość obrazu;
Użyj (-1, -1) jako
minsize aby ominąć sprawdzanie wielkości obrazu.
Oto kilka przykładów:
- Sprawdzenie, czy przesyłany plik zgodny jest z którymś z obsługiwanych formatów graficznych:
requires = IS_IMAGE():code
- Sprawdzenie, czy przesyłany plik, to JPEG albo PNG:
requires = IS_IMAGE(extensions=('jpeg', 'png'))
:code
- Sprawdzenie, czy przesyłany plik, to PNG z rozmiarem nie większym od 200x200 pikseli:
requires = IS_IMAGE(extensions=('png'), maxsize=(200, 200)) :code
- Uwaga: przy wyświetlaniu formularza edycyjnego dla tabeli zawierającej
requires = IS_IMAGE() nie pojawi się pole wyboru
delete, ponieważ usunięcie
pliku spowodowałoby niepowodzenie walidacji. Dla wyświetlenie pola wyboru
delete
użyj takiej walidacji:
requires = IS_EMPTY_OR(IS_IMAGE()) :code
#####
IS_UPLOAD_FILENAME
IS_UPLOAD_FILENAME:inxx
Weryfikator ten sprawdza, czy nazwa i rozszerzenia pliku, przesyłanego za pośrednictwem
wejścia plikowego, zgadza się z określonymi kryteriami.
W żaden sposób nie sprawdza on typu pliku. Jeśli nie zostaną przesłane żadne dane,
to zwraca błąd walidacji.
Jego argumentami są:
-
filename: wyrażenie regularne nazwy pliku (przed kropką);
-
extension: wyrażenie regularne rozszerzenia (po kropce);
-
lastdot: ustala, którą kropkę powinno się traktować jako seperator rozszerzenia
(nazwy pliku):
True wskazuje na ostatnią kropke (np. "file.tar.gz" zostanie
podzielona na "file.tar" i "gz"), gdy
False oznacza pierwszą kropkę (np.
"file.tar.gz" zostanie podzielony na "file" i "tar.gz");
-
case: 0 oznacza utrzymanie stanu; 1 oznacza przekształcenie łańcucha
na małe litery (domyślnie); 2 oznacza przekształcenie łańcucha na duże litery.
Jeśli nie ma kropki, sprawdzanie rozszerzenia będzie wykonane dla pustego łańcucha
a nazwa pliku zostanie wykonana dla całej wartości.
Przykłady:
Sprawdzenie, czy plik ma rozszerzenie pdf (wielkość liter ma znaczenie):
requires = IS_UPLOAD_FILENAME(extension='pdf'):code
Sprawdzenie, czy plik ma rozszerzenie tar.gz i nazwę rozpoczynająca się od ''backup'':
requires = IS_UPLOAD_FILENAME(filename='backup.*', extension='tar.gz', lastdot=False)
:code
Sprawdzenie, czy plik nie ma rozszerzenia i czy jego nazwa zgadza się ze słowem
''README'' (wielkość liter ma znaczenie):
requires = IS_UPLOAD_FILENAME(filename='^README$', extension='^$', case=0) :code
#####
IS_IPV4
IS_IPV4:inxx
Weryfikator ten sprawdza, czy wartość pola jest adresem IP w wersji 4 w postaci
dziesiętnej. można go ustawić na wymuszanie adresów z określonego zakresu.
Wyrażenie regularne dla IPv4 pochodzi z ref.
regexlib:cite
Jego argumentami są:
-
minip - najniższy dopuszczalny adres; akceptuje: **łańcuch znaków**, np. 192.168.0.1;
**iterowalna sekwencję liczb**, np. [192, 168, 0, 1]; **liczbę całkowitą**, np. 3232235521;
-
maxip najwyższ dopuszczalny adres; akceptuje wartości jak wyżej.
Wszystkie trzy przykładowe wartości są takie same, ponieważ adresy są przekształcane
do liczb całkowitych dla włączenia sprawdzania z następującą funkcją:
number = 16777216 * IP[0] + 65536 * IP[1] + 256 * IP[2] + IP[3]:code
Przykłady:
Sprawdzenie prawidłowości adresu IPv4:
requires = IS_IPV4()
:code
Sprawdzenie prawidłowości adresu dla sieci IPv4:
requires = IS_IPV4(minip='192.168.0.1', maxip='192.168.255.255') :code
#### Inne walidatory
#####
CLEANUP
CLEANUP:inxx
Jest to filtr. Nigdy nie agłasza bledów, po prostu usuwa wszystkie znaki, których
dziesietne kodu ASCII nie znajdują się na liście [10, 13, 32-127].
requires = CLEANUP() :code
#### Walidatory bazy danych
#####
IS_NOT_IN_DB
IS_NOT_IN_DB:inxx
######Streszczenie:
IS_NOT_IN_DB(db|set, 'table.field')
Rozważmy następujący przykład:
db.define_table('person', Field('name')) db.person.name.requires = IS_NOT_IN_DB(db, 'person.name') :code
Wymaga to, aby wstawienie nowej osoby było możliwe, tylko jeśli jej nazwa nie
jest już w bazie danych.
Zamiast
db można użyć
set.
Tak jak w przypadku innych walidatorów, to wymaganie jest egzekwowane na poziomie
przetwarzanego formularza, nie na poziomie bazy danych. Oznacza to, że istnieje
nikłe prawdopodobieństwo, że dwóch odwiedzających będzie próbować jednocześnie
wprowadzić rekordy z tą sama wartością dla
person.name, co w rezultacie
doprowadziłoby do zaakceptowania obu rekordów, mimo sytuacji konfliktowej Dlatego
bezpieczniej jest poinformować bazę danych, że pole powinno mieć unikatową wartość:
db.define_table('person', Field('name', unique=True)) db.person.name.requires = IS_NOT_IN_DB(db, 'person.name') :code
Teraz, jeśli wystąpi konflikt, baza danych zgłosi OperationalError i jedno z tych
dwóch wstawień zostanie odrzucone.
Pierwszym argumentem
IS_NOT_IN_DB może być połączenie z bazą danych lub obiekt
Set. W tym drugim przypadku można by sprawdzić tylko zestwa określony przez obiekt
Set.
Kompletna lista argumentów dla
IS_NOT_IN_DB() jest następująca:
IS_NOT_IN_DB(dbset, field, error_message='value already in database or empty', allowed_override=[], ignore_common_filters=True):code
Poniższy kod nie pozwala na rejestrację dwóch osób o tej samej nazwie w przciagu
10 dni:
import datetime now = datetime.datetime.today() db.define_table('person', Field('name'), Field('registration_stamp', 'datetime', default=now)) recent = db(db.person.registration_stamp>now-datetime.timedelta(10)) db.person.name.requires = IS_NOT_IN_DB(recent, 'person.name') :code
#####
IS_IN_DB
IS_IN_DB:inxx
[[validate_IS_IN_DB]]
######Streszczenie:
IS_IN_DB(db|set,'table.value_field','%(representing_field)s',zero='choose one')gdzie trzeci i czwarty argument są opcjonalne.
Możliwe jest również użycie argumentu
multiple=, jeśli typem pola jest lista.
Domyślnie ma on wartość
False. Jeśli ustawi się go na
True lub na krotkę
(min,max) ograniczy się liczbę wybranych wartości. Tak na przykład,
multiple=(1,10)wymusi wybór co najmniej 1 i co najwyżej 10 elementów.
Pozostałe argumenty omówione są poniżej.
######Przykład
Rozważmy następujące tabele i wymagania:
db.define_table('person', Field('name', unique=True)) db.define_table('dog', Field('name'), Field('owner', db.person) db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s', zero=T('choose one')) *or using a Set* db.person.name.requires = IS_IN_DB(db(db.person.id>10), 'person.id', '%(name)s') :code
Jest to wymuszane na poziomie formularzy INSERT/UPDATE/DELETE dla encji 'dog'.
W tym przykładzie wymaga się, aby
dog.owner był prawidłowym identyfikatorem
w polu
person.id w bazie danych
db. Ze względu na ten walidator pole
dog.owner jest prezentowane w postaci rozwijanej listy. Trzeci argumet walidatora
jest łańcuchem, który opisuje elementy w rozwijanej liście. Na przykład, można
spowodować wyświetlanie na liście nazwy osoby,
%(name)s, zamiast identyfikatora
%(id)s.
%(...)s jest zamieniane przez wartość pola w nawiasach dla każdego
rekordu.
Opcja
zero działa bardzo podobnie jak w walidatorze
IS_IN_SET.
Pierwszym argumentem walidatora może być połączenie z bazą danych lub obiekt Set,
tak jak w a
IS_NOT_IN_DB. może to być przydatne, na przykład, gdy chce się
ograniczyć rekody ujmowane na rozwijanej liscie. W tym przykładzie jest to
IS_IN_DBużyty w kontrolerze do dynamicznego ograniczania rekordów, przy każdym wywołaniu
kontrolera:
def index(): (...) query = (db.table.field == 'xyz') #in practice 'xyz' would be a variable db.table.field.requires=IS_IN_DB(db(query),....) form=SQLFORM(...) if form.process().accepted: ... (...):code
Jeśli chce się sprawdzać pole, ale nie chce się stosować rozwijanej listy, trzeba
umieścić walidator w liście:
db.dog.owner.requires = [IS_IN_DB(db, 'person.id', '%(name)s')] :code
_and:inxx
Czasem potrzeba zastosować rozwijaną listę (więc nie można użyć wyżej podanej
składni), ale chce się użyć dodatkowych walidatorów. W tym celu walidator
IS_IN_DBma dodatkowy argument
_and, w którym można wskazać listę innych walidatorów,
jakie chce się zastosować, jeśli sprawdzana wartość przeszła walidację
IS_IN_DB.
Na przykład, dla sprawdzenia wszystkich psów właściciela w
db, które nie znajdują
się w
subset:
subset=db(db.person.id>100) db.dog.owner.requires = IS_IN_DB(db, 'person.id', '%(name)s', _and=IS_NOT_IN_DB(subset,'person.id')) :code
IS_IN_DB ma domyślny argument
distinct, którego domyślną wartością jest
False. Po ustawieniu na
True zabezpieczy się wartość przed powtarzaniem
na rozwijanej liście.
IS_IN_DB pobiera również argument
cache, który działa podobnie jak argument
cache metody select.
#####
IS_IN_DB a tagowanie
tags:inxx
multiple:inxx
Walidator
IS_IN_DB ma opcjonalny atrybut
multiple=False. Jeśli ustawi się
go na
True, można w jednym polu przechowywać wiele wartości. Pole to powinno
być typu
list:reference, tak jak opisano to w rozdziale 6. Tam też omówiony
jest jasny przykład tagowania (określania znaczników HTML). Referencje
multiple
są obsługiwane automatycznie przy tworzeniu i aktualizowaniu formularzy, ale są
niewidoczne dla DAL. Sugerujemy korzystanie z wtyczki ''multiselect'' jQuery do
renderowania wielowartościowych
pól.
#### Własne walidatory
własny walidator:inxx
Wszystkie walidatory wykorzystują poniższy prototyp:
class sample_validator: def __init__(self, *a, error_message='error'): self.a = a self.e = error_message def __call__(self, value): if validate(value): return (parsed(value), None) return (value, self.e) def formatter(self, value): return format(value) :code
Działa to tak. Gdy zostaje wywołana walidacja jakiejś wartości, walidator zwraca
krotkę
(x, y). Jeśli
y wynosi
None, to wartość przechodzi walidację
a
x zawiera sparsowaną wartość. Na przykład, jeśli walidator wymaga, aby wartość
była liczbą całkowitą,
x jest konwertowane do
int(value). Jeśli wartość
nie przechodzi walidacji, to
x zawiera wartość wejściową a
y zawiera komunikat
błędu wyjaśniający niepowodzenie walidacji. Ten komunikat błędu jest używany do
raportowania błędu w formularzu, który nie jest prawidłowy.
Walidatory mogą również zawierać metodę
formatter. Musi ona wykonać odwrotną
konwersję do tej, wykonanej w metodzie
__call__. Na przykład, rozważmy taki
kod źródłowy dla
IS_DATE:
class IS_DATE(object): def __init__(self, format='%Y-%m-%d', error_message='must be YYYY-MM-DD!'): self.format = format self.error_message = error_message def __call__(self, value): try: y, m, d, hh, mm, ss, t0, t1, t2 = time.strptime(value, str(self.format)) value = datetime.date(y, m, d) return (value, None) except: return (value, self.error_message) def formatter(self, value): return value.strftime(str(self.format)) :code
W przypadku sukcesu, metoda
__call__ odczytuje z formularza łańcuch daty
i konwertuje go do obiektu datetime.date używając łańcucha formatujacego określonego
w konstruktorze. Obiekt
formatter pobiera obiekt datetime.date i konwertuje
go do reprezentacji znakowej z wykorzystaniem tego samego formatu.
formatterjest wywołaywany automatycznie w formularzach, ale można również wywołać go jawnie
do konwersji obiektów do ich właściwej reprezentacji. Na przykład:
>>> db = DAL() >>> db.define_table('atable', Field('birth', 'date', requires=IS_DATE('%m/%d/%Y'))) >>> id = db.atable.insert(birth=datetime.date(2008, 1, 1)) >>> row = db.atable[id] >>> print db.atable.formatter(row.birth) 01/01/2008 :code
Gdy wymaganych jest wiele walidatorów (i są one przechowywane w liście), to
wykonywane są one w kolejności a wyjście jednego jest przekazywane do wejścia
nastęþnego walidatora. Łańcuch ten się urywa, gdy walidacja się nie powiedzie.
Odwrotnie, podczas wywołania metody
formatter pola, formatery związanych
walidatorów są również łączone, ale w odrotnej kolejności.
------
Proszę mieć na uwadze, że zamiast indywidualnych walidatorów, można
użyć argumentu
onvalidate metody
form.accepts(...),
form.process(...) i
form.validate(...).
------
#### Walidatory z zależnościami
Zazwyczaj walidatory są ustawiane raz na zawsze w modelach.
Czasem zachodz potrzeba sprawdzenia pola, ale walidator powinien zależeć
od wartortości innego pola. Można to osiągnąć w różny sposób, w modelu lub kontrolerze.
Na przykład, oto strona generująca formularz rejestracyjny, który pyta o nazwę
użytkownika i hasło (dwukrotnie). Żadne z pól nie może być puste a oba hasła muszą
być zgodne:
def index(): form = SQLFORM.factory( Field('username', requires=IS_NOT_EMPTY()), Field('password', requires=IS_NOT_EMPTY()), Field('password_again', requires=IS_EQUAL_TO(request.vars.password))) if form.process().accepted: pass # or take some action return dict(form=form):code
Ten sam mechanizm można zastosować do obiektów FORM i SQLFORM.
### Widżety
Oto lista widżetów dostępnych w web2py:
SQLFORM.widgets.string.widget SQLFORM.widgets.text.widget SQLFORM.widgets.password.widget SQLFORM.widgets.integer.widget SQLFORM.widgets.double.widget SQLFORM.widgets.time.widget SQLFORM.widgets.date.widget SQLFORM.widgets.datetime.widget SQLFORM.widgets.upload.widget SQLFORM.widgets.boolean.widget SQLFORM.widgets.options.widget SQLFORM.widgets.multiple.widget SQLFORM.widgets.radio.widget SQLFORM.widgets.checkboxes.widget SQLFORM.widgets.autocomplete :code
Pierwsze dziesięć z nich, to domyślne widżety dla odpowiednich typów pól.
Widżet "options" używany jest gdy wymaganiami pola są walidatory
IS_IN_SETlub
IS_IN_DB z opcją
multiple=False (domyślne ustawienie).
Widżet "multiple" stosowany jest gdy wymaganiami pola są walidatory
IS_IN_SETlub
IS_IN_DB z opcja
multiple=True.
Widżety "radio" i "checkboxes" nigdy nie są stosowane domyślnie, ale mogą być
ustawione ręcznie.
Widżet "autocomplete" jest specjalny i omówiony jest w następnym rozdziale.
Na przykład, aby uzyskać pole "string" reprezentowane przez pole obszaru tekstowego
trzeba wykonać:
Field('comment', 'string', widget=SQLFORM.widgets.text.widget)
:code
Widżety mogą być również przypisywane do pól, "po fakcie":
db.mytable.myfield.widget = SQLFORM.widgets.string.widget
W pewnych warunkach widżety mogą pobierać dodatkowe argumenty i trzeba określić
ich wartości. W takim przypadku można wykorzystać
lambda:
db.mytable.myfield.widget = lambda field,value: SQLFORM.widgets.string.widget(field,value,_style='color:blue')
Widżety są pomocniczymi fabrykami i ich pierwsze dwa argumenty, to zawsze
fieldi
value. Pozostałe argumenty mogą zawierać zwykłe atrybuty helperów, takie jak
_style,
_class itd. Pewne widżety mgą również zawierać specjalne argumenty.
W szczególności
SQLFORM.widgets.radio i
SQLFORM.widgets.checkboxes pobierają
argument
style (nie mylić z
_style), który można ustawić na "table", "ul"
lub "divs" w celu dopasowania
formstyle formularza.
Można utworzyć nowe widżety lub rozszerzyć widżety istniejące.
SQLFORM.widgets[type] jest klasą a
SQLFORM.widgets[type].widget jest
statyczną funkcją odpowiedniej klasy. Każda funkcja widżetu pobiera dwa argumenty:
obiekt pola i aktualna wartość tego pola. Zwraca ona reprezentację widżetu.
Na przykład, widżet 'string' można ponownie zakodować, w ten sposób:
def my_string_widget(field, value): return INPUT(_name=field.name, _id="%s_%s" % (field._tablename, field.name), _class=field.type, _value=value, requires=field.requires)
Field('comment', 'string', widget=my_string_widget) :code
Wartości
id i
class muszą być zgodne z konwencją opisaną w następnym rozdziale.
Widżet może zawierać swoje własne walidatory, lecz dobra praktyka jest skojarzenie
walidatorów z atrybutem "requires" pola i pobieranie widżetu stamtąd.
#### Widżet autouzupełniania
autouzupełnianie:inxx
Istnieją dwa możliwie zastosowania widżetu autouzupełniania: do autouzupełniania
pola, które pobiera wartość z listy lub do autouzupełniania pola referencyjnego
(gdzie łańcuch mający być autouzupełniony jest reprezentacją odniesienia, które
jest implementowane jako id).
Pierwszy przypadek jest prosty:
db.define_table('category',Field('name')) db.define_table('product',Field('name'),Field('category')) db.product.category.widget = SQLFORM.widgets.autocomplete( request, db.category.name, limitby=(0,10), min_length=2) :code
Gdzie
limitby instruuje widżet, aby wyświetlał jednorazowo nie więcej niż
10 podpowiedzi a
min_length instruuje widżet, aby wykonywał wywołanie zwrotne
Ajax do pobierania podpowiedzi tylko po wpisaniu przez użytkownika co najmniej
2 znaki w polu wyszukiwania.
Drugi przypadek jest bardziej skomplikowany:
db.define_table('category',Field('name')) db.define_table('product',Field('name'),Field('category')) db.product.category.widget = SQLFORM.widgets.autocomplete( request, db.category.name, id_field=db.category.id) :code
W tym przypadku wartość
id_field informuje widżet, że nawet jeśli wartością
mająca być uzupełnioną automatycznie jest
db.category.name, to wartością mającą
być zapisana jest wartość odpowiedniego pola
db.category.id. Opcjonalnym
parametrem jest
orderby, który instruuje widżet, aby posortował podpowiedzi
(domyślnie alfabetycznie).
Widżet ten działa poprzez Ajax. A gdzie wywołanie zwrotne Ajax? W tym widżecie
działa nie jedna magia. Wywołanie zwrotne jest metodą samego obiektu widżetu.
Jak jest udostępniane? W web2py dowolna porcja kodu może wygenerować odpowiedź
przez zgłoszenie wyjątku HTTP. Widżet ten wykorzystuje tą możliwość w następujacy
sposób: najpierw widżet wywołuje Ajax do tego samego adresu URL, z którego wygenerowano
widżet i wstawia specjalny token w request.vars. Jeśli widget pobiera ponownie
instancję, to zgłosi wyjątek HTTP, który odpowie na żądanie. Wszystko to odbywa
sie "pod maską" i jest ukryte dla programisty.
##
SQLFORM.grid i
SQLFORM.smartgrid
-------
Uwaga:
grid i
smartgrid były eksperymentalne w wersjach web2py
wcześniejszych niż 2.0 i były podatne na wyciek informacji. Obecnie nie są już
eksperymentalne, ale w warstwie prezentacji siatki nie mają kompatybilności wstecznej.
Kompatybilność ta zapewniona jest tylko w interfejsach API.
-------
Są to dwa obiekty wysokiego poziomu, które tworzą złożone kontrolki CRUD.
Zapewniają one stronicowanie, możliwość podglądu, wyszukiwanie, sortowanie, tworzenie,
aktualizowanie i usuwanie rekordów z pojedynczego obiektu.
Ponieważ obiekty HTML web2py zbudowne są na podstawie prostszych obiektów, to siatki
tworzące formularze SQLFORM umożliwiające przeglądanie, edytowanie i tworzenie wierszy.
Wiele argumentów siatek jest przekazywanych poprzez ten obiekt SQLFORM. Oznacza to,
że dokumentacja dla SQLFORM (i FORM) jest adekwatna. Na przykład, siatka pobiera
wywołanie zwrotne
onvalidation. Logika przetwarzania siatki ostatecznie przekazuje
to do podstawowej metody
process() obiektu FORM, co oznacza, że należy siegnąć
do dokumentacji
onvalidation obiektów FORM.
Siatka może posiadać różne stany, takie jak edytowanie wiersza. Informacja o stanie
siatki znajduje się w
request.args.
###
SQLFORM.grid
Najprostszym z tych dwóch obiektów jest
SQLFORM.grid. Oto przykład zastosowania:
@auth.requires_login() def manage_users(): grid = SQLFORM.grid(db.auth_user) return locals() :code
co wytwarza następującą stronę:
[[image http://www.web2py.com/books/default/image/42/pl6700.png center 480px]]
Pierwszym argumentem
SQLFORM.grid może być tabela lub zapytanie. Obiekt siatki
zapewnia dostęp do rekordów zgodnych z zapytaniem.
Przed zagłebieniem się w listę argumentów obiektu siatki, konieczne jest zrozumienie
jak to działa. Obiekt ten analizuje
request.args w celu zdecydowania co trzeba
zrobić (przeglądać, wyszukiwać, tworzyć, aktualizować, usuwać itd.). Każdy przycisk
tworzony przez obiekt linkuje tą samą funkcję (w tym przypadku
manage_users),
ale przekazuje różną listę
request.args.
#### Logowanie wymagane domyślnie dla aktualizowania danych
Domyślnie wszystkie adresy URL generowane przez siatkę są podpisane cyfrowo i zweryfikowane.
Oznacza to, że nie można wykonać pewnych akcji (tworzenie, aktualizowanie, usuwanie)
bez zalogowania się. Ograniczenia te można złagodzić:
def manage_users(): grid = SQLFORM.grid(db.auth_user,user_signature=False) return locals() :code
ale nie zalecamy tego.
#### Wiele siatek w jednej funkcji kontrolera
-----
Ze względu na sposób działania siatki, można mieć tylko jedną siatkę w jednej
funkcji kontrolera, chyba że zostaną one osadzone jako komponenty poprzez
LOAD.
Do wykonania domyślnego wyszukiwania, działającego w więcej niż jedna
załadowanej siatce trzeba użyć inne
formname dla każdej z nich.
-----
#### Bezpieczne stosowanie requests.args
Ponieważ funkcja kontrolera zawierajaca siatkę może sama manipulować argumentami
URL (nazywanymi w web2py jako
response.args i
response.vars), siatka musi
wiedzieć, który argument ma być użyty do obługi siatki a który nie.
Oto przykład kodu, który pozwala zarządzać dowolna tabelą:
@auth.requires_login() def manage(): table = request.args(0) if not table in db.tables(): redirect(URL('error')) grid = SQLFORM.grid(db[table],args=request.args[:1]) return locals() :code
Argument
args siatki
grid określa który
request.args powinien zostać
przekazany dalej a który ignorowany przez
grid. W naszym przypadku
request.args[:1] jest nazwą tabeli, którą chcemy zarządzać i która jest obsługiwana
przez samą funkcje
manage, a nie przez
grid. Tak więc,
args=request.args[:1]
informuje siatkę, aby zachowała pierwszy argument URL we wszystkich linkach, które
generuje, dołączając wszystkie argumenty specyficzne dla siatki po tym pierwszym
argumencie.
#### Sygnatura SQLFORM.grid
Pełna sygnatura dla tej siatki jest następująca:
SQLFORM.grid( query, fields=None, field_id=None, left=None, headers={}, orderby=None, groupby=None, searchable=True, sortable=True, paginate=20, deletable=True, editable=True, details=True, selectable=None, create=True, csv=True, links=None, links_in_grid=True, upload='<default>', args=[], user_signature=True, maxtextlengths={}, maxtextlength=20, onvalidation=None, oncreate=None, onupdate=None, ondelete=None, sorter_icons=(XML('↑'), XML('↓')), ui = 'web2py', showbuttontext=True, _class="web2py_grid", formname='web2py_grid', search_widget='default', ignore_rw = False, formstyle = 'table3cols', exportclasses = None, formargs={}, createargs={}, editargs={}, viewargs={}, buttons_placement = 'right', links_placement = 'right' ) :code
-
fields jest listą pól do pobrania z bazy danych. Jest również używana do
określenia, czy pola mają być pokazane w widoku siatki. Jednak nie kontroluje,
co jest wyświetlane w oddzielnym formularzu używanym do edytowania wierszy.
W tym celu trzeba użyć atrybutu
readable i
writalbe pól bazy danych.
Na przykład, w edytowalnej siatce, stłumienie aktualizowania pola można wykonać
tak: przed utworzeniem SQLFORM.grid, trzeba ustawić:
db.my_table.a_field.writable = False db.my_table.a_field.readable = False:code
-
field_id musi być polem tabeli, które będzie wykorzystywane jako ID,
na przykład
db.mytable.id;
-
left jest opcjonalnym wyrażeniem lewego złączenia używanym do budowy
...select(left=...);
-
headers jest słownikiem mapującym 'tablename.fieldname' na odpowienią etykietę
nagłówka, np.
{'auth_user.email' : 'Email Address'};
-
orderby używany jest do ustawiania domyślnego porządku wierszy;
Zobacz [[rozdział DAL ../06#orderby]] (możliwe jest wykorzystywanie wielu pól);
-
groupby służy do grupowania zestawu. Trzeba użyć tej samej składni co w
select(groupby=...);
-
searchable,
sortable,
deletable,
editable,
details,
create ustala odpowiednio, czy można wyszukiwać, sortować, usuwać, edytować, pokazywać
sczegóły i tworzyć nowe rekordy;
-
selectable może zostać wykorzystane do wywołania własnej funkcji na wielu
rekordach (pole wyboru zostanie wstawione w każdym wierszu) np.:
selectable = lambda ids : redirect(URL('default', 'mapping_multiple', vars=dict(id=ids))):code
lub dla przycisków wielu akcji, stosując listę krotek:
selectable = [('button label1',lambda...),('button label2',lambda ...)] :code
-
paginate ustawia maksymalną liczbę wierszy na stronie;
-
csv jeśli ustaione na
True, pozwala pobrać siatke w różnych formatach (wiecej o tym później);
-
links jest używane do wyświetlania nowych kolumn, które mogą być linkowane
z innymi stronami. Argument
links musi być listą
dict(header='name',body=lambda row: A(...)),
gdzie
header jest nagłówkiem nowej kolumny a
body jest funkcją, która
pobiera wiersz i zwraca wartość. Na przykład, wartością jest helper
A(...);
-
links_in_grid, jeśli ustawi się go na
False, linki będą wyświetlane
tylko na stronie "details" i "edit" (a więc nie w głównej siatce);
-
upload jest taki sam jak w SQLFORM. web2py używa akcji z tym adresem URL
do pobrania pliku;
-
maxtextlength ustawia maksymalną długość tekstu, jaki ma być wyświetlony dla
każdej wartości pola, w widoku siatki. Wartość ta może być nadpisywana dla każdego
pola używając
maxtextlengths słownika 'tablename.fieldname':length np.
{'auth_user.email' : 50};
-
onvalidation,
oncreate,
onupdate i
ondelete są funkcjami wywołań
zwrotnych. Wszystkie, ale nie
ondelete, pobieraja na wejściu obiekt formularza;
ondelete pobiera tabelę i id rekordu;
Ponieważ furmularz jest obiektem SQLFORM, który rozszerza FORM, to te wywołania
zwrotne są zasadniczo używane tak, jak opisano to w rozdziałach dotyczących
FORM i SQLFORM;
Oto szkieletowy kod:
def myonvalidation(form): print "In onvalidation callback" print form.vars form.errors= True #this prevents the submission from completing
...or to add messages to specific elements on the form form.errors.first_name = "Do not name your child after prominent deities" form.errors.last_name = "Last names must start with a letter" response.flash = "I don't like your submission"
def myoncreate(form): print 'create!' print form.vars
def myonupdate(form): print 'update!' print form.vars
def myondelete(table, id): print 'delete!' print table, id :code
onupdate i
oncreate są tymi samymi wywołaniami zwrotnymi dostępnymi w SQLFORM.process();
-
sorter_icons jest listą dwóch łańcuchów (lub helperów), które będą użyte do
reprezentowania sortowania opcji dla każdego pola;
-
ui może być ustawiony na wartość równą 'web2py', co spowoduje generowanie
przyjaznych dla web2py nazw klas; może też być ustawiony na wartość
jquery-ui,
co spowoduje generowanie nazw klas przyjaznych dla UI jQuery, ale może to być
również własny zestaw nazw klas dla różnych komponentów siatki:
ui = dict( widget=, header=, content=, default=, cornerall=, cornertop=, cornerbottom='', button='button', buttontext='buttontext button', buttonadd='icon plus', buttonback='icon leftarrow', buttonexport='icon downarrow', buttondelete='icon trash', buttonedit='icon pen', buttontable='icon rightarrow', buttonview='icon magnifier') :code
-
search_widget pozwala zastąpić domyślny widżet wyszukiwania i zalecamy
zapoznania się z kodem źródłowym w "gluon/sqlhtml.py" w celu poznania szczegółów;
-
showbuttontext pozwala na wyświetlanie przycisku bez tekstu (będzie to tylko
ikona)
-
_class jest klasą dla kontenera siatki;
-
exportclasses pobiera słownik krotek: domyślnie jest to zdefiniowane jako:
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'), csv=(ExporterCSV, 'CSV'), xml=(ExporterXML, 'XML'), html=(ExporterHTML, 'HTML'), tsv_with_hidden_cols=(ExporterTSV, 'TSV (Excel compatible, hidden cols)'), tsv=(ExporterTSV, 'TSV (Excel compatible)')) :code
ExporterCSV, ExporterXML, ExporterHTML i ExporterTSV są zdefioniowane w gluon/sqlhtml.py.
Można je wykorzystać do tworzenia własnych eksporterów. Jeśli przekaże się słownik,
taki jak
dict(xml=False, html=False), to wyłączy się eksport formatu xml i html.
-
formargs jest przekazywany do wszystkich obiektów SQLFORM używanych przez
siatkę, podczas gdy
createargs,
editargs i
viewargs są przekazywane
tylko do określonych obiektów SQLFORM (formularzy tworzących, edycyjnych i szczegółowych SQLFORM);
-
formname,
ignore_rw i
formstyle są przekazywane do obiektów SQLFORM
używanych przez siatkę przez formularze tworząće i aktualizujące;
-
buttons_placement i
links_placement pobierają parametr ('right', 'left',
'both'), który ma wpływ na to, gdzie będą umieszczone przyciski (lub odnośniki).
-
deletable,
editable i
details są zwykle wartościami logicznymi, ale
ale mogą być tez funkcjami pobierającymi obiekt
row i decydują o wyświetlaniu
odpowiedniego przycisku.
#### Wirtualne pola w SQLFORM.grid i smartgrid
W wersjach wyższych web2py od 2.6, wirtualne pola są pokazywan w siatkach jak
zwykłe pola, albo domyślnie wyświetlając je wraz ze wszystkimi innymi polami,
albo przez włączenie ich do argumentu
fields. Jednakże wirtualne pola nie są
możliwe do sortowania.
W starszych wersjach web2py vwyświetlanie wirtualnych pól wymaga użycia argumentu
links. Jest to nadal obsługiwane w nowszych wersjach. Jeśli tabela db.t1 ma
pole o nazwie
t1.vfield, które opiera się na wartościach
t1.field1 i
t1.field2, należy zrobić:
grid = SQLFORM.grid(db.t1, ..., fields = [t1.field1, t1.field2,...], links = [dict(header='Virtual Field 1',body=lambda row:row.vfield),...] ) :code
We wszystkich przypadkach, ponieważ
t1.vfield zależy od
t1.field1 i
t1.field2,
pola te muszą być obecne w wierszu. W powyższym przykładzie, gwarantuje to włączenie
t1.field1 i
t1.field2 do argumentu
fields. Alternatywnie, będzie też
działać wyświetlanie wszystkich pól. Można wykluczyć pole z wyświetlania ustawiając
atrybut
readable na
False.
Należy pamiętać, że przy definiowaniu wirtualnego pola, funkcja lambda musi kwalifikować
pola z nazwą bazy danych, ale w argumencie
links nie jest to konieczne.
Tak więc w rozpatrywanym przykladzie pole wirtualne może być zdefiniowane tak:
db.define_table('t1',Field('field1','string'), Field('field2','string'), Field.Virtual('virtual1', lambda row: row.t1.field1 + row.t1.field2), ...) :code
### SQLFORM.smartgrid
SQLFORM.smartgrid wygląda bardzo podobnie jak
grid, w rzeczywistości
zawierając siatkę, ale zostało to zaprojektowane, aby pobierać na wejściu nie
zapytanie, lecz tabelę i w celu przeglądania tej tabeli oraz wyboru powiązanych
tabel.
Rozważmy dla przykładu następującą strukturę tabeli:
db.define_table('parent',Field('name')) db.define_table('child',Field('name'),Field('parent','reference parent'))
:code
W SQLFORM.grid można wykazać wszystkich rodziców:
SQLFORM.grid(db.parent)
:code
wszystkie dzieci:
SQLFORM.grid(db.child)
:code
oraz wszystkich rodziców i wszystkie dzieci w jednej tabeli:
SQLFORM.grid(db.parent,left=db.child.on(db.child.parent==db.parent.id))
:code
W SQLFORM.smartgrid można umieścić wszystkie dane w jednym obiekcie, który
wytwarza obie tabele:
@auth.requires_login() def manage(): grid = SQLFORM.smartgrid(db.parent,linked_tables=['child']) return locals() :code
co wygląda tak:
[[image http://www.web2py.com/books/default/image/42/pl6800.png center 480px]]
Proszę zwrócić uwagę na dodatkowe liniki "children". Można utworzyć dodatkowe
links posługując się zwykłym obiektem
grid, ale wskaże on inną akcję.
W
smartgrid są one tworzone automatycznie i obsługiwane przez ten sam obiekt.
Proszę też zauważyć, że po kliknięciu na link "children" dla danego rodzica, tylko
zostanie pobrana lista dzieci tego rodzica (co jest oczywiste), ale jeśli ktoś
teraz będzie próbował dodać nowe dziecko, wartość rodzica dla nowego dziecka
zostanie automatycznie ustawiona na wybranego rodzica (wyświetlanie w okruszkach
związanych z tym obiektem). Wartość tego pola może być zastąpiona - aby temu zapobiec,
trzeba uczynić go niezapisywalnym (tylko do odczytu):
@auth.requires_login(): def manage(): db.child.parent.writable = False grid = SQLFORM.smartgrid(db.parent,linked_tables=['child']) return locals() :code
Jeśli argument
linked_tables nie jest określony, wszystkie powiązanae tabele
linkowane są automatycznie. W każdym razie, aby uniknąć przypadkowego ujawnienia
danych, zalecamy jawne listowanie tabel, które mają zostać zlinkowane.
Poniższy kod tworzy bardzo zaawansowany intrefejs zarządzania dla wszystkich
tabel w systemie:
@auth.requires_membership('managers'): def manage(): table = request.args(0) or 'auth_user' if not table in db.tables(): redirect(URL('error')) grid = SQLFORM.smartgrid(db[table],args=request.args[:1]) return locals() :code
#### Sygnatura smartgrid
Obiekt
smartgrid ma te same argumenty co
grid oraz kilka dodatkowych,
z pewnymi zastrzeżeniami:
- Pierwszy argument jest tabelą, a nie zapytaniem;
- Istnieje dodatkowy argument
constraints będący słownikiem
'tablename':query,
który może być użyty do dalszego ograniczania dostępu do rekordów wyświetlanych
w siatce 'tablename';
- Istnieje dodatkowy argument
linked_tables, który jest listą nazw tabel,
które powinny zostać powiązane poprzez smartgrid;
-
divider pozwala określić znak, jaki ma być używany w łańcuchu nawigacyjnym
okruszków;
breadcrumbs_class będzie zastosowana do klasy elementu;
- Wszystkie argumenty, oprócz
table,
args,
linked_tables i
user_signatures
mogą być słownikami, tak jak wyjaśniono to niżej.
Przyjrzyjmy się poprzedniej siatce:
grid = SQLFORM.smartgrid(db.parent,linked_tables=['child']) :code
Pozwala ona na dostęp zarówno do
db.parent jak i
db.child.
Poza kontrolkami nawigacyjnymi, dla każdej tabeli,
smarttable nie wnosi nic
innego niż
grid. Oznacza to, że w tym przypadku jeden
smartgrid może stworzyć
jedną siatkę dla rodziców i jedną dla dzieci. Można przekazać do tych siatek
różne zestawy parametrów, na przykład, różne zestawy parametrów
searchable.
Podczas gdy dla
grid przekazujemy wartość logiczną:
grid = SQLFORM.grid(db.parent,searchable=True) :code
dla
smartgrid przekazujemy słownik z wartościami logicznymi:
grid = SQLFORM.smartgrid(db.parent,linked_tables=['child'], searchable= dict(parent=True, child=False)) :code
W ten sposób uczyniliśmy siatkę rodziców możliwą do przeszukiwania, ale dzieci
poszczególnych rozdziców nie mają możliwości przeszukiwania (nie potrzebują one
widżetu wyszukiwania).
### Kontrola dostępu do grid i smartgrid
Obiekty
grid i
smartgrid nie wymuszają automatycznie kontroli dostępu,
tak jak czyni to
CRUD, ale można zintegrować je z
auth stosując jawnie
sprawdzanie uprawnień:
grid = SQLFORM.grid(db.auth_user, editable = auth.has_membership('managers'), deletable = auth.has_membership('managers')):code
lub
grid = SQLFORM.grid(db.auth_user, editable = auth.has_permission('edit','auth_user'), deletable = auth.has_permission('delete','auth_user')) :code
### Liczba mnoga w smartgrid
Obiekt
smartgrid jest jedynym obiektem web2py wyswietlajacym nazwę tabeli
i potrzebuje obsługi zarówni liczby pojedynczej jak i mnogiej. Na przykład,
jeden rodzic może mieć dziecko lub wiele dzieci. Dlatego obiekt
table musi
znać zarówno nazwy w liczbie pojedynczej, jak i mnogiej.
web2py zwykle się je domyśla, ale można ustawić jawnie:
db.define_table('child', ..., singular="Child", plural="Children") :code
lub poprzez:
liczba pojedyncza:inxx
liczba mnoga:inxx
db.define_table('child', ...) db.child._singular = "Child" db.child._plural = "Children" :code
Powinno się je również umiędzynarodowić za pomocą operatora
T.
Wartości dla liczby pojedynczej i mnogiej są następnie wykorzystywane przez
smartgrid`` do dostarczenia prawidłowych nazw nagłówków i linków.