views:

138

answers:

1

I have a class User() that throw exceptions when attributes are incorrectly set. I am currently passing the exceptions from the models through the controller to the templates by essentially catching exceptions two times for each variable.

Is this a correct way of doing it? Is there a better (but still simple) way? I prefer not to use any third party error or form handlers due to the extensive database queries we already have in place in our classes.

Furthermore, how can I "stop" the chain of processing in the class if one of the values is invalid? Is there like a "break" syntax or something?

Thanks.

>>> u = User()
>>> u.name = 'Jason Mendez'
>>> u.password = '1234'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "topic/model/user.py", line 79, in password
    return self._password
ValueError: Your password must be greater than 6 characters

In my controller "register," I have:

class RegisterController(BaseController):

    def index(self):
        if request.POST:
            c.errors = {}
            u = User()

            try:
                u.name = c.name = request.POST['name']
            except ValueError, error:
                c.errors['name'] = error

            try:
                u.email = c.email = request.POST['email']
            except ValueError, error:
                c.errors['email'] = error

            try:
                u.password = c.password = request.POST['password']
            except ValueError, error:
                c.errors['password'] = error

            try:
                u.commit()
            except ValueError, error:
                pass

        return render('/register.mako')
A: 

You could remove repetition from the code as a semi-measure:

class RegisterController(BaseController):

    def index(self):
        if request.POST:
            c.errors = {}
            u = User()
            for key in "name email password".split():
                try:
                    value = request.POST[key]
                    for o in (u, c):
                        setattr(o, key, value)
                except ValueError, error:
                    c.errors[key] = error

            u.commit() # allow to propagate (show 500 on error if no middleware)

        return render('/register.mako')

Form Handling page from Pylons docs describes several simple approaches to form validating that you could adapt for your project.

class RegisterController(BaseController):

    @save(User, commit=True)
    # `save` uses filled c.form_result to create User() 
    @validate(schema=register_schema, form='register')
    # `validate` fills c.form_errors, c.form_result dictionaries
    def create(self):
        return render('/register.mako')

Where validate() is similar to Pylons validate() decorator and save() could be implemented as follows (using decorator module):

def save(klass, commit=False):
    def call(action, *args, **kwargs):
        obj = klass()
        for attr in c.form_result:
            if attr in obj.setable_attrs():
               setattr(obj, attr, c.form_result[attr])
        if commit:
           obj.commit()
        return action(*args, **kwargs)
    return decorator.decorator(call)
J.F. Sebastian
Hi -- I understand the first example, thank you very much. I'm a little confused about the second. What is "schema" and "register_schema?" Would it be possible to expand that sample code a bit so I can see the whole picture? Thank you!
ensnare
@ensnare: Read the link I've provided (near "Validation the Quick Way" words).
J.F. Sebastian
Hi, I saw that but it requires the use of formencode. Would there be anything wrong with your first example? Would the second example be preferred at all? And if so, why? Thanks again.
ensnare
@ensnare: If you have only one or two forms then the first example is fine. If you have dozens forms then you'll see that validation logic is scattered across deep in guts of numerous controllers and thus it is hard to change it. Abstracting such logic away helps to ensure single point of truth (SPOT) principle (+ don't repeat yourself (DRY) rule) http://www.faqs.org/docs/artu/ch04s02.html#spot_rule
J.F. Sebastian
Thanks again. So the @validate decorator would do all the form processing and would fill the error dicts ... but I am confused as to what @save does? The code looks a little above my head I'm wondering if there's a simple way to explain its purpose? Thank you!
ensnare
@ensnare: `save()` just saves all applicable attributes from `c.form_result` dictionary to (presumably) database object represented by `klass` class. In the second example it creates `User()` object and populates its attributes using almost the same code as the first example.
J.F. Sebastian
Got it. That's so elegant! This definitely looks like the best way to do it. I will test implementations tomorrow. Thanks again.
ensnare
Now how could I modify @validate so it uses the error handling (exceptions) already implemented in my class when attributes are set? Would it just make sense to write a custom @validate function, and if so, do you have any sample code for that too :)?
ensnare
@ensnare, you can't modify @validate to handle exceptions because this decorator checks validation rules before actual action code is invoked. To handle action exceptions you need either to use try-except inside the action or write your custom decorator wrapping action in try-except block.
Yaroslav