Commit 0a239f91 authored by Serge S. Koval's avatar Serge S. Koval

Related models will use subqueryload in the sqla list view.

parent 528641b6
...@@ -13,10 +13,7 @@ ...@@ -13,10 +13,7 @@
- Custom paginator class? - Custom paginator class?
- Custom CSS/JS in admin interface - Custom CSS/JS in admin interface
- SQLA Model Admin - SQLA Model Admin
- Automatic required validator if field is not nullable
- Validation of the joins in the query - Validation of the joins in the query
- Automatic joined load for foreign keys
- Through the hint
- Built-in filtering support - Built-in filtering support
- Many2Many support - Many2Many support
- Verify if it is working properly - Verify if it is working properly
......
...@@ -28,9 +28,14 @@ ...@@ -28,9 +28,14 @@
.. autoattribute:: BaseModelView.form_columns .. autoattribute:: BaseModelView.form_columns
.. autoattribute:: BaseModelView.form_args .. autoattribute:: BaseModelView.form_args
.. autoattribute:: BaseModelView.page_size
SQLAlchemy-related Customizations
---------------------------------
.. autoattribute:: ModelView.hide_backrefs .. autoattribute:: ModelView.hide_backrefs
.. autoattribute:: BaseModelView.page_size .. autoattribute:: ModelView.auto_select_related
.. autoattribute:: ModelView.list_select_related
Constructor Constructor
----------- -----------
...@@ -83,3 +88,4 @@ ...@@ -83,3 +88,4 @@
------------ ------------
.. automethod:: ModelView._get_url .. automethod:: ModelView._get_url
.. automethod:: ModelView.scaffold_auto_joins
\ No newline at end of file
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
.. automodule:: flask.ext.adminex.form .. automodule:: flask.ext.adminex.form
.. autoclass:: AdminForm .. autoclass:: BaseForm
.. autoattribute:: has_file_field .. autoattribute:: has_file_field
from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm import subqueryload
from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import desc
from wtforms import ValidationError, fields, validators from wtforms import ValidationError, fields, validators
...@@ -8,7 +9,8 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleF ...@@ -8,7 +9,8 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleF
from flask import flash from flask import flash
from flask.ext.adminex import model, form from flask.ext.adminex import form
from flask.ext.adminex.model import BaseModelView
class Unique(object): class Unique(object):
...@@ -141,7 +143,7 @@ class AdminModelConverter(ModelConverter): ...@@ -141,7 +143,7 @@ class AdminModelConverter(ModelConverter):
return form.TimeField(**field_args) return form.TimeField(**field_args)
class ModelView(model.BaseModelView): class ModelView(BaseModelView):
""" """
SQLALchemy model view SQLALchemy model view
...@@ -156,6 +158,35 @@ class ModelView(model.BaseModelView): ...@@ -156,6 +158,35 @@ class ModelView(model.BaseModelView):
Set this to False if you want to see multiselect for model backrefs. Set this to False if you want to see multiselect for model backrefs.
""" """
auto_select_related = True
"""
Enable automatic detection of displayed foreign keys in this view
and perform automatic joined loading for related models to improve
query performance.
Please note that detection is not recursive: if `__unicode__` method
of related model uses another model to generate string representation, it
will still make separate database call.
"""
list_select_related = None
"""
List of parameters for SQLAlchemy `subqueryload`. Overrides `auto_select_related`
property.
For example::
class PostAdmin(ModelAdmin):
list_select_related = ('user', 'city')
You can also use properties::
class PostAdmin(ModelAdmin):
list_select_related = (Post.user, Post.city)
Please refer to the `subqueryload` on list of possible values.
"""
def __init__(self, model, session, def __init__(self, model, session,
name=None, category=None, endpoint=None, url=None): name=None, category=None, endpoint=None, url=None):
""" """
...@@ -178,6 +209,16 @@ class ModelView(model.BaseModelView): ...@@ -178,6 +209,16 @@ class ModelView(model.BaseModelView):
super(ModelView, self).__init__(model, name, category, endpoint, url) super(ModelView, self).__init__(model, name, category, endpoint, url)
# Configuration
if not self.list_select_related:
self._auto_joins = self.scaffold_auto_joins()
else:
self._auto_joins = self.list_select_related
# Internal API
def _get_model_iterator(self):
return self.model._sa_class_manager.mapper.iterate_properties
# Scaffolding # Scaffolding
def scaffold_list_columns(self): def scaffold_list_columns(self):
""" """
...@@ -185,9 +226,7 @@ class ModelView(model.BaseModelView): ...@@ -185,9 +226,7 @@ class ModelView(model.BaseModelView):
""" """
columns = [] columns = []
mapper = self.model._sa_class_manager.mapper for p in self._get_model_iterator():
for p in mapper.iterate_properties:
if hasattr(p, 'direction'): if hasattr(p, 'direction'):
if p.direction.name == 'MANYTOONE': if p.direction.name == 'MANYTOONE':
columns.append(p.key) columns.append(p.key)
...@@ -209,9 +248,7 @@ class ModelView(model.BaseModelView): ...@@ -209,9 +248,7 @@ class ModelView(model.BaseModelView):
""" """
columns = dict() columns = dict()
mapper = self.model._sa_class_manager.mapper for p in self._get_model_iterator():
for p in mapper.iterate_properties:
if hasattr(p, 'columns'): if hasattr(p, 'columns'):
# Sanity check # Sanity check
if len(p.columns) > 1: if len(p.columns) > 1:
...@@ -239,6 +276,26 @@ class ModelView(model.BaseModelView): ...@@ -239,6 +276,26 @@ class ModelView(model.BaseModelView):
field_args=self.form_args, field_args=self.form_args,
converter=AdminModelConverter(self)) converter=AdminModelConverter(self))
def scaffold_auto_joins(self):
"""
Return list of joined tables by going through the
displayed columns.
"""
relations = set()
for p in self._get_model_iterator():
if hasattr(p, 'direction'):
if p.direction.name == 'MANYTOONE':
relations.add(p.key)
joined = []
for prop, name in self._list_columns:
if prop in relations:
joined.append(getattr(self.model, prop))
return joined
# Database-related API # Database-related API
def get_list(self, page, sort_column, sort_desc, execute=True): def get_list(self, page, sort_column, sort_desc, execute=True):
""" """
...@@ -257,6 +314,10 @@ class ModelView(model.BaseModelView): ...@@ -257,6 +314,10 @@ class ModelView(model.BaseModelView):
count = query.count() count = query.count()
# Auto join
for j in self._auto_joins:
query = query.options(subqueryload(j))
# Sorting # Sorting
if sort_column is not None: if sort_column is not None:
if sort_column in self._sortable_columns: if sort_column in self._sortable_columns:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment