2012-11-02 8 views
6

Piszę stronę administratora, która kontroluje kilka stron internetowych z tym samym schematem programu i bazy danych, ale inną treść. Adres URL, który zaprojektowałem tak:Jak obsłużyć złożony adres URL w elegancki sposób?

http://example.com/site     A list of all sites which under control 
http://example.com/site/{id}   A brief overview of select site with ID id 
http://example.com/site/{id}/user  User list of target site 
http://example.com/site/{id}/item  A list of items sold on target site 
http://example.com/site/{id}/item/{iid} Item detailed information 
# ...... something similar 

Jak widać, prawie wszystkie adresy URL wymagają adresu site_id. I prawie we wszystkich widokach, muszę wykonać kilka typowych zadań, takich jak model strony zapytania dla bazy danych z adresem site_id. Ponadto muszę przekazać identyfikator site_id za każdym razem, gdy wywoływam request.route_path.

A więc ... czy mam w ogóle ułatwić sobie życie?

Odpowiedz

5

Przydałaby się metoda hybrydowa w celu załadowania witryny.

def groupfinder(userid, request): 
    user = request.db.query(User).filter_by(id=userid).first() 
    if user is not None: 
     # somehow get the list of sites they are members 
     sites = user.allowed_sites 
     return ['site:%d' % s.id for s in sites] 

class SiteFactory(object): 
    def __init__(self, request): 
     self.request = request 

    def __getitem__(self, key): 
     site = self.request.db.query(Site).filter_by(id=key).first() 
     if site is None: 
      raise KeyError 
     site.__parent__ = self 
     site.__name__ = key 
     site.__acl__ = [ 
      (Allow, 'site:%d' % site.id, 'view'), 
     ] 
     return site 

Będziemy używać groupfinder do mapowania użytkowników do zleceniodawców. Wybraliśmy tutaj, aby mapować je tylko na strony, do których mają członkostwo. Nasze proste przejście wymaga tylko obiektu głównego. Aktualizuje załadowany site z __acl__, który używa tych samych zleceń, aby utworzyć konfigurację groupfinder.

Musisz ustawić podane wzory w Piramidzie Cookbook.

def site_pregenerator(request, elements, kw): 
    # request.route_url(route_name, *elements, **kw) 
    from pyramid.traversal import find_interface 
    # we use find_interface in case we improve our hybrid traversal process 
    # to take us deeper into the hierarchy, where Site might be context.__parent__ 
    site = find_interface(request.context, Site) 
    if site is not None: 
     kw['site_id'] = site.id 
    return elements, kw 

Pregenerator można znaleźć site_id i dodać go do adresów URL automatycznie.

def add_site_route(config, name, pattern, **kw): 
    kw['traverse'] = '/{site_id}' 
    kw['factory'] = SiteFactory 
    kw['pregenerator'] = site_pregenerator 

    if pattern.startswith('/'): 
     pattern = pattern[1:] 
    config.add_route(name, '/site/{site_id}/' + pattern, **kw) 

def main(global_conf, **settings): 
    config = Configurator(settings=settings) 

    authn_policy = AuthTktAuthenticationPolicy('seekrit', callback=groupfinder) 
    config.set_authentication_policy(authn_policy) 
    config.set_authorization_policy(ACLAuthorizationPolicy()) 

    config.add_directive(add_site_route, 'add_site_route') 

    config.include(site_routes) 
    config.scan() 
    return config.make_wsgi_app() 

def site_routes(config): 
    config.add_site_route('site_users', '/user') 
    config.add_site_route('site_items', '/items') 

Tutaj konfigurujemy naszą aplikację. Przenieśliśmy również trasy do funkcji włączalnej, która pozwoli nam łatwiej testować trasy.

@view_config(route_name='site_users', permission='view') 
def users_view(request): 
    site = request.context 

Nasze poglądy są następnie uproszczone. Są one wywoływane tylko wtedy, gdy użytkownik ma uprawnienia dostępu do witryny, a obiekt witryny jest już załadowany dla nas.

Hybrid przemierzania

Niestandardowy dyrektywa add_site_route jest dodawany w celu zwiększenia obiekt config z obwolutą wokół add_route który będzie automatycznie dodać wsparcie przejścia do trasy. Po dopasowaniu tej trasy, pobierze ona symbol zastępczy {site_id} ze wzoru trasy i użyje go jako ścieżki przejścia (/{site_id} to ścieżka, którą definiujemy na podstawie struktury naszego drzewa traversal).

