Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
e364662e
Commit
e364662e
authored
Feb 18, 2014
by
Petrus J.v.Rensburg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 'Adding a new model backend' page.
parent
0c3e9406
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
140 additions
and
144 deletions
+140
-144
model_guidelines.rst
doc/model_guidelines.rst
+140
-144
No files found.
doc/model_guidelines.rst
View file @
e364662e
Adding new model backend
Adding
a
new model backend
========================
========================
==
If you want to implement new database backend to use with model views, follow steps found in this guideline.
Flask-Admin makes a few assumptions about the database models that it works with. If you want to implement your own
database backend, and still have Flask-Admin's model views work as expected, then you should take note of the following:
There are few assumptions about models:
1. Each model must have one field which acts as a `primary key` to uniquely identify instances of that model.
However, there are no restriction on the data type or the field name of the `primary key` field.
2. Models must make their data accessible as python properties.
1. Model has "primary key" - value which uniquely identifies
If that is the case, then you can implement your own database backend by extending the `BaseModelView` class,
one model in a data store. There's no restriction on the
and implementing the set of scaffolding methods listed below.
data type or field name.
2. Model has readable python properties
3. It is possible to get list of models (optionally - sorted,
filtered, etc) from data store
4. It is possible to get one model by its primary key
Extending BaseModelView
-------------------------
Steps to add new model backend:
Start off by defining a new class, which derives from from :class:`~flask.ext.admin.model.BaseModelView`::
1. Create new class and derive it from :class:`~flask.ext.admin.model.BaseModelView`::
class MyDbModel(BaseModelView):
class MyDbModel(BaseModelView):
pass
pass
By default, all model views accept model class and it
This class inherits BaseModelView's `__init__` method, which accepts a model class as first argument. The model
will be stored as ``self.model``
.
class is stored as the attribute ``self.model`` so that other methods may access it
.
2. Implement following scaffolding method
s:
Now, implement the following scaffolding methods for the new clas
s:
-
:meth:`~flask.ext.admin.model.BaseModelView.get_pk_value`
1.
:meth:`~flask.ext.admin.model.BaseModelView.get_pk_value`
This method will return
primary key value from
This method returns a
primary key value from
the model. For example, in SQLAlchemy backend,
the model instance. In the SQLAlchemy backend, it gets the primary key from the model
it gets primary key from the model
using :meth:`~flask.ext.admin.contrib.sqla.ModelView.scaffold_pk`, caches it
using :meth:`~flask.ext.admin.contrib.sqla.ModelView.scaffold_pk`, caches it
and returns actual value from the model when
requested.
and then returns the value from the model whenever
requested.
For example::
For example::
...
@@ -39,11 +37,9 @@ Steps to add new model backend:
...
@@ -39,11 +37,9 @@ Steps to add new model backend:
def get_pk_value(self, model):
def get_pk_value(self, model):
return self.model.id
return self.model.id
- :meth:`~flask.ext.admin.model.BaseModelView.scaffold_list_columns`
2. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_list_columns`
Returns list of columns to be displayed in a list view.
For example::
Returns a list of columns to be displayed in a list view.
For example::
class MyDbModel(BaseModelView):
class MyDbModel(BaseModelView):
def scaffold_list_columns(self):
def scaffold_list_columns(self):
...
@@ -56,20 +52,19 @@ Steps to add new model backend:
...
@@ -56,20 +52,19 @@ Steps to add new model backend:
return columns
return columns
-
:meth:`~flask.ext.admin.model.BaseModelView.scaffold_sortable_columns`
3.
:meth:`~flask.ext.admin.model.BaseModelView.scaffold_sortable_columns`
Returns dictionary of sortable columns. Key in a dictionary is field name. Value - implementation
Returns a dictionary of sortable columns. The keys in the dictionary should correspond to the model's
specific, value that will be used by you backend implementation to do actual sort operation
.
field names. The values should be those variables that will be used for sorting
.
For example, in SQLAlchemy backend it is possible to
For example, in the SQLAlchemy backend it is possible to sort by a foreign key field. So, if there is a
sort by foreign key. If there's a field `user` and
field named `user`, which is a foreign key for the `Users` table, and the `Users` table also has a name
it is foreign key for a `Users` table which has a name
field, then the key will be `user` and value will be `Users.name`.
field, key will be `user` and value will be `Users.name`.
If your backend does not support sorting, return
If your backend does not support sorting, return
`None` or
empty dictionary.
`None` or an
empty dictionary.
-
:meth:`~flask.ext.admin.model.BaseModelView.init_search`
4.
:meth:`~flask.ext.admin.model.BaseModelView.init_search`
Initialize search functionality. If your backend supports
Initialize search functionality. If your backend supports
full-text search, do initializations and return `True`.
full-text search, do initializations and return `True`.
...
@@ -81,64 +76,73 @@ Steps to add new model backend:
...
@@ -81,64 +76,73 @@ Steps to add new model backend:
it will add a join, etc) and caches this information for
it will add a join, etc) and caches this information for
future use.
future use.
- :meth:`~flask.ext.admin.model.BaseModelView.is_valid_filter
`
5. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_form
`
Verify if provided object is a valid filter
.
Generate `WTForms` form class from the model
.
Each model backend should have its own set of
For example::
filter implementations. It is not possible to use
filters from SQLAlchemy models in non-SQLAlchemy backend.
This also means that different backends might have
different set of available filters.
Filter is a class derived from :class:`~flask.ext.admin.model.filters.BaseFilter` which implements at least two methods:
class MyDbModel(BaseModelView):
def scaffold_form(self):
class MyForm(Form):
pass
1. :meth:`~flask.ext.admin.model.filters.BaseFilter.apply`
# Do something
2. :meth:`~flask.ext.admin.model.filters.BaseFilter.operation`
return MyForm
`apply` method accepts two parameters: `query` object and a value from the client. Here you will add
6. :meth:`~flask.ext.admin.model.BaseModelView.get_list`
filtering logic for this filter type.
Lets take SQLAlchemy model backend as an example.
This method should return list of model instances with paging,
All SQLAlchemy filters derive from :class:`~flask.ext.admin.contrib.sqla.filters.BaseSQLAFilter` class
.
sorting, etc applied
.
Each filter implements one simple filter SQL operation
For SQLAlchemy backend it looks like:
(like, not like, greater, etc) and accepts column as
input parameter.
Whenever model view wants to apply a filter to a query
1. If search was enabled and provided search value is not empty,
object, it will call `apply` method in a filter class
generate LIKE statements for each field from `self.searchable_columns`
with a query and value. Filter will then apply
real filter operation.
For example::
2. If filter values were passed, call `apply` method
with values::
class MyBaseFilter(BaseFilter):
for flt, value in filters:
def __init__(self, column, name, options=None, data_type=None):
query = self._filters[flt].apply(query, value)
super(MyBaseFilter, self).__init__(name, options, data_type)
self.column = column
3. Execute query to get total number of rows in the
database (count)
class MyEqualFilter(MyBaseFilter):
4. If `sort_column` was passed, will do something like (with some extra FK logic which is omitted in this example)::
def apply(self, query, value):
return query.filter(self.column == value)
def operation(self):
if sort_desc:
return gettext('equals')
query = query.order_by(desc(sort_field))
else:
query = query.order_by(sort_field)
# You can validate values. If value is not valid,
5. Apply paging
# return `False`, so filter will be ignored.
def validate(self, value):
return True
# You can "clean" values before they will be
6. Return count, list as a tuple
# passed to the your data access layer
def clean(self, value):
7. :meth:`~flask.ext.admin.model.BaseModelView.get_one`
return value
Return a model instance by its primary key.
8. :meth:`~flask.ext.admin.model.BaseModelView.create_model`
Create a new instance of the model from the `Form` object.
9. :meth:`~flask.ext.admin.model.BaseModelView.update_model`
Update the model instance with data from the form.
10. :meth:`~flask.ext.admin.model.BaseModelView.delete_model`
- :meth:`~flask.ext.admin.model.BaseModelView.scaffold_filters`
Delete the specified model instance from the data store.
Return list of filter objects for one model field.
11. :meth:`~flask.ext.admin.model.BaseModelView.is_valid_filter`
Verify whether the given object is a valid filter.
12. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_filters`
Return a list of filter objects for one model field.
This method will be called once for each entry in the
This method will be called once for each entry in the
`self.column_filters` setting.
`self.column_filters` setting.
...
@@ -155,66 +159,58 @@ Steps to add new model backend:
...
@@ -155,66 +159,58 @@ Steps to add new model backend:
if isinstance(attr, MyDbTextField):
if isinstance(attr, MyDbTextField):
return [MyEqualFilter(name, name)]
return [MyEqualFilter(name, name)]
- :meth:`~flask.ext.admin.model.BaseModelView.scaffold_form`
Implementing filters
--------------------
Generate `WTForms` form class from the model.
For example::
class MyDbModel(BaseModelView):
def scaffold_form(self):
class MyForm(Form):
pass
# Do something
Each model backend should have its own set of filter implementations. It is not possible to use the
return MyForm
filters from SQLAlchemy models in a non-SQLAlchemy backend.
This also means that different backends might have different set of available filters.
- :meth:`~flask.ext.admin.model.BaseModelView.get_list`
The filter is a class derived from :class:`~flask.ext.admin.model.filters.BaseFilter` which implements at least two methods:
This method should return list of models with paging,
sorting, etc applied.
For SQLAlchemy backend it looks like:
1. :meth:`~flask.ext.admin.model.filters.BaseFilter.apply`
2. :meth:`~flask.ext.admin.model.filters.BaseFilter.operation`
1. If search was enabled and provided search value is not empty,
generate LIKE statements for each field from `self.searchable_columns`
2. If filter values were passed, call `apply` method
with values::
for flt, value in filters:
query = self._filters[flt].apply(query, value)
3. Execute query to get total number of rows in the
`apply` method accepts two parameters: `query` object and a value from the client. Here you can add
database (count)
filtering logic for the filter type.
4. If `sort_column` was passed, will do something like (with some extra FK logic which is omitted in this example):
:
Lets take SQLAlchemy model backend as an example
:
if sort_desc:
All SQLAlchemy filters derive from :class:`~flask.ext.admin.contrib.sqla.filters.BaseSQLAFilter` class.
query = query.order_by(desc(sort_field))
else:
query = query.order_by(sort_field)
5. Apply paging
Each filter implements one simple filter SQL operation (like, not like, greater, etc) and accepts a column as
input parameter.
6. Return count, list as a tuple
Whenever model view wants to apply a filter to a query
object, it will call `apply` method in a filter class
with a query and value. Filter will then apply
real filter operation.
- :meth:`~flask.ext.admin.model.BaseModelView.get_one`
For example::
Return one model by its primary key.
class MyBaseFilter(BaseFilter):
def __init__(self, column, name, options=None, data_type=None):
super(MyBaseFilter, self).__init__(name, options, data_type)
- :meth:`~flask.ext.admin.model.BaseModelView.create_model`
self.column = column
Create new model from the `Form` object.
class MyEqualFilter(MyBaseFilter):
def apply(self, query, value):
return query.filter(self.column == value)
- :meth:`~flask.ext.admin.model.BaseModelView.update_model`
def operation(self):
return gettext('equals')
Update provided model with the data from the form.
# You can validate values. If value is not valid,
# return `False`, so filter will be ignored.
def validate(self, value):
return True
- :meth:`~flask.ext.admin.model.BaseModelView.delete_model`
# You can "clean" values before they will be
# passed to the your data access layer
def clean(self, value):
return value
Delete provided model from the data store.
Feel free ask questions if you have problem adding new model backend.
Feel free ask questions if you have problems adding a new model backend.
Also, it is good idea to take a look on SQLAlchemy model backend to
Also, if you get stuck, try taking a look at the SQLAlchemy model backend and use it as a reference.
see how it works in different circumstances.
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment