Chapter 12: Komponenty i wtyczki

Komponenty i wtyczki

komponent
wtyczka

Komponenty i wtyczki są stosunkowo nową funkcjonalnością web2py i istnieje pewna różnica zdań między programistami, co do tego czym są te elementy i czym powinny być. Większość nieporozumień bieże się z różnych zastosowań komponentów i wtyczek w innych systemach oraz z faktu, że programiści ciągle jeszcze pracuja nad specyfikacjami.

Jednakże, wsparcie wtyczek jest ważną funkcjonalnością i musimy podać kilka definicji. Definicje te nie są ostateczne, po prostu zgodne są one ze wzorcami programowania i omówimy je w tym rozdziale.

Postaramy się rozwiązać tutaj dwa problemy:

  • Jak można budować mudułowe aplikacje, które minimalizują obciążenie serwera i maksymalizują ponowne wykorzystanie kodu?
  • Jak można dystrybuować fragmenty kodu w stylu bardziej lub mniej zbliżonym do "plugin-and-play"?

Komponenty rozwiązują pierwszy problem, wtyczki drugi.

Komponety LOAD i Ajax

load
LOAD
Ajax
Komponent jest funkcjonalnie autonomiczną częścią strony internetowej.

Komponet może składać się z modułów, kontrolerów i widoków, ale nie ma ścisłego wymogu w tym zakresie. Podczas osadzania na stronie internetowej musi być zlokalizowany między znacznikami html (na przykład w DIV, SPAN lub IFRAME) i musi wykonywać swoje zadanie niezależnie od reszty kodu strony. Jesteśmy szczególnie zainteresowani komponentami, które są ładowane na stronie i komunikują się z funkcją kontrolera komponentu poprzez Ajax.

Przykładem komponentu jest "komponent komentarzy", który jest zawarty w DIV i pokazuje komantarze użytkowników oraz formularz tworzenia nowego komentarza. Gdy formularz jest zgłaszany, jest wysyłany na serwer poprzez Ajax, lista jest aktualizowana i komentarz zostaje zapisany po stronie serwera w bazie danych. Zawartość DIV jest odświeżana bez przeładowywania postałej części strony.

LOAD

Funkcja LOAD web2py wykonuje to łatwo bez konieczności jawnego tworzenia kodu JavaScript i Ajax lub programowania.

Naszym celem jest umożliwienie tworzenia aplikacji internetowej przez montaż komponentów na układach stron.

Rozpatrzmy prostą aplikację "test" rozszerzającą szkieletową aplikację z indywidualnym modelem w pliku "models/db_comments.py":

1
2
3
db.define_table('comment_post',
   Field('body','text',label='Your comment'),
   auth.signature)

jedną akcją w "controllers/comments.py"

1
2
3
4
@auth.requires_login()
def post():
    return dict(form=SQLFORM(db.comment_post).process(),
                comments=db(db.comment_post).select())

i odpowiednim widokiem "views/comments/post.html"

1
2
3
4
5
6
7
8
{{extend 'layout.html'}}
{{for post in comments:}}
<div class="post">
  On {{=post.created_on}} {{=post.created_by.first_name}}
  says <span class="post_body">{{=post.body}}</span>
</div>
{{pass}}
{{=form}}

Dostęp do niej można uzyskać z adresu:

1
http://127.0.0.1:8000/test/comments/post

Jak na razie nie ma nic szczególnego w tej akcji, ale możemy przekształcić ją w komponent przez zdefiniowanie nowego widoku z rozszerzeniem ".load", który nie rozszerza układu.

Dlatego tworzymy "views/comments/post.load":

1
2
3
4
5
6
7
{{for post in comments:}}
<div class="post">
  On {{=post.created_on}} {{=post.created_by.first_name}}
  says <blockquote class="post_body">{{=post.body}}</blockquote>
</div>
{{pass}}
{{=form}}

Możemy uzyskać dostęp do tego widoku z adresu:

1
http://127.0.0.1:8000/test/comments/post.load

Jest to komponent, który mozemy umieścić na każdej innej stronie, robiąc po prostu tak:

1
{{=LOAD('comments','post.load',ajax=True)}}

Na przykład, w "controllers/default.py" możemy edytować

