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 @@
- Paginator class
- Custom CSS/JS in admin interface
- SQLA Model Admin
- Do not rely on id as a primary key
- Built-in filtering support
- Many2Many support
- Verify if it is working properly
......
......@@ -267,6 +267,15 @@ class ModelView(BaseModelView):
return self.model._sa_class_manager.mapper.iterate_properties
# 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):
"""
Return list of columns from the model.
......
......@@ -114,7 +114,12 @@ class BaseModelView(BaseView):
"""
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
......@@ -196,6 +201,12 @@ class BaseModelView(BaseView):
"""
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
self._list_columns = self.get_list_columns()
self._sortable_columns = self.get_sortable_columns()
......@@ -219,7 +230,17 @@ class BaseModelView(BaseView):
self._filter_names = 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):
"""
Return list of the model field names. Must be implemented in
......@@ -308,6 +329,9 @@ class BaseModelView(BaseView):
"""
Verify that provided filter object is valid.
Override in model backend implementation to verify if
provided filter type is allowed.
`filter`
Filter object to verify.
"""
......@@ -316,6 +340,9 @@ class BaseModelView(BaseView):
def get_filters(self):
"""
Return list of filter objects.
If your model backend implementation does not support filters,
override this method and return `None`.
"""
if self.column_filters:
collection = []
......@@ -330,8 +357,6 @@ class BaseModelView(BaseView):
else:
collection.append(n)
print collection
return collection
else:
return None
......@@ -504,8 +529,10 @@ class BaseModelView(BaseView):
value = request.args.get(param + 'v', None)
if idx >= 0 and idx < len(self._filters):
if self._filters[idx].validate(value):
filters.append((idx, value))
flt = self._filters[idx]
if flt.validate(value):
filters.append((idx, flt.clean(value)))
else:
filters = None
......@@ -610,6 +637,7 @@ class BaseModelView(BaseView):
sortable_columns=self._sortable_columns,
# Stuff
enumerate=enumerate,
gey_pk_value=self.get_pk_value,
get_value=get_value,
return_url=self._get_url('.index_view',
page,
......
class BaseFilter(object):
"""
Base filter class.
"""
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.options = options
self.data_type = data_type
def get_options(self, view):
"""
Return list of predefined options.
Override to customize behavior.
`view`
Associated administrative view class.
"""
return self.options
def validate(self, value):
"""
Validate value.
If value is valid, returns `True` and `False` otherwise.
`value`
Value to validate
"""
return True
def clean(self, value):
"""
Parse value into python format.
`value`
Value to parse
"""
return value
def apply(self, query):
"""
Apply search criteria to the query and return new query.
`query`
Query
"""
raise NotImplemented()
def __unicode__(self):
......@@ -19,6 +63,9 @@ class BaseFilter(object):
# Customized filters
class BaseBooleanFilter(BaseFilter):
"""
Base boolean filter, uses fixed list of options.
"""
def __init__(self, name, data_type=None):
super(BaseBooleanFilter, self).__init__(name,
(('1', 'Yes'), ('0', 'No')),
......@@ -29,6 +76,9 @@ class BaseBooleanFilter(BaseFilter):
class BaseDateFilter(BaseFilter):
"""
Base Date filter. Uses client-side date picker control.
"""
def __init__(self, name, options=None):
super(BaseDateFilter, self).__init__(name,
options,
......@@ -40,6 +90,9 @@ class BaseDateFilter(BaseFilter):
class BaseDateTimeFilter(BaseFilter):
"""
Base DateTime filter. Uses client-side date picker control.
"""
def __init__(self, name, options=None):
super(BaseDateTimeFilter, self).__init__(name,
options,
......@@ -51,6 +104,11 @@ class BaseDateTimeFilter(BaseFilter):
def convert(*args):
"""
Decorator for field to filter conversion routine.
See :mod:`flask.ext.adminex.ext.sqlamodel.filters` for usage example.
"""
def _inner(func):
print args
func._converter_for = args
......@@ -59,6 +117,12 @@ def convert(*args):
class BaseFilterConverter(object):
"""
Base filter converter.
Derive from this class to implement custom field to filter conversion
logic.
"""
def __init__(self):
self.converters = dict()
......
......@@ -89,12 +89,12 @@
<tr>
<td>
{%- 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>
</a>
{%- endif -%}
{%- 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?')">
<i class="icon-remove"></i>
</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