Sunday, November 17, 2013

Form and JSON Post Request Value to Model Mapper Functions For Flask

One of the iritating part that you have to deal with when you're handling POST requests in Flask is how you map the values into the model. It could be in JSON or in common form format.

Usually this is what you do
# say this is your model
# using SQLAlchemy
class User(db.Model):
username = db.Column(db.String(50))
password = db.Column(db.String(50))
        email = db.Column(db.String(50))
first_name = db.Column(db.String(50))
last_name = db.Column(db.String(50))
# this handles the common form request
app.route('/process_form',methods=['POST'])
def data_process():
new_user = User(
                                 username = request.values.get('username')
                                 password = request.values.get('password')
                                 email = request.values.get('email')
                                 first_name = request.values.get('first_name')
                                 last_name = request.values.get('last_name')
                            )
       # okay and then you do the rest
# or perhaps JSON post request handling
app.route('/process_json',methods=['POST'])
def data_process():
data = json.loads(request.data)
new_user = User(
                                 username = data['username']
                                 password = data['password']
                                 email = data['email']
                                 first_name = data['first_name']
                                 last_name = data['last_name']
                            )
       # okay and then you do the rest
That's if the model has 5 properties, what if you have more than 10 or perhaps more than 20 properties? That would be tedious. We should try making it simple and DRY, right? I wrote helper functions to help you simplify it.
import json 
def new_json_parser(passed_object, payload_data):
"""
Maps passed json object from client into expected object.
Use this for creation of new object by passing an instantiated
empty object into the passed_object variable
"""
payload = json.loads(payload_data)
for key, value in payload.items():
if hasattr(passed_object, key):
setattr(passed_object, key, value)
return passed_object
# this part is for form request value handling
def new_form_parser(passed_object, request_data):
        """
Maps form request object from client into expected object.
Use this for creation of new object by passing an instantiated
empty object into the passed_object variable
"""
        for item in request_data.values:
            if hasattr(passed_object, item) and request_data.values.get(item):
                 setattr(passed_object, item, request_data.values.get(item))
        return passed_object

Using these helpers, all you need to do for the previous request is way simpler.

# using form
app.route('/process_form',methods=['POST'])
def data_process():
new_user = User()
        new_user = new_form_parser(new_user, request)
       # and there you go, your new user object based on form post request is ready
#using json
app.route('/process_json',methods=['POST'])
def data_process():
        new_user = User()
        new_user = new_json_parser(new_user, request.data)
       # and there you go, your new user object based on json post request is ready

Neat codes, eh? And no matter how many attributes in your class, it is that simple. You can also use this for handling edit operations. Simply passed in the object having query results into the parser functions.
def edit_json_parser(passed_object, payload_data):
"""
Maps value from passed json object for data edit purposes.
You need to pass in object resulting from query into the
passed_object variable
"""
payload = json.loads(payload_data)
for key, value in payload.items():
if key != "id" and value != None:
if hasattr(passed_object, key):
setattr(passed_object, key, value)
return passed_object
def edit_form_parser(passed_object, request_data):
        """
Maps value from passed json object for data edit purposes.
You need to pass in object resulting from query into the
passed_object variable
"""
        for item in request_data.values:
            if item != "id" and hasattr(passed_object, item) and request_data.values.get(item):
                setattr(passed_object, item, request_data.values.get(item))
        return passed_object

Have a better and more DRY code? Great news! Hope this helps.

Btw, I'll be shipping these methods with my EmeraldBox framework very soon, while my backyard framework already has it :)

regards

-E-

follow @femmerling on twitter
visit my website

2 comments:

  1. Hi,

    I think if we use Flask-WTF, we can solve this easily using .populate_obj method of form object. Don't you think so?

    This is my code that handle Json form POST request : https://github.com/CoderDojoIndonesia/bio/blob/master/wsgi/main.py#L261

    I paste it in full here :

    @application.route('/portfolio_add_update', methods = ['POST'])
    @login_required #how to protect this in ajax called only for signed user?
    def portfolio_add_update():

    form = PortoForm(request.form)
    if form.validate():
    result = {}
    result['iserror'] = False

    if not form.portfolio_id.data:
    user = Users.query.filter_by(username = session['username']).first()
    if user is not None:
    user.portfolio.append(Portfolio(title = form.title.data, description = form.description.data, tags = form.tags.data))
    print 'id ', form.portfolio_id
    db.session.commit()
    result['savedsuccess'] = True
    else:
    result['savedsuccess'] = False
    else:
    portfolio = Portfolio.query.get(form.portfolio_id.data)
    form.populate_obj(portfolio)
    db.session.commit()
    result['savedsuccess'] = True

    return json.dumps(result)

    form.errors['iserror'] = True
    print form.errors
    return json.dumps(form.errors)

    ReplyDelete
  2. Well, that works too and you can use it if you want. I write this one simply because I dont want to use another extension and makes it simpler and lighter to run :)

    ReplyDelete