1
2
def index():
    return dict()

i w odpowiednim widoku dodać ten komponent:

1
2
{{extend 'layout.html'}}
{{=LOAD('comments','post.load',ajax=True)}}

Odwiedzając stronę

1
http://127.0.0.1:8000/test/default/index

zwykłą jej zwartość oraz dodany komponent komentarzy:

image

Komponent {{=LOAD(...)}} jest renderowany następująco:

1
2
3
<script type="text/javascript"><!--
web2py_component("/test/comment/post.load","c282718984176")
//--></script><div id="c282718984176">loading...</div>

Faktycznie wygenerowany kod zależy od opcji przekazanych do funkcji LOAD.

Funkcja web2py_component(url,id) jest zdefiniowana w "web2py_ajax.html" i wykonuje całą magię: wywołuje url poprzez Ajax i osadza odpowiedź w DIV o odpowiednim id. Wychwytuje to każdy formularz przesłany do DIV i składa te formularze poprzez Ajax. Celem Ajax jest zawsze sam DIV.

Sygnatura LOAD

Pełna sygnatura helpera LOAD jest następująca:

1
2
3
4
5
6
LOAD(c=None, f='index', args=[], vars={},
     extension=None, target=None,
     ajax=False, ajax_trap=False,
     url=None,user_signature=False,
     timeout=None, times=1,
     content='loading...',**attr):

Tutaj:

  • pierwsze dwa argumenty c i f są odpowiednio kontrolerem i funkcją, którą chce się wywołać;
  • args i vars to argumenty i zmienne, które chce się przekazać do funkcji; pierwsze jest listą a drugie słownikiem;
  • extension to ewentualne rozszerzenie, rozszerzenie moze być również przekazane jako część argumentu f (funkcja), tak jak f='index.load';
  • target to id docelowego DIV; jeśli nie zostanie określony, to wygenerowany zostanie losowy identyfikator celu;
  • ajax powinien zostać ustawiony na True, jeśli DIV ma być wypełniany przez Ajax a na False, jeśli DIV ma być wypełniony przed tym, gdy zwrócona zostaje bieżąca strona (unikając w ten sposób wywołania Ajax);
  • ajax_trap=True oznacza, że musi zostać przechwycone każde złożenie formularza w DIV i przesłane za pośrednictwem Ajax a odpowiedź musi być renderowana wewnątrz DIV; ajax_trap=False wskazuje, że formularze muszą być zgłaszane w sposób zwykły, więc przeładowywana będzie cała strona; jeśli ajax=True, to ajax_trap jest ignorowane i przyjmuje się, że ma wartość True;
  • url, jeśli jest określony, nadpisuje wartości c, f, args, vars i extension oraz ładuje komponent z adresu url; jest używany do ładowania stron komponentów serwowanych przez inne aplikacje (które mogły być utworzone w web2py lub nie);
  • user_signature ma domyślną wartość False, ale jeśli uzytkownik jest zalogowany, to powinien być ustawiony na True; pozwala to upewnić się, że wywołanie zwrotne Ajax jest podpisane cyfrowo; jest to udokumentowane w rozdziale 4;
  • times określa, ile razy komponet może być żądany; użycie "infinity" wskazuje, że ładowanie komponentu jest utrzymywane ciagle; opcja ta jest przydatna przy wyzwalaniu zwykłych procedur dla żądania określonego dokumentu;
  • timeout ustawia czas oczekiwania w millisekundach przed rozpoczęciem żądania lub częstotliwość, jeśli times ma wartość większą niż 1;
  • content to zawartość, jak ma być wyświetlana podczas wykonywania wywołań Ajax; może to być helper, jak w content=IMG(..);
  • ewenytulny **attr (atrybuty), który może być przekazany do zawartego DIV.

Jeśli nie jest określony żaden widok .load, wykotrzystywany jest generic.load, który renderuje słownik zwracany przez akcję poza układem. Działa to najlepiej, gdy słownik zawiera pojedynczy element.

Jeśli ładuje się (przy użyciu LOAD) komponent mający rozszerzenie .load i odpowiednią funkcję kontrolera, przekierowującą do innej akcji (na przykład do formularza logowania), propagowane jest rozszerzenie .load a nowy adres URL (także ten przekierowujący) jest również ładowany z rozszerzeniem .load.

