Commit fedd6792 authored by Serge S. Koval's avatar Serge S. Koval

PK name is now scaffolded from the model. More docs.

parent 73ed6524
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
- Paginator class - Paginator class
- Custom CSS/JS in admin interface - Custom CSS/JS in admin interface
- SQLA Model Admin - SQLA Model Admin
- Do not rely on id as a primary key
- Built-in filtering support - Built-in filtering support
- Many2Many support - Many2Many support
- Verify if it is working properly - Verify if it is working properly
......
...@@ -267,6 +267,15 @@ class ModelView(BaseModelView): ...@@ -267,6 +267,15 @@ class ModelView(BaseModelView):
return self.model._sa_class_manager.mapper.iterate_properties return self.model._sa_class_manager.mapper.iterate_properties
# Scaffolding # Scaffolding
def scaffold_pk(self):
for p in self._get_model_iterator():
if hasattr(p, 'columns'):
for c in p.columns:
if c.primary_key:
return p.key
return None
def scaffold_list_columns(self): def scaffold_list_columns(self):
""" """
Return list of columns from the model. Return list of columns from the model.
......
...@@ -114,7 +114,12 @@ class BaseModelView(BaseView): ...@@ -114,7 +114,12 @@ class BaseModelView(BaseView):
""" """
Collection of the column filters. Collection of the column filters.
TBD: Doc Can contain either field names or instances of :class:`flask.ext.admin.model.filters.BaseFilter` classes.
For example:
class MyModelView(BaseModelView):
column_filters = ('user', 'email')
""" """
form_columns = None form_columns = None
...@@ -196,6 +201,12 @@ class BaseModelView(BaseView): ...@@ -196,6 +201,12 @@ class BaseModelView(BaseView):
""" """
Refresh various cached variables. Refresh various cached variables.
""" """
# Primary key
self._primary_key = self.scaffold_pk()
if self._primary_key is None:
raise Exception('Model %s does not have primary key.' % self.model.__name__)
# List view # List view
self._list_columns = self.get_list_columns() self._list_columns = self.get_list_columns()
self._sortable_columns = self.get_sortable_columns() self._sortable_columns = self.get_sortable_columns()
...@@ -219,7 +230,17 @@ class BaseModelView(BaseView): ...@@ -219,7 +230,17 @@ class BaseModelView(BaseView):
self._filter_names = None self._filter_names = None
self._filter_types = None self._filter_types = None
# Public API # Primary key
def scaffold_pk(self):
"""
Find model primary key name
"""
raise NotImplemented()
def get_pk_value(self, model):
return getattr(model, self._primary_key)
# List view
def scaffold_list_columns(self): def scaffold_list_columns(self):
""" """
Return list of the model field names. Must be implemented in Return list of the model field names. Must be implemented in
...@@ -308,6 +329,9 @@ class BaseModelView(BaseView): ...@@ -308,6 +329,9 @@ class BaseModelView(BaseView):
""" """
Verify that provided filter object is valid. Verify that provided filter object is valid.
Override in model backend implementation to verify if
provided filter type is allowed.
`filter` `filter`
Filter object to verify. Filter object to verify.
""" """
...@@ -316,6 +340,9 @@ class BaseModelView(BaseView): ...@@ -316,6 +340,9 @@ class BaseModelView(BaseView):
def get_filters(self): def get_filters(self):
""" """
Return list of filter objects. Return list of filter objects.
If your model backend implementation does not support filters,
override this method and return `None`.
""" """
if self.column_filters: if self.column_filters:
collection = [] collection = []
...@@ -330,8 +357,6 @@ class BaseModelView(BaseView): ...@@ -330,8 +357,6 @@ class BaseModelView(BaseView):
else: else:
collection.append(n) collection.append(n)
print collection
return collection return collection
else: else:
return None return None
...@@ -504,8 +529,10 @@ class BaseModelView(BaseView): ...@@ -504,8 +529,10 @@ class BaseModelView(BaseView):
value = request.args.get(param + 'v', None) value = request.args.get(param + 'v', None)
if idx >= 0 and idx < len(self._filters): if idx >= 0 and idx < len(self._filters):
if self._filters[idx].validate(value): flt = self._filters[idx]
filters.append((idx, value))
if flt.validate(value):
filters.append((idx, flt.clean(value)))
else: else:
filters = None filters = None
...@@ -610,6 +637,7 @@ class BaseModelView(BaseView): ...@@ -610,6 +637,7 @@ class BaseModelView(BaseView):
sortable_columns=self._sortable_columns, sortable_columns=self._sortable_columns,
# Stuff # Stuff
enumerate=enumerate, enumerate=enumerate,
gey_pk_value=self.get_pk_value,
get_value=get_value, get_value=get_value,
return_url=self._get_url('.index_view', return_url=self._get_url('.index_view',
page, page,
......
class BaseFilter(object): class BaseFilter(object):
"""
Base filter class.
"""
def __init__(self, name, options=None, data_type=None): def __init__(self, name, options=None, data_type=None):
"""
Constructor.
`name`
Displayed name
`options`
List of fixed options. If provided, will use drop down instead of textbox.
`data_type`
Client-side widget type to use.
"""
self.name = name self.name = name
self.options = options self.options = options
self.data_type = data_type self.data_type = data_type
def get_options(self, view): def get_options(self, view):
"""
Return list of predefined options.
Override to customize behavior.
`view`
Associated administrative view class.
"""
return self.options return self.options
def validate(self, value): def validate(self, value):
"""
Validate value.
If value is valid, returns `True` and `False` otherwise.
`value`
Value to validate
"""
return True return True
def clean(self, value):
"""
Parse value into python format.
`value`
Value to parse
"""
return value
def apply(self, query): def apply(self, query):
"""
Apply search criteria to the query and return new query.
`query`
Query
"""
raise NotImplemented() raise NotImplemented()
def __unicode__(self): def __unicode__(self):
...@@ -19,6 +63,9 @@ class BaseFilter(object): ...@@ -19,6 +63,9 @@ class BaseFilter(object):
# Customized filters # Customized filters
class BaseBooleanFilter(BaseFilter): class BaseBooleanFilter(BaseFilter):
"""
Base boolean filter, uses fixed list of options.
"""
def __init__(self, name, data_type=None): def __init__(self, name, data_type=None):
super(BaseBooleanFilter, self).__init__(name, super(BaseBooleanFilter, self).__init__(name,
(('1', 'Yes'), ('0', 'No')), (('1', 'Yes'), ('0', 'No')),
...@@ -29,6 +76,9 @@ class BaseBooleanFilter(BaseFilter): ...@@ -29,6 +76,9 @@ class BaseBooleanFilter(BaseFilter):
class BaseDateFilter(BaseFilter): class BaseDateFilter(BaseFilter):
"""
Base Date filter. Uses client-side date picker control.
"""
def __init__(self, name, options=None): def __init__(self, name, options=None):
super(BaseDateFilter, self).__init__(name, super(BaseDateFilter, self).__init__(name,
options, options,
...@@ -40,6 +90,9 @@ class BaseDateFilter(BaseFilter): ...@@ -40,6 +90,9 @@ class BaseDateFilter(BaseFilter):
class BaseDateTimeFilter(BaseFilter): class BaseDateTimeFilter(BaseFilter):
"""
Base DateTime filter. Uses client-side date picker control.
"""
def __init__(self, name, options=None): def __init__(self, name, options=None):
super(BaseDateTimeFilter, self).__init__(name, super(BaseDateTimeFilter, self).__init__(name,
options, options,
...@@ -51,6 +104,11 @@ class BaseDateTimeFilter(BaseFilter): ...@@ -51,6 +104,11 @@ class BaseDateTimeFilter(BaseFilter):
def convert(*args): def convert(*args):
"""
Decorator for field to filter conversion routine.
See :mod:`flask.ext.adminex.ext.sqlamodel.filters` for usage example.
"""
def _inner(func): def _inner(func):
print args print args
func._converter_for = args func._converter_for = args
...@@ -59,6 +117,12 @@ def convert(*args): ...@@ -59,6 +117,12 @@ def convert(*args):
class BaseFilterConverter(object): class BaseFilterConverter(object):
"""
Base filter converter.
Derive from this class to implement custom field to filter conversion
logic.
"""
def __init__(self): def __init__(self):
self.converters = dict() self.converters = dict()
......
...@@ -89,12 +89,12 @@ ...@@ -89,12 +89,12 @@
<tr> <tr>
<td> <td>
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ url_for('.edit_view', id=row.id, url=return_url) }}"> <a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}">
<i class="icon-pencil"></i> <i class="icon-pencil"></i>
</a> </a>
{%- endif -%} {%- endif -%}
{%- if admin_view.can_delete -%} {%- if admin_view.can_delete -%}
<form class="icon" method="POST" action="{{ url_for('.delete_view', id=row.id, url=return_url) }}"> <form class="icon" method="POST" action="{{ url_for('.delete_view', id=get_pk_value(row), url=return_url) }}">
<button onclick="return confirm('You sure you want to delete this item?')"> <button onclick="return confirm('You sure you want to delete this item?')">
<i class="icon-remove"></i> <i class="icon-remove"></i>
</button> </button>
......
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