# flask-serialize
# DB Model JSON serialization with PUT, POST write for Flask applications using SQLAlchemy
## Installation
```bash
pip install flask-serialize
```
## Simple and quick to get going in two steps.
*One* Import and add the FlaskSerializeMixin mixin to a model:
```python
from flask_serialize import FlaskSerialize
# create a flask-serialize mixin instance from
# the factory method `FlaskSerialize`
fs_mixin = FlaskSerialize(db)
class Item(db.Model, fs_mixin):
id = db.Column(db.Integer, primary_key=True)
# other fields ...
```
*Two* Configure the route with the do all mixin method:
```python
@app.route('/item/<int:item_id>')
@app.route('/items')
def items(item_id=None):
return Item.fs_get_delete_put_post(item_id)
```
*Three* Done! Returns JSON as a single item or a list with only a single route.
Flask-serialize is intended for joining a Flask SQLAlchemy Python backend with
a JavaScript Web client. It allows read JSON serialization
from the db and easy to use write back of models using PUT and POST.
4 times faster than marshmallow for simple dict serialization.
# Example
## Model setup
```python
# example database model
from flask_serialize import FlaskSerialize
from datetime import datetime
# required to set class var db for writing to a database
from app import db
fs_mixin = FlaskSerialize(db)
class Setting(fs_mixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
setting_type = db.Column(db.String(120), index=True, default='misc')
key = db.Column(db.String(120), index=True)
value = db.Column(db.String(30000), default='')
active = db.Column(db.String(1), default='y')
created = db.Column(db.DateTime, default=datetime.utcnow)
updated = db.Column(db.DateTime, default=datetime.utcnow)
# serializer fields
__fs_create_fields__ = __fs_update_fields__ = ['setting_type', 'value', 'key', 'active']
# checks if Flask-Serialize can delete
def __fs_can_delete__(self):
if self.value == '1234':
raise Exception('Deletion not allowed. Magic value!')
return True
# checks if Flask-Serialize can create/update
def __fs_verify__(self, create=False):
if len(self.key or '') < 1:
raise Exception('Missing key')
if len(self.setting_type or '') < 1:
raise Exception('Missing setting type')
return True
def __repr__(self):
return '<Setting %r %r %r>' % (self.id, self.setting_type, self.value)
```
## Routes setup
Get a single item as JSON.
```python
@app.route('/get_setting/<item_id>', methods=['GET'])
def get_setting( item_id ):
return Setting.fs_get_delete_put_post(item_id)
# Returns a Flask response with a JSON object, example:
```
```JavaScript
{id:1, value: "hello"}
```
Put an update to a single item as JSON.
```python
@app.route('/update_setting/<item_id>', methods=['PUT'])
def update_setting( item_id ):
return Setting.fs_get_delete_put_post(item_id)
# Returns a Flask response with the result as a JSON object:
```
```JavaScript
{message: "success message"}
```
Delete a single item.
```python
@app.route('/delete_setting/<item_id>', methods=['DELETE'])
def delete_setting( item_id ):
return Setting.fs_get_delete_put_post(item_id)
# Returns a Flask response with the result and item deleted as a JSON response:
```
```JavaScript
{message: "success message", item: {"id":5, name: "gone"}}
```
Get all items as a JSON list.
```python
@app.route('/get_setting_all', methods=['GET'])
def get_setting_all():
return Setting.fs_get_delete_put_post()
# Returns a Flask response with a list of JSON objects, example:
```
```JavaScript
[{id:1, value: "hello"},{id:2, value: "there"},{id:3, value: "programmer"}]
```
All of: get-all, get, put, post, and delete can be combined in one route.
```python
@app.route('/setting/<int:item_id>', methods=['GET', 'PUT', 'DELETE', 'POST'])
@app.route('/setting', methods=['GET', 'POST'])
def route_setting_all(item_id=None):
return Setting.fs_get_delete_put_post(item_id)
```
Updating from a JSON object in the Flask put request
JQuery example:
```javascript
function put(setting_id) {
return $.ajax({
url: `/update_setting/${setting_id}`,
method: 'PUT',
contentType: "application/json",
data: {setting_type:"x",value:"100"},
}).then(response => {
alert("OK:"+response.message);
}).fail((xhr, textStatus, errorThrown) => {
alert(`Error: ${xhr.responseText}`);
});
}
}
```
Flask route:
```python
@app.route('/update_setting/<int:item_id>', methods=['PUT'])
def update_setting(item_id):
return Setting.fs_get_delete_put_post(item_id)
```
Create or update from a WTF form:
```python
@app.route('/setting_edit/<int:item_id>', methods=['POST'])
@app.route('/setting_add', methods=['POST'])
def setting_edit(item_id=None):
if item_id:
item = Setting.query.get_or_404(item_id)
else:
item = {}
form = EditForm(obj=item)
if form.validate_on_submit():
if item_id:
try:
item.fs_request_update_form()
flash('Your changes have been saved.')
except Exception as e:
flash(str(e), category='danger')
return redirect(url_for('setting_edit', item_id=item_id))
else:
try:
new_item = Setting.fs_request_create_form()
flash('Setting created.')
return redirect(url_for('setting_edit', item_id=new_item.id))
except Exception as e:
flash('Error creating item: ' + str(e))
return render_template(
'setting_edit.html',
item=item,
title='Edit or Create item',
form=form
)
```
# Create a child database object:
## Using POST.
As example: add a `Stat` object to a Survey object using the `fs_request_create_form` convenience method. The foreign key
to the parent `Survey` is provided as a `kwargs` parameter to the method.
```python
@app.route('/stat/<int:survey_id>', methods=['POST'])
def stat_add(survey_id=None):
survey = Survey.query.get_or_404(survey_id)
return Stat.fs_request_create_form(survey_id=survey.id).fs_as_dict
```
## Using fs_get_delete_put_post.
As example: add a `Stat` object to a Survey object using the `fs_get_delete_put_post` convenience method. The foreign key
to the parent `Survey` is provided in the form data as survey_id. `__fs_create_fields__` list must then include `survey_id` as
the foreign key field to be set if you specify any `__fs_create_fields__`. By default, all fields are allowed to be included
when creating.
```html
<form>
<input type="hidden" name="survey_id" value="56">
<input name="value">
</form>
```
```python
@app.route('/stat/', methods=['POST'])
def stat_add():
return Stat.fs_get_delete_put_post()
```
# Writing and creating
When using any of the convenience methods to update, create or delete an object these properties and
methods control how flask-serialize handles the operation.
## Updating from a form or JSON
```python
def fs_request_update_json():
"""
Update an item from request JSON data or PUT params, probably from a PUT or PATCH.
Throws exception if not valid
:return: True if item updated
"""
```
Example. To update a Message object using a GET, call this method with the parameters to update as request arguments. ie:
/update_message/12/?body=hello&subject=something
```python
@route('/update_message/<int:message_id>/')
def update_message(message_id)
message = Message.fs_get_by_user_or_404(message_id, user=current_user)
if message.fs_request_update_json():
return 'Updated'
```
```python
def fs_request_update_json():
"""
Update an item from request JSON data or PUT params, probably from a PUT or PATCH.
Throws exception if not valid
:return: True if item updated
"""
```
Example. To update a Message using a POST, call this method with the parameters to update as request arguments. ie:
```
/update_message/12/
form data {body="hello", subject="something"}
```
```python
@route('/update_message/<int:message_id>/', methods=['POST'])
def update_message(message_id)
message = Message.fs_get_by_user_or_404(message_id, user=current_user)
if message.fs_request_update_form():
return 'Updated'
```
## `__fs_verify__` write and create
```python
def __fs_verify__(self, create=False):
"""
raise exception if item is not valid for put/patch/post
:param: create - True if verification is for a new item
"""
```
Override the mixin `__fs_verify__` method to provide control and verification
when updating and creating model items. Simply raise an exception
when there is a problem. You can also modify `self` data before writing. See model example.
## Delete
To control when a deletion using `fs_get_delete_put_post` override the `__fs_can_delete`
hook. Return False or raise and exception to prevent deletion. Return True to
allow deletion.
```python
def __fs_can_delete__(self):
```
Override the mixin `__fs_can_delete__` to provide control over when an
item can be deleted. Simply raise an exception
when there is a problem. By default `__fs_can_delete__`
calls `__fs_can_update__` unless overridden. See model example.
## `__fs_can_update__`
```python
def __fs_can_update__(self):
"""
raise exception if item cannot be updated
"""
```
Override the mixin `__fs_can_update__` to provide control over when an
item can be updated. Simply raise an exception
when there is a problem or return False. By default `__fs_can_update__`
uses the result from `__fs_can_access__` unless overridden.
## `__fs_can_access__`
```python
def __fs_can_access__(self):
"""
return False if item can't be accessed
"""
```
Override the mixin `__fs_can_access__` to provide control over when an
item can be read or accessed. Return False to exclude from results.
## Private fields
Fields can be made private by overriding the `__fs_private_field__` method
and returning `True` if the field is to be private. These fields will not be returned
in JSON results.
Private fields will be excluded for any get, put and post methods.
Example:
To exclude private fields when a user is not the admin.
```python
def __fs_private_field__(self, field_name):
if not is_admin_user() and field_name.upper().startswith('PRIVATE_'):
return True
return False
```
## `__fs_update_fields__`
List of model fields to be read from a form or JSON when updating an object. Normally
admin fields such as login_counts or security fields are excluded. Do not put foreign keys or primary
keys here. By default, when `__fs_update_fields__` is empty all Model fields can be updated.
```python
__fs_update_fields__ = []
```
## `__fs_update_properties__`
When returning a success result from a put or post update, a dict
composed of the property values from the `__fs_update_properties__` list is returned
as "properties".
Example return `JSON`:
```python
class ExampleModel(db.Model, FlaskSerializeMixin):
head_size = db.Column(db.Integer())
ear_width = db.Column(db.Integer())
__fs_update_fields__ = ['head_size', 'ear_width']
__fs_update_properties__ = ['hat_size']
@property
def hat_size(self):
return self.head_size * self.ear_width
```
```JavaScript
// result update return message
{"message": "Updated", "properties": {hat_size: 45.67} }
```
This can be used to communicate from the model on the server to the JavaScript code
interesting things from updates
## `__fs_create_fields__`
List of model fields to be read from a form or JSON when creating an object. Can be the specified as either 'text' or
the field. Do not put primary keys here. Do not put foreign keys here if using SQLAlchemy child insertion.
This is usually the same as `__fs_update_fields__`. When `__fs_create_fields__` is empty all column fields can be inserted.
Used by these methods:
- fs_request_create_form
- fs_get_delete_put_post
```python
__fs_create_fields__ = []
```
Example:
```python
class Setting(fs_mixin, FormPageMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
setting_type = db.Column(db.String(120), index=True, default='misc')
private = db.Column(db.String(3000), default='secret')
value = db.Column(db.String(3000), default='')
__fs_create_fields__ = [setting_type, 'value']
```
## Update DateTime fields specification
The class methods: `fs_request_update_form`, `fs_request_create_form`, `fs_request_update_json` will automatically stamp your
model's timestamp fields using the `__fs_update_timestamp__` class method.
`__fs_timestamp_fields__` is a list of fields on the model to be set when updating or creating
with the value of `datetime.datetime.utcnow()`. The default field names to update are: `['timestamp', 'updated']`.
Example:
```python
class ExampleModel(db.Model, FlaskSerializeMixin):
# ....
modified = db.Column(db.DateTime, default=datetime.utcnow)
__fs_timestamp_fields__ = ['modified']
```
Override the timestamp default of `utcnow()` by replacing the `__fs_timestamp_stamper__` class property with your
own. Example:
```python
class ExampleModel(db.Model, FlaskSerializeMixin):
# ....
__fs_timestamp_stamper__ = datetime.datetime.now
```
# Filtering and sorting
## Exclude fields
List of model field names to not serialize at all.
```python
__fs_exclude_serialize_fields__ = []
```
List of model field names to not serialize when returning as JSON.
```python
__fs_exclude_json_serialize_fields__ = []
```
## Built in query_by using request arg on GET
`fs_get_delete_put_post` by default supports automatic passing of GET request args to the query method using
a `filter_by` clause. Example using a Flask route that be default returns all messages:
```python
@route('/message/', methods=['GET'])
def route_message():
return Message.fs_get_by_user_or_404()
```
Call the endpoint using URL like:
/message/?name=hello&lines=12
Will return any message records with the name of 'hello' and with 12 lines.
This feature can be disabled by using `__fs_filter_by = False` on the model definition.
## Filtering JSON list results
Json result lists can be filtered by using the `prop_filters` parameter on either
the `fs_get_delete_put_post` method or the `fs_json_list` method.
The filter consists of one or more properties in the JSON result and
the value that it must match. Filter items will match against the
first `prop_filter` property to exactly equal the value.
NOTE: The filter is not applied with single a GET or, the PUT, POST and DELETE methods.
Example to only return dogs:
```python
result = fs_get_delete_put_post(prop_filters = {'key':'dogs'})
```
## Sorting JSON list results
Json result lists can be sorted by using the `__fs_order_by_field__` or the `__fs_order_by_field_desc__` properties. The results
are sorted after the query is converted to JSON. As such you can use any property from a class to sort. To sort by id
ascending use this example:
```python
__fs_order_by_field__ = 'id'
```
A `lambda` or `method` can be used as the `__fs_order_by_field__`, in which case custom sorting can be achieved. The
passed value to the `lambda` is a dictionary of the field and properties of a result row.
Example:
```python
__fs_order_by_field__ = lambda r: -int(r["value"])
```
## Filtering query results using `__fs_can_access__` and user.
The `fs_query_by_access` method can be used to filter a SQLAlchemy result set so that
the `user` property and `__fs_can_access__` hook method are used to restrict to allowable items.
Example:
```python
result_list = Setting.fs_query_by_access(user='Andrew', setting_type='test')
```
Any keyword can be supplied after `user` to be passed to `filter_by` method of `query`.
## Relationships list of property names that are to be included in serialization
```python
__fs_relationship_fields__ = []
```
In default operation relationships in models are not serialized. Add any
relationship property name here to be included in serialization. NOTE: take care
to not include circular relationships. Flask-Serialize does not check for circular
relationships.
# Serialization converters
There are three built in converters to convert data from the database
to a good format for serialization:
- DATETIME - Removes the fractional second part and makes it a string
- PROPERTY - Enumerates and returns model added properties
- RELATIONSHIP - Deals with children model items.
Set one of these to None or a value to remove or replace it's behaviour.
## Adding and overriding converter behaviour
Add values to the class property:
```python
__fs_column_type_converters__ = {}
```
Where the key is the column type name of the database column
and the value is a method to provide the conversion.
Example:
To convert VARCHAR(100) to a string:
```python
__fs_column_type_converters__ = {'VARCHAR': lambda v: str(v)}
```
To change DATETIME conversion behaviour, either change the DATETIME column_type_converter or
override the `__fs_to_date_short__` method of the mixin. Example:
```python
import time
class Model(db.model, FlaskSerializeMixin):
# ...
# ...
def __fs_to_date_short__(self, date_value):
"""
convert a datetime.datetime type to
a unix like milliseconds since epoch
:param date_value: datetime.datetime {object}
:return: number
"""
if not date_value:
return 0
return int(time.mktime(date_value.timetuple())) * 1000
```
## Conversion types when writing to database during update and create
Add or replace to db conversion methods by using a dictionary that specifies conversions for SQLAlchemy columns.
- str(type): is the key to the dictionary for a python object type
- the value is a lambda or method to provide the conversion to a database acceptable value.
Example:
```python
__fs_convert_types__ = {
str(bool): lambda v: (type(v) == bool and v) or str(v).lower() == "true"
}
```
First the correct conversion will be attempted to be determined from the type of the updated or
new field value. Then, an introspection from the destination column type will be used to get the
correct value converter type.
@property values are converted using the `__fs_property_converter__` class method. Override or extend it
for unexpected types.
Notes:
- The order of convert types will have an effect. For example, the Python boolean type is derived from an int. Make sure
boolean appears in the list before any int convert type.
- To undertake a more specific column conversion use the `__fs_verify__` method to explicitly set the class instance value. The
`__fs_verify__` method is always called before a create or update to the database.
- When converting values from query strings or form values the type will always be `str`.
- To add or modify values from a Flask request object before they are applied to the instance use the `__fs_before_update__` hook.
`__fs_verify__` is called after `__fs_before_update__`.
- To undertake actions after a commit use the `__fs_after_commit__` hook.
# Mixin Helper methods and properties
## fs_get_delete_put_post(item_id, user, prop_filters)
Put, get, delete, post and get-all magic method handler.
- `item_id`: the primary key of the item - if none and method is 'GET' returns all items
- `user`: user to user as query filter.
- `prop_filters`: dictionary of key:value pairs to limit results when returning get-all.
| Method Operation | item_id | Response |
|------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| GET | primary key | returns one item when `item_id` is a primary key. {property1:value1,property2:value2,...} |
| GET | None | returns all items when `item_id` is None. [{item1},{item2},...] |
| PUT | primary key | updates item using `item_id` as the id from request JSON data. Calls the model `__fs_verify__` `{message:message,item:{model_fields,...}`, properties:{`__fs_update_properties__`}} before updating. Returns new item as {item} |
| DELETE | primary key | removes the item with primary key of `item_id` if self.__fs_can_delete__ does not throw an error. `{property1:value1,property2:value2,...}` Returns the item removed. Calls `__fs_can_delete__` before delete. |
| POST | None | creates and returns a Flask response with a new item as JSON from form body data or JSON body data {property1:value1,property2:value2,...} When `item_id` is None. Calls the model `__fs_verify__` method before creating. |
| POST | primary key | updates an item from form data using `item_id`. Calls the model ` __fs_verify__` method before updating. |
On error returns a response of 'error message' with http status code of 400.
Set the `user` parameter to restrict a certain user. By default uses the
relationship of `user`. Set another relationship field by setting the `__fs_user_field__` to the name of the
relationship.
Prop filters is a dictionary of `property name`:`value` pairs. Ie: {'group': 'admin'} to restrict list to the
admin group. Properties or database fields can be used as the property name.
## fs_as_dict
Convert a db object into a dictionary. Example:
```python
item = Setting.query.get_or_404(2)
dict_item = item.fs_as_dict()
```
## fs_as_json
Convert a db object into a JSON Flask response using `jsonify`. Example:
```python
@app.route('/setting/<int:item_id>')
def get_setting(item_id):
item = Setting.query.get_or_404(item_id)
return item.fs_as_json()
```
## __fs_after_commit__(self, create=False)
Hook to call after any of `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` has been called so that
you can do what you like. `self` is the updated or created (create==True) item. Example:
```python
def __fs_after_commit__(self, create=False):
logging.info(f'changed! {self}')
```
NOTE: not called after a `DELETE`
## `__fs_before_update__(cls, data_dict)`
- data_dict: a dictionary of new data to apply to the item
- return: the new `data_dict` to use when updating
Hook to call before any of `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` is called so that
you may alter or add update values before the item is written to `self` in preparation for update to db.
NOTE: copy `data_dict` to a normal dict as it may be an `Immutable` type from the request object.
Example, make sure active is 'n' if no value from a request.
```python
def __fs_before_update__(self, data_dict):
d = dict(data_dict)
d['active'] = d.get('active', 'n')
return d
```
## fs_dict_list(cls, query_result)
return a list of dictionary objects
from the sql query result using `__fs_can_access__()` to filter
results.
```python
@app.route('/items')
def get_items():
items = Setting.query.all()
return jsonify(Setting.fs_dict_list(items))
```
## fs_json_list(query_result)
Return a flask response in JSON list format from a sql alchemy query result.
.. code:: python
python
```
@bp.route('/address/list', methods=['GET'])
@login_required
def address_list():
items = Address.query.filter_by(user=current_user)
return Address.fs_json_list(items)
```
## fs_json_filter_by(kw_args)
Return a flask list response in JSON format using a filter_by query.
Example:
```python
@bp.route('/address/list', methods=['GET'])
@login_required
def address_list():
return Address.fs_json_filter_by(user=current_user)
```
## fs_json_first(kwargs)
Return the first result in JSON format using filter_by arguments.
Example:
```python
@bp.route('/score/<course>', methods=['GET'])
@login_required
def score(course):
return Score.fs_json_first(class_name=course)
```
## __fs_previous_field_value__
A dictionary of the previous field values before an update is applied from a dict, form or JSON update operation. Helpful
in the `__fs_verify__` method to see if field values are to be changed.
Example:
```python
def __fs_verify__(self, create=False):
previous_value = self.__fs_previous_field_value__.get('value')
if previous_value != self.value:
current_app.logger.warning(f'value is changing from {previous_value}')
```
## fs_request_create_form(kwargs)
Use the contents of a Flask request form or request JSON data to create a item
in the database. Calls `__fs_verify__(create=True)`. Returns the new item or throws error.
Use kwargs to set the object properties of the newly created item.
Example:
Create a `score` item with the parent being a `course`.
```python
@bp.route('/score/<course_id>', methods=['POST'])
@login_required
def score(course_id):
course = Course.query.get_or_404(course_id)
return Score.fs_request_create_form(course_id=course.id).fs_as_dict
```
## fs_request_update_form()
Use the contents of a Flask request form or request JSON data to update an item
in the database. Calls `__fs_verify__()` and `__fs_can_update__()` to check
if can update. Returns True on success.
Example:
Update a score item.
```
/score/6?value=23.4
```
```python
@bp.route('/score/<int:score_id>', methods=['PUT'])
@login_required
def score(score_id):
score = Score.query.get_or_404(score_id)
if Score.fs_request_update_form():
return 'ok'
else:
return 'update failed'
```
# FormPageMixin
Easily add WTF form page handling by including the FormPageMixin.
Example:
```python
from flask_serialize.form_page import FormPageMixin
class Setting(FlaskSerializeMixin, FormPageMixin, db.Model):
# ....
```
This provides a method and class properties to quickly add a standard way of dealing with WTF forms on a Flask page.
## form_page(cls, item_id=None)
Do all the work for creating and editing items using a template and a wtf form.
Prerequisites.
Setup the class properties to use your form items.
| Property | Usage |
|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| form_page_form | **Required**. WTForm Class name |
| form_page_route_create | **Required**. Name of the method to redirect after create, uses: url_for(cls.form_route_create, item_id=id) |
| form_page_route_update | **Required**. Name of the method to redirect after updating, uses: url_for(cls.form_route_update, item_id=id) |
| form_page_template | **Required**. Location of the template file to allow edit/add |
| form_page_update_format | Format string to format flash message after update. `item` (the model instance) is passed as the only parameter. Set to '' or None to suppress flash. |
| form_page_create_format | Format string to format flash message after create. `item` (the model instance) is passed as the only parameter. Set to '' or None to suppress flash. |
| form_page_update_title_format | Format string to format title template value when editing. `item` (the model instance) is passed as the only parameter. |
| form_page_create_title_format | Format string to format title template value when creating. `cls` (the model class) is passed as the only parameter. |
The routes must use item_id as the parameter for editing. Use no parameter when creating.
Example:
To allow the Setting class to use a template and WTForm to create and edit items. In this example after create the index page is
loaded, using the method `page_index`. After update, the same page is reloaded with the new item values in the form.
Add these property overrides to the Setting Class.
```python
# form_page
form_page_form = EditForm
form_page_route_update = 'route_setting_form'
form_page_route_create = 'page_index'
form_page_template = 'setting_edit.html'
form_page_new_title_format = 'New Setting'
```
Add this form.
```python
class EditForm(FlaskForm):
value = StringField('value')
```
Setup these routes.
```python
@app.route('/setting_form_edit/<int:item_id>', methods=['POST', 'GET'])
@app.route('/setting_form_add', methods=['POST'])
def route_setting_form(item_id=None):
return Setting.form_page(item_id)
```
Template.
The template file needs to use WTForms to render the given form. `form`, `item`, `item_id` and `title` are passed as template
variables.
Example to update using POST, NOTE: only POST and GET are supported by form submit:
```html
<h3>{{title}}</h3>
<form method="POST" submit="{{url_for('route_setting_form', item_id=item.id)}}">
<input name="value" value="{{form.value.data}}">
<input type="submit">
</form>
```
Example to create using POST:
```html
<h3>{{title}}</h3>
<form method="POST" submit="{{url_for('route_setting_form')}}">
<input name="value" value="{{form.value.data}}">
<input type="submit">
</form>
```
# NOTES
## Version 2.0.1 update notes
Version 2.0.1 changes most of the properties, hooks and methods to use a more normal Python naming convention.
- Regularly called mixin methods now start with `fs_`.
- Hook methods start with `__fs_` and end with `__`.
- Control properties start with `__fs_` and end with `__`.
- Some hook functions can now return False or True rather than just raise Exceptions
- fs_get_delete_put_post now returns a HTTP code that is more accurate of the cause
## Release Notes
- 2.2.0 - Allow arg parameters to be used in `fs_get_delete_put_post` 'GET' as query_by filters
- 2.1.3 - Allow sorting by lambda
- 2.1.2 - Fix readme table format
- 2.1.1 - Improve sqlite JSON handling
- 2.1.0 - Convert readme to markdown. Add support for JSON columns. Withdraw Python 3.6 Support. Use unittest instead of pytest. NOTE: Changes `__fs_convert_types__` to a `dict`.
- 2.0.3 - Allow more use of model column variables instead of "quoted" field names. Fix missing import for FlaskSerialize.
- 2.0.2 - Fix table formatting.
- 2.0.1 - Try to get properties and methods to use more appropriate names.
- 1.5.2 - Test with flask 2.0. Add `__fs_after_commit__` method to allow post create/update actions. Improve documentation.
- 1.5.1 - Fix TypeError: unsupported operand type(s) for +=: 'ImmutableColumnCollection' and 'list' with newer versions of SQLAlchemy
- 1.5.0 - Return item from POST/PUT updates. Allow `__fs_create_fields__` and `__fs_update_fields__` to be specified using the column fields. None values serialize as null/None. Restore previous `__fs_update_properties__` behaviour. By default, updates/creates using all fields. Exclude primary key from create and update.
- 1.4.2 - by default return all props with `__fs_update_properties__`
- 1.4.1 - Add better exception message when `db` mixin property not set. Add `FlaskSerialize` factory method.
- 1.4.0 - Add `__fs_private_field__` method.
- 1.3.1 - Fix incorrect method signatures. Add fs_query_by_access method.
- 1.3.0 - Add `__fs_can_update__` and `__fs_can_access__` methods for controlling update and access.
- 1.2.1 - Add support to change the user field name for fs_get_delete_put_post user= parameter.
- 1.2.0 - Add support for decimal, numeric and clob. Treat all VARCHARS the same. Convert non-list relationship.
- 1.1.9 - Allow FlaskSerializeMixin to be converted when a property value.
- 1.1.8 - Move form_page to separate MixIn. Slight refactoring. Add support for complex type to db.
- 1.1.6 - Make sure all route returns use jsonify as required for older Flask versions. Add `__fs_before_update__` hook.
- 1.1.5 - Add `__fs_previous_field_value__` array that is set during update. Allows comparing new and previous values during `__fs_verify__`.
- 1.1.4 - Fix doco typos and JavaScript examples. Add form_page method. Improve test and example apps. Remove Python 2, 3.4 testing and support.
- 1.1.3 - Fix duplicate db writes. Return item on delete. Remove obsolete code structures. Do not update with non-existent fields.
- 1.1.2 - Add 400 http status code for errors, remove error dict. Improve documentation.
- 1.1.0 - Suppress silly errors. Improve documentation.
- 1.0.9 - Add kwargs to fs_request_create_form to pass Object props to be used when creating the Object instance
- 1.0.8 - Cache introspection to improve performance. All model definitions are cached after first use. It is no longer possible to alter model definitions dynamically.
- 1.0.7 - Add JSON request body support to post update.
- 1.0.5 - Allow sorting of JSON lists.
## Licensing
- Apache 2.0
Raw data
{
"_id": null,
"home_page": "https://github.com/Martlark/flask-serialize",
"name": "flask-serialize",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "flask sqlalchemy serialize serialization serialise",
"author": "Andrew Rowe",
"author_email": "rowe.andrew.d@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/35/e4/a94f0a42bebbd695e360c16d2b3aaed9b67cc51602c6e97ec4ae86a6f5bf/flask-serialize-2.2.0.tar.gz",
"platform": null,
"description": "# flask-serialize\n\n# DB Model JSON serialization with PUT, POST write for Flask applications using SQLAlchemy\n\n## Installation\n\n```bash\npip install flask-serialize\n```\n\n## Simple and quick to get going in two steps.\n\n*One* Import and add the FlaskSerializeMixin mixin to a model:\n\n```python\nfrom flask_serialize import FlaskSerialize\n\n# create a flask-serialize mixin instance from\n# the factory method `FlaskSerialize`\nfs_mixin = FlaskSerialize(db)\n\nclass Item(db.Model, fs_mixin):\n id = db.Column(db.Integer, primary_key=True)\n # other fields ...\n```\n\n*Two* Configure the route with the do all mixin method:\n\n```python\n@app.route('/item/<int:item_id>')\n@app.route('/items')\ndef items(item_id=None):\n return Item.fs_get_delete_put_post(item_id)\n```\n\n*Three* Done! Returns JSON as a single item or a list with only a single route.\n\nFlask-serialize is intended for joining a Flask SQLAlchemy Python backend with\na JavaScript Web client. It allows read JSON serialization\nfrom the db and easy to use write back of models using PUT and POST.\n\n4 times faster than marshmallow for simple dict serialization.\n\n# Example\n\n## Model setup\n\n```python\n# example database model\nfrom flask_serialize import FlaskSerialize\nfrom datetime import datetime\n\n# required to set class var db for writing to a database\nfrom app import db\n\nfs_mixin = FlaskSerialize(db)\n\nclass Setting(fs_mixin, db.Model):\n id = db.Column(db.Integer, primary_key=True)\n\n setting_type = db.Column(db.String(120), index=True, default='misc')\n key = db.Column(db.String(120), index=True)\n value = db.Column(db.String(30000), default='')\n active = db.Column(db.String(1), default='y')\n created = db.Column(db.DateTime, default=datetime.utcnow)\n updated = db.Column(db.DateTime, default=datetime.utcnow)\n \n # serializer fields\n __fs_create_fields__ = __fs_update_fields__ = ['setting_type', 'value', 'key', 'active']\n\n # checks if Flask-Serialize can delete\n def __fs_can_delete__(self):\n if self.value == '1234':\n raise Exception('Deletion not allowed. Magic value!')\n return True\n\n # checks if Flask-Serialize can create/update\n def __fs_verify__(self, create=False):\n if len(self.key or '') < 1:\n raise Exception('Missing key')\n\n if len(self.setting_type or '') < 1:\n raise Exception('Missing setting type')\n return True\n\n def __repr__(self):\n return '<Setting %r %r %r>' % (self.id, self.setting_type, self.value)\n```\n\n## Routes setup\n\nGet a single item as JSON.\n\n```python\n@app.route('/get_setting/<item_id>', methods=['GET'])\ndef get_setting( item_id ):\n return Setting.fs_get_delete_put_post(item_id)\n\n# Returns a Flask response with a JSON object, example:\n```\n\n```JavaScript\n{id:1, value: \"hello\"}\n```\n\nPut an update to a single item as JSON.\n\n```python\n@app.route('/update_setting/<item_id>', methods=['PUT'])\ndef update_setting( item_id ):\n return Setting.fs_get_delete_put_post(item_id)\n\n# Returns a Flask response with the result as a JSON object:\n\n```\n\n```JavaScript\n{message: \"success message\"}\n```\n\nDelete a single item.\n\n```python\n@app.route('/delete_setting/<item_id>', methods=['DELETE'])\ndef delete_setting( item_id ):\n return Setting.fs_get_delete_put_post(item_id)\n\n# Returns a Flask response with the result and item deleted as a JSON response:\n```\n\n```JavaScript\n{message: \"success message\", item: {\"id\":5, name: \"gone\"}}\n```\n\nGet all items as a JSON list.\n\n```python\n@app.route('/get_setting_all', methods=['GET'])\ndef get_setting_all():\n return Setting.fs_get_delete_put_post()\n\n# Returns a Flask response with a list of JSON objects, example:\n```\n\n```JavaScript\n[{id:1, value: \"hello\"},{id:2, value: \"there\"},{id:3, value: \"programmer\"}]\n```\n\nAll of: get-all, get, put, post, and delete can be combined in one route.\n\n```python\n@app.route('/setting/<int:item_id>', methods=['GET', 'PUT', 'DELETE', 'POST'])\n@app.route('/setting', methods=['GET', 'POST'])\ndef route_setting_all(item_id=None):\n return Setting.fs_get_delete_put_post(item_id)\n```\n\nUpdating from a JSON object in the Flask put request\n\nJQuery example:\n\n```javascript\nfunction put(setting_id) {\n return $.ajax({\n url: `/update_setting/${setting_id}`,\n method: 'PUT',\n contentType: \"application/json\",\n data: {setting_type:\"x\",value:\"100\"},\n }).then(response => {\n alert(\"OK:\"+response.message);\n }).fail((xhr, textStatus, errorThrown) => {\n alert(`Error: ${xhr.responseText}`);\n });\n }\n}\n```\n\nFlask route:\n\n```python\n@app.route('/update_setting/<int:item_id>', methods=['PUT'])\ndef update_setting(item_id):\n return Setting.fs_get_delete_put_post(item_id)\n```\n\nCreate or update from a WTF form:\n\n```python\n @app.route('/setting_edit/<int:item_id>', methods=['POST'])\n @app.route('/setting_add', methods=['POST']) \n def setting_edit(item_id=None):\n if item_id:\n item = Setting.query.get_or_404(item_id)\n else:\n item = {}\n form = EditForm(obj=item)\n \n if form.validate_on_submit():\n if item_id:\n try:\n item.fs_request_update_form()\n flash('Your changes have been saved.')\n except Exception as e:\n flash(str(e), category='danger')\n return redirect(url_for('setting_edit', item_id=item_id))\n else:\n try:\n new_item = Setting.fs_request_create_form()\n flash('Setting created.')\n return redirect(url_for('setting_edit', item_id=new_item.id))\n except Exception as e:\n flash('Error creating item: ' + str(e))\n \n return render_template(\n 'setting_edit.html',\n item=item,\n title='Edit or Create item',\n form=form\n )\n```\n\n# Create a child database object:\n\n## Using POST.\n\nAs example: add a `Stat` object to a Survey object using the `fs_request_create_form` convenience method. The foreign key\nto the parent `Survey` is provided as a `kwargs` parameter to the method.\n\n```python\n @app.route('/stat/<int:survey_id>', methods=['POST'])\n def stat_add(survey_id=None):\n survey = Survey.query.get_or_404(survey_id)\n return Stat.fs_request_create_form(survey_id=survey.id).fs_as_dict\n```\n\n## Using fs_get_delete_put_post.\n\nAs example: add a `Stat` object to a Survey object using the `fs_get_delete_put_post` convenience method. The foreign key\nto the parent `Survey` is provided in the form data as survey_id. `__fs_create_fields__` list must then include `survey_id` as\nthe foreign key field to be set if you specify any `__fs_create_fields__`. By default, all fields are allowed to be included\nwhen creating.\n\n```html\n <form>\n <input type=\"hidden\" name=\"survey_id\" value=\"56\">\n <input name=\"value\">\n </form>\n```\n\n```python\n @app.route('/stat/', methods=['POST'])\n def stat_add():\n return Stat.fs_get_delete_put_post()\n```\n\n# Writing and creating\n\nWhen using any of the convenience methods to update, create or delete an object these properties and\nmethods control how flask-serialize handles the operation.\n\n## Updating from a form or JSON\n\n```python\ndef fs_request_update_json():\n \"\"\"\n Update an item from request JSON data or PUT params, probably from a PUT or PATCH.\n Throws exception if not valid\n\n :return: True if item updated\n\n \"\"\"\n```\n\nExample. To update a Message object using a GET, call this method with the parameters to update as request arguments. ie:\n\n/update_message/12/?body=hello&subject=something\n\n```python\n @route('/update_message/<int:message_id>/')\n def update_message(message_id)\n message = Message.fs_get_by_user_or_404(message_id, user=current_user)\n if message.fs_request_update_json():\n return 'Updated'\n```\n\n```python\ndef fs_request_update_json():\n \"\"\"\n Update an item from request JSON data or PUT params, probably from a PUT or PATCH.\n Throws exception if not valid\n\n :return: True if item updated\n\n \"\"\"\n```\n\nExample. To update a Message using a POST, call this method with the parameters to update as request arguments. ie:\n\n```\n/update_message/12/\n\nform data {body=\"hello\", subject=\"something\"}\n```\n\n```python\n @route('/update_message/<int:message_id>/', methods=['POST'])\n def update_message(message_id)\n message = Message.fs_get_by_user_or_404(message_id, user=current_user)\n if message.fs_request_update_form():\n return 'Updated'\n```\n\n## `__fs_verify__` write and create\n\n```python\ndef __fs_verify__(self, create=False):\n \"\"\"\n raise exception if item is not valid for put/patch/post\n :param: create - True if verification is for a new item\n \"\"\"\n```\n\nOverride the mixin `__fs_verify__` method to provide control and verification\nwhen updating and creating model items. Simply raise an exception\nwhen there is a problem. You can also modify `self` data before writing. See model example.\n\n## Delete\n\nTo control when a deletion using `fs_get_delete_put_post` override the `__fs_can_delete`\nhook. Return False or raise and exception to prevent deletion. Return True to\nallow deletion.\n\n```python\ndef __fs_can_delete__(self):\n```\n\nOverride the mixin `__fs_can_delete__` to provide control over when an\nitem can be deleted. Simply raise an exception\nwhen there is a problem. By default `__fs_can_delete__`\ncalls `__fs_can_update__` unless overridden. See model example.\n\n## `__fs_can_update__`\n\n```python\ndef __fs_can_update__(self):\n \"\"\"\n raise exception if item cannot be updated\n \"\"\"\n```\n\nOverride the mixin `__fs_can_update__` to provide control over when an\nitem can be updated. Simply raise an exception\nwhen there is a problem or return False. By default `__fs_can_update__`\nuses the result from `__fs_can_access__` unless overridden.\n\n## `__fs_can_access__`\n\n```python\ndef __fs_can_access__(self):\n \"\"\"\n return False if item can't be accessed\n \"\"\"\n```\n\nOverride the mixin `__fs_can_access__` to provide control over when an\nitem can be read or accessed. Return False to exclude from results.\n\n## Private fields\n\nFields can be made private by overriding the `__fs_private_field__` method\nand returning `True` if the field is to be private. These fields will not be returned\nin JSON results.\n\nPrivate fields will be excluded for any get, put and post methods.\n\nExample:\n\nTo exclude private fields when a user is not the admin.\n\n```python\ndef __fs_private_field__(self, field_name):\n if not is_admin_user() and field_name.upper().startswith('PRIVATE_'):\n return True\n return False\n```\n\n## `__fs_update_fields__`\n\nList of model fields to be read from a form or JSON when updating an object. Normally\nadmin fields such as login_counts or security fields are excluded. Do not put foreign keys or primary\nkeys here. By default, when `__fs_update_fields__` is empty all Model fields can be updated.\n\n```python\n__fs_update_fields__ = []\n```\n\n## `__fs_update_properties__`\n\nWhen returning a success result from a put or post update, a dict\ncomposed of the property values from the `__fs_update_properties__` list is returned\nas \"properties\".\n\nExample return `JSON`:\n\n```python\nclass ExampleModel(db.Model, FlaskSerializeMixin):\n head_size = db.Column(db.Integer())\n ear_width = db.Column(db.Integer())\n __fs_update_fields__ = ['head_size', 'ear_width']\n __fs_update_properties__ = ['hat_size']\n\n @property\n def hat_size(self):\n return self.head_size * self.ear_width\n```\n\n```JavaScript\n// result update return message\n{\"message\": \"Updated\", \"properties\": {hat_size: 45.67} }\n```\n\nThis can be used to communicate from the model on the server to the JavaScript code\ninteresting things from updates\n\n## `__fs_create_fields__`\n\nList of model fields to be read from a form or JSON when creating an object. Can be the specified as either 'text' or\nthe field. Do not put primary keys here. Do not put foreign keys here if using SQLAlchemy child insertion.\nThis is usually the same as `__fs_update_fields__`. When `__fs_create_fields__` is empty all column fields can be inserted.\n\nUsed by these methods:\n\n- fs_request_create_form\n- fs_get_delete_put_post\n\n```python\n__fs_create_fields__ = []\n```\n\nExample:\n\n```python\nclass Setting(fs_mixin, FormPageMixin, db.Model):\n id = db.Column(db.Integer, primary_key=True)\n\n setting_type = db.Column(db.String(120), index=True, default='misc')\n private = db.Column(db.String(3000), default='secret')\n value = db.Column(db.String(3000), default='')\n\n __fs_create_fields__ = [setting_type, 'value']\n```\n\n## Update DateTime fields specification\n\nThe class methods: `fs_request_update_form`, `fs_request_create_form`, `fs_request_update_json` will automatically stamp your\nmodel's timestamp fields using the `__fs_update_timestamp__` class method.\n\n`__fs_timestamp_fields__` is a list of fields on the model to be set when updating or creating\nwith the value of `datetime.datetime.utcnow()`. The default field names to update are: `['timestamp', 'updated']`.\n\nExample:\n\n```python\nclass ExampleModel(db.Model, FlaskSerializeMixin):\n # ....\n modified = db.Column(db.DateTime, default=datetime.utcnow)\n __fs_timestamp_fields__ = ['modified']\n```\n\nOverride the timestamp default of `utcnow()` by replacing the `__fs_timestamp_stamper__` class property with your\nown. Example:\n\n```python\nclass ExampleModel(db.Model, FlaskSerializeMixin):\n # ....\n __fs_timestamp_stamper__ = datetime.datetime.now\n```\n\n# Filtering and sorting\n\n## Exclude fields\n\nList of model field names to not serialize at all.\n\n```python\n__fs_exclude_serialize_fields__ = []\n```\n\nList of model field names to not serialize when returning as JSON.\n\n```python\n__fs_exclude_json_serialize_fields__ = []\n```\n\n## Built in query_by using request arg on GET\n\n`fs_get_delete_put_post` by default supports automatic passing of GET request args to the query method using\na `filter_by` clause. Example using a Flask route that be default returns all messages:\n\n\n```python\n @route('/message/', methods=['GET'])\n def route_message():\n return Message.fs_get_by_user_or_404()\n```\n\nCall the endpoint using URL like:\n\n /message/?name=hello&lines=12\n\nWill return any message records with the name of 'hello' and with 12 lines.\n\nThis feature can be disabled by using `__fs_filter_by = False` on the model definition.\n\n## Filtering JSON list results\n\nJson result lists can be filtered by using the `prop_filters` parameter on either\nthe `fs_get_delete_put_post` method or the `fs_json_list` method.\n\nThe filter consists of one or more properties in the JSON result and\nthe value that it must match. Filter items will match against the\nfirst `prop_filter` property to exactly equal the value.\n\nNOTE: The filter is not applied with single a GET or, the PUT, POST and DELETE methods.\n\nExample to only return dogs:\n\n```python\nresult = fs_get_delete_put_post(prop_filters = {'key':'dogs'})\n```\n\n## Sorting JSON list results\n\nJson result lists can be sorted by using the `__fs_order_by_field__` or the `__fs_order_by_field_desc__` properties. The results\nare sorted after the query is converted to JSON. As such you can use any property from a class to sort. To sort by id\nascending use this example:\n\n```python\n__fs_order_by_field__ = 'id'\n```\n\nA `lambda` or `method` can be used as the `__fs_order_by_field__`, in which case custom sorting can be achieved. The\npassed value to the `lambda` is a dictionary of the field and properties of a result row.\n\nExample:\n\n```python\n__fs_order_by_field__ = lambda r: -int(r[\"value\"])\n```\n\n\n## Filtering query results using `__fs_can_access__` and user.\n\nThe `fs_query_by_access` method can be used to filter a SQLAlchemy result set so that\nthe `user` property and `__fs_can_access__` hook method are used to restrict to allowable items.\n\nExample:\n\n```python\nresult_list = Setting.fs_query_by_access(user='Andrew', setting_type='test')\n```\n\nAny keyword can be supplied after `user` to be passed to `filter_by` method of `query`.\n\n## Relationships list of property names that are to be included in serialization\n\n```python\n__fs_relationship_fields__ = []\n```\n\nIn default operation relationships in models are not serialized. Add any\nrelationship property name here to be included in serialization. NOTE: take care\nto not include circular relationships. Flask-Serialize does not check for circular\nrelationships.\n\n# Serialization converters\n\nThere are three built in converters to convert data from the database\nto a good format for serialization:\n\n- DATETIME - Removes the fractional second part and makes it a string\n- PROPERTY - Enumerates and returns model added properties\n- RELATIONSHIP - Deals with children model items.\n\nSet one of these to None or a value to remove or replace it's behaviour.\n\n## Adding and overriding converter behaviour\n\nAdd values to the class property:\n\n```python\n__fs_column_type_converters__ = {}\n```\n\nWhere the key is the column type name of the database column\nand the value is a method to provide the conversion.\n\nExample:\n\nTo convert VARCHAR(100) to a string:\n\n```python\n__fs_column_type_converters__ = {'VARCHAR': lambda v: str(v)}\n```\n\nTo change DATETIME conversion behaviour, either change the DATETIME column_type_converter or\noverride the `__fs_to_date_short__` method of the mixin. Example:\n\n```python\nimport time\n\nclass Model(db.model, FlaskSerializeMixin):\n # ...\n # ...\n def __fs_to_date_short__(self, date_value):\n \"\"\"\n convert a datetime.datetime type to\n a unix like milliseconds since epoch\n :param date_value: datetime.datetime {object}\n :return: number\n \"\"\"\n if not date_value:\n return 0\n\n return int(time.mktime(date_value.timetuple())) * 1000\n```\n\n## Conversion types when writing to database during update and create\n\nAdd or replace to db conversion methods by using a dictionary that specifies conversions for SQLAlchemy columns.\n\n- str(type): is the key to the dictionary for a python object type\n- the value is a lambda or method to provide the conversion to a database acceptable value.\n\nExample:\n\n```python\n __fs_convert_types__ = {\n str(bool): lambda v: (type(v) == bool and v) or str(v).lower() == \"true\"\n }\n```\n\nFirst the correct conversion will be attempted to be determined from the type of the updated or\nnew field value. Then, an introspection from the destination column type will be used to get the\ncorrect value converter type.\n\n@property values are converted using the `__fs_property_converter__` class method. Override or extend it\nfor unexpected types.\n\nNotes:\n\n- The order of convert types will have an effect. For example, the Python boolean type is derived from an int. Make sure\n boolean appears in the list before any int convert type.\n\n- To undertake a more specific column conversion use the `__fs_verify__` method to explicitly set the class instance value. The\n `__fs_verify__` method is always called before a create or update to the database.\n\n- When converting values from query strings or form values the type will always be `str`.\n\n- To add or modify values from a Flask request object before they are applied to the instance use the `__fs_before_update__` hook.\n `__fs_verify__` is called after `__fs_before_update__`.\n\n- To undertake actions after a commit use the `__fs_after_commit__` hook.\n\n# Mixin Helper methods and properties\n\n## fs_get_delete_put_post(item_id, user, prop_filters)\n\nPut, get, delete, post and get-all magic method handler.\n\n- `item_id`: the primary key of the item - if none and method is 'GET' returns all items\n- `user`: user to user as query filter.\n- `prop_filters`: dictionary of key:value pairs to limit results when returning get-all.\n\n| Method Operation | item_id | Response |\n|------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| GET | primary key | returns one item when `item_id` is a primary key. {property1:value1,property2:value2,...} |\n| GET | None | returns all items when `item_id` is None. [{item1},{item2},...] |\n| PUT | primary key | updates item using `item_id` as the id from request JSON data. Calls the model `__fs_verify__` `{message:message,item:{model_fields,...}`, properties:{`__fs_update_properties__`}} before updating. Returns new item as {item} |\n| DELETE | primary key | removes the item with primary key of `item_id` if self.__fs_can_delete__ does not throw an error. `{property1:value1,property2:value2,...}` Returns the item removed. Calls `__fs_can_delete__` before delete. |\n| POST | None | creates and returns a Flask response with a new item as JSON from form body data or JSON body data {property1:value1,property2:value2,...} When `item_id` is None. Calls the model `__fs_verify__` method before creating. |\n| POST | primary key | updates an item from form data using `item_id`. Calls the model ` __fs_verify__` method before updating. |\n\nOn error returns a response of 'error message' with http status code of 400.\n\nSet the `user` parameter to restrict a certain user. By default uses the\nrelationship of `user`. Set another relationship field by setting the `__fs_user_field__` to the name of the\nrelationship.\n\nProp filters is a dictionary of `property name`:`value` pairs. Ie: {'group': 'admin'} to restrict list to the\nadmin group. Properties or database fields can be used as the property name.\n\n## fs_as_dict\n\nConvert a db object into a dictionary. Example:\n\n```python\nitem = Setting.query.get_or_404(2)\ndict_item = item.fs_as_dict()\n```\n\n## fs_as_json\n\nConvert a db object into a JSON Flask response using `jsonify`. Example:\n\n```python\n@app.route('/setting/<int:item_id>')\ndef get_setting(item_id):\n item = Setting.query.get_or_404(item_id)\n return item.fs_as_json()\n```\n\n## __fs_after_commit__(self, create=False)\n\nHook to call after any of `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` has been called so that\nyou can do what you like. `self` is the updated or created (create==True) item. Example:\n\n```python\ndef __fs_after_commit__(self, create=False):\n logging.info(f'changed! {self}')\n```\n\nNOTE: not called after a `DELETE`\n\n## `__fs_before_update__(cls, data_dict)`\n\n- data_dict: a dictionary of new data to apply to the item\n- return: the new `data_dict` to use when updating\n\nHook to call before any of `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` is called so that\nyou may alter or add update values before the item is written to `self` in preparation for update to db.\n\nNOTE: copy `data_dict` to a normal dict as it may be an `Immutable` type from the request object.\n\nExample, make sure active is 'n' if no value from a request.\n\n```python\ndef __fs_before_update__(self, data_dict):\n d = dict(data_dict)\n d['active'] = d.get('active', 'n')\n return d\n```\n\n## fs_dict_list(cls, query_result)\n\nreturn a list of dictionary objects\nfrom the sql query result using `__fs_can_access__()` to filter\nresults.\n\n```python\n@app.route('/items')\ndef get_items():\n items = Setting.query.all()\n return jsonify(Setting.fs_dict_list(items))\n```\n\n## fs_json_list(query_result)\n\nReturn a flask response in JSON list format from a sql alchemy query result.\n\n.. code:: python\npython\n\n```\n@bp.route('/address/list', methods=['GET'])\n@login_required\ndef address_list():\n items = Address.query.filter_by(user=current_user)\n return Address.fs_json_list(items)\n```\n\n## fs_json_filter_by(kw_args)\n\nReturn a flask list response in JSON format using a filter_by query.\n\nExample:\n\n```python\n@bp.route('/address/list', methods=['GET'])\n@login_required\ndef address_list():\n return Address.fs_json_filter_by(user=current_user)\n```\n\n## fs_json_first(kwargs)\n\nReturn the first result in JSON format using filter_by arguments.\n\nExample:\n\n```python\n@bp.route('/score/<course>', methods=['GET'])\n@login_required\ndef score(course):\n return Score.fs_json_first(class_name=course)\n```\n\n## __fs_previous_field_value__\n\nA dictionary of the previous field values before an update is applied from a dict, form or JSON update operation. Helpful\nin the `__fs_verify__` method to see if field values are to be changed.\n\nExample:\n\n```python\ndef __fs_verify__(self, create=False):\n previous_value = self.__fs_previous_field_value__.get('value')\n if previous_value != self.value:\n current_app.logger.warning(f'value is changing from {previous_value}')\n```\n\n## fs_request_create_form(kwargs)\n\nUse the contents of a Flask request form or request JSON data to create a item\nin the database. Calls `__fs_verify__(create=True)`. Returns the new item or throws error.\nUse kwargs to set the object properties of the newly created item.\n\nExample:\n\nCreate a `score` item with the parent being a `course`.\n\n```python\n@bp.route('/score/<course_id>', methods=['POST'])\n@login_required\ndef score(course_id):\n course = Course.query.get_or_404(course_id)\n return Score.fs_request_create_form(course_id=course.id).fs_as_dict\n```\n\n## fs_request_update_form()\n\nUse the contents of a Flask request form or request JSON data to update an item\nin the database. Calls `__fs_verify__()` and `__fs_can_update__()` to check\nif can update. Returns True on success.\n\nExample:\n\nUpdate a score item.\n\n```\n/score/6?value=23.4\n```\n\n```python\n@bp.route('/score/<int:score_id>', methods=['PUT'])\n@login_required\ndef score(score_id):\n score = Score.query.get_or_404(score_id)\n if Score.fs_request_update_form():\n return 'ok'\n else:\n return 'update failed'\n```\n\n# FormPageMixin\n\nEasily add WTF form page handling by including the FormPageMixin.\n\nExample:\n\n```python\nfrom flask_serialize.form_page import FormPageMixin\n\nclass Setting(FlaskSerializeMixin, FormPageMixin, db.Model):\n # ....\n```\n\nThis provides a method and class properties to quickly add a standard way of dealing with WTF forms on a Flask page.\n\n## form_page(cls, item_id=None)\n\nDo all the work for creating and editing items using a template and a wtf form.\n\nPrerequisites.\n\nSetup the class properties to use your form items.\n\n| Property | Usage |\n|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|\n| form_page_form | **Required**. WTForm Class name |\n| form_page_route_create | **Required**. Name of the method to redirect after create, uses: url_for(cls.form_route_create, item_id=id) |\n| form_page_route_update | **Required**. Name of the method to redirect after updating, uses: url_for(cls.form_route_update, item_id=id) |\n| form_page_template | **Required**. Location of the template file to allow edit/add |\n| form_page_update_format | Format string to format flash message after update. `item` (the model instance) is passed as the only parameter. Set to '' or None to suppress flash. |\n| form_page_create_format | Format string to format flash message after create. `item` (the model instance) is passed as the only parameter. Set to '' or None to suppress flash. |\n| form_page_update_title_format | Format string to format title template value when editing. `item` (the model instance) is passed as the only parameter. |\n| form_page_create_title_format | Format string to format title template value when creating. `cls` (the model class) is passed as the only parameter. |\n\nThe routes must use item_id as the parameter for editing. Use no parameter when creating.\n\nExample:\n\nTo allow the Setting class to use a template and WTForm to create and edit items. In this example after create the index page is\nloaded, using the method `page_index`. After update, the same page is reloaded with the new item values in the form.\n\nAdd these property overrides to the Setting Class.\n\n```python\n# form_page\nform_page_form = EditForm\nform_page_route_update = 'route_setting_form'\nform_page_route_create = 'page_index'\nform_page_template = 'setting_edit.html'\nform_page_new_title_format = 'New Setting'\n```\n\nAdd this form.\n\n```python\nclass EditForm(FlaskForm):\n value = StringField('value')\n```\n\nSetup these routes.\n\n```python\n@app.route('/setting_form_edit/<int:item_id>', methods=['POST', 'GET'])\n@app.route('/setting_form_add', methods=['POST'])\ndef route_setting_form(item_id=None):\n return Setting.form_page(item_id)\n```\n\nTemplate.\n\nThe template file needs to use WTForms to render the given form. `form`, `item`, `item_id` and `title` are passed as template\nvariables.\n\nExample to update using POST, NOTE: only POST and GET are supported by form submit:\n\n```html\n<h3>{{title}}</h3>\n<form method=\"POST\" submit=\"{{url_for('route_setting_form', item_id=item.id)}}\">\n <input name=\"value\" value=\"{{form.value.data}}\">\n <input type=\"submit\">\n</form>\n```\n\nExample to create using POST:\n\n```html\n<h3>{{title}}</h3>\n<form method=\"POST\" submit=\"{{url_for('route_setting_form')}}\">\n <input name=\"value\" value=\"{{form.value.data}}\">\n <input type=\"submit\">\n</form>\n```\n\n# NOTES\n\n## Version 2.0.1 update notes\n\nVersion 2.0.1 changes most of the properties, hooks and methods to use a more normal Python naming convention.\n\n- Regularly called mixin methods now start with `fs_`.\n- Hook methods start with `__fs_` and end with `__`.\n- Control properties start with `__fs_` and end with `__`.\n- Some hook functions can now return False or True rather than just raise Exceptions\n- fs_get_delete_put_post now returns a HTTP code that is more accurate of the cause\n\n## Release Notes\n\n- 2.2.0 - Allow arg parameters to be used in `fs_get_delete_put_post` 'GET' as query_by filters\n- 2.1.3 - Allow sorting by lambda\n- 2.1.2 - Fix readme table format\n- 2.1.1 - Improve sqlite JSON handling\n- 2.1.0 - Convert readme to markdown. Add support for JSON columns. Withdraw Python 3.6 Support. Use unittest instead of pytest. NOTE: Changes `__fs_convert_types__` to a `dict`.\n- 2.0.3 - Allow more use of model column variables instead of \"quoted\" field names. Fix missing import for FlaskSerialize.\n- 2.0.2 - Fix table formatting.\n- 2.0.1 - Try to get properties and methods to use more appropriate names.\n- 1.5.2 - Test with flask 2.0. Add `__fs_after_commit__` method to allow post create/update actions. Improve documentation.\n- 1.5.1 - Fix TypeError: unsupported operand type(s) for +=: 'ImmutableColumnCollection' and 'list' with newer versions of SQLAlchemy\n- 1.5.0 - Return item from POST/PUT updates. Allow `__fs_create_fields__` and `__fs_update_fields__` to be specified using the column fields. None values serialize as null/None. Restore previous `__fs_update_properties__` behaviour. By default, updates/creates using all fields. Exclude primary key from create and update.\n- 1.4.2 - by default return all props with `__fs_update_properties__`\n- 1.4.1 - Add better exception message when `db` mixin property not set. Add `FlaskSerialize` factory method.\n- 1.4.0 - Add `__fs_private_field__` method.\n- 1.3.1 - Fix incorrect method signatures. Add fs_query_by_access method.\n- 1.3.0 - Add `__fs_can_update__` and `__fs_can_access__` methods for controlling update and access.\n- 1.2.1 - Add support to change the user field name for fs_get_delete_put_post user= parameter.\n- 1.2.0 - Add support for decimal, numeric and clob. Treat all VARCHARS the same. Convert non-list relationship.\n- 1.1.9 - Allow FlaskSerializeMixin to be converted when a property value.\n- 1.1.8 - Move form_page to separate MixIn. Slight refactoring. Add support for complex type to db.\n- 1.1.6 - Make sure all route returns use jsonify as required for older Flask versions. Add `__fs_before_update__` hook.\n- 1.1.5 - Add `__fs_previous_field_value__` array that is set during update. Allows comparing new and previous values during `__fs_verify__`.\n- 1.1.4 - Fix doco typos and JavaScript examples. Add form_page method. Improve test and example apps. Remove Python 2, 3.4 testing and support.\n- 1.1.3 - Fix duplicate db writes. Return item on delete. Remove obsolete code structures. Do not update with non-existent fields.\n- 1.1.2 - Add 400 http status code for errors, remove error dict. Improve documentation.\n- 1.1.0 - Suppress silly errors. Improve documentation.\n- 1.0.9 - Add kwargs to fs_request_create_form to pass Object props to be used when creating the Object instance\n- 1.0.8 - Cache introspection to improve performance. All model definitions are cached after first use. It is no longer possible to alter model definitions dynamically.\n- 1.0.7 - Add JSON request body support to post update.\n- 1.0.5 - Allow sorting of JSON lists.\n\n## Licensing\n\n- Apache 2.0\n",
"bugtrack_url": null,
"license": "Apache Software License",
"summary": "Easy to use JSON serialization and update/create for Flask and SQLAlchemy.",
"version": "2.2.0",
"project_urls": {
"Download": "https://github.com/Martlark/flask-serialize/archive/2.2.0.tar.gz",
"Homepage": "https://github.com/Martlark/flask-serialize"
},
"split_keywords": [
"flask",
"sqlalchemy",
"serialize",
"serialization",
"serialise"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "5a52a3e1c4408193b2165b37f86d58c795302f2801716c0c0a7b7049d91a51e7",
"md5": "af5f49fae9c8526801049bc169002812",
"sha256": "f6567cdbdcdfec96b1ce22c53b8a33225d9d527bd0aebceee8ff5bf154a045a9"
},
"downloads": -1,
"filename": "flask_serialize-2.2.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "af5f49fae9c8526801049bc169002812",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 24926,
"upload_time": "2024-04-13T07:08:20",
"upload_time_iso_8601": "2024-04-13T07:08:20.355178Z",
"url": "https://files.pythonhosted.org/packages/5a/52/a3e1c4408193b2165b37f86d58c795302f2801716c0c0a7b7049d91a51e7/flask_serialize-2.2.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "35e4a94f0a42bebbd695e360c16d2b3aaed9b67cc51602c6e97ec4ae86a6f5bf",
"md5": "bd7a4439d0c43af98810b15a166dbb86",
"sha256": "65557d621782de506de94e9853d8cb526d12317775e1f63815aadb62d7751524"
},
"downloads": -1,
"filename": "flask-serialize-2.2.0.tar.gz",
"has_sig": false,
"md5_digest": "bd7a4439d0c43af98810b15a166dbb86",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 52494,
"upload_time": "2024-04-13T07:08:22",
"upload_time_iso_8601": "2024-04-13T07:08:22.443462Z",
"url": "https://files.pythonhosted.org/packages/35/e4/a94f0a42bebbd695e360c16d2b3aaed9b67cc51602c6e97ec4ae86a6f5bf/flask-serialize-2.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-13 07:08:22",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Martlark",
"github_project": "flask-serialize",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "flask",
"specs": [
[
"==",
"2.3.2"
]
]
},
{
"name": "wtforms",
"specs": [
[
"==",
"3.1.2"
]
]
},
{
"name": "SQLAlchemy",
"specs": [
[
"==",
"2.0.29"
]
]
},
{
"name": "Permissive-Dict",
"specs": [
[
"==",
"1.0.4"
]
]
},
{
"name": "flask-sqlalchemy",
"specs": [
[
"==",
"3.1.1"
]
]
},
{
"name": "flask-wtf",
"specs": [
[
"==",
"1.2.1"
]
]
},
{
"name": "setuptools",
"specs": [
[
"==",
"69.2.0"
]
]
},
{
"name": "wheel",
"specs": [
[
"==",
"0.43.0"
]
]
},
{
"name": "twine",
"specs": [
[
"==",
"5.0.0"
]
]
},
{
"name": "black",
"specs": [
[
"==",
"24.4.0"
]
]
},
{
"name": "flask-unittest",
"specs": [
[
"==",
"0.1.3"
]
]
},
{
"name": "flask-migrate",
"specs": [
[
"==",
"4.0.7"
]
]
}
],
"lcname": "flask-serialize"
}