TW2 In Pyramid Using URL Dispatch

94 views
Skip to first unread message

Bruce Coble

unread,
Feb 7, 2012, 11:37:57 PM2/7/12
to toscawidge...@googlegroups.com
Hi all,

The tutorial for using TW2 in Pyramid uses the "traversal" approach of resource location, which is one of 2 possible approaches, the other bing "URL dispatch". The main difference there is that URL dispatch doesn't have much interaction with the context of a request. View Callables simply accept a WebOb request object parameter, do whatever the view needs to do (eg query a database) & then (for a form) pass the request on to the template.

To implement TW2 in Pyramid using URL dispatch as the routing mechanism, I have:

1. Created a model:

class User(Base):
    id = Column(Integer, primary_key=True)
    login = Column(Unicode(100), nullable=False, unique=True)
    password = Column(String(255), nullable=False)
   
2. Created a TW2 form:

class UserForm(tw2.sqla.DbFormPage):
    entity = models.User
    title = 'User'
    class child(tw2.dynforms.CustomisedTableForm):
        id = tw2.forms.HiddenField()
        login = tw2.forms.TextField()
        password = tw2.forms.TextField()

3. Created a view:

@view_config(route_name='user_edit', renderer='my_mako_template.html')  
def user_edit_view(request):
    dbsession = DBSession()
    user = dbsession.query(User).filter(User.id==1).all()
    return {'form':UserForm()}

4. Created a template:

<html><body> ${form.display()|n} </body></html>

Using this approach, I can display the empty form without a problem. The problem I have is how to inject the entity into the form. The Pyramid tutorial adds the TW2 form to the view via the context in config.add_view('path.to.my.pyramid.viewcallable', context='path.to.my.tw2.form.UserForm', renderer='path/to/template'), but that approach causes errors for me using the URL dispatch method.

Does anyone have any suggestions on the best approach for using TW2 in a Pyramid app if using URL dispatch?

Cheers!

RJ Bean

unread,
Feb 8, 2012, 7:34:37 AM2/8/12
to toscawidgets-discuss
Hello Bruce,
I think the only piece you are missing is a call to UserForm's
.fetch_data(request) method. Give that a try in your user_edit_view view
callable. For instance::

@view_config(route_name='user_edit', renderer='my_mako_template.html')
def user_edit_view(request):

form = UserForm()
form.fetch_data(requeset)
return {'form': form}

Warning: I haven't tried this out yet. Will you let us know if it works for
you?

All the best,
Ralph

Message has been deleted

Bruce Coble

unread,
Feb 9, 2012, 2:43:57 AM2/9/12
to toscawidge...@googlegroups.com
Hi Ralph,

That approach looked good in theory, but still gives me trouble:

"TypeError: unbound method fetch_data() must be called with UserForm_d 
instance as first argument (got Request instance instead)"

I had a look at the tw2.sqla.DbFormPage class, & fetch_data looks like this:

    """
    A page that contains a form with database synchronisation. The `fetch_data` method loads a record
    from the database, based on the primary key in the URL (no parameters for a new record). The
    `validated_request` method saves the data to the database.
    """
    def fetch_data(self, req):
        self.value = req.GET and self.entity.query.filter_by(**req.GET.mixed()).first() or None

The first problem here is that DbFormPage.fetch_data() is expecting a particular type of URL - one with a GET value, eg /user/edit?id=1. I am using a different approach to formatting a URL, eg /user/1/edit, & with Pyramid & URL dispatch, you would access those parameters via the request object itself eg: id = request.matchdict.get('id', None). In fact, my request.GET.mixed() is empty. So this implementation of fetch_data does make the assumption that you are formatting your URL parameters in a particular way, & it fails if you use any other approach.

That said, even if I craft my URL params the way it expects, I still get the above error, so I assume I am still missing something. "Unbound method" suggests a need to bind things somehow, but I'm not sure how that should be done at this stage...

I have to also say that while it is handy to have fetch_data automagically get my data, I would feel more comfortable either passing a complete query string or a query result object to the form, so I can have control over the queries in my application, rather than pass that on to TW2. I understand that for many people this is handy "magic", but it's not what I am trying to achieve...

However, I did finally manage to work out how to do this in Pyramid. Rather than fetch_data, which sets the value of self.value, I just set it directly in the view:


@view_config(route_name='user_edit', renderer='my_mako_template.html')   
def user_edit_view(request):
    id = request.matchdict.get('id', None)
    dbsession = DBSession()
    form = UserForm()
    user = dbsession.query(User).filter(User.id==id).first()
    form.value = user

    return {'form':form}

So hopefully that helps anyone else trying to achieve the same thing...

Cheers,

Bruce

Paul Johnston

unread,
Feb 9, 2012, 4:57:06 AM2/9/12
to toscawidge...@googlegroups.com
Hi,

The first problem here is that DbFormPage.fetch_data() is expecting a particular type of URL - one with a GET value, eg /user/edit?id=1.

Yes, that's the idea. I intended DbFormPage as a very "batteries included" widget, mostly for using with tw2 standalone, i.e. not with Pyramid/etc. If you want a quick way to have a form with database support and minimal code, it's what you want. If you want more control of the URL, etc. it's not for you.
 
Unbound method" suggests a need to bind things somehow

To use the widget, all your model classes must have a "query" attribute. This is true if you use Elixir, or the TG2 model templates.
 
However, I did finally manage to work out how to do this in Pyramid. Rather than fetch_data, which sets the value of self.value, I just set it directly in the view:

That's absolutely fine, but I expect there's now no need to use DbFormPage. You're as well just using FormPage. I take it you're not using the auto-saving functionality of DbFormPage as well?
 
@view_config(route_name='user_edit', renderer='my_mako_template.html')   
def user_edit_view(request):
    id = request.matchdict.get('id', None)
    dbsession = DBSession()
    form = UserForm()
    user = dbsession.query(User).filter(User.id==id).first()
    form.value = user
    return {'form':form}

How about:


@view_config(route_name='user_edit', renderer='my_mako_template.html')   
def user_edit_view(request):
    id = request.matchdict.get('id', None)
    dbsession = DBSession()
    user = dbsession.query(User).filter(User.id==id).first()
    return {'form':UserForm.req(value=user)}

Paul

Bruce Coble

unread,
Feb 9, 2012, 7:25:56 AM2/9/12
to toscawidge...@googlegroups.com
Hi Paul,

I appreciate the comments. I was beginning to get the idea that DbFormPage was designed for a just-add-water use-case scenario, & for that it does look great. I was also beginning to think that it might not be the right package for my needs, so thank you for confirming that I'm better off to just go with the generic FormPage.

Thanks too for the code abbreviation suggestion :)

Cheers,

Bruce

Paul Johnston

unread,
Feb 9, 2012, 7:32:35 AM2/9/12
to toscawidge...@googlegroups.com
Hi,

Thanks too for the code abbreviation suggestion :)

No problem. Can I just highlight that within a request you should use

UserForm.req(value=user)

rather than

UserForm(value=user)

The latter form actually generates a subclass. The reasons for this are described in the docs.

Paul

Bruce Coble

unread,
Feb 9, 2012, 6:11:51 PM2/9/12
to toscawidge...@googlegroups.com
Hi Paul...thanks for the tip - 1 line is indeed better than 3 :)
Reply all
Reply to author
Forward
0 new messages