Przekierowanie z komponentu

Przekierownie z poziomu komponentu można wykonać stosując:

1
redirect(URL(...),client_side=True)

należy jednak pamiętać, że adres URL przekierowania jest domyślnie pobierany z argumentu extension. Zapoznaj się z uwagami na temat argumentu extension w funkcji URL w rozdziale 4

Odświeżenie strony poprzez przekierowanie po złożeniu komponentu

Jeśli wywołuje się akcję poprzez Ajax i chce się wymusić przekierowanie strony nadrzędnej, można to zrobić przez przekierowanie z poziomy funkcji kontrolera LOAD. Jeśli chce się przeładować stronę nadrzędną, można wykonać przekierowanie do niej. Adres URL strony nadrzędnej jest znany (zobacz "Komunikacja klient-server w komponentach" ), więc po przetworzeniu zgłoszenia formularza, funkcja kontrolera odświeża stronę nadrzędną poprzez przekierowanie:

1
2
3
if form.process().accepted: 
    ...
    redirect( request.env.http_web2py_component_location,client_side=True)

Proszę mieć na uwadze, że w rozdziale "Komunikacja klient-server w komponentach" opisuje, jak komponent może zwracać kod JavaScript, który mógłby być wykorzystany do bardziej zaawansowanych działań gdy komponent jest składany. Szczególnym przypadkiem przeładowania jest inny komponent, opisany dalej.

Przeładowanie innego komponentu

Jeśli korzysta się na stronie z wielu komponentów, można złożyć jeden z nich, aby przeładować inny. Robi się to przez złożenie komponentu w zwracanym kodzie JavaScript.

Jest to możliwe przez wykonanie sztywnego kodu w docelowym DIV, ale w tej recepcie użyjemy zmienną łańcucha zapytania dla poinformowania kontrolera składającego, który komponent chcemy przeładować. Zostaje on zidentyfikowany przez identyfikator elementu DIV, który zawiera docelowy komponent. W tym przypadku element DIV ma identyfikator 'map'. Należy pamiętać, że niezbędne jest zastosowanie target='map' w LOAD celu. Bez tego identyfikator celu jest obliczany losowo i reload() nie bedzie działać. Zobacz powyższą sygnaturę LOAD.

W widoku wykonaj to:

1
{{=LOAD('default','submitting_component.load',ajax=True,vars={'reload_div':'map'})}}

Kontroler przynależny do składanego komponentu wymaga odesłania z powrotem kodu JavaScript, więć wystarczy dodać poniższe wyrażenie do kodu kontrolera, gdzie przetwarzane jest zgłoszenie:

1
2
3
4
if form.process().accepted:
...
    if request.vars.reload_div:
        response.js =  "jQuery('#%s').get(0).reload()" % request.vars.reload_div

Oczywiście należy usunąć to przekierowanie, jeśli używa się rozwiązania omówionego w poprzednim rozdziale.

To jest to. Biblioteki JavaScript pilnują przeładowania. Można to uogólnić do obsługi wielu komponentów przy użyciu kodu wyglądającego tak:

1
jQuery('#div1,#div2,#div3').get(0).reload()

Więcej informacji o response.js można znaleźć w rozdziale "Komunikacja klient-server w komponentach" (poniżej).

Przesyłka Ajax nie obsługuje wiele formularzy

Ponieważ przesyłka Ajax nie obsługuje wiele formularzy, czyli przesyłania plików, przesyłane pola nie będa działać z komponentem LOAD. Można dać się zwieść, że to działa, ponieważ wysłane pola będą działać normalnie, jeśli żądanie POST zostało wykonane z indywidualnego widoku .load komponentu. Zamiast tego, przesyłanie jest realizowane za pomocą zewnętrznych widżetów zgodnych z Ajax oraz ręcznych poleceń przechowujących web2py.

LOAD a komunikacja klient-server w komponentach

Gdy akcja komponentu jest wywoływana poprzez Ajax, web2py przekazuje w żądaniu dwa nagłówki HTTP:

1
2
web2py-component-location
web2py-component-element

które można pozyskać w akcji poprzez zmienne:

1
2
request.env.http_web2py_component_location
request.env.http_web2py_component_element