Traversal dzieje się na ścieżce /{site_id}, gdzie pierwszym krokiem jest znalezienie katalogu głównego drzewa (/). Trasa jest skonfigurowana do wykonywania przemierzania przy użyciu SiteFactory jako katalogu głównego ścieżki.Ta klasa jest tworzona jako root, a __getitem__ jest wywoływany za pomocą klucza, który jest następnym segmentem w ścieżce ({site_id}). Następnie znajdujemy obiekt lokacji pasujący do tego klucza i ładujemy go, jeśli to możliwe. Obiekt witryny jest następnie aktualizowany za pomocą __parent__ i __name__, aby umożliwić działanie find_interface. Zostało również ulepszone poprzez zapewnienie uprawnień wymienionych poniżej.

Pregenerator

Każda trasa jest aktualizowana z pregenerator który próbuje znaleźć instancję Site w hierarchii traversal dla wniosku. Może to się nie powieść, jeśli bieżące żądanie nie rozwiąże problemu z adresem URL witryny. Pregenerator następnie aktualizuje słowa kluczowe wysłane do route_url z identyfikatorem witryny.

Authentication

Przykład pokazuje, jak można mieć politykę uwierzytelniania, która odwzorowuje użytkownika do zleceniodawców wskazujących, że ta osoba jest w „site:” grupy. Witryna (request.context) jest następnie aktualizowana, aby mieć listę ACL mówiącą, że jeśli site.id == 1 ktoś z grupy "site: 1" powinien mieć uprawnienie "widok". Aktualizacja users_view wymaga uprawnienia "zobacz". Spowoduje to zgłoszenie wyjątku HTTPForbidden, jeśli użytkownik nie ma dostępu do widoku. Możesz napisać widok wyjątku, aby warunkowo przetłumaczyć go na 404, jeśli chcesz.

Celem mojej odpowiedzi jest pokazanie, jak podejście hybrydowe może sprawić, że poglądy będą odrobinę przyjemniejsze dzięki obsłudze części wspólnych adresu URL w tle. HTH.

+0

Z powodu mojej nieszczelności wiedzy Pyramid, nie mogę w pełni zrozumieć twojego przykładu, ale przeczytam dokumenty i spróbuję to zrozumieć ... :-) BTW: Czy twoje rozwiązanie może wyeliminować parametr "site_id", gdy wezwę żądanie .route_path? –

+1

Możesz wyeliminować 'site_id', dodając pregenerator do tras, które wypełniają identyfikator site_id automatycznie podczas generowania trasy. To nie jest oczywiste, dlatego napisałem to, by zaostrzyć twój apetyt. Ale twoje poglądy mogą stać się całkiem proste, z pewną deklaratywną konfiguracją z góry. –

+0

Nadal jestem w bałaganie ... Czy masz ochotę zaktualizować swój przykład tym pregeneratorem, o którym wspomniałeś? Dzięki! Przy okazji, jeśli chcę dodać różne trasy dla różnych Site.type, co powinienem zrobić? Na przykład, jeśli site1.type to "paszport", a site1.id to 1, site2.type to "www", a site2.id to 2 to/site/1/użytkownik powinien zostać zmapowany do widoku, ale/site/2/użytkownik powinien zgłosić błąd 404. –

3

dla widoków, można użyć klasy toteż wspólne zadania mogą być wykonywane w sposób __init__ (docs):

from pyramid.view import view_config 

class SiteView(object): 
    def __init__(self, request): 
     self.request = request 
     self.id = self.request.matchdict['id'] 
     # Do any common jobs here 

    @view_config(route_name='site_overview') 
    def site_overview(self): 
     # ... 

    @view_config(route_name='site_users') 
    def site_users(self): 
     # ... 

    def route_site_url(self, name, **kw): 
     return self.request.route_url(name, id=self.id, **kw) 

I można użyć prefiksu trasy do obsługi adresów URL (docs) . Nie jestem pewien, czy byłoby to pomocne w twojej sytuacji, czy nie.

from pyramid.config import Configurator 

def site_include(config): 
    config.add_route('site_overview', '') 
    config.add_route('site_users', '/user') 
    config.add_route('site_items', '/item') 
    # ... 

def main(global_config, **settings): 
    config = Configurator() 
    config.include(site_include, route_prefix='/site/{id}') 
+0

Dzięki za pomoc! prefiks trasy to sposób, w jaki już zorganizowałem mój adres URL. Umieszczanie poglądów w klasie jest naprawdę świetnym pomysłem. Ale co powiesz na path_path/route_url? Nadal muszę przekazywać id_strony za każdym razem. –

+0

Możesz zrobić to nieco łatwiej, dodając metodę do klasy, aby to zrobić. Zaktualizowałem mój przykład na przykładzie. – grc