Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Pyramid traversal

I've been trying out pyramid and this traversal thing is sending me nuts. I'm basically fiddling around to make a control panel for a shopping cart and this is the basic structure that I have in mind.

The login page

localhost:6543/admin_login

Upon successful login

localhost:6543/admin/home 

To view all existing products

localhost:6543/admin/product

To edit product X

localhost:6543/admin/product/edit/1

So my folder structure is something like this ( Capitalize files are models )

  • mycart
    • resources.py
    • Admin.py
    • Product.py
    • static
    • templates
    • views
      • __init__.py
      • admin.py
      • root.py

My resources.py

    from pyramid.security import Authenticated
    from pyramid.security import Allow
    from pyramid.response import Response

    class Root(object):
       __name__ = ''
       __parent__ = None

       def __init__(self, request):
          pass

       def __getitem__(self, key):

           if key == 'admin_login':
              return Admin()

           elif key == 'admin':
              return Admin()

           raise KeyError

    class Admin(object):

        __name__ = ''
        __parent__ = Root
        __acl__ = [(Allow, Authenticated, 'admin')]

        def __init__(self):
           pass

In views/__init.py, it's simply a blank file. As for root.py, it's simply a httpexceptions.HTTPNOTFOUND, 404 code

For views/admin.py

    from pyramid.view import view_config, render_view
    import mycart.resources

    from pyramid.httpexceptions import HTTPNotFound, HTTPFound
    from mycart.views.root import strip_tags
    from pyramid_mailer import get_mailer
    from pyramid_mailer.message import Message

    from pyramid.security import remember , forget , authenticated_userid

    from pyramid.events import subscriber , BeforeRender

    from mycart.Admin import Admin
    from mycart.Product import Product


    @view_config(context='mycart:resources.Admin',   request_method='POST', renderer='admin/login.jinja2')
    def login_post(context, request):

      if 'btnLogin' in request.params:
        token = request.session.get_csrf_token()
        login = request.params['txtLogin']
        password = request.params['txtPassword']

        admin = Admin(login, request)

        if admin.validate_user( password):

            record = admin.find_user_by_login( login )

            request.session['bs_admin_id'] = str(record['_id'])
            request.session['bs_admin_name'] = record['usr']['fname'] + ' ' + record['usr']['lname'];
            request.session['bs_admin_type'] = record['usr']['type']
            headers = remember(request, login )
            return HTTPFound('/admin/home',  headers=headers)

        message = 'Failed login'

      return {'message': message,  'url': '/admin_login', 'page_title': 'Failed Login'}


      @view_config(context='mycart:resources.Admin', name="home", renderer='admin/home.jinja2', permission='admin')
      def home(context, request):
          logged_in = authenticated_userid(request)
          url = request.path_info

          admin = Admin( logged_in, request )
          rec = admin.find_user_by_objectid( request.session['bs_admin_id'] ) ;

          return { 'firstname': rec['usr']['fname']  }


     @view_config(context='mycart:resources.Admin', name="product", renderer='admin/product_listing.jinja2', permission='admin')
          def product_list(context, request):
          print ('yes, showing product listing requested by ', request.session['bs_admin_id'] )

After logging in, I point the url to localhost:6543/admin/product, I notice that it still rendering the home page, instead of the product page.

I know I missed out something but I can't seem to find out why. Looking through http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/traversal.html, I know that I on the right track as there might be arbitary segments.

I've tried modifying resources.py to be the following

   .....

   class Admin(object):

       __name__ = ''
       __parent__ = Root
       __acl__ = [(Allow, Authenticated, 'admin')]

       def __init__(self):
           pass

       def __getitem__(self, key):

          if key == 'product':
             print ("WOOT! Listing products")
             ## this is the part where I don't know what should I return or set or how should I hook it up with view_config

          if key == 'home':
             print ("yes, I'm home!")
             ## this is the part where I don't know what should I return or set or how should I hook it up with view_config

          raise KeyError

For this part, I made some progress where it's definitely printing the respective message in the console. However , I have no inkling how should I hook it up with the view_configs and what should be parameters be for the view_configs if any changes need to be made.

I do not know if version affects anything but anyway, I am using python 3.3

Any help will be appreciated. Thanks!