Ten drugi jest też dostępny poprzez:

request.cid

1
request.cid

Pierwszy nagłówek zawiera adres URL strony, która wywołuje akcję komponentu. Drugi zawiera id elementu DIV, który zawiera odpowiedź.

Akcja komponentu może również przechowywać dane w dwóch specjalnych nagłówkach odpowiedzi HTTP, które są interpretowane przez całą stronę odpowiedzi. Są to:

1
2
web2py-component-flash
web2py-component-command

Mogą być one ustawione przez:

1
2
response.headers['web2py-component-flash']='....'
response.headers['web2py-component-command']='...'

lub (jeśli akcja jest wywoływana przez komponent) automatycznie poprzez:

1
2
response.flash='...'
response.js='...'

Pierwszy zawiera tekst, który ma być wyświetlony w odpowiedzi. Ten drugi zawiera kod JavaScript, który ma być wykonany w odpowiedzi. Nie może zawierać znaków nowej linii.

Dla przykładu, zdefiniujmy komponent formularza kontaktowego w "controllers/contact/ask.py", który umożliwia użytkownikowi zadanie pytania. Komponent będzie wysyłał wiadomość email z tym pytaniem do administratora systmeu, wyświetlał komunikat fleszowy "thank you" i usuwał komponent ze strony:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def ask():
    form=SQLFORM.factory(
        Field('your_email',requires=IS_EMAIL()),
        Field('question',requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        if mail.send(to='admin@example.com',
                  subject='from %s' % form.vars.your_email,
                  message = form.vars.question):
            response.flash = 'Thank you'
            response.js = "jQuery('#%s').hide()" % request.cid
        else:
            form.errors.your_email = "Unable to send the email"
    return dict(form=form)

Pierwsze cztery linie tego kodu definiują formularz i zatwierdzają go. Obiekt mail, używany do wysyłania wiadomości, jest zdefiniowany w domyślnej aplikacji szkieletowej. Ostatnie cztery linie implementują całą logike specyficzną dla komponentu przez pobranie danych z nagłówka żądania HTTP i ustawiają nagłówki odpowiedzi HTTP.

Teraz można osadzić formularz kontaktowy na jakiejś stronie poprzez:

1
{{=LOAD('contact','ask.load',ajax=True)}}

Proszę zwrócić uwagę, że nie definiujemy tu widoku .load dla komponentu ask. Nie musimy tego robić, ponieważ zwracany jest pojedynczy obiekt (form) i dlatego wykorzystany będzie widok generyczny "generic.load". Trzeba pamiętać, że widoki generyczne są narzędziem programistycznym. W środowisku produkcyjnym należy skopiować "views/generic.load" do "views/contact/ask.load".

user_signature
requires_signature
Można zablokować dostęp do funkcji wywoływanych poprzez Ajax przez cyfrowy podpis adresu URL, używając argument user_signature:

1
{{=LOAD('contact','ask.load',ajax=True,user_signature=True)}}

który dodaje podpis cyfrowy do adresu URL. Podpis cyfrowy musi być sprawdzony przez dekorator w funkcji wywołania zwrotnego:

1
2
@auth.requires_signature()
def ask(): ...

Przechwycone odnośniki Ajax a helper

A
odnośniki Ajax

Zwykle odnośnik nie jest przechwytywany a przez klikniecie na odnośnik w komponencie ładowana jest cała zlinkowana strona. Czasem zachodzi potrzeba zlinkowania strony, tak aby ładowana ona była w komponencie. Można to osiągnąć przy użyciu funkcji pomocniczej A:

1
{{=A('linked page',_href='http://example.com',cid=request.cid)}}

Jeśli określi się cid, to zlinkowana strona zostanie załadowana poprzez Ajax. Atrybut cid jest identyfikatorem (id) elemntu html, w którym ładowana jest zawartość strony. W naszym przypadku ustawiliśmy to na request.cid, czyli id komponentu, w którym generowany jest odnośnik. Linkowana strona może być i zazwyczaj jest wewnętrznym lokalizatorem URL generowanym przy użyciu funkcji pomocniczej URL .

Wtyczki

Wtyczka jest to każdy podzbiór plików aplikacji.

i naprawdę znaczy każdy:

  • Wtyczka nie jest modułem, nie jest modelem, nie jest kontrolerem, nie jest widokiem, ale może zawierać moduły, modele, kontrolery lub widoki.
  • Wtyczka nie musi być funkcjonalnie niezależna ale może zależeć od kodu innych wtyczek lub komponentów.
  • Wtyczka nie jest systemem wtyczek i dlatego nie ma koncepcji ich rejestracji ani izolacji, choć dostarczane są zasady nazewnicze pozwalające osiągnąć jakąś izolację.
  • Mówimy o wtyczce do określonej aplikacji a nie o wtyczce do web2py.

Więc dlaczego nazywamy to wtyczką? Dlatego że, zapewniony jest mechanizm pakowania podzbiorów kodu aplikacji i rozpakowywania ich w innych aplikacjach (czyli są wtykowe). Zgodnie z tą definicją za wtyczkę można uznać każdy plik aplikacji.

Podczas dystrybuowania aplikacji jej wtyczki są pakowane i dystrybuowane razem z nią.

W praktyce, aplikacja admin dostarcza interfejs do pakowania i rozpakowywania wtyczek, oddzielnie dla każdej aplikacji. Pliki i foldery aplikacji, które mają przedrostek plugin_name można pakować do pliku o nazwie:

web2py.plugin.name.w2p

i dystrybuowane razem.

image

Pliki, które tworzą wtyczkę nie są traktowane przez web2py w inny sposób, niż pozostałe pliki, z tym wyjątkiem, że aplikacja admin rozróżnia ich nazwy, które są przeznaczone razem do dystrybucji i wyświetla je na oddzielnej stronie:

image

Jednak w rzeczywistości według powyższej definicji, wtyczki te są bardziej ogólne, niż te uznawane za takie przez aplikację admin.

W praktyce będziemy mieć do czynienia tylko z dwoma rodzajami wtyczek:

  • Wtyczki komponentowe. Są to wtyczki, które zawieraja kod komponentów, tak jak zdefiniowano to w poprzednim rozdziale. Wtyczka komponentu może zawierać jeden lub więcej komponentów. Można mieć na przykład wtyczkę plugin_comments, która zawiera komponent comments zaproponowany powyżej. Innym przykładem może być wtyczka plugin_tagging, która zawiera komponent tagging oraz wtyczke tag-cloud zawierającą komponent, który udostępnia jakieś tabele bazy danych, tez zdefiniowanych jako wtyczka.
  • Wtyczki z układem. Sa to wtyczki zawierające widok układu i pliki statyczne wymagane przez układ. Zastosowanie takiej wtyczki nadaje aplikacji nowy wygląd.

Zgodnie z powyższymi definicjami, komponenty tworzone w poprzednim rozdziale, na przykład "controllers/contact.py", są też wtyczkami. Możemy przenieść je z jednej aplikacji do innej i użyć te komponenty zgodnie z ich definicją. Jednak nie sa one uznawane za takie przez aplikację admin, bo nie ma nic, co oznacza je jako wtyczki. Związane są z tym dwa problemy:

  • W nazewnictwie plików wtyczek trzeba stosować pewną konwencję, tak aby aplikacja admin mogła rozpoznawać te pliki jako należące do tej samej wtyczki.
  • Jeśli wtyczka zawiera pliki modelu, trzeba ustalić konwencję tak, aby obiekty w nich zdefiniowane nie zanieczyszczały przestrzeni nazw i niekolidowały z sobą.

Załóżmy, że wtyczka nazywa się name. Oto zasady, które powinny być stosowane:

Zasada 1

Wtyczki modeli i kontrolerów powinny być nazywane w ten sposób:

  • models/plugin_name.py
  • controllers/plugin_name.py

a wtyczki widoków, modułów oraz statycznych i prywatnych plików powinny się znajdować w folderach o nazwach:

  • views/plugin_name/
  • modules/plugin_name/
  • static/plugin_name/
  • private/plugin_name/

Zasada 2

W modelach wtyczek można definiować tylko obiekty z nazwą rozpoczynającą się od:

  • plugin_name
  • PluginName
  • _

Zasada 3

Modele wtyczek mogą definiować tylko zmienne sesji z nazwami rozpoczynającymi się od:

  • session.plugin_name
  • session.PluginName

Zasada 4

Wtyczki powinny zawierać licencję i dokumentację. Powinny być one umieszczone w plikach:

  • static/plugin_name/license.html
  • static/plugin_name/about.html

Zasad 5

Wtyczka może polegać tylko na istnieniu obiektów globalnych zdefiniowanych w szkielecie "db.py", czyli:

  • połączeni z bazż danych o nazwie db,
  • instancji klasy Auth o nazwie auth,
  • instancji klasy Crud o nazwie crud,
  • instancji klasy Service o nazwie service

Niektóre wtyczki są bardziej skomplikowane i mają parametr konfiguracyjny w przypadku istnienia więcej instancji niż jedna.

Zasada 6

Jeśli wtyczka potrzebuje parametrów konfiguracyjnych, należy ustawic je poprzez PluginManager, tak jak opisano niżej.

PluginManager

Stosując się do powyższych zasad można mieć pewność, że:

  • Aplikacja admin rozpozna wszystkie pliki i foldery o nazwach plugin_name jako część jednej encji.
  • Wtyczki nie kolidują z sobą.

Powyższe zasady nie rozwiązują problemu wersjonowania i zależności wtyczek. Jest to poza zakresem tego podręcznika.

Wtyczki komponentowe

wtyczka komponentowa

Wtyczki komponentowe są wtyczkami definiujacymi komponenty. Zwykle, komponenty mają dostęp do bazy danych i definiuja swoje własne modele.

W tym miejscu włączymy poprzedni komponent comments do wtyczki comments_plugin wykorzystując ten sam kod o którym pisaliśmy wcześniej, ale postępując zgodnie z wyżej podanymi zasadami.

Po pierwsze, utworzymy model o nazwie "models/plugin_comments.py":

1
2
3
4
5
6
db.define_table('plugin_comments_comment',
   Field('body','text', label='Your comment'),
   auth.signature)

def plugin_comments():
    return LOAD('plugin_comments','post',ajax=True)

Proszę zwrócić uwagę, że ostatnie dwie linie definiują funkcję, która upraszcza osadzanie wtyczki.

Po drugie, definiujemy "controllers/plugin_comments.py"

1
2
3
4
5
6
def post():
    if not auth.user:
        return A('login to comment',_href=URL('default','user/login'))
    comment = db.plugin_comments_comment
    return dict(form=SQLFORM(comment).process(),
                comments=db(comment).select())

Po trzecie, tworzymy widok o nazwie "views/plugin_comments/post.load":

1
2
3
4
5
6
7
{{for comment in comments:}}
<div class="comment">
  on {{=comment.created_on}} {{=comment.created_by.first_name}}
  says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}

Teraz możemy użyć interfejsu admin do spakowania wtyczki dla celów dystrubucyjnych. Wtyczka zostanie zapisana jako:

1
web2py.plugin.comments.w2p

Spakowaną wtyczkę możemy uzyć w dowolnym widoku, instalując ją poprzez stronę edit w aplikacji admin i następnie dodając ją do widoku wyrażeniem:

1
{{=plugin_comments()}}

Oczywiście można wykonać wtyczkę bardziej skomplikowaną, w której komponety pobierają parametry i mają opcje konfiguracyjne. Im bardziej skomplikowane komponenty, tym większe prawdopodobieństwo kolizji nazewniczych. Poniżej opisana klasa Plugin Manager została stworzona, aby unikać tego problemu.

Plugin manager

"PluginManager" to klasa zdefiniowana w gluon.tools. Zanim omówimy jej działanie, wyjaśnimy jak ją stosować.

Tutaj rozważymy poprzednią wtyczkę plugin_comments i zrobimy ja lepiej. Chcemy uzyskać możliwość jej dostosowywania:

1
db.plugin_comments_comment.body.label

bez konieczności edytowania samego kodu wtyczki.

Oto jak możemy to zrobić.

Po pierwsze, przepisujemy wtyczkę "models/plugin_comments.py" w ten sposób:

1
2
3
4
5
6
7
8
9
def _():
    from gluon.tools import PluginManager
    plugins = PluginManager('comments', body_label='Your comment')

    db.define_table('plugin_comments_comment',
        Field('body','text',label=plugins.comments.body_label),
        auth.signature)
    return lambda: LOAD('plugin_comments','post.load',ajax=True)
plugin_comments = _()

Proszę zwrócić uwagę jak cały kod jest zamknięty w pojedynczej funkcji o nazwie _, tak że nie zanieczyszcza on globalnej przestrzeni nazw. Proszę też zauważyć, jak ta funkcja tworzy instancję PluginManager.

Teraz w każdym innym modelu aplikacji, na przykład w "models/db.py", można skonfigurować tą wtyczkę następująco:

1
2
3
from gluon.tools import PluginManager
plugins = PluginManager()
plugins.comments.body_label = T('Post a comment')
Obiekt plugins istnieje już w domyślnej aplikacji szkieletowej w "models/db.py".

Obiekt klasy PluginManager jest obiektem pojedynczego składowania na poziomie wątku obiektów Storage. Oznacza to, że mozna tworzyć instancje tego obiektu tyle razy, ile się chce w tej samej aplikacji, ale (niezależnie od tego, czy mają one taka samą nazwę, czy nie) działaja one tak jakby to była jedna instancja PluginManager.

W szczególności w każdym pliku wtyczki można utworzyć jej własny obiekt PluginManager i go zarejestrować wraz z własnymi parametrami:

1
plugins = PluginManager('name', param1='value', param2='value')

Można zastąpić te parametry gdziekolwiek (na przykład w "models/db.py") z kodem:

1
2
plugins = PluginManager()
plugins.name.param1 = 'other value'

W jednym miejscu można skonfigurować więcej wtyczek:

1
2
3
4
5
6
plugins = PluginManager()
plugins.name.param1 = '...'
plugins.name.param2 = '...'
plugins.name1.param3 = '...'
plugins.name2.param4 = '...'
plugins.name3.param5 = '...'
Podczas definiowania wtyczki, PluginManager musi pobrać argumenty: nazwę wtyczki i opcjonalne argumenty nazwane, które są prametrami domyślnymi. Jednak, podczas konfigurowania wtyczki, konstruktor PluginManager nie musi pobierać argumentów. Konfiguracja musi poprzedzać definicję wtyczki (czyli musi znajdować się w pliku modelu, który dostarczany jest alfabetycznie jako pierwszy).

Wtyczki układu

wtyczka układu

Wtyczki układu sa prostsze niż wtyczki komponentowe, ponieważ zazwyczaj nie zawierają kodu Pythona, ale tylko widoki i pliki statyczne. Jednak należy tu też przestrzegać dobrych praktyk.

Po pierwsze, utwórz folder o nazwie "static/plugin_layout_name/" (gdzie name to nazwa układu) i umieść tam wszystkie statyczne pliki.

Po drugie, utwórz plik układu o nazwie "views/plugin_layout_name/layout.html", który zawiera układ i odnosniki do plików obrazów, CSS i JavaScript w "static/plugin_layout_name/".

Po trzecie, zmodyfikuje plik "views/layout.html", tak aby był prosty w odczycie:

1
2
{{extend 'plugin_layout_name/layout.html'}}
{{include}}

Zaletą tego rozwiązania jest to, że użytkownicy mogą instalować wiele układów i wybierać ten, który ma być zastosowany, przez prostą edycję "views/layout.html". Co więcej, "views/layout.html" nie zostanie spakowany z wtyczka przez admin, więc nie ma ryzyka, że wtyczka nadpisze kod użytkownika, w przypadku poprzednio zainstalowanego układu.

Repozytoria wtyczek, instalowanie wtyczek poprzez interfejs administracyjny

Choć nie ma jednego repozytorium wtyczek web2py, można znaleźć wiele z nich pod następujacymi adresami URL:

Najnowsze wersje aplikacji admin web2py pozwalają automatycznie pobrać i zainstalować wtyczki ze strony web2pyslices. W celu dodania wtyczki do aplikacji, trzeba edytować aplikację admin application i wybrać opcję Download Plugins, obecnie umieszczoną na dole ekranu.

Jeśli chce się publikować własne wtyczki, trzeba założyć konto na web2pyslices.

Oto zrzut ekranu pokazujący kilka samo instalujacych się wtyczek:

image

 top