Chapter 3: Przegląd
Przegląd
Uruchomienie
Platforma web2py dostarczana jest w pakietach binarnych dla Windows i Mac OS X. Zawierają one interpreter Pythona, więc nie trzeba go instalować wcześniej. Istnieje również wersja kodu źródłowego, która działa pod Windows, Mac, Linux i innych systemach uniksowych. Pakiet źródłowy nie posiada interpretera Pythona, więc trzeba go mieć wcześniej zainstalowanego na swoim komputerze.
Platforma web2py nie wymaga instalacji. Wystarczy rozpakować pobrany plik zip dla swojego systemu operacyjnego i wykonać odpowiednio plik web2py
.
Na Unix i Linux (dystrybucja źródłowa) uruchom:
python web2py.py
Na OS X (dystrybucja binarna) uruchom:
open web2py.app
Na Windows (dystrybucja binarna) uruchom:
web2py.exe
Na Windows (dystrybucja źródłowa web2py) uruchom:
c:/Python27/python.exe web2py.py
Uwaga, aby uruchomić web2py na Windows z wersji źródłowej, trzeba najpierw zainstalować rozszerzenia win32 Marka Hammonda ze strony projektu pywin32.
Program web2py obsługuje różne opcje linii poleceń, które zostaną omówione później.
Domyślnie przy starcie web2py wyświetla okno startowe a następnie wyświetla widżet GUI z prośbą o wybranie:
- jednorazowego hasła administratora,
- adresu IP interfejsu sieciowego, który ma zostać użyty dla serwera internetowego
- numeru portu dla obsługiwanych żądań.
Domyślnie web2py uruchamia swój serwer pod adresem 127.0.0.1:8000 (port 8000 na localhost), ale można go uruchamiać na dowolnym dostępnym adresie IP i porcie. Można wypytać adres IP swojego interfejsu sieciowego przez otworzenie linii poleceń i wpisanie ipconfig
w Windows albo ifconfig
na OS X i Linux. Od teraz zakładamy, że web2py jest uruchamiany na localhost (127.0.0.1:8000). Użyj 0.0.0.0:80 aby uruchomić web2py publicznie na dowolnym interfejsie sieciowym.
Jeśli nie dostarczy się hasła administratora, to interfejs administracyjny będzie niedostępny. Jest to środek bezpieczeństwa, aby publicznie nie ujawniać interfejsu administracyjnego.
Interfejs administracyjny (aplikacja admin) jest dostępny tylko z localhost, chyba że uruchamia się web2py na serwerze Apache z modułem mod_proxy. Jeśli aplikacja admin wykryje proxy, ciasteczko sesji zostaje ustawione na tryb bezpieczny a logowanie admin nie będzie działało, gdy komunikacja pomiędzy klientem a proxy nie następuje w trybie HTTPS. Jest to środek bezpieczeństwa.
Cała komunikacja pomiędzy klientem a aplikacją admin zawsze musi być lokalna lub szyfrowana. W przeciwnym razie atakujący będzie w stanie wykonać atak.
Po ustawieniu hasła administratora, web2py uruchamia przeglądarkę internetową na stronie:
http://127.0.0.1:8000/
Jeśli komputer nie posiada domyślnej przeglądarki internetowej, trzeba ręcznie otworzyć przeglądarkę i wprowadzić adres URL.
Kliknięcie odnośnika "Interfejs administracyjny" przeniesie Cię do strony logowania interfejsu administracyjnego.
Hasło administratora, to hasło, które wybrało się na starcie. Proszę zauważyć, że jest tylko jeden administrator i dlatego jest tylko jedno hasło administratora. Ze względów bezpieczeństwa, programista jest proszony o wybranie nowego hasła przy każdym uruchomieniu web2py, chyba że jest określona opcja <recycle>. Różni się to od mechanizmu uwierzytelniania w aplikacjach web2py.
Po zalogowaniu administratora w web2py, przeglądarka jest przekierowywana do strony głównej (site).
Strona ta wykazuje wszystkie zainstalowane aplikacje web2py i umożliwia administratorowi zarządzanie nimi. Platforma web2py dostarczana jest z trzema aplikacjami:
- aplikacją admin, to ta która używasz teraz;
- aplikacją examples z interaktywną dokumentacją internetową i repliką oficjalnej witryny internetowej web2py;
- aplikacją welcome. Jest to podstawowy szablon dowolnej aplikacji web2py.
Aplikacja welcome, to tzw. aplikacja szkieletowa. Jest to też aplikacja witająca użytkowników przy rozpoczęciu web2py.
Gotowe do użycia aplikacje web2py są nazywane dodatkami aplikacyjnymi (ang. appliances). Można pobrać wiele dostępnych za darmo aplikacji ze strony dodatków aplikacyjnych[appliances] . Użytkownicy web2py są zachęcani do zgłaszania nowych aplikacji, zarówno w formie otwartego jak i zamkniętego kodu (skomilowanych i spakowanych).
Ze strony interfejsu administracyjnego (aplikacji admin) można wykonać następujące operacje:
- instalować aplikację wypełniając formularz dostępny poprzez przycisk w prawym dolnym rogu strony. Nadaj nazwę aplikacji, wybierz plik zawierający pakiet aplikacji lub podaj adres URL, gdzie aplikacja jest zlokalizowana i kliknij "Wprowadź";
- odinstalowywać aplikację, klikając odpowiedni przycisk. Spowoduje to wyświetlenie strony z potwierdzaniem;
- tworzyć nową aplikację, wybierając nazwę i klikając przycisk "Utwórz";
- pakować aplikację w celach dystrybucyjnych, klikając odpowiedni przycisk. Aplikacja jest pakowana do pliku archiwum TAR i zawiera on wszystkie elementy aplikacji, łącznie z bazą danych. Nie należy rozpakowywać tego pliku – jest on automatycznie rozpakowywany przez web2py podczas instalacji z poziomu admin;
- czyścić tymczasowe pliki aplikacji, takie jak sesje, pliki błędów i pamięci podręcznej;
- włączać i wyłączać każdą aplikację. Gdy aplikacja jest wyłączona, to nie można wywoływać jej zdalnie, ale jest ona dostępna poprzez localhost. Oznacza to, że aplikacja wyłączona jest stale dostępna za serwerem proxy. Aplikacja jest wyłączana poprzez utworzenie w folderze aplikacji pliku o nazwie "DISABLED". Użytkownicy którzy próbują uzyskać dostęp będą otrzymywać stronę błędu 503 HTTP. Można wykorzystać parametr routes_onerror do dostosowania strony błędu.
- edytować aplikację.
Podczas tworzenia nowej aplikacji przy użyciu admin, zaczyna się od sklonowania aplikacji szkieletowej "welcome" w "models/db.py", co tworzy bazę danych SQLite, podłącza do niej nową aplikację, tworzy instancję Auth, Crud i Service oraz je konfiguruje. Dostarcza również "controller/default.py", który udostępnia akcje "index", "download", "user" dla zarządzania użytkownikami oraz "call" dla usług. W dalszej części rozdziału założono, że te pliki zostały usunięte – będziemy tworzyć aplikacje od podstaw.
Platforma web2py dostarczana jest również z kreatorem, opisanym w dalszej części rozdziału, który może pisać alternatywny kod szkieletowy na podstawie układów i wtyczek dostępnych w sieci i na podstawie opisu wysokiego poziomu modeli.
Proste przykłady
Przywitaj się
Oto, na przykład, możemy utworzyć prostą aplikację wyświetlającą użytkownikówi komunikat "Hello from MyApp". Będziemy mogli wywołać tą aplikacje przez "myapp". Dodamy również licznik zliczający ile razy ten sam użytkownik odwiedził tą stronę.
Nową aplikacje można utworzyć wpisując po prostu jej nazwę w formularzu w prawej górnej części strony głównej w interfejsie administracyjnym.
Po naciśnięciu przycisku Utwórz, zostanie utworzona aplikacja jako kopia wbudowanej aplikacji powitalnej.
Aby uruchomić tą aplikację, odwiedź:
http://127.0.0.1:8000/myapp
Teraz mamy już utworzoną kopię aplikacji powitalnej.
W celu edytowania aplikacji kliknij przycisk edytuj dla nowo tworzonej aplikacji.
Strona Edycja aplikacji informuje o tym, co jest wewnątrz aplikacji. Każda aplikacja web2py składa się z określonych plików, które w większości należą do jednej z sześciu kategorii:
- modele: opisują reprezentację danych;
- kontrolery: opisują logikę aplikacji i przetwarzanie;
- widoki: opisują prezentację danych;
- języki: opisują jak przetłumaczyć prezentacje aplikacji na inne języki;
- moduły: moduły Pythona, które należą do tej aplikacji;
- pliki statyczne: statyczne obrazy, pliki CSS[css-w] [css-o] [css-school] , pliki JavaScript[js-w] [js-b] itd.;
- wtyczki: grupy plików zaprojektowane do wspólnego działania.
Wszystko jest zorganizowane według wzorca Model-Widok-Kontroler. Każda sekcja na stronie Edycja aplikacji odnosi się do podfolderu w folderze aplikacji.
Zwróć uwagę, że klikając na nagłówki sekcji przełącza się ich treść. Nazwy folderów dla plików statycznych są również rozwijane.
Każdy plik wykazany w sekcji odpowiada fizycznemu plikowi umieszczonemu w podfolderze. Każda operacja wykonywana na pliku poprzez interfejs admin (tworzenie, edytowanie, usuwanie) może być wykonana bezpośrednio z powłoki przy użyciu ulubionego edytora.
Aplikacja zawiera też inne rodzaje plików (bazę danych, pliki sesji, pliki błędów itd.), ale nie są one wykazane na stronie Edycja aplikacji, ponieważ nie są tworzone lub modyfikowane przez administratora – są one tworzone i modyfikowane przez samą aplikację.
Kontrolery zawierają logikę i przetwarzanie aplikacji. Każdy adres URL zostaje odwzorowany na wywołanie jednej z funkcji w kontrolerach (akcji). Są dwa domyślne kontrolery: "appadmin.py" i "default.py". Kontroler appadmin dostarcza interfejs administracyjny bazy danych – nie potrzebujemy go teraz. Plik "default.py" jest kontrolerem, który potrzebujemy edytować i jest tym, który jest domyślnie wywoływany, gdy żaden kontroler nie jest określony w adresie URL. Edytujmy funkcję "index" w następujący sposób:
def index():
return "Hello from MyApp"
Oto jak wygląda to w edytorze internetowym:
Zapisz to i wróć do strony Edycja aplikacji. Kliknij na odnośnik index dla kontrolera defaul.py, aby odwiedzić nowo utworzoną stronę.
Gdy odwiedzisz adres URL
http://127.0.0.1:8000/myapp/default/index
wywołana zostaje akcja index w domyślnym kontrolerze aplikacji. Zwracany jest ciąg znaków, który zostaje wyświetlony przez przeglądarkę. Wygląda to podobnie do tego:
Teraz edytuj "default.py" i zmień funkcję "index" w następujący sposób:
def index():
return dict(message="Hello from MyApp")
Również na stronie Edycja aplikacji edytuj widok "default/index.html" (plik widoku związany z akcją) i całkowicie wymień istniejącą zawartość tego pliku na to:
<html>
<head></head>
<body>
<h1>{{=message}}</h1>
</body>
</html>
Nasza akcja zwraca słownik definiujący message
a gdy akcja zwraca słownik, web2py wyszukuje widok o nazwie
[controller]/[function].[extension]
i wykonuje go. Tutaj [extension]
jest żądanym rozszerzeniem. Jeśli żadne rozszerzenie nie zostanie określone, to domyślnie przyjęte zostanie rozszerzenie ".html" i tak założyliśmy tutaj. Przy tym założeniu widok jest plikiem HTML, który osadza kod Pythona przy użyciu specjalnych znaczników {{ }}. W szczególności, w tym przykładzie, wyrażenie {{=message}}
instruuje web2py aby wymienił ten kod na wartość message
zwracaną przez akcję. Proszę zwrócić uwagę, że message
nie jest tutaj słowem kluczowym web2py, ale jest zmienną zdefiniowaną w akcji. Do tej pory nie korzystaliśmy z żadnych słów kluczowych web2py.
Jeśli web2py nie znajdzie żadnego żądanego widoku, to stosuje widok "generic.html", który jest dostarczany w każdej aplikacji.
Mac MailGoogle MapsjsonpJeśli określone jest inne rozszerzenie niż "html" (na przykład "json") i plik widoku "[controller]/[function].json" nie zostanie znaleziony, to web2py wyszuka widok "generic.json". Platforma web2py dostarczana jest z widokami generic.html, generic.json, generic.jsonp, generic.xml, generic.rss, generic.ics (dla Mac Mail Calendar), generic.map (do osadzania w Google Maps) i generic.pdf (opartym na fpdf). Te generyczne widoki mogą być indywidualnie modyfikowane w każdej aplikacji i łatwo mogą być dodawane dodatkowe widoki.
Widoki generyczne są narzędziem programistycznym. W środowisku produkcyjnym każda akcja powinna mieć swój własny widok. W rzeczywistości, widoki ogólne są domyślnie dostępne tylko na localhost.
Można również określić widok stosując
response.view = 'default/something.html'
Czytaj wiecej na ten temat w rozdziale 10.
Jeśli powrócisz do "Edycja aplikacji" i klikniesz na index
, to zobaczysz następującą stronę HTML:
Pasek narzędziowy debugowania
W celach debugowania można wstawić do kodu widoku
{{=response.toolbar()}}
a pokaże się kilka użytecznych informacji, łącznie z żądaniem, odpowiedzią i obiektami sesji oraz listą wszystkich zapytań do bazy danych z ich wykazem czasowym.
Policzmy
Dodajmy teraz do naszej strony licznik, który będzie zliczał ile razy ten sam odwiedzający odwiedził tą stronę.
Platforma web2py automatycznie i przejrzyście śledzi odwiedzających wykorzystując sesje i ciasteczka. Dla każdego nowego odwiedzającego jest tworzona sesja i przypisywana jest jej unikalna wartość "session_id". Sesja jest kontenerem dla zmiennych, które są zapisywane po stronie serwera. Ta unikalna wartość identyfikatora jest przypisywana do przeglądarki poprzez ciasteczko. Gdy odwiedzający zażąda innej strony z tej samej aplikacji, przeglądarka odsyła ciasteczko z powrotem, co jest przechwytywane przez web2py i przywracana jest odpowiednia sesja.
Aby wykorzystać sesję, zmodyfikujmy domyślny kontroler:
def index():
if not session.counter:
session.counter = 1
else:
session.counter += 1
return dict(message="Hello from MyApp", counter=session.counter)
Trzeba mieć na uwadze, że counter
nie jest słowem kluczowym web2py, ale session
jest. Pytamy web2py aby sprawdził czy istnieje zmienna counter
w obiekcie sesji i jeśli nie, to ją tworzymy i ustawiamy na 1. Jeśli counter
istnieje, to polecamy web2py aby zwiększył wartość counter o 1. Na koniec przekazujemy tą wartość do widoku.
Bardziej krótszym sposobem na zakodowanie tej samej funkcji jest:
def index():
session.counter = (session.counter or 0) + 1
return dict(message="Hello from MyApp", counter=session.counter)
Zmodyfikujmy teraz widok, dodając linię, która wyświetla wartość licznika:
<html>
<head></head>
<body>
<h1>{{=message}}</h1>
<h2>Number of visits: {{=counter}}</h2>
</body>
</html>
Gdy ponownie odwiedzisz stronę index (i jeszcze raz), to zobaczysz stronę HTML podobną do tej:
Licznik jest związany z indywidualnym odwiedzającym i jest zwiększany za każdym razem,gdy użytkownik ten odwiedzi stronę. Różni odwiedzający zobaczą inny licznik.
Wypowiedz moje imię
Teraz utworzymy dwie strony (first i second), gdzie pierwsza strona zawiera formularz pytający odwiedzającego o imię i przekierowujący do drugiej strony, która wita odwiedzających po imieniu.
Napiszmy odpowiednie akcje w domyślnym kontrolerze:
def first():
return dict()
def second():
return dict()
Następnie utwórzmy widok "default/first.html" dla pierwszej akcji i wprowadźmy tam ten kod:
{{extend 'layout.html'}}
<h1>Jak masz na imię?</h1>
<form action="second">
<input name="visitor_name" />
<input type="submit" />
</form>
Na koniec, tworzymy widok "default/second.html" dla drugiej akcji:
{{extend 'layout.html'}}
<h1>Witaj {{=request.vars.visitor_name}}</h1>
W obu widokach dokonaliśmy ich rozszerzenia o bazowy widok "layout.html", który dostarczany jest wraz z web2py. Widok układu zachowuje zgodny wygląd obydwu stron. Plik układu można łatwo edytować i wymieniać, ponieważ zawiera kod HTML.
Jeśli teraz odwiedzisz pierwszą stronę, to wpisz swoje imię:
i wyślij formularz, a dostaniesz powitanie:
Zgłoszenia zwrotne
Mechanizm wysyłania formularzy, który kiedyś był powszechny, nie jest dobrą praktyką. Wszystkie dane wejściowe powinny być sprawdzane i w powyższym przykładzie ciężar walidacji spadnie na akcję second. Zatem akcja, która wykonuje walidację jest różna od akcji generującej formularz. To zwykle powoduje nadmiarowość kodu.
Lepszym wzorcem wysyłania formularzy jest przesyłanie formularza do tej samej akcji, która go wygenerowała, w naszym przykładzie akcji "first". Akcja "first" powinna otrzymać zmienne, przetworzyć je, zapisać je po stronie serwera i przekierować odwiedzającego do strony "second", która pobierze zmienne. Mechanizm ten nazywany jest zgłoszeniem zwrotnym (ang. postback).
Zmodyfikujmy domyślny kontroler, tak aby implementował samo zgłoszenie:
def first():
if request.vars.visitor_name:
session.visitor_name = request.vars.visitor_name
redirect(URL('second'))
return dict()
def second():
return dict()
Następnie zmodyfikujmy widok "default/first.html":
{{extend 'layout.html'}}
What is your name?
<form>
<input name="visitor_name" />
<input type="submit" />
</form>
oraz widok "default/second.html" potrzebny do pobierania danych z session
zamiast z request.vars
:
{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>
Z punktu widzenie odwiedzającego, samoskładanie działa dokładnie tak samo jak poprzednia implemetacja. Nie dodaliśmy jeszcze walidacji, ale teraz jest już jasne, że walidacja powinna się odbyć w pierwszej akcji.
To podejście jest lepsze, również dlatego, że nazwa odwiedzającego pozostaje w sesji i może być dostępna dla wszystkich akcji i widoków w aplikacji bez konieczności jawnego jej przekazywania w kółko.
Proszę zauważyć, że jeśli akcja "second" jest zawsze wywoływana przed ustawieniem nazwy odwiedzającego, to zostanie wyświetlone "Witaj anonymous" ponieważ session.visitor_name
zwraca None
. Alternatywnie możemy dodać następujący kod do kontrolera (wewnatrz funkcji second
):
if not request.function=='first' and not session.visitor_name:
redirect(URL('first'))
Jest to mechanizm ad hoc, który można użyć do wymuszenia uwierzytelniania w kontrolerach, choć są lepsze metody omówione w rozdziale 9.
Z web2py możemy pójść krok dalej i poprosić web2py aby wygenerował formularz za nas, łącznie z walidacją. Platforma web2py dostarcza helpery (FORM, INPUT, TEXTAREA i SELECT/OPTION) z tą samą nazwą co równoważny znacznik HTML. Moga być one wykorzystane do budowy formularzy zarówno w kontrolerze jak i w widoku.
Na przykład, oto jeden z możliwych sposobów na przepisanie pierwszej akcji:
def first():
form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if form.process().accepted:
session.visitor_name = form.vars.visitor_name
redirect(URL('second'))
return dict(form=form)
gdzie widzimy znacznik FORM zawierający dwa znaczniki INPUT. Atrybuty znaczników input są określone przez nazwane argumenty rozpoczynające się znakiem podkreślenia. Argument requires
nie jest argumentem znacznika (ponieważ nie rozpoczyna się znakiem podkreślenia) ale ustawia walidator dla wartości nazwy odwiedzającego.
Oto jeszcze lepszy sposób na utworzenie takiego formularza:
def first():
form = SQLFORM.factory(Field('visitor_name',
label='what is your name?',
requires=IS_NOT_EMPTY()))
if form.process().accepted:
session.visitor_name = form.vars.visitor_name
redirect(URL('second'))
return dict(form=form)
Obiekt form
można łatwo serializować do HTML przez osadzenie go w widoku "default/first.html".
{{extend 'layout.html'}}
{{=form}}
Metoda form.process()
stosuje walidatory i zwraca formularz. Zmienna form.accepted
zostaje ustawiona na True, jeśli formularz został przetworzony i przeszedł walidację. Jeśli samo zgłaszający się formularz przechodzi walidację, to zmienne zostają zapisane w sesji a odwiedzający zostaje zostaje przekierowany, jak miało to miejsce wcześniej. Jeśli formularz nie przechodzi walidacji, to wyświetlane są komunikaty umieszczone w formularzu, jak niżej:
W następnym rozdziale pokażemy, jak może być automatycznie wygenerowany formularz z modelu.
We wszystkich naszych przykładach wykorzystaliśmy sesję do przekazania imienia użytkownika z pierwszej akcji do drugiej. Moglibyśmy użyć innego mechanizmu i przekazać dane jako część adresu URL przekierowania:
def first():
form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
if form.process().accepted:
name = form.vars.visitor_name
redirect(URL('second',vars=dict(name=name)))
return dict(form=form)
def second():
name = request.vars.visitor_name or redirect(URL('first'))
return dict(name=name)
Trzeba mieć na uwadze, że ogólnie nie jest dobrym pomysłem przekazywanie danych z jednej akcji do drugiej przy użyciu adresu URL. To sprawia, że trudno jest zabezpieczyć aplikację. Bezpieczniej jest przechowywać dane w sesji.
Umiędzynarodowienie
Kod może zawierać sztywne ciągi znakowe, takie jak "Jak masz na imię?". Powinno się być w stanie dostosować ciągi znakowe bez edytowania kodu a w szczególności wstawić tłumaczenia dla różnych wersji językowych. W ten sposób, jeśli odwiedzający ma ustawiony w przeglądarce preferowany język, na przykład na "włoski", to web2py zastosuje włoskie tłumacze ciągów znakowych, jeśli jest ono dostępne. Ta funkcjonalność web2py nosi nazwę "umiędzynarodowienia" i jest szczegółowo opisana w następnym rozdziale.
Tutaj po prostu widzimy, że w celu użycia tej funkcjonalności trzeba oznaczyć ciąg znaków, który wymaga tłumaczenia. Odbywa się to przez opakowanie cytowanego ciągu znaków, taki jak ten:
"Jak masz na imię?"
w operator T
:
T("Jak masz na imię?")
Można również oznakować do tłumaczenia sztywny ciąg znakowy w widokach. Na przykład
<h1>Jam masz na imie?</h1>
zmienić na:
<h1>{{=T("Jak masz na imię?")}}</h1>
Dobrą praktyką jest robić to dla każdego ciągu znakowego w kodzie (etykiet pól, komunikatów fleszowych itd.) z wyjątkiem tabel i nazw pól.
Po tym jak ciągi zostaną zidentyfikowane i oznaczone, web2py zajmie się prawie wszystkim. Interfejs administracyjny dostarcza również stronę, na której można przetłumaczyć każdy ciąg znakowy na języki, które chce się obsługiwać.
Platforma web2py zawiera również zaawansowany motor liczby mnogiej, opisany w następnym rozdziale. Jest on zintegrowany zarówno z motorem umiędzynarodowienia jak i z renderowaniem markmin.
Blog fotograficzny
Tutaj, jako kolejny przykład, utworzymy aplikację internetową, która umożliwia administratorowi zamieszczanie zdjęć i nadawanie im nazwy oraz umożliwia odwiedzającym oglądanie obrazów z nazwami i komentowanie wpisów.
Tak jak poprzednio, z poziomu strony głównej (site) w interfejsie administracyjnym, utwórzmy nową aplikację o nazwie images i przejdźmy do strony Edycja aplikacji:
Rozpocznijmy od utworzenia modelu, reprezentacji trwałych danych w aplikacji (załadowane pliki zdjęć, ich nazwy i komentarze). Po pierwsze, trzeba utworzyć i edytować plik modelu, który z braku wyobraźni, nazwiemy "db.py". Zakładamy, że nowy kod zastąpi całkowicie istniejący kod w "db.py". Modele i kontrolery muszą mieć rozszerzenie .py
ponieważ zawierają kod Pythona. Jeśli rozszerzenie nie zostanie podane, to zostanie dodane przez web2py. Widoki natomiast maja rozszerzenie .html
, gdyż zawierają kod HTML.
Edytuj plik "db.py" klikając na odpowiedni przycisk:
i wprowadź następujący kod:
db = DAL("sqlite://storage.sqlite")
db.define_table('image',
Field('title', unique=True),
Field('file', 'upload'),
format = '%(title)s')
db.define_table('post',
Field('image_id', 'reference image'),
Field('author'),
Field('email'),
Field('body', 'text'))
db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.post.author.requires = IS_NOT_EMPTY()
db.post.email.requires = IS_EMAIL()
db.post.body.requires = IS_NOT_EMPTY()
db.post.image_id.writable = db.post.image_id.readable = False
Przeanalizujmy ten kod linia po linii.
Linia 1 definiuje zmienną o nazwie db
, która reprezentuje połączenie z bazą danych. W tym przypadku jest to połączenie z bazą danych SQLite zlokalizowaną w pliku "applications/images/databases/storage.sqlite". Podczas korzystania z bazy danych SQLite, jeśli plik bazy danych nie istnieje, to zostanie utworzony. Można zmienić nazwę pliku, jak również nazwę zmiennej globalnej db
, ale konwencją jest nadawanie tej samej nazwy zmiennej co bazie danych – łatwiej się to pamięta.
Linie 3-6 definiują tabelę "image". define_table
jest metodą obiektu db
. Pierwszy argument, "image", jest nazwą tabeli, która definiujemy. Pozostałe argumenty definiują pola należące do tabeli. Tabela ta ma pola o nazwach "title", "file" i "id". Pole "id" ma przypisany klucz podstawowy ("id" nie jest deklarowane jawnie, ponieważ wszystkie tabele mają domyślnie pole id). Pole "title" jest typu łańcuchowego (string) a pole "file" jest typu "upload". Typ "upload" jest specjalnym typem pola używanym w web2py przez warstwę abstrakcji bazy danych (DAL) do przechowywania nazw załadowanych plików. Platforma web2py wie jak ładować pliki (poprzez strumieniowanie jeśli są duże), zmieniać ich nazwę w celach bezpieczeństwa i przechowywać je.
Gdy tabela jest zdefiniowana, web2py podejmuje jedną z kilku możliwych akcji:
- jeśli tabela nie istnieje, to jest tworzona;
- jeśli tabela istnieje ale nie zgadza się z definicją, to tabela jest zmieniana zgodnie z definicją, a jeżeli jakieś pole jest innego typu niż określono to w definicji, to web2py próbuje przekształcić dane w tym polu;
- jeśli tabela istnieje i zgadza się z definicja, web2py nic nie robi.
To zachowanie nazywa się "migracją". W web2py migracje są automatyczne, ale mogą zostać wyłączone dla poszczególnych tabel przez przekazanie migrate=False
jako ostatniego argumentu define_table
.
Linia 6 definiuje łańcuch formatujący dla tabeli. Łańcuch ten określa, jak powinien być reprezentowany rekord jako ciąg znakowy. Proszę zwrócić uwagę, że argument format
może być również funkcją pobierającą rekord i zwracającą ciąg znakowy. Na przykład:
format=lambda row: row.title
Linie 8-12 definiują inną tabelę o nazwie "post". Tabela post ma pola "author" i "email" (mamy zamiar zapisać adres email autora wpisu), "body" typu "text" (mamy zamiar użyć go do zapisywania aktualnego komentarza wpisanego przez autora) i pole "image_id" typu odniesienie, które wskazuje db.image
poprzez pole "id".
W linii 14 db.image.title
reprezentuje pole "title" tabeli "image". Atrybut requires
umożliwia ustawienie wymagań i ograniczeń, które będą wymuszane przez formularz web2py. Tutaj wymagamy, aby "title" było obowiązkowe:
IS_NOT_IN_DB(db, db.image.title)
INFORMACJA: Jest to opcjonalne, ponieważ jest ustawiane automatycznie, bo Field('title', unique=True)
.
Obiekty reprezentujące te ograniczenia nazywane są walidatorami. Można grupować walidatory na liście. Walidatory są wykonywane w kolejności ich występowania. Walidator IS_NOT_IN_DB(a, b)
to specjalny walidator, który sprawdza czy wartość pola b
dla nowego rekordu nie jest już w a
.
Linia 15 wymaga, aby pole "image_id" tabeli "post" było w db.image.id
. O ile chodzi o bazę danych, już deklarowaliśmy to podczas definiowania tabeli "post". Teraz wyraźnie powiadamiamy model, że ten warunek powinien być egzekwowany przez web2py także na poziomie przetwarzania formularza gdy wysyłany jest nowy wpis, tak więc nieprawidłowa wartość nie jest wprowadzana z pola wejściowego formularza do bazy danych. Wymagamy również, aby "image_id" było reprezentowane przez "title", '%(title)s'
odpowiedniego rekordu.
Linia 20 wskazuje, że pole "image_id" tabeli "post" nie powinno być pokazywane w formularzu, ani jako pole mogące być zapisywanym (writable=False
) i ani jako pole mogące być odczytywanym (readable=False
).
Znaczenie walidatorów w liniach 17-18 powinno być oczywiste.
Proszę zauważyć, że walidator
db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
można pominąć (będzie ustawiany automatycznie) jeśli określimy format dla wskazanej tabeli:
db.define_table('image', ..., format='%(title)s')
gdzie format może być ciągiem znakowym lub funkcją, która pobiera rekord i zwraca ciąg znakowy.
Gdy model jest zdefiniowany, jeśli nie ma błędów, web2py tworzy interfejs administracyjny appadmin do zarządzania bazą danych. Można uzyskać do niego dostęp poprzez odnośnik "administracja bazą danych" na stronie Edycja aplikacji lub bezpośrednio:
http://127.0.0.1:8000/images/appadmin
Oto zrzut ekranu z widokiem interfejsu appadmin:
Interfejs ten jest zakodowany w kontrolerze o nazwie "appadmin.py" i odpowiadającym mu widoku "appadmin.html". Od teraz będziemy odnosić się do tego interfejsu po prostu jako do appadmin. Pozwala on administratorowi wstawić nowe rekordy do bazy danych, edytować i usuwać istniejące rekordy, przeglądać tabele i dokonywać złączeń w bazie danych.
Przy pierwszym dostępie do interfejsu appadmin wykonywany jest model i tworzone są tabele bazy danych. DAL web2py tłumaczy kod Pythona na wyrażenia SQL, które są specyficzne dla wybranego typu bazy danych (w naszym przykładzie SQLite). Można również generować SQL z poziomu strony Edycja aplikacji klikając na odnośnik "sql.log" w grupie "models". Trzeba pamiętać, że ten odnośnik nie jest dostępny dopóki nie zostaną utworzone tabele.
Jeśli model został ponownie edytowany i dokonamy dostępu do appadmin, web2py wygeneruje SQL do zmiany istniejących tabel. Generowany SQL jest rejestrowany w pliku "sql.log".
Teraz powróćmy do appadmin i spróbujmy wstawić nowy rekord obrazu:
Kod web2py transformuje pole "upload" tabeli db.image.file
na formularz pobierania dla określonego pliku. Po wysłaniu formularza i przesłaniu pliku, zmieniana jest nazwa pliku, w bezpieczny sposób, zachowując dotychczasowe rozszerzenie. Plik ten zapisywany jest z nową nazwą w folderze "uploads" i ta nowa nazwa jest zapisywana w polu db.image.file
. Proces ten ma na celu zapobieżenie atakom typu directory traversal.
Trzeba pamiętać, że każdy typ pola jest renderowany przez widżet. Domyślne widżety można przesłaniać.
Po kliknięciu na nazwę tabeli w appadmin, web2py wykona instrukcję select
na wszystkich rekordach bieżącej tabeli, pokreślonych w zapytaniu DAL
db.image.id > 0
i wyrenderuje wynik.
Można wybrać inny zestaw rekordów edytując zapytanie DAL i wciskając przycisk [Submit].
Aby edytować lub skasować pojedynczy rekord, wystarczy kliknąć na numer id rekordu.
Ponieważ walidator IS_IN_DB
odwołuje się do pola "image_id", jest to wykorzystywane do renderowania rozwijanego menu. Pozycje rozwijanej listy są przechowywane jako klucze (db.image.id
), ale są reprezentowane przez wartość db.image.title
, w sposób określony przez walidator.
Walidatory są zaawansowanymi obiektami, które wiedzą jak reprezentować pola, filtrować wartości pól, generować błędy i formatować wartości wyekstrahowane z pól.
Poniższy rysunek pokazuje, co się dzieje, gdy formularz nie przechodzi walidacji:
Ten sam formularz, który jest generowany automatycznie przez appadmin może również być generowany programowo przez helper SQLFORM
i osadzany w aplikacjach. Formularze te są przyjazne dla CSS i mogą być dostosowywane.
Każda aplikacja ma swój własny interfejs appadmin. Dlatego można modyfikować appadmin bez wpływu na inne aplikacje.
Dotychczas poznaliśmy, jak aplikacja przechowuje dane i zobaczyliśmy, jak uzyskać dostęp do bazy danych poprzez interfejs appadmin. Dostęp do appadmin jest zastrzeżony tylko dla administratora, ale nie jest to internetowy interfejs produkcyjny aplikacji – stąd następna część naszego działania. W szczególności potrzebujemy:
- Stronę "index", która wykazuje wszystkie dostępne obrazy posortowane według tytułu i odnośniki do stron poszczeglnych obrazów.
- Stronę "show/[id]", która wyświetla żądany obraz i umożliwia odwiedzającemu oglądać i wprowadzać komentarze.
- Akcję "download/[name]" do pobierania przesyłanych obrazów.
Przedstawiono to schematycznie tutaj:
Powróć do strony Edycja aplikacji i edytuj kontroler "default.py", zamieniając jego zawartość na to:
def index():
images = db().select(db.image.ALL, orderby=db.image.title)
return dict(images=images)
Akcja ta zwraca słownik. Klucze elementów słownika są interpretowane jak zmienne przekazywane do widoku związanego z akcją. W czasie programowania, jeśli brakuje takiego widoku, to akcja jest renderowana przez widok "generic.html", który dostarczany jest przez aplikację web2py.
Akcja index wykonuje instrukcję select wszystkich pól (db.image.ALL
) z tabeli image, sortując według pola db.image.title
. Wynikiem instrukcji select jest obiekt Rows
zawierający rekordy. Przypisujemy go do zmiennej o nazwie images
zwracanej przez akcję do widoku. Zmienna images
jest iterowalna i jej elementy są wybranymi wierszami. Dla każdego wiersza kolumny elementy te mogą być dostępne jako słowniki: images[0]['title']
lub równoważnie jako images[0].title
.
Jeśli nie napisze się widoku, słownik będzie renderowany przez widok "views/generic.html" a wywołanie akcji index wyglądać będzie tak:
Nie stworzyliśmy jeszcze widoku dla tej akcji, więc web2py renderuje zestaw rekordów jako zwykły formularz tabelaryczny.
Przystąpmy do utworzenia widoku dla akcji index. Powróć do interfejsu admin, edytuj "default/index.html" i wymień istniejąca zawartość na tą:
{{extend 'layout.html'}}
<h1>Bieżący obraz</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>
Pierwszą rzeczą, która zauważamy, jest to, że widok jest czystym HTML ze specjalnymi znacznikami {{...}}
. Kod osadzony w znacznikach {{...}}
jest czystym kodem Pythona z jednym zastrzeżeniem: wcięcia nie mają znaczenia. Bloki kodu rozpoczynają się liniami zakończonymi dwukropkiem (:) a kończą liniami ze słowem kluczowym pass
. W pewnych przypadkach koniec bloku jest oczywisty i użycie słowa pass
nie jest wymagane.
Linie 5-7 wykonują pętlę na wierszach images
i dla każdego wiersza wyświetlają image
:
LI(A(image.title, _href=URL('show', args=image.id))
Jest to znacznik <li>...</li>
zawierający znacznik <a href="...">...</a>
, który z kolei zawiera wartość image.title
. Wartością odniesienia hipertekstowego (atrybutu href
) jest:
URL('show', args=image.id)
tj. adres URL w tej samej aplikacji i kontroler jako bieżące żądanie wywołujące funkcję o nazwie "show". Funkcja ta przekazuje pojedynczy argument args=image.id
do funkcji URL. Obiekty LI
, A
itd. są helperami odwzorowującymi odpowiednie znaczniki HTML. Ich nienazwane argumenty są interpretowane jako obiekty do serializacji i osadzenia w innerHTML znacznika. Nazwane argumenty rozpoczynające się znakiem pokreślenia (na przykład _href
) są interpretowane jako atrybuty znacznika, ale bez znaku podkreślenia. Na przyklad _href
jest atrybutem href
, _class
jest atrybutem class
itd.
Dla przykładu, następujące wyrażenie:
{{=LI(A('coś tam', _href=URL('show', args=123))}}
jest renderowane jako:
<li><a href="/images/default/show/123">coś tam</a></li>
Kilka helperów (INPUT
, TEXTAREA
, OPTION
i SELECT
) obsługuje również kilka specjalnych nazwanych atrybutów nie rozpoczynających się znakiem podkreślenia (value
i requires
). Są one ważne dla budowania własnych formularzy i zostaną omówione dalej.
Powróćmy do strony Edycja aplikacji. Widzimy tam, że przy pliku default.py jest eksponowany odnośnik index. Klikając na index odwiedzisz nowo utworzoną stronę:
http://127.0.0.1:8000/images/default/index
która wygląda podobnie do tego:
Jeśli kliknie się na odnośnik z nazwą obrazu, zostanie się przekierowanym do:
http://127.0.0.1:8000/images/default/show/1
i w rezultacie otrzyma się błąd, ponieważ nie została utworzona akcja o nazwie "show" w kontrolerze "default.py".
Edytujmy kontroler "default.py" i zastąpmy jego zawartość na to:
def index():
images = db().select(db.image.ALL, orderby=db.image.title)
return dict(images=images)
def show():
image = db.image(request.args(0,cast=int)) or redirect(URL('index'))
db.post.image_id.default = image.id
form = SQLFORM(db.post)
if form.process().accepted:
response.flash = 'your comment is posted'
comments = db(db.post.image_id==image.id).select()
return dict(image=image, comments=comments, form=form)
def download():
return response.download(request, db)
Kontroler składa się z dwóch akcji: "show" i "download". Akcja "show" wybiera obraz z określonym id
z argumentów żądania i wszystkie komentarze związane z obrazem. Następnie akcja "show" przekazuje to wszystko do widoku "default/show.html".
Do identyfikatora obrazu odwołujemy w "default/index.html" się przez:
URL('show', args=image.id)
Może być on dostępny w akcji "show" jako request.args(0,cast=int)
.
Argument cast=int
jest opcjonalny, ale bardzo ważny. Próbuje on zrzucić wartość ciągu znakowego przekazanego w zmiennej PATH_INFO do liczby całkowitej. W przypadku błędu wywołać wyjątek zamiast powodować wystawienie biletu. Można również określić przekierowanie w przypadku braku zrzutu:
request.args(0,cast=int,otherwise=URL('error'))
Ponadto db.image(...)
jest skrótem dla
db(db.image.id==...).select().first()
Akcja "download" oczekuje nazwy pliku w request.args(0)
, buduje ścieżkę do lokalizacji w której ma być plik i odsyła ją z powrotem do klienta. Jeśli plik jest za duży, to następuje jego strumieniowanie, bez żadnego narzutu pamięci.
Przyjrzyjmy się następującym wyrażeniom:
- Linia 7 ustawia wartość dla przywoływanego pola, które nie jest częścią pola wejściowego w formularzu, gdyż nie jest na liście pól określonych powyżej.
- Linia 8 tworzy formularz wstawiania SQLFORM dla tabeli
db.post
wykorzystując tylko określone pola. - Linia 9 przetwarza wysłany formularz (zmienne wysłanego formularza znajdują się w
request.vars
) w bieżącej sesji (sesja jest wykorzystywana do zapobieżenia podwójnemu zgłoszeniu i wymuszeniu nawigacji). Jeśli zmienne zgłoszonego formularza są poprawne, to do tabelidb.post
wstawiany jest nowy komentarz. W przeciwnym wypadku formularz jest modyfikowany w celu wyświetlenia komunikatów błędów (na przykład, jeśli adres email autora jest nieprawidłowy). To wszystko odbywa się w linii 9!. - Linia 10 jest wykonywana tylko wtedy, gdy formularz jest akceptowany, po tym jak rekord zostanie wstawiony do tabeli bazy danych.
response.flash
jest zmienną web2py, która jest wyświetlana w widokach i wykorzystywana jest do powiadamiania odwiedzającego o tym co się stało. - Linia 11 wybiera wszystkie komentarze, które odwołują się do bieżącego obrazu.
Akcja "download" jest już zdefiniowana w kontrolerze "default.py" aplikacji szkieletowej.
Akcja "download" nie zwraca słownika, więc nie potrzeba tworzyć widoku. Akcja "show" jednak powinna mieć widok, więc powróć do admin i utwórz nowy widok o nazwie "default/show.html".
Edytuj tej nowy plik i wymień jego zwartość na następującą:
{{extend 'layout.html'}}
<h1>Obraz: {{=image.title}}</h1>
<center>
<img width="200px"
src="{{=URL('download', args=image.file)}}" />
</center>
{{if len(comments):}}
<h2>Komentarze</h2><br /><p>
{{for post in comments:}}
<p>{{=post.author}} says <i>{{=post.body}}</i></p>
{{pass}}</p>
{{else:}}
<h2>Nie ma jeszcze żadnego komentarza</h2>
{{pass}}
<h2>Skomentuj</h2>
{{=form}}
Widok ten wyświetla image.file przez wywołanie akcji "download" wewnątrz znacznika <img ... />
. Jeśli są jakieś komentarze, to wykonywana jest na nich pętla i wyświetlany jest każdy z nich.
Oto jak to wszystko pojawi się odwiedzającemu.
Gdy użytkownik wysyła komentarz ze strony, to komentarz jest zapisywany do bazy danych i dołącza go w dole strony.
Dodawanie uwierzytelniania
API web2py dla kontroli dostępu opartej na rolach jest dość skomplikowane, ale na razie zajmiemy się tylko ograniczeniem dostępu do akcji show
dla uwierzytelnionych użytkowników, odraczając szczegółowsze omówienie tego tematu do rozdziału 9.
Aby ograniczyć dostęp do użytkowników uwierzytelnionych, musimy wykonać trzy kroki. W modelu, na przykład w "db.py", musimy dodać:
from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=True)
W naszym kontrolerze musimy dodać jedną akcję:
def user():
return dict(form=auth())
Jest to wystarczające do udostępniania logowania, rejestracji i wylogowania na stronach. Domyślny układ wyświetla także odpowiednie opcje w prawym górnym rogu strony.
Teraz możemy udekorować funkcje, które chcemy ograniczyć, na przykład:
@auth.requires_login()
def show():
...
Każda próba dostępu
http://127.0.0.1:8000/images/default/show/[image_id]
będzie wymagała logowania. Jeżeli użytkownik nie jest zalogowany, to zostanie przekierowany do
http://127.0.0.1:8000/images/default/user/login
Funkcja user
udostępnia między innymi następujące akcje:
http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized
Teraz, za pierwszym razem użytkownik musi się zarejestrować, aby móc się zalogować i czytać komentarze.
Zarówno obiekt
auth
jak i funkcjauser
są już zdefiniowane w aplikacji szkieletowej. Obiektauth
jest wysoce konfigurowalny i może sobie radzić z weryfikacją email, zezwoleniami rejestracyjnymi, CAPTCHA i alternatywnymi metodami logowania poprzez wtyczki.
Dodawanie siatki
Możemy poprawić stworzoną funkcjonalność wykorzystując gadżety SQLFORM.grid
i SQLFORM.smartgrid
, tworząc w ten sposób interfejs zarządzania dla naszej aplikacji. W kontrolerze default.py dodaj:
@auth.requires_membership('manager')
def manage():
grid = SQLFORM.smartgrid(db.image,linked_tables=['post'])
return dict(grid=grid)
a w odpowiednim dla tej akcji widoku "views/default/manage.html":
{{extend 'layout.html'}}
<h2>Interfejs zarządznia</h2>
{{=grid}}
Korzystając z appadmin utwórz grupę "manager" i dodaj jakichś członków tej grupy. Będą oni mogli uzyskiwać dostęp
http://127.0.0.1:8000/images/default/manage
oraz przeglądać i wyszukiwać:
tworzyć, aktualizować i usuwać obrazy i ich komentarze:
Konfigurowanie układu
Można skonfigurować domyślny układ edytując "views/layout.html" ale można skonfigurować go również bez edytowania kodu HTML. W rzeczywistości arkusz stylów "static/base.css" jest bardzo dobrze udokumentowany i opisany w rozdziale 5. Można zmienić kolor, kolumny, wielkość, obramowania i tło bez edytowania kodu HTML. Jeśli chce się edytować menu, tytuł lub podtytuł, można to zrobić w dowolnym pliku modelu. Aplikacja szkieletowa ustawia domyślne wartości tych parametrów w pliku "models/menu.py":
response.title = request.application
response.subtitle = 'dcustomize me!'
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]
Proste wiki
W tym rozdziale zbudujemy od podstaw proste wiki wykorzystując tylko interfejsy API niskiego poziomu (w przeciwieństwie do wykorzystywania wbudowanej w web2py funkcjonalności wiki zademonstrowanej w następnym rozdziale). Odwiedzający będzie mógł tworzyć strony, przeszukiwać (wg tytułu) i edytować je. Odwiedzający będzie również mógł wprowadzać komentarze (dokładnie tak, jak w poprzedniej aplikacji) oraz wprowadzać dokumenty (jako załączniki do stron) i linkować je ze stronami. Dla składni naszego wiki, zgodnie z konwencją, adaptujemy składnię Markmin. Wdrożymy również stronę wyszukiwania w Ajax, kanał RSS dla stron i obsługę przeszukiwania stron poprzez XML-RPC[xmlrpc] . Poniższy diagram zawiera listę akcji, które musimy zaimplementować i odnośniki jakie zamierzamy w nie wbudować.
Rozpocznijmy od utworzenia nowego szkieletu aplikacji o nazwie "mywiki".
Model musi zawierać trzy tabele: page, comment i document. Zarówno comment jak i document odwołują się do page, ponieważ należą do strony. Tabela document zawiera pole file typu upload, tak jak w poprzedniej aplikacji images.
Oto kompletny model:
db = DAL('sqlite://storage.sqlite')
from gluon.tools import *
auth = Auth(db)
auth.define_tables()
crud = Crud(db)
db.define_table('page',
Field('title'),
Field('body', 'text'),
Field('created_on', 'datetime', default=request.now),
Field('created_by', 'reference auth_user', default=auth.user_id),
format='%(title)s')
db.define_table('comment',
Field('page_id', 'reference page'),
Field('body', 'text'),
Field('created_on', 'datetime', default=request.now),
Field('created_by', 'reference auth_user', default=auth.user_id))
db.define_table('document',
Field('page_id', 'reference page'),
Field('name'),
Field('file', 'upload'),
Field('created_on', 'datetime', default=request.now),
Field('created_by', 'reference auth_user', default=auth.user_id),
format='%(name)s')
db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False
db.post.body.requires = IS_NOT_EMPTY()
db.post.page_id.readable = db.post.page_id.writable = False
db.post.created_by.readable = db.post.created_by.writable = False
db.post.created_on.readable = db.post.created_on.writable = False
db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False
Edytuj kontroler "default.py" i stwórz następujące akcje:
- index: wykazuje wszystkie strony wiki
- create: dodawanie nowej strony wiki
- show: wyświetla stronę wiki i jej komentarze oraz umożliwia dodawanie nowych komentarzy
- edit: edytowanie istniejącej strony
- documents: zarządzanie dokumentami załączonymi do strony
- download: pobiera dokument (tak jak w przykładzie images)
- search: wyświetla okno wyszukiwania i poprzez wywołanie zwrotne Ajax zwraca wszystkie dopasowane tytuły jakie wpisał odwiedzający
- callback: funkcja wywołania zwrotnego Ajax. Zwraca kod HTML, który zostaje osadzony na stronie wyszukiwania podczas wpisywania przez odwiedzającego.
Oto kontroler "default.py":
def index():
""" this controller returns a dictionary rendered by the view
it lists all wiki pages
>>> index().has_key('pages')
True
"""
pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
return dict(pages=pages)
@auth.requires_login()
def create():
"""creates a new empty wiki page"""
form = SQLFORM(db.page).process(next=URL('index'))
return dict(form=form)
def show():
"""shows a wiki page"""
this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
db.post.page_id.default = this_page.id
form = SQLFORM(db.post).process() if auth.user else None
pagecomments = db(db.post.page_id==this_page.id).select()
return dict(page=this_page, comments=pagecomments, form=form)
@auth.requires_login()
def edit():
"""edit an existing wiki page"""
this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
form = SQLFORM(db.page, this_page).process(
next = URL('show',args=request.args))
return dict(form=form)
@auth.requires_login()
def documents():
"""browser, edit all documents attached to a certain page"""
page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
db.document.page_id.default = page.id
db.document.page_id.writable = False
grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id])
return dict(page=page, grid=grid)
def user():
return dict(form=auth())
def download():
"""allows downloading of documents"""
return response.download(request, db)
def search():
"""an ajax wiki search page"""
return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
_onkeyup="ajax('callback', ['keyword'], 'target');")),
target_div=DIV(_id='target'))
def callback():
"""an ajax callback that returns a <ul> of links to wiki pages"""
query = db.page.title.contains(request.vars.keyword)
pages = db(query).select(orderby=db.page.title)
links = [A(p.title, _href=URL('show',args=p.id)) for p in pages]
return UL(*links)
Linie 2-6 stanowią komentarz dla akcji index. Linie 4-5 wewnątrz komentarza są interpretowane przez Python jako kod testowy (doctest). Testy mogą być uruchamiane poprzez interfejs administracyjny. W tym przypadku testy weryfikują, czy akcja index uruchamia się bez błędów.
Linie 18, 27 i 35 próbują pobrać rekord page
o id określonym w request.args(0)
.
Linie 13, 20 definiują i przetwarzają tworzony formularz dla nowej strony i nowego komentarza, natomiast
linia 28 definiuje i przetwarza zaktualizowany formularz dla strony wiki.
Linia 38 tworzy obiekt grid
, który umożliwia przeglądanie, dodawanie i aktualizowanie komentarzy powiązanych ze stroną.
W linii 51 dzieje się jakaś magia. Ustawiany jest atrybut onkeyup
w znaczniku INPUT o id "keyword". Za każdym razem, jak odwiedzający zwolni klawisz podczas wypełniania pola, wykonywany jest kod JavaScript wewnątrz atrybutu onkeyup
po stronie klienta. Oto ten kod JavaScript:
ajax('callback', ['keyword'], 'target');
ajax
jest funkcją JavaScript zdefiniowana w pliku "web2py.js", który jest domyślnie dołączany przez "layout.html". Pobiera on trzy parametry: adres URL akcji wykonującej synchroniczne wywołanie zwrotne, listę identyfikatorów ID zmiennych, które mają być przesłane w wywołaniu zwrotnym (["keyword"]) i identyfikator ID dla którego odpowiedź ma być osadzona ("target").
Jak tylko wpiszesz coś w polu wyszukiwania i zwolnisz klawisz, klient wywołuje serwer i przesyła zawartość pola 'keyword' i gdy serwer odpowie, odpowiedź jest osadzana na tej samej stronie jako innerHTML znacznika 'target'.
Znacznik 'target' jest elementem DIV zdefiniowanym w linii 52. Można to równie dobrze zdefiniować w widoku.
Oto kod dla widoku "default/create.html":
{{extend 'layout.html'}}
<h1>Tworzenie nowej strony wiki</h1>
{{=form}}
Po zarejestrowaniu się i zalogowaniu, jeśli odwiedzisz stronę create, zobaczysz następujący ekran:
Oto kod widoku "default/index.html":
{{extend 'layout.html'}}
<h1>Dostępne strony wiki</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
{{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]
Generuje on następująca stronę:
Oto kod dla widoku "default/show.html":
{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Komentarze</h2>
{{for post in comments:}}
<p>{{=db.auth_user[post.created_by].first_name}} on {{=post.created_on}}
says <i>{{=post.body}}</i></p>
{{pass}}
<h2>Wpis komentarza</h2>
{{=form}}
Jeśli chcesz użyć składni markdown zamiast składni markmin zaimportuj:
from gluon.contrib.markdown import WIKI as MARKDOWN
i zastosuj helper MARKDOWN
zamiast helpera MARKMIN
. Alternatywnie można wybrać akceptowanie surowego HTML zamiast składni markmin. W takim przypadki zamień:
{{=MARKMIN(page.body)}}
na:
{{=XML(page.body)}}
(ale XML nie został tu zabezpieczony sekwencjami ucieczki, co web2py zwykle dokonuje tego domyślnie w celach bezpieczeństwa).
Można to zrobić lepiej:
{{=XML(page.body, sanitize=True)}}
Ustawiając sanitize=True
powiadamiamy web2py aby zabezpieczył niebezpieczne znaczniki XML znakami ucieczki, takie jak "<script>", a tym samym zabezpieczył kod przed atakami XSS.
Teraz jeśli z poziomu strony index kliknie się na tytuł strony, zobaczy się stronę, która została utworzona:
Oto kod dla widoku "default/edit.html":
{{extend 'layout.html'}}
<h1>Edytuj stronę wiki</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
{{=form}}
Generuje to stronę wyglądającą niemal identycznie jak tworzona strona.
Oto kod dla widoku "default/documents.html":
{{extend 'layout.html'}}
<h1>Dokumenty dla strony: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Dokumenty</h2>
{{=grid}}
Jeśli na stronie "show" kliknie się na dokumentach, będzie można teraz zarządzać dokumentami załączonymi do strony.
Na koniec trzeba jeszcze wprowadzić kod dla widoku "default/search.html":
{{extend 'layout.html'}}
<h1>Wyszukiwanie na stronach wiki</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}
który generuje następujący formularz Ajax:
Można również wypróbować bezpośrednio akcję callback odwiedzając, na przykład, następujący adres URL:
http://127.0.0.1:8000/mywiki/default/callback?keyword=wiki
Jeśli spojrzysz na źródło strony, zobaczysz następpujący kod zwracany przez wywołanie zwrotne:
<ul><li><a href="/mywiki/default/show/4">Wykonałem Wiki</a></li></ul>
Generowanie kanału RSS stron wiki przy użyciu web2py jest łatwe, ponieważ web2py zawiera gluon.contrib.rss2
. Wystarczy dołączyć następującą akcję do domyślnego kontrolera:
def news():
"""generates rss feed from the wiki pages"""
response.generic_patterns = ['.rss']
pages = db().select(db.page.ALL, orderby=db.page.title)
return dict(
title = 'mywiki rss feed',
link = 'http://127.0.0.1:8000/mywiki/default/index',
description = 'mywiki news',
created_on = request.now,
items = [
dict(title = row.title,
link = URL('show', args=row.id, scheme=True,
host=True, extension=False),
description = MARKMIN(row.body).xml(),
created_on = row.created_on
) for row in pages])
a kiedy odwiedzi się stronę
http://127.0.0.1:8000/mywiki/default/news.rss
zobaczy się kanał RSS (wyjście zależy od czytnika). Proszę zwrócić uwagę, że słownik zostaje automatycznie przekształcany do RSS, dzięki rozszerzeniu .rss w adresie URL.
web2py również zawiera parser kanałów informacyjnych do czytania kanałów z kodem firm trzecich.
Zwróćmy uwagę na linię:
response.generic_patterns = ['.rss']
która instruuje web2py aby wykorzystał ogólny widok (w naszym przypadku"views/generic.rss"), gdy adres URL kończy się w globalnym wzorcu rozszerzeniem ".rss". Domyślnie, ogólne widoki dostępne są tylko z poziomu localhost dla celów programistycznych.
Na koniec, dodaj obsługę XML-RPC, która umożliwia przeszukiwanie wiki programowo:
service = Service()
@service.xmlrpc
def find_by(keyword):
"""finds pages that contain keyword for XML-RPC"""
return db(db.page.title.contains(keyword)).select().as_list()
def call():
"""exposes all registered services, including XML-RPC"""
return service()
Tutaj akcja handlera po prostu publikuje (poprzez XML-RPC) funkcje określone na liście, w tym przypadku, find_by
. Funkcja find_by
nie jest akcją (ponieważ pobiera argument). Wysyła ona zapytanie do bazy danych z instrukcją .select()
, a następnie ekstrahuje rekordy jako listę z .response
i zwraca listę.
Oto przykład, jak uzyskać dostęp do obsługi XML-RPC z zewnętrznego programu Pythona.
>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
print item['created_on'], item['title']
Handler może uzyskać dostęp z poziomu wielu języków programowania, które obsługują XML-RPC, w tym C, C++, C# i Java.
O formatach date
, datetime
i time
Istnieją trzy różne reprezentacje dla każdego pola typu date
, datetime
i time
:
- reprezentacja bazy danych,
- wewnętrzna reprezentacja web2py,
- reprezentacja łańcuchowa w formularzu i tabelach.
Reprezentacja bazy danych jest sprawą wewnętrzną i nie wpływa na kod. Wewnętrznie, na poziomie web2py, są one przechowywane odpowiednio jako obiekty datetime.date
, datetime.datetime
i datetime.time
i można nimi manipulować w ten sposób:
for page in db(db.page).select():
print page.title, page.day, page.month, page.year
Gdy daty są przekształcane do ciągów w formularzach, to przekształcane są zgodnie reprezentacją ISO, %Y-%m-%d %H:%M:%S
, ale reprezentacja ta jest internacjonalizowana i można wykorzystać stronę tłumaczeń w interfejsie administracyjnym w celu zmiany formatu danych. Na przykład, %m/%d/%Y %H:%M:%S
.
Należy pamiętać, że domyślny język angielski nie jest tłumaczony, ponieważ web2py zakłada, że aplikacja jest pisana w języku angielskim. Jeśli chce się umiędzynarodowić aplikację do pracy z językiem angielskim, to trzeba utworzyć plik tłumaczenia (wykorzystując interfejs administracyjny) i trzeba zadeklarować, że bieżącym językiem aplikacji jest inny język niż angielski, na przykład:
T.current_languages = ['null']
Wiki wbudowane w web2py
Teraz możemy zapomnieć kod, który zbudowaliśmy w poprzednim rozdziale (nie to co dowiedzieliśmy się o interfejsach API web2py, ale kod specyficzny dla tego rozdziału) jako że przerobimy przykład wiki wbudowanego w web2py.
W rzeczywistości web2py dostarczany jest z funkcjonalnością wiki, łącznie z załącznikami multimedialnymi, tagami, chmurą tagów, uprawnieniami strony i obsługą formatu oembed[oembed] i komponentów (rozdział 14). Ten kod wiki to może być stosowany z dowolną aplikacją web2py.
Proszę mieć na względzie, że wbudowane wiki jest nadal uważane za eksperymentalne i małe zmiany są możliwe.
Tutaj zakładamy, że zaczynamy od zera, od prostego klonu aplikacji "welcome" o nazwie "wikidemo". Jeśli nie, upewnij się, że włączone są migracje w db.py
, inaczej tabele wymagane przerz wiki nie będą automatycznie tworzone.
Edytuj kontroler i wymień kod akcji "index" na następujący:
def index(): return auth.wiki()
Gotowe! Masz w pełni funkcjonalne wiki. W tym momencie nie została jeszcze utworzona żadna strona i w tym celu konieczne jest zalogowanie się oraz członkostwo w grupie o nazwie "wiki_editor" lub "wiki_author". Jeśli jest się zalogowanym jako administrator, to grupa "wiki_editor" zostaje utworzona automatycznie i użytkownik zostaje przydzielony do tej grupy. Różnica w uprawnieniach redaktora (wiki_editor) a autora (wiki_author) jest taka, że redaktor może tworzyć strony, edytować i usuwać każdą stronę, gdy autor może tylko tworzyć strony (z pewnymi ograniczeniami) i może tylko edytować i usuwać strony przez siebie stworzone.
Funkcja auth.wiki()
zwraca słownik z kluczem content
, który jest zrozumiały dla widoku "views/default/index.html". Dla tej akcji można wykonać własny widok:
{{extend 'layout.html'}}
{{=content}}
i w razie potrzeby dodać dodatkowy kod. Nie trzeba korzystać z akcji "index" aby udostępnić wiki. Można w tym celu wykorzystać akcję o innej nazwie.
Aby wypróbować wiki, wystarczy zalogować się do interfejsu administracyjnego i odwiedzić stronę
http://127.0.0.1:8000/wikidemo/default/index
Następnie trzeba wybrać krótką nazwę (ang. slug) i zostanie się przeniesionym do pustej strony, którą będzie można edytować stosując składnię MARKMIN. Dodane zostanie nowe menu o nazwie "[wiki]", które pozwala tworzyć, wyszukiwać i edytować strony. Strony Wiki mają adresy URL podobne do tego:
http://127.0.0.1:8000/wikidemo/default/index/[slug]
Strony usług mają nazwy, które rozpoczynają się znakiem podkreślenia:
http://127.0.0.1:8000/wikidemo/default/index/_create
http://127.0.0.1:8000/wikidemo/default/index/_search
http://127.0.0.1:8000/wikidemo/default/index/_could
http://127.0.0.1:8000/wikidemo/default/index/_recent
http://127.0.0.1:8000/wikidemo/default/index/_edit/...
http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
http://127.0.0.1:8000/wikidemo/default/index/_preview/...
Spróbuj utworzyć więcej stron takich jak "index", "aboutus" czy "contactus". Spróbuj je edytować.
Metoda wiki
ma następującą sygnaturę:
def wiki(self, slug=None, env=None, render='markmin',
manage_permissions=False, force_prefix='',
restrict_search=False, resolve=True,
extra=None, menugroups=None)
Pobiera następujące argumenty:
render
ma domyślną wartość'markmin'
, ale może być również ustalony na'html'
. Określa składnię wiki. Znaczniki markmin wiki omówimy później. Jeśli zmieni się to na HTML, można użyć edytor wysiwyg javascript taki jak TinyMCE lub NicEdit;manage_permissions
. Jest ustawiony domyślnie naFalse
i tylko rozpoznaje uprawnienia dla "wiki_editor" i "wiki_author". Jeśli zmieni się to naTrue
, to prawo do tworzenia i edytowania strony będzie mogło być ustalone przez określenie nazwy grup, których członkowie mają prawo do odczytywania i edytowania strony. Istnieje grupa "everybody", która zawiera wszystkich użytkowników;force_prefix
. Jeśli jest ustawiony na coś podobnego do'%(id)s-'
, to powodować będzie ograniczenie praw autorów (nie redaktorów) do tworzenia tylko stron z przyrostkiem takim jak "[user id]-[page name]". Przyrostek może zawierać identyfikator (%(id)s
) lub nazwę użytkownik (%(username)s
) lub jakiegokolwiek inne pole z tabeli auth_user, o ile odpowiednia kolumna zawiera prawidłowy ciąg, przechodzący walidację adresu URL;restrict_search
. Domyślnie ma wartośćFalse
przy którym każdy zalogowany użytkownik może przeszukiwać wszystkie strony wiki (choć nie koniecznie ma ma prawo do ich odczytywania i edytowania). Jeśli zostanie ustawiony naTrue
, autorzy będą mogli przeszukiwać tylko własne strony, redaktorzy będą mogli przeszukiwać wszystkie strony, inni użytkownicy nie będą mogli przeszukiwać niczego;menu_groups
. Domyślnie ustawiony naNone
i wskazuje, że menu zarządzania wiki (wyszukiwanie, tworzenie, edytowanie itd.) jest zawsze wyświetlane. Argument ten może być ustawiony na nazwy grup, której członkowie są uprawnieni jako jedyni do oglądania i dostępu do menu wiki, na przykład['wiki_editor','wiki_author']
. Proszę zauważyć, że nawet jeśli menu jest dostępne dla każdego, to nie oznacza to, że każdy może wykonywać działania ujęte w menu, bo dostęp do tych działań jest regulowany w systemie kontroli dostępu.
Metoda wiki
ma kilka dodatkowych parametrów, które zostaną wyjaśnione dalej, takie jak slug
, env
i extra
.
Podstawy MARKMIN
Składnia MARKMIN pozwala na zaznaczanie tekstu pogrubionego przy użyciu **znacznika bold**
, kursywy przy użyciu ''znacznika italic''
oraz kodu
po ujęciu tekstu kodu w lewe apostrofy. Tytuły muszą być poprzedzone znakiem #, rozdziały znakami ## a podtytuły przez ###. Użycie znaku minus(-) poprzedza element listy nieuporządkowanej a znaku plus(+) element listy uporządkowanej. Adresy URL są automatycznie przekształcane w odnośniki. Oto przykład tekstu markmin:
# To jest tytuł
## to jest tytuł rozdziału
### to jest tytuł podrozdziału
Tekst może być **pogrubiony**, ''kursywą'', ``kodem`` itd.
Więcej nformacji znajdziesz na:
http://web2py.com
Można użyć parametr extra
w metodzie auth.wiki
, aby przekaza do helpera MARKMIN dodatkowe zasady renderowania.
Więcej informacji o składni MARKMIN znajdziesz w rozdziale 5.
Metoda auth.wiki
jest mocniejsza niż szkielet helperów MARKMIN, obsługując protokół oembed i komponenty.
Można użyć parametr env
metody auth.wiki
do udostępnienia funkcji swojego wiki. Na przykład:
auth.wiki(env=dict(join=lambda a,b,c:"%s-%s-%s" % (a,b,c)))
umożliwi wykorzystywanie składni znacznika:
@(join:1,2,3)
Wywoła to funkcję join przekazującą dodatkowe parametry a,b,c=1,2,3
i zrenderuje je jako 1-2-3
.
Protokół oembed
W tekście strony wiki można wpisać (lub wkleić) dowolny adres URL, który zostanie zrenderowany jako odnośnik do tego adresu URL. Istnieją pewne wyjątki:
- Jeśli adres URL kończy się rozszerzeniem wskazującym na plik obrazu, odnośnik jest osadzany jako znacznik obrazu
<img/>
. - Jeśli adres URL kończy się rozszerzeniem wskazującym na plik audio, odnośnik jest osadzany jako znacznik audio HTML5
<audio/>
. - Jeśli adres URL kończy się rozszerzeniem wskazującym na plik wideo, odnośnik jest osadzany jako znacznik video HTML5
<video/>
. - Jeśli adres URL kończy się rozszerzeniem pliku Office lub PDF, osadzany jest Google Doc Viewer wyświetlający treść tego dokumentu (działa tylko na publicznych dokumentach).
- Jeśli adres URL wskazuje na stronę YouTube, Vimeo lub Flickr, web2py kontaktuje się z odpowiednim serwisem internetowym i pyta o prawidłowy sposób osadzenia treści. Wykonywane jest to przy wykorzystaniu protokołu
oembed
.
Oto kompletny wykaz wspieranych formatów:
Image (.PNG, .GIF, .JPG, .JPEG)
Audio (.WAV, .OGG, .MP3)
Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)
Obsługiwane przez Google Doc Viewer:
Microsoft Excel (.XLS and .XLSX)
Microsoft PowerPoint 2007 / 2010 (.PPTX)
Apple Pages (.PAGES)
Adobe PDF (.PDF)
Adobe Illustrator (.AI)
Adobe Photoshop (.PSD)
Autodesk AutoCad (.DXF)
Scalable Vector Graphics (.SVG)
PostScript (.EPS, .PS)
TrueType (.TTF)
xml Paper Specification (.XPS)
Obsługiwane przez oembed:
flickr.com
youtube.com
hulu.com
vimeo.com
slideshare.net
qik.com
polleverywhere.com
wordpress.com
revision3.com
viddler.com
Jest to zaimplementowane w pliku web2py gluon.contrib.autolinks
a konkretniej w funkcji expand_one
. Można rozszerzyć obsługę oembed rejestrując więcej usług. Odbywa się to przez dodanie wpisu do listyEMBED_MAPS
:
from gluon.contrib.autolinks import EMBED_MAPS
EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
'http://vimeo.com/api/oembed.json'))
Odwoływanie się do treści wiki
Jeśli utworzyło się stronę z krótką nazwą "contactus", to można się do niej odwołać tak:
@////contactus
Tutaj @//// oznacza
@/app/controller/function/
ale "app", "controller" i "function" są pomijane, gdyż z założenia są domyślne.
Podobnie można użyć menu wiki do ładowania plików mediów (na przykład obrazów) zlinkowanych ze stroną. Strona "Zarządzanie mediami" pokazuje wszystkie pliki, które zostały przesłane i pokaże właściwe wyrażenie do zlinkowanego pliku. Jeśli, na przykład, przesłany będzie plik o nazwie "test.jpg" o tytule "Plaża", to wyrażenie odnośnika będzie podobne do tego:
@////15/Plaża.jpg
@////
jest tym samym przedrostkiem opisanym wcześniej. 15
to id rekordu przechowującego plik zdjęcia. Plaża
to tytuł. .jpg
jest rozszerzeniem oryginalnego pliku.
Jeśli wytnie się i osadzi @////15/Plaża.jpg
na stronie wiki, to osadzi się obraz.
Trzeba pamiętać, że pliki mediów są linkowane do stron i dziedziczą uprawnienia dostępu ze stron.
Menu wiki
Jeśli utworzy się stronę o krótkiej nazwie "wiki-menu", to będzie ona interpretowana jako opis menu. Oto przykład:
- Start > @////index
- Info > @////info
- web2py > http://www.web2py.com
- - O nas > @////aboutus
- - Kontakt z nami > @////contactus
Każda linia jest elementem menu. Używamy podwójnych myślników do zagnieżdżania menu. Symbol >
oddziela tytuł elementu menu od odnośnika elementu menu.
Trzeba pamiętać, że menu jest dołączone do response.menu
. Nie należy tego zamieniać. Element [wiki]
menu z funkcjami serwisowymi jest dodawany automatycznie.
Funkcje serwisowe
Jeśli, na przykład, chce się wykorzystać wiki do tworzenia edytowalnego paska bocznego, to można utworzyć stronę z slug="sidebar"
i osadzić ją w layout.html stosując:
{{=auth.wiki(slug='sidebar')}}
Proszę zwrócić uwagę, że tu nie ma nic specjalnego ze słowem "sidebar". Każda strona wiki może być pobierana i osadzana w każdym punkcie kodu. Pozwala to łączyć funkcjonalność wiki ze zwykłą funkcjonalnością web2py.
Należy również pamietać, że
jest tym samym, coauth.wiki('sidebar'), ponieważ argument slug jest pierwszym w sygnaturze metody. Poprzednie wyrażenie ma trochę prostszą składnię.auth.wiki(slug='sidebar')
Można również osadzać specjalne funkcje wiki, takie jak wyszukiwanie wg tagów:
{{=auth.wiki('_search')}}
lub chmurę tagów:
{{=auth.wiki('_cloud')}}
Rozszerzanie możliwości auth.wiki
Gdy aplikacja udostępniająca wiki jest bardziej skomplikowana, być może trzeba dostosować rekordy bazy danych wiki zarządzane przez interfejs Auth lub udostępnić własny formularz dla zadań CRUD wiki. Na przykład, można chcieć dostosować reprezentację rekordu tablicy wiki lub dodać nowy walidator pola. Nie jest to dostępne domyślnie, ponieważ model wiki jest definiowany dopiero po tym, jak interfejs wiki jest wywoływany przez metodę auth.wiki(). Aby umożliwić dostęp do specyficznych ustawień bazy danych wiki w modelu aplikacji (tj. db.py) trzeba dodać następujące wyrażenie:
# Upewnij się, że to jest wywoływane po tym, jak utworzona została instancja auth
# i przed jakąkolwiek zmianą tabel wiki
auth.wiki(resolve=False)
Przy użyciu powyższej linii kodu w modelu dostępne będą tabele wiki (tj. wiki_page
) dla indywidualnego CRUD lub innych zadań bazy danych.
Trzeba jeszcze wywołać auth.wiki() w kontrolerze lub widoku w celu udostępnienia interfejsu wiki, ponieważ parametr
resolve=False
nakazuje, aby obiekt auth po prostu zbudował model wiki bez jakiegokolwiek ustawiania interfejsu.
Również przez ustawienie parametru resolve na False
w wywołaniu metody, tabele wiki będą teraz dostępne za pośrednictwem domyślnego w aplikacji interfejsu bazy danych na <app>/appadmin
do zarządzania rekordami wiki.
Innym możliwym dostosowaniem jest dodanie dodatkowych pól do standardowych tabel wiki (w ten sam sposób jak w tabelach auth_user
, co opisano w rozdziale 9). Oto jak:
# Umieść to po inicjowaniu obiektu auth
auth.settings.extra_fields["wiki_page"] = [Field("ablob", "blob"),]
Powyższa linia dodaje pole blob
do tabeli wiki_page
. Nie ma potrzeby wywoływania
auth.wiki(resolve=False)
Komponenty
Jedna z najmocniejszych funkcji nowego web2py polega na możliwości osadzania akcji wewnątrz innych akcji. Taką osadzoną akcję nazywamy to komponentem.
Rozważmy następujący model:
db.define_table('thing',Field('name',requires=IS_NOT_EMPTY()))
i następującą akcję:
@auth.requires_login()
def manage_things():
return SQLFORM.grid(db.thing)
Akcja ta jest szczególna, ponieważ zwraca widżet (helper) a nie słownik obiektów. Teraz możemy osadzić akcję manage_things
w jakimś widoku, stosując:
{{=LOAD('default','manage_things',ajax=True)}}
Pozwala to odwiedzającym wchodzić w interakcję z komponentem poprzez Ajax bez przeładowywania strony hosta na której osadzony jest widżet. Akcja jest wywoływana poprzez Ajax, dziedziczy style ze strony hosta i przechwytuje wszystkie zgłoszenia formularzy oraz wiadomości fleszowe, tak że są one obsługiwane na bieżącej stronie. Na szczycie SQLFORM.grid
widżet wykorzystuje podpisane cyfrowo adresy URL w celu ograniczenia dostępu. Więcej informacji można znaleźć w rozdziale 13.
Komponenty, takie jak powyższy, mogą być osadzane na stronach wiki przy użyciu składni MARKMIN:
@{component:default/manage_things}
Informuje to web2py, że chcemy dołączyć akcję "manage_things" zdefiniowaną w kontrolerze "default" jako "komponent" Ajax.
Większość użytkowników jest w stanie zbudować względnie złożoną aplikację po prostu stosując
auth.wiki
do tworzenia stron i menu oraz osadzania własnych komponentów na stronach wiki. Wiki można traktować jako mechanizm do umożliwienia członkom grupy tworzenia stron, ale można również traktować jako sposób na tworzenie aplikacji w modułowy sposób.
Więcej o aplikacji admin
Aplikacja admin dostarcza dodatkowe możliwości, które krótko tutaj omawiamy.
Strona 'site'
Strona site jest stroną główną interfejsu administracyjnego w web2py. Po lewej stronie ekranu wykazuje wszystkie zainstalowane aplikacje , a po prawej znajduje się kilka formularzy specjalnych procedur.
Pierwszy z tych formularzy wyświetla wersję web2py i proponuje aktualizację, jeśli jest dostępna nowsza wersja. Oczywiście przed dokonaniem aktualizacji należy wykonać kopię zapasową! Następne dwa formularze pozwalają na utworzenie nowej aplikacji (prostą lub przez wykorzystanie kreatora online) przez określenie jej nazwy.
https://github.com/rochacbruno/Movuca
lub CMS Instant Press stworzony przez Martina Mulone:
http://code.google.com/p/instant-press/
lub jedną z wielu przykładowych aplikacji dostępnych na:
http://web2py.com/appliances
Pliki web2py są pakietami takimi jak pliki
.w2p
. Są plikami archiwów TAR skompresowanymi przy użyciu gzip. Web2py używa rozszerzenia.w2p
zamiast.tgz
w celu zapobieżenia przed rozpakowywaniem tych plików przez przeglądarkę. Można je rozpakować ręcznie stosując polecenietar zxvf [filename]
, choć nie jest to konieczne.
Po pomyślnym przesłaniu, web2py wyświetla sumę kontrolną MD5 przesłanych plików. Można to wykorzystać do sprawdzenia, czy plik nie został uszkodzony podczas przesyłania. Nazwa InstantPress pojawi się na wykazie zainstalowanych aplikacji.
Jeśli ma się uruchomiony web2py ze źródła i ma się zainstalowany program gitpython
(jeśli to konieczne, ustaw go z 'easy_install gitpython'), można instalować aplikacje bezpośrednio z repozytoriów, używając adresu URL .git
. W tym przypadku będzie można mieć możliwość korzystania z interfejsu administracyjnego do wypychania zmian z powrotem do repozytorium, ale jest to funkcja eksperymentalna.
Na przykład można lokalnie zainstalować aplikację, która wyświetla tą książkę na witrynie web2py pod tym adresem URL:
https://github.com/mdipierro/web2py-book.git
Repozytorium to hostuje bieżącą, zaktualizowaną wersją tej książki (która może różnić się ze stabilną wersją, jaką można zobaczyć na witrynie internetowej web2py). Serdecznie zachęcamy do składania ulepszeń, poprawek i korekt w formie żądań aktualizacji (ang. pull request).
Dla każdej zainstalowanej aplikacji można wykorzystać stronę site do:
- Bezpośredniego przechodzenia do aplikacji przez kliknięcie na jej nazwę.
- Odinstalowywania aplikacji.
- Odwiedzania strony O aplikacji (czytaj niżej).
- Odwiedzania strony Edycja aplikacji (czytaj niżej).
- Odwiedzania do strony Błędy (czytaj niżej).
- Czyszczenia plików tymczasowych (sesje, błędy i pliki cache.disk).
- Pakowania wszystkiego. Zwraca to plik tar zawierający kompletna kopię aplikacji. Sugerujemy, aby przed pakowaniem aplikacji wyczyścić wszystkie pliki tymczasowe.
- Kompilowania aplikacji. Jeśli nie ma błędów, to opcja ta skompiluje do kodu bajtowego wszystkie modele, kontrolery i widoki. Ponieważ widoki mogą rozszerzać lub dołączać inne widoki w drzewie, przed kompilacją, widoki dla każdego kontrolera są zwijane do pojedynczego pliku. Sieciowym efektem bajtowej kompilacji kodu jest to, że aplikacje takie są szybsze, ponieważ nie wymagają dalszego parsowania szablonów lub zamiany ciągów znakowych w czasie wykonania.
- Pakowania skompilowanego. Opcja ta jest tylko dostępna dla aplikacji z kodem skompilowanym bajtowo. Umożliwia pakowanie aplikacji bez kodu źródłowego dla dystrybucji o zamkniętym kodzie. Proszę pamiętać, że Python (tak jak inne języki programowania) może technicznie być dekompilowany, dlatego kompilacja nie zapewnia pełnej ochrony kodu źródłowego. Niemniej jednak dekompilacja może być trudna i jest nielegalna.
- Usuwać pliki skompilowane. Po prostu usuwa z aplikacji skompilowane do kodu bajtowego modele, widoki i kontrolery. Jeśli aplikacja została zapakowana wraz z kodem źródłowym lub jest edytowana lokalnie, to nie ma nic złego w usunięciu plików z kodem skompilowanym i aplikacja nadal będzie działać. Jeśli natomiast została zainstalowana z pakietu z plikami skompilowanymi, to nie jest to bezpieczne, ponieważ nie ma kodu źródłowego, który mógłby przywrócić działanie aplikacji.
Wszystkie funkcje dostępne na stronie głównej interfejsu administracyjnego web2py, są również dostępne programowo poprzez API zdefiniowane w module
gluon/admin.py
. Wystarczy otworzyć powłokę Pythona i zaimportować ten moduł.
Jeśli jest zainstalowany Google App Engine SDK, to strona główna interfejsu administracyjnego wyświetla przycisk do wypychania aplikacji na GAE. Jeśli zainstalowany jest python-git
, to wyświetlany jest przycisk do wypychania aplikacji na Open Shift. Aby zainstalować aplikację na Heroku
lub innym systemie hostingowym należy w tym celu odnaleźć odpowiedni skryptu w folderze "scripts".
Strona 'O aplikacji'
Karta O aplikacji umożliwia edytowanie opisu aplikacji i jej licencji. Są one zapisywane odpowiednio w plikach ABOUT i LICENSE w folderze aplikacji.
Można wykorzystać składnię MARKMIN
lub gluon.contrib.markdown.WIKI
, tak jak opisano to w ref.[markdown2] .
Projekt
W tym rozdziale już używaliśmy stronę Edycja aplikacji. Tutaj chcemy wskazać jeszcze kilka możliwości tej strony:
- Jeśli kliknie się na nazwę pliku, to będzie można zobaczyć zawartość tego pliku z podświetleniem składni.
- Jeśli kliknie się na odnośnik edytuj, będzie można edytować plik w interfejsie internetowym.
- Jeśli kliknie się na odnośnik usuń, to można usunąć plik (całkowicie).
- Jeśli kliknie się na odnośnik testuj, to web2py uruchomi testy. Testy są napisane przez programistę z użyciem doctest Pythona i każda funkcja może mieć swoje własne testy.
- Można dodawać pliki językowe, skanować aplikacje w celu odkrycia wszystkich ciągów znakowych i edytować tłumaczenia łańcuchów tekstowych poprzez interfejs internetowy.
- Jeśli pliki statyczne są zorganizowane w foldery i podfolder, to taki hierarchiczny folder może być przełączany przez klikniecie na nazwę folderu.
Poniższy obraz przedstawia wyjście strony testowej dla aplikacji powitalnej.
Następny obraz przedstawia kartę języków dla aplikacji powitalnej.
Natomiast na tym obrazie pokazani edytowanie pliku językowego, w tym przypadku języka "it" (włoskiego) dla aplikacji powitalnej.
Zintegrowany debugger internetowy
(requires Python 2.6 or later)
Intefejs administracyjny web2py zawiera debugger internetowy.
Stosując dostarczany edytor internetowy można z poziomu powiązanej konsoli debuggera dodawać w kodzie Pythona punkty przerwania oraz można sprawdzać zmienne systemowe w tych punktach przerwań i wznowić wykonanie, tak jak obrazuje to poniższy zrzut ekranu:
Funkcjonalność ta oparta jest na debuggerze Qdb stworzonym przez Mariano Reingarta. Wykorzystuje ona multiprocessing.connection do komunikacji pomiędzy zapleczem a częścią frontową , z protokołem strumieniowym [qdb], podobnym do JSON-RPC.
Ustawianie punktów przerwań w kodzie
Dołącz ten kod do debuggera:
from gluon.debug import dbg
a ten w wybranym miejscu kodu:
dbg.set_trace()
Aplikacja debuggera ma menadżera punktów przerwań.
Uwagi:
web2py nie wie, czy aktualnie ma się otwarte okno debugowania w swojej przeglądarce – mimo wszystko wykonanie zawiesi się.
Platformy IDE zazwyczaj mają własne debuggery międzyprocesowe, np. PyCharm lub PyDev.
Mogą one zgłaszać problemy, gdy dołączy się bibliotekę gluon.
Internetowa powłoka Pythona
Jeśli kliknie się na odnośnik "powłoka" w zakładce kontrolerów na stronie Edycja aplikacji, web2py otworzy internetową powłokę Pythona i będzie wykonywał modele dla bieżącej aplikacji. Pozwala to na interaktywny dialog z aplikacja.
Bądź ostrożny korzystając z internetowej powłoki – ponieważ odmienne żądania powłoki będą wykonywane w różnych wątkach. To łatwo daje błędy, jeśli realizuje się tworzenie i połączenia z bazą danych. Do działań takich jak te (tj. jeśli potrzeba wytrwałości) lepiej jest używać linii poleceń Pythona.
Interaktywna konsola obsługuje również notatnik Pythona.
Crontab
W zakładce kontrolerów na stronie Edycja aplikacji znajduje się również odnośnik "crontab". Klikając na ten odnośnik będzie można edytować plik crontab web2py. Wykorzystuje on ta sama składnię, co crontab uniksowy, ale nie jest uzależniony od systemu Unix. W rzeczywistości, wymaga tylko web2py i działa na Windows. Pozwala na zarejestrowanie akcji, które mają być wykonane w tle o wyznaczonych godzinach. Więcej informacji o tym znajdziesz w następnym rozdziale.
Błędy
Podczas programowania web2py, będziesz nieuchronnie popełniać błędy i wprowadzać poprawki. web2py pomaga na dwa sposoby: 1) pozwala tworzyć testy dla dla każdej funkcji, którą można uruchomić w przeglądarce z poziomu strony Edycja aplikacji i 2) podczas pojawienia się błędu, wystawiany jest odwiedzającemu bilet a błąd jest rejestrowany.
Celowo wprowadźmy błąd w aplikacji images aby zobaczyć jak to działa:
def index():
images = db().select(db.image.ALL,orderby=db.image.title)
1/0
return dict(images=images)
Jeśli teraz uruchomisz akcję index, otrzymasz następujący bilet:
Dostęp do biletu może uzyskać tylko administrator:
Bilet pokazuje komunikat z ostaniego wywołania (ang. traceback) i zawartość pliku, który spowodował problem oraz kompletny stan systemu (zmienne, żądania, sesje itd.). Jeśli w widoku pojawi się błąd, web2py wyświetli widok przekształcony z kodu HTML do Pythona. Pozwala to na łatwą identyfikację logicznej struktury pliku.
Domyślnie bilety są przechowywane w systemie plików i wyświetlane są w grupach przez komunikat dotyczacy ostatniej transakcji (traceback). Interfejs administracyjny dostarcza widoki zagregowane (typ komunikatu z ostatniej transakcji i liczbę występowania) i widoki szczegółowe (wykazywane są wszystkie bilety wg id). Administrator może przełączać się pomiędzy tymi widokami.
Proszę zwrócić uwagę, że interfejs administracyny wyświetla wszędzie kod z podświetlaniem składni (na przykład, w raportach błędów słowa kluczowe web2py są wyświetlane na pomarańczowo). Jeśli kliknie się na słowo kluczowe web2py, to zostaje się przekierowanym do strony dokumentacji o słowach kluczowych.
Jeśli naprawisz błąd dzielenia przez zero w akcji index i wprowadzisz ten kod w widoku index:
{{extend 'layout.html'}}
<h1>Bieżace obrazy</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>
to otrzymasz następujacy bilet:
Trzeba mieć na uwadze, że web2py przekształca widok z HTML do pliku Pythona, a błąd opisany w bilecie odnosi się do wygenerowanego kodu Pythona na NIE do oryginalnego pliku widoku:
Może to wydawać się na początku mylące, ale w praktyce sprawia, ze debugowanie jest prostsze, ponieważ wcięcia Pythona podświetlają strukturę logiczną kodu, która osadza się w widokach.
Kod ten jest wyświetlany na dole tej samej strony.
Wszystkie bilety są wykazywane w interfejsie administracyjnym na stronie Błędy dla każdej aplikacji:
Mercurial
Jeśli platforma web2py została uruchomiona z kodu źródłowego, to interfejs administracyjny wyświetla jeszcze jeden element menu o nazwie "Wersjonowanie".
Wprowadzając komentarz i wciskając przycisk "commit" wynikowa strona zostanie zapisana w bieżącej aplikacji. Przy pierwszym zapisie zostanie utworzone lokalne repozytorium Mercurial dla określonej aplikacji. Mercurial zapisuje, w tle, informacje o dokonanych zmianach kodu w ukrytym folderze ".hg" podfolderu aplikacji. Każda aplikacja ma swój własny folder ".hg" i swój własny plik ".hgignore" file (informuje on Mercurial, które pliki mają być ignorowane). Aby móc wykorzystać ta funkcjonalność, trzeba mieć zainstalowany system kontroli wersji Mercurial (ostatnia wersja, to 1.9):
easy_install mercurial
Interfejs internetowy Mercurial umożliwia przeglądanie poprzednich rewizji i różnic w plikach, ale zalecamy bezpośrednie stosowanie systemu Mercurial z poziomu powłoki lub jednego z wielu klientów Mercurial z interfejsem GUI, ponieważ mają więcej możliwości. Na przykład, pozwalają na synchronizowanie aplikacji ze zdalnym repozytorium.
O Mercurialu możesz przeczytać więcej na stronie Mercurial SCM.
Integracja z Git
Aplikacja interfejsu administracyjnego zawiera również integrację z git. Wymagane są biblioteki git Pythona, np.
pip install gitpython
A następnie dla każdej aplikacji musi się sklonować lub inaczej skonfigurować repozytorium git.
Po tych krokach, zostanie wyświetlone menu Manage dla każdej aplikacji zarządzanej, z opcjami git push
i git pull
. Aplikacje, które nie są zarządzane przez przez git są ignorowane. Można ściagać i wypychać aplikacje na zdalne domyślne repozytorium.
Kreator aplikacji (eksperymantalnie)
Interfejs administracyjny zawiera kreatora, który pomaga w tworzeniu aplikacji. Do kreatora tego można uzyskać dostęp ze strony "Witryny", tak jak pokazano to niżej.
Kreator poprowadzi Cię przez serię kroków tworzenia nowej aplikacji:
- Wybierz nazwę aplikacji;
- Skonfigurujaplikację i wybierz niebedne wtyczki;
- Zbuduj niezbędne modele (utworzy to strony CRUD dla każdego modelu);
- Edytuj widoki tych stron przy użyciu składni MARKMIN.
Poniższy obraz pokazuje drugi krok procedury.
Widoczna jest rozwijana lista, gdzie można wybrać wtyczkę układu (z web2py.com/layouts
), rozwijana lista wielokrotnego wyboru do zaznaczenia innych wtyczek (z web2py.com/plugins
) oraz pole "login config" do umieszczania Janrain "domain:key".
Pozostałe kroki są oczywiste.
Kreator działa dobrze w swoim zakresie, ale dalej jest uważany za funkcję eksperymentalną z dwóch powodów:
- Aplikacje utworzone przez kreator i edytowane później ręcznie nie mogą być później modyfikowane przez kreator.
- Interfejs kreatora będzie zmieniany stopniowo i powiększany o nowe funkcje oraz ułatwienia wizualnego programowania.
W każdym bądź razie, kreator jest poręcznym narzędziem dla szybkiego prototypowania i może być stosowany do zapoczątkowywania nowych aplikacji z alternatywnymi układami i opcjonalnymi wtyczkami.
Konfigurowanie interfejsu administracyjnego
Zwykle nie ma potrzeby wykonywania jakiejkolwiek konfiguracji interfejsu administracyjnego, ale jest możliwych kilka modyfikacji. Po zalogowaniu się do zaplecza administracyjnego, można edytować plik konfiguracyjny interfejsu administracyjnego poprzez adres URL:
http://127.0.0.1:8000/admin/default/edit/admin/models/0.py
Proszę zauważyć, że interfejs administracyjny może być wykorzystywany do edytowania samego siebie. W rzeczywistości intefejs administracyjny jest aplikacją, jak każda inna.
Plik "0.py" jest mniej więcej udokumentowany, tak czy inaczej, opisujemy tutaj kilka ważniejszych możliwości dostosowań:
GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))
Powinno to wskazywać lokalizację pliku "appcfg.py", który jest dostarczany wraz z Google App Engine SDK. Jeśli masz SDK, to musisz zmienić ustawienie tego parametru do wartości prawidłowej. Pozwoli to na wdrożenie GAE z poziomu interfejsu administracyjnego.
Można również ustawić interfejs administracyjny web2py do trybu demo:
DEMO_MODE = True
FILTER_APPS = ['welcome']
Wówczas dostępne będą tylko aplikacje wymienione w FILTER_APPS i to tylko w trybie do odczytu.
Jeśli jesteś nauczycielem i chcesz udostępnić interfejs administracyjny studentom, to możesz udostępnić ten interfejs dla ich projektów (myślę o wirtualnym laboratorium), poprzez ustawienie:
MULTI_USER_MODE = True
W ten sposób studenci będą mogli zalogować się i uzyskać dostęp tylko do swoich aplikacji w interfejsie administracyjnym. Ty, jako pierwszy użytkownik (nauczyciel) uzyskasz dostęp do wszystkiego.
W trybie wielodostępu, można rejestrować studentów wykorzystując odnośnik "bulk register" dostępny w interfejsie administracyjnym i zarządzać nimi poprzez odnośnik "manage students". System śledzi czy studenci są zalogowani i jak wiele linii kodu oni dodali lub usunęli. Dane te są prezentowane na wykresie dostępnym na stronie "O aplikacji".
Trzeba pamiętać, że ten mechanizm zakłada, że wszyscy użytkownicy są zaufani. Wszystkie aplikacje utworzone z poziomu interfejsu administracyjnego uruchamiane są z takim samym poświadczeniem na tym samym systemie plików. Jest więc możliwe uzyskanie dostępu do aplikacji stworzonej przez jednego studenta do aplikacji innego studenta. Jest też możliwe utworzenie przez studenta aplikacji blokującej serwer.
Interfejs administracyjny dla urządzeń mobilnych
Proszę zwrócić uwagę, że aplikacja interfejsu administracyjnego zawiera "plugin_jqmobile", który ma zapakowana bibliotekę jQuery Mobile. Gdy interfejs administracyjny jest dostępny z urządzenia mobilnego, jest to wykrywane przez web2py i wyświetlany jest interfejs wykorzystujący układ przyjazny dla urządzeń mobilnych:
Więcej na temat interejsu administracyjnego appadmin
Interfejs administracyjny appadmin jest graficznym interfejsem dostępu bazy danych i nie jest przeznaczony do publicznego udostępniania. Składa się tylko z dwóch plików: kontrolera "appadmin.py" i widoku "appadmin.html", który jest wykorzystywany przez wszystkie akcje w kontrolerze.
Kontroler appadmin jest względnie mały i czytelny. Dostarcza przykład projektowania interfejsu bazy danych.
Interfejs appadmin pokazuje, które bazy danych są dostępne i jakie tabele istnieją w każdej bazie danych. Można wstawiać rekordy oraz listować indywidualnie wszystkie rekordy dla poszczególnych tabel. Stronicowanie interfejsu appadmin wyprowadza jednorazowo 100 rekordów.
Po wybraniu zestawu rekordów, nagłówek strony zmienia się, umożliwiając aktualizowanie lub usuwanie wybranych rekordów.
W celu zaktualizowania rekordów, wprowadź zapytanie SQL przypisane w polu tekstowym Query:
title = 'test'
gdzie wartości ciągu znakowego muszą być ujęte w znaki apostrofu. Można specyfikować wiele pól, oddzielając je przecinkami.
Aby usunąć rekord, kliknij na odpowiednie pole wyboru w celu potwierdzenia, że na pewno chcesz to zrobić.
W interfejs appadmin można również dokonywać złączeń tabel, jeśli SQL FILTER zawiera warunek SQL, który ma dwie lub więcej tabel. Na przykład spróbuj:
db.image.id == db.post.image_id
Jest to przekazywane do DAL i traktowane jak kwerenda łączącą dwie tabele. Dlatego obydwie wybrane tabele są wyrażeniem INNER JOIN. Oto wyjście:
Jeśli kliknie się na numer id pola, otrzymuje się stronę edycji dla rekordu z odpowiednim id.
Jeśli kliknie się na numer odnośnego pola, wyświetli się strona edycji odnośnego rekordu.
Nie można zaktualizować lub usunąć wskazanych w złączeniu rekordów, ponieważ dotyczy to rekordów z wielu tabel i byłoby nie jednoznaczne.
Dodatkowo, możliwości zarządzania bazą danych przez interfejs appadmin obejmują wyświetlanie szczegółowych informacji o zawartości obiektu cache
aplikacji (w /yourapp/appadmin/cache
) jak również zawartości bieżących obiektów request
, response
i session
(w /yourapp/appadmin/state
).
Interfejs appadmin zamienia response.menu
na swoje własne menu,które udostępnia odnośniki do stron edit aplikacji w interfejsie admin, stronę db (zarządzania bazą danych), stronę state i stronę cache. Jeśli układ aplikacji nie generuje menu przy użyciu response.menu
, to nie zobaczy się menu appadmin. W takim przypadku trzeba zmodyfikować plik appadmin.html i dodać {{=MENU(response.menu)}}
w celu wyświetlania menu.