This is my first time coding in python after years of java. So there might be some terms / concepts that I'm not familiar with respect to pyramid / python.


Ok, I think I kinda got my mind to wrap around this traversal thing. Reading through http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/narr/traversal.html, 2 things caught my attention.

For example, if the path info sequence is ['a', 'b', 'c']:

- Traversal starts by acquiring the root resource of the application by calling the root   factory. The root factory can be configured to return whatever object is appropriate as the traversal root of your application.

- Next, the first element ('a') is popped from the path segment sequence and is used as a key to lookup the corresponding resource in the root. This invokes the root resource’s __getitem__ method using that value ('a') as an argument.

- If the root resource “contains” a resource with key 'a', its __getitem__ method will return it. The context temporarily becomes the “A” resource.

So based on localhost:6543/admin/products, the settings for view_config is like the following:

@view_config(context=Admin, name='products', .... )

So after making changes to resources.py

    ## class Root(object):
       ....


    class ProductName(object):
        def __init__(self, _key):
            pass

    class Products(object):
        __name__ = ''
        __parent__ = Root


        def __init__(self):
            pass

        def __getitem__(self, key):
            print ('products: ', key)
            if key == 'add':
                return ProductName(key)

            print ('Approaching KeyError')
            raise KeyError


     class Admin(object):

        __name__ = ''
        __parent__ = Root
        __acl__ = [(Allow, Authenticated, 'admin')]

        def __init__(self):
            pass


        def __getitem__(self, key):

            if key == 'products':
               print ('admin: ', key)
               return Products()

            raise KeyError

And in views/admin.py

    @view_config(context=Admin, name='products',  renderer='admin/products.jinja2', permission = 'admin')
    def product_add(context, request):
        print 'hey products_add'
        return { 'msg': ''}

Somehow or rather, it isn't rendering the product template, but the default 404.

like image 680
Gino Avatar asked Nov 20 '12 14:11

Gino


2 Answers

You take a look at the doc about traversal, because you've haven't got it quite right. This tutorial is also quite useful in understanding traversal. I'll try to do a quick explanation in your context :

First of all, the path of the request is split intro segments. For example /admin/product is split into ['admin', 'product'].

Then, pyramid tries to determine the context for this request. For that, it recursively call __getitem__ (which is just another way to say it does object[segment]) for each segment from the root (it traverses). In the exemple, it does root['admin'], which returns an admin object, then does admin['product']. It stops when it encounters a KeyError.

Once we have a context, pyramid searches for a view with this context, and whose view name is the part that wasn't traversed. For example, if admin['product'] raise a KeyError, then pyramid looks for a view that configured with @view_config(context=Admin, name="product").


So, how do you make an app from that ? First, you determine what is your resource tree. In your case, it might looks like this :

  • Root
    • Admin
      • ProductContainer
        • Product

There is a view named home for the Admin context (/admin/home), a view with no name for the ProductContainer (/admin/product) and a view named edit for the product (/admin/product/1/edit).

like image 115
madjar Avatar answered Sep 27 '22 19:09

madjar


While I do not know if the code below is elegant or any loopholes, it is definitely working for me now. I'll put it in , in case someone is facing the same problem like me.

resources.py

    class ProductName(object):
        __name__ = ''
        __parent__ = Root
        __acl__ = [(Allow, Authenticated, 'admin')]

        def __init__(self, _key):
            pass

    class Products(object):

        __name__ = ''
        __parent__ = Root
        __acl__ = [(Allow, Authenticated, 'admin')]

        def __init__(self):
            pass

        def __getitem__(self, key):
            print ('products: ' + key)
            if key == 'add':
               return ProductName(key)

            print ('Approaching KeyError')
            raise KeyError

views/admin.py

    @view_config(context="**mycart:resources.ProductName**",  name="",     renderer='admin/product_add.jinja2', permission = 'admin')
        def product_add(context, request):
        print 'hey product add'
        return { 'msg': ''}

    @view_config(context="**mycart:resources.Products**", name='' , renderer='admin/product.jinja2', permission = 'admin')
    def product(context, request):
        print 'hey products listing'
        return { 'msg': ''}
like image 35
Gino Avatar answered Sep 27 '22 19:09

Gino