Commit 80b8377d authored by Serge S. Koval's avatar Serge S. Koval

Refactoring, column renaming.

parent 4f019346
- Core - Core
- Pregenerate URLs for menu - Pregenerate URLs for menu
- Override base URL (/admin/) - Override base URL (/admin/)
- Model admin - Model Admin
- Ability to override sortable fields Ability to sort by fields that are not visible?
- SQLA Model Admin - SQLA Model Admin
- Sort by foreign key - Sort by foreign key
- Validation of the joins in the query - Validation of the joins in the query
- Automatic joined load for foreign keys - Automatic joined load for foreign keys
- Automatic PK detection - Automatic PK detection
- Ability to override displayed form fields
- Ability to rename list columns
- Ability to change form without messing with form creation
- Filtering - Filtering
- Many2Many editing - Many2Many editing
- Many2One editor - One2Many editor
- File admin - File admin
- Documentation - Documentation
- Examples - Examples
......
...@@ -49,7 +49,8 @@ def index(): ...@@ -49,7 +49,8 @@ def index():
class PostAdmin(sqlamodel.ModelView): class PostAdmin(sqlamodel.ModelView):
list_columns = ('title', 'user') list_columns = ('title', 'user')
sortable_columns = dict(title='title', user=User.username) sortable_columns = ('title', ('user', User.username))
rename_columns = dict(title='Tiiitle')
def __init__(self, session): def __init__(self, session):
super(PostAdmin, self).__init__(Post, session) super(PostAdmin, self).__init__(Post, session)
......
...@@ -71,7 +71,7 @@ class ModelView(BaseModelView): ...@@ -71,7 +71,7 @@ class ModelView(BaseModelView):
if column.foreign_keys or column.primary_key: if column.foreign_keys or column.primary_key:
continue continue
columns.append((p.key, self.prettify_name(p.key))) columns.append(p.key)
return columns return columns
...@@ -81,11 +81,7 @@ class ModelView(BaseModelView): ...@@ -81,11 +81,7 @@ class ModelView(BaseModelView):
mapper = self.model._sa_class_manager.mapper mapper = self.model._sa_class_manager.mapper
for p in mapper.iterate_properties: for p in mapper.iterate_properties:
if isinstance(p, RelationshipProperty): if isinstance(p, ColumnProperty):
if p.direction is MANYTOONE:
# TODO: Detect PK
columns[p.key] = '%s.id' % p.target.name
elif isinstance(p, ColumnProperty):
# TODO: Check for multiple columns # TODO: Check for multiple columns
column = p.columns[0] column = p.columns[0]
...@@ -103,22 +99,20 @@ class ModelView(BaseModelView): ...@@ -103,22 +99,20 @@ class ModelView(BaseModelView):
converter=AdminModelConverter(self.session)) converter=AdminModelConverter(self.session))
# Database-related API # Database-related API
def get_list(self, page, sort_column, sort_desc): def get_list(self, page, sort_column, sort_desc, execute=True):
query = self.session.query(self.model) query = self.session.query(self.model)
count = query.count() count = query.count()
# Sorting # Sorting
column = self._get_column_by_idx(sort_column) if sort_column is not None:
if column is not None: if sort_column in self._sortable_columns:
name = column[0] sort_field = self._sortable_columns[sort_column]
if name in self._sortable_columns:
sort_field = self._sortable_columns[name]
# Try to handle it as a string # Try to handle it as a string
if isinstance(sort_field, basestring): if isinstance(sort_field, basestring):
# Create automatic join if string contains dot # Create automatic join against a table if column name
# contains dot.
if '.' in sort_field: if '.' in sort_field:
parts = sort_field.split('.', 1) parts = sort_field.split('.', 1)
query = query.join(parts[0]) query = query.join(parts[0])
...@@ -139,7 +133,10 @@ class ModelView(BaseModelView): ...@@ -139,7 +133,10 @@ class ModelView(BaseModelView):
query = query.limit(self.page_size) query = query.limit(self.page_size)
return count, query.all() if execute:
query = query.all()
return count, query
def get_one(self, id): def get_one(self, id):
return self.session.query(self.model).get(id) return self.session.query(self.model).get(id)
......
...@@ -50,26 +50,39 @@ class BaseModelView(BaseView): ...@@ -50,26 +50,39 @@ class BaseModelView(BaseView):
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
list_columns = ('name', 'last_name', 'email') list_columns = ('name', 'last_name', 'email')
"""
rename_columns = None
"""
Dictionary where key is column name and value is string to display.
If you want to rename column, use tuple instead of the name, For example::
where first value is field name and second is display name.
You can also mix these values::
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
list_columns = (('name', 'First Name'), rename_columns = dict(name='Name', last_name='Last Name')
('last_name', 'Family Name'),
'email')
""" """
sortable_columns = None sortable_columns = None
""" """
Dictionary of the sortable columns names and property references. Collection of the sortable columns for the list view.
If set to `None`, will get them from the model. If set to `None`, will get them from the model.
For example:: For example::
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
sortable_columns = dict(name='name', user='user.id') sortable_columns = ('name', 'last_name')
If you want to explicitly specify field/column to be used while
sorting, you can use tuple::
class MyModelView(BaseModelView):
sortable_columns = ('name', ('user', 'user.username'))
For SQLAlchemy models, you can pass attribute instead of the string
too::
class MyModelView(BaseModelView):
sortable_columns = ('name', ('user', User.username))
""" """
form_columns = None form_columns = None
...@@ -136,9 +149,7 @@ class BaseModelView(BaseView): ...@@ -136,9 +149,7 @@ class BaseModelView(BaseView):
Expected return format is list of tuples with field name and Expected return format is list of tuples with field name and
display text. For example:: display text. For example::
[('name', 'Name'), ['name', 'first_name', 'last_name']
('email', 'Email'),
('last_name', 'Last Name')]
""" """
raise NotImplemented('Please implement scaffold_list_columns method') raise NotImplemented('Please implement scaffold_list_columns method')
...@@ -148,18 +159,20 @@ class BaseModelView(BaseView): ...@@ -148,18 +159,20 @@ class BaseModelView(BaseView):
set, returns it. Otherwise calls `scaffold_list_columns` set, returns it. Otherwise calls `scaffold_list_columns`
to generate list from the model. to generate list from the model.
""" """
result = []
if self.list_columns is None: if self.list_columns is None:
columns = self.scaffold_list_columns() columns = self.scaffold_list_columns()
else: else:
columns = [] columns = self.list_columns
for c in self.list_columns: for c in columns:
if not isinstance(c, tuple): if self.rename_columns and c in self.rename_columns:
columns.append((c, self.prettify_name(c))) result.append((c, self.rename_columns[c]))
else: else:
columns.append(c) result.append((c, self.prettify_name(c)))
return columns return result
def scaffold_sortable_columns(self): def scaffold_sortable_columns(self):
""" """
...@@ -180,10 +193,17 @@ class BaseModelView(BaseView): ...@@ -180,10 +193,17 @@ class BaseModelView(BaseView):
`scaffold_sortable_columns` to get them from the model. `scaffold_sortable_columns` to get them from the model.
""" """
if self.sortable_columns is None: if self.sortable_columns is None:
print self.__class__.__name__
return self.scaffold_sortable_columns() return self.scaffold_sortable_columns()
else: else:
return self.sortable_columns result = dict()
for c in self.sortable_columns:
if isinstance(c, tuple):
result[c[0]] = c[1]
else:
result[c] = c
return result
def scaffold_form(self): def scaffold_form(self):
""" """
...@@ -226,9 +246,18 @@ class BaseModelView(BaseView): ...@@ -226,9 +246,18 @@ class BaseModelView(BaseView):
# Helpers # Helpers
def is_sortable(self, name): def is_sortable(self, name):
"""
Verify if column is sortable.
`name`
Column name.
"""
return name in self._sortable_columns return name in self._sortable_columns
def _get_column_by_idx(self, idx): def _get_column_by_idx(self, idx):
"""
Return column index by
"""
if idx is None or idx < 0 or idx >= len(self._list_columns): if idx is None or idx < 0 or idx >= len(self._list_columns):
return None return None
...@@ -245,31 +274,82 @@ class BaseModelView(BaseView): ...@@ -245,31 +274,82 @@ class BaseModelView(BaseView):
`page` `page`
Page number, 0 based. Can be set to None if it is first page. Page number, 0 based. Can be set to None if it is first page.
`sort_field` `sort_field`
Sort field index in the `self.list_columns` or None. Sort column name or None.
`sort_desc` `sort_desc`
If set to True, sorting is in descending order. If set to True, sorting is in descending order.
""" """
raise NotImplemented('Please implement get_list method') raise NotImplemented('Please implement get_list method')
def get_one(self, id): def get_one(self, id):
"""
Return one model by its id.
Must be implemented in the child class.
`id`
Model id
"""
raise NotImplemented('Please implement get_one method') raise NotImplemented('Please implement get_one method')
# Model handlers # Model handlers
def create_model(self, form): def create_model(self, form):
"""
Create model from the form.
Returns `True` if operation succeeded.
Must be implemented in the child class.
`form`
Form instance
"""
raise NotImplemented() raise NotImplemented()
def update_model(self, form, model): def update_model(self, form, model):
"""
Update model from the form.
Returns `True` if operation succeeded.
Must be implemented in the child class.
`form`
Form instance
`model`
Model instance
"""
raise NotImplemented() raise NotImplemented()
def delete_model(self, model): def delete_model(self, model):
"""
Delete model.
Returns `True` if operation succeeded.
Must be implemented in the child class.
`model`
Model instance
"""
raise NotImplemented() raise NotImplemented()
# Various helpers # Various helpers
def prettify_name(self, name): def prettify_name(self, name):
"""
Prettify pythonic variable name.
For example, 'hello_world' will be converted to 'Hello World'
`name`
Name to prettify
"""
return ' '.join(x.capitalize() for x in name.split('_')) return ' '.join(x.capitalize() for x in name.split('_'))
# URL generation helper # URL generation helper
def _get_extra_args(self): def _get_extra_args(self):
"""
Return arguments from query string.
"""
page = request.args.get('page', 0, type=int) page = request.args.get('page', 0, type=int)
sort = request.args.get('sort', None, type=int) sort = request.args.get('sort', None, type=int)
sort_desc = request.args.get('desc', None, type=int) sort_desc = request.args.get('desc', None, type=int)
...@@ -277,16 +357,37 @@ class BaseModelView(BaseView): ...@@ -277,16 +357,37 @@ class BaseModelView(BaseView):
return page, sort, sort_desc return page, sort, sort_desc
def _get_url(self, view, page, sort, sort_desc): def _get_url(self, view, page, sort, sort_desc):
"""
Generate page URL with current page, sort column and
other parameters.
`view`
View name
`page`
Page number
`sort`
Sort column index
`sort_desc`
Use descending sorting order
"""
return url_for(view, page=page, sort=sort, desc=sort_desc) return url_for(view, page=page, sort=sort, desc=sort_desc)
# Views # Views
@expose('/') @expose('/')
def index_view(self): def index_view(self):
"""
List view
"""
# Grab parameters from URL # Grab parameters from URL
page, sort, sort_desc = self._get_extra_args() page, sort_idx, sort_desc = self._get_extra_args()
# Map column index to column name
sort_column = self._get_column_by_idx(sort_idx)
if sort_column is not None:
sort_column = sort_column[0]
# Get count and data # Get count and data
count, data = self.get_list(page, sort, sort_desc) count, data = self.get_list(page, sort_column, sort_desc)
# Calculate number of pages # Calculate number of pages
num_pages = count / self.page_size num_pages = count / self.page_size
...@@ -299,7 +400,7 @@ class BaseModelView(BaseView): ...@@ -299,7 +400,7 @@ class BaseModelView(BaseView):
if p == 0: if p == 0:
p = None p = None
return self._get_url('.index_view', p, sort, sort_desc) return self._get_url('.index_view', p, sort_idx, sort_desc)
def sort_url(column, invert=False): def sort_url(column, invert=False):
desc = None desc = None
...@@ -320,13 +421,13 @@ class BaseModelView(BaseView): ...@@ -320,13 +421,13 @@ class BaseModelView(BaseView):
sortable_columns=self._sortable_columns, sortable_columns=self._sortable_columns,
# Stuff # Stuff
get_value=get_value, get_value=get_value,
return_url=self._get_url('.index_view', page, sort, sort_desc), return_url=self._get_url('.index_view', page, sort_idx, sort_desc),
# Pagination # Pagination
pager_url=pager_url, pager_url=pager_url,
num_pages=num_pages, num_pages=num_pages,
page=page, page=page,
# Sorting # Sorting
sort_column=sort, sort_column=sort_idx,
sort_desc=sort_desc, sort_desc=sort_desc,
sort_url=sort_url sort_url=sort_url
) )
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
{% set column = 0 %} {% set column = 0 %}
{% for c, name in list_columns %} {% for c, name in list_columns %}
<th> <th>
{% if view.is_sortable(c) %}
{% if sort_column == column %} {% if sort_column == column %}
<a href="{{ sort_url(column, True) }}"> <a href="{{ sort_url(column, True) }}">
{{ name }} {{ name }}
...@@ -21,6 +22,9 @@ ...@@ -21,6 +22,9 @@
{% else %} {% else %}
<a href="{{ sort_url(column) }}">{{ name }}</a> <a href="{{ sort_url(column) }}">{{ name }}</a>
{% endif %} {% endif %}
{% else %}
{{ name }}
{% endif %}
</th> </th>
{% set column = column + 1 %} {% set column = column + 1 %}
{% endfor %} {% endfor %}
......
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