summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2023-05-29 10:30:46 +0000
committerCoprDistGit <infra@openeuler.org>2023-05-29 10:30:46 +0000
commit88f5a3f4d3cd560dcafc7b46178ae0ff34e9f117 (patch)
tree3f2026b79ae2089bcc3a3cffd9fa325cefc501db
parentdbe64f4c14710bf0946e8a9454b0c571027ecedd (diff)
automatic import of python-flask-serialize
-rw-r--r--.gitignore1
-rw-r--r--python-flask-serialize.spec2992
-rw-r--r--sources1
3 files changed, 2994 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..786ab69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/flask-serialize-2.1.3.tar.gz
diff --git a/python-flask-serialize.spec b/python-flask-serialize.spec
new file mode 100644
index 0000000..99aa000
--- /dev/null
+++ b/python-flask-serialize.spec
@@ -0,0 +1,2992 @@
+%global _empty_manifest_terminate_build 0
+Name: python-flask-serialize
+Version: 2.1.3
+Release: 1
+Summary: Easy to use JSON serialization and update/create for Flask and SQLAlchemy.
+License: Apache Software License
+URL: https://github.com/Martlark/flask-serialize
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/43/3a/988cd32081270b710f6cb4ddf3e594d254ba7bcb1ff8483c958a051aad74/flask-serialize-2.1.3.tar.gz
+BuildArch: noarch
+
+Requires: python3-Permissive-Dict
+
+%description
+# 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 for certain reasons by overriding the `__fs_private_field__` method
+and returning `True` if the field is to be private.
+
+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__ = []
+```
+
+## 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)`
+
+
+```python
+def __fs_after_commit__(self, create=False):
+```
+
+Hook to call after any `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` has been called so that
+you do what you like. `self` is the updated or created (create==True) item.
+
+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.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.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 get_put_post_delete 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
+
+
+%package -n python3-flask-serialize
+Summary: Easy to use JSON serialization and update/create for Flask and SQLAlchemy.
+Provides: python-flask-serialize
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-flask-serialize
+# 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 for certain reasons by overriding the `__fs_private_field__` method
+and returning `True` if the field is to be private.
+
+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__ = []
+```
+
+## 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)`
+
+
+```python
+def __fs_after_commit__(self, create=False):
+```
+
+Hook to call after any `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` has been called so that
+you do what you like. `self` is the updated or created (create==True) item.
+
+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.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.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 get_put_post_delete 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
+
+
+%package help
+Summary: Development documents and examples for flask-serialize
+Provides: python3-flask-serialize-doc
+%description help
+# 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 for certain reasons by overriding the `__fs_private_field__` method
+and returning `True` if the field is to be private.
+
+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__ = []
+```
+
+## 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)`
+
+
+```python
+def __fs_after_commit__(self, create=False):
+```
+
+Hook to call after any `fs_update_from_dict`, `fs_request_update_form`, `fs_request_update_json` has been called so that
+you do what you like. `self` is the updated or created (create==True) item.
+
+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.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.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 get_put_post_delete 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
+
+
+%prep
+%autosetup -n flask-serialize-2.1.3
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-flask-serialize -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Mon May 29 2023 Python_Bot <Python_Bot@openeuler.org> - 2.1.3-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..b17a671
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+0484028272494bdefbadeeadc739d7e4 flask-serialize-2.1.3.tar.gz