Integrations
sheraf integrates well with some other libraries. Here are some examples.
WTForms
WTForms Form
and sheraf BaseModel
both interact well with python dicts. And because they can both behave like dicts, they interact well with each other. On one hand wtforms.form.BaseForm.process()
or the Form
constructor can be initialized with a dict matching its structure, and a BaseModel
can easilly be casted as a dict. On the other hand, the model sheraf.models.base.BaseModel.assign()
method can update the model values from a dictionnary matching the form structure, and Form
can also be casted as a dict. Let us see how to make them work together.
Writing models and forms
Let us define some simple, but nested models:
>>> import sheraf
>>> class Horse(sheraf.InlineModel):
... name = sheraf.SimpleAttribute()
...
>>> class Cowboy(sheraf.Model):
... table = "cowboys"
...
... name = sheraf.SimpleAttribute()
... age = sheraf.IntegerAttribute()
... horses = sheraf.SmallListAttribute(sheraf.InlineModelAttribute(Horse))
Now we build forms matching the same structure as the models. Form fields must have the same names as the model attributes:
>>> import wtforms
>>> class HorseForm(wtforms.Form):
... name = wtforms.StringField(label="The horse name")
...
>>> class CowboyForm(wtforms.Form):
... name = wtforms.StringField(label="The cowboy name")
... age = wtforms.IntegerField(label="The cowboy age")
... horses = wtforms.FieldList(
... wtforms.FormField(HorseForm),
... )
The last thing needed is some glue between WTForms and sheraf. Let us apply that glue in the function handling our http request:
>>> @sheraf.commit
... def cowboy_form(request, cowboy_id):
... cowboy = Cowboy.read(cowboy_id)
...
... form = CowboyForm(request.form or None, cowboy)
...
... if request.method == 'POST':
... if form.validate():
... cowboy.assign(**form.data)
... else:
... print(form.errors)
...
... # Usually this is where we return a werkzeug answer,
... # but for the test, let's return the form and the model
... return cowboy, form
First we get the Cowboy object from the database (note: raises a
ObjectNotFoundException
if the cowboy_id is not valid).Then we initialize the
Form
with both the request form and the Cowboy. If no form was sent in the request, theForm
will be initialized with the model values. Else it will be initialize with the data sent in the form.If there is a request form and WTForms validates it, then we update the Cowboy with the form data, with
assign()
. Note that you may want to useupdate()
instead ofassign()
if you do not want to delete data from your form.Finally, we should return some HTTP answer. We do not focus on that here, so for the sake of the test, we return our form and object.
Note
We use sheraf.models.base.BaseModel.assign()
instead of wtforms.form.Form.populate_obj()
because populate_obj()
has no way to know how to instantiate a new Horse if there are more horses in the form than in the base. assign()
will be able to add or delete a horse to/from the horses list depending on the data it gets from the form.
Warning
Because of a bug in WTForms 2.2 there can be some unexpected behaviors with BooleanField
if request.form
is empty but not None
. This is why it is preferable to use request.form or None
.
Usage
>>> from werkzeug.test import EnvironBuilder
>>> from werkzeug.wrappers import Request
>>> with sheraf.connection(commit=True):
... george = Cowboy.create(name="George Abitbol", age=50, horses=[
... {"name": "Jolly Jumper"},
... ])
Now that we have a cowboy, let us see how the form is initialized:
>>> # This is an utility to simulate a real werkzeug request
>>> request = Request(EnvironBuilder(method='GET').get_environ())
>>> with sheraf.connection():
... cowboy, form = cowboy_form(request, george.id)
... cowboy.name
'George Abitbol'
>>> form.name.data
'George Abitbol'
>>> form.horses.data
[{'name': 'Jolly Jumper'}]
We can check that the form is initialized with the cowboy data. George Abitbol is 51 years old, and he has another horse. Let us edit the form and send it back:
>>> request = Request(EnvironBuilder(method='POST', data={
... 'name': 'George Abitbol',
... 'age': 'fifty-one',
... 'horses-0-name': 'Jolly Jumper',
... 'horses-1-name': 'Polly Pumper',
... }).get_environ())
>>> with sheraf.connection():
... cowboy, form = cowboy_form(request, george.id)
{'age': ['Not a valid integer value.']}
>>> with sheraf.connection():
... cowboy.age
50
>>> with sheraf.connection():
... len(cowboy.horses)
1
We made a mistake here by setting the age as a text value instead of an integer value. As WTForms did not validate the form, the model was not edited. Neither the cowboy age nor its horses have changed. Let us try again with some better values:
>>> request = Request(EnvironBuilder(method='POST', data={
... 'name': 'George Abitbol',
... 'age': 51,
... 'horses-0-name': 'Jolly Jumper',
... 'horses-1-name': 'Polly Pumper',
... }).get_environ())
...
>>> with sheraf.connection():
... cowboy, form = cowboy_form(request, george.id)
... cowboy.age
51
Now we realize that Polly Pumper is an old horse, and has a stupid name anyway. So we do not include it in the form, and it will be deleted.
>>> request = Request(EnvironBuilder(method='POST', data={
... 'name': 'George Abitbol',
... 'age': 51,
... 'horses-0-name': 'Jolly Jumper',
... }).get_environ())
>>> with sheraf.connection():
... cowboy, form = cowboy_form(request, george.id)
... len(cowboy.horses)
1