Commit 6f4ef689 authored by Paul Brown's avatar Paul Brown

simplify editable list view, fix list view multiple select

parent d7df4fe4
......@@ -5,8 +5,7 @@ from flask import request, flash, abort, Response
from flask_admin import expose
from flask_admin.babel import gettext, ngettext, lazy_gettext
from flask_admin.model import BaseModelView
from flask_admin.model.form import wrap_fields_in_fieldlist
from flask_admin.model.fields import ListEditableFieldList
from flask_admin.model.form import create_editable_list_form
from flask_admin._compat import iteritems, string_types
import mongoengine
......@@ -426,17 +425,16 @@ class ModelView(BaseModelView):
return form_class
def scaffold_list_form(self, custom_fieldlist=ListEditableFieldList,
validators=None):
def scaffold_list_form(self, widget=None, validators=None):
"""
Create form for the `index_view` using only the columns from
`self.column_editable_list`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
:param validators:
`form_args` dict with only validators
{'name': {'validators': [required()]}}
:param custom_fieldlist:
A WTForm FieldList class. By default, `ListEditableFieldList`.
"""
form_class = get_form(self.model,
self.model_form_converter(self),
......@@ -444,9 +442,8 @@ class ModelView(BaseModelView):
only=self.column_editable_list,
field_args=validators)
return wrap_fields_in_fieldlist(self.form_base_class,
form_class,
custom_fieldlist)
return create_editable_list_form(self.form_base_class, form_class,
widget)
# AJAX foreignkey support
def _create_ajax_loader(self, name, opts):
......
......@@ -5,8 +5,7 @@ from flask import flash
from flask_admin._compat import string_types, iteritems
from flask_admin.babel import gettext, ngettext, lazy_gettext
from flask_admin.model import BaseModelView
from flask_admin.model.form import wrap_fields_in_fieldlist
from flask_admin.model.fields import ListEditableFieldList
from flask_admin.model.form import create_editable_list_form
from peewee import PrimaryKeyField, ForeignKeyField, Field, CharField, TextField
......@@ -265,26 +264,24 @@ class ModelView(BaseModelView):
return form_class
def scaffold_list_form(self, custom_fieldlist=ListEditableFieldList,
validators=None):
def scaffold_list_form(self, widget=None, validators=None):
"""
Create form for the `index_view` using only the columns from
`self.column_editable_list`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
:param validators:
`form_args` dict with only validators
{'name': {'validators': [required()]}}
:param custom_fieldlist:
A WTForm FieldList class. By default, `ListEditableFieldList`.
"""
form_class = get_form(self.model, self.model_form_converter(self),
base_class=self.form_base_class,
only=self.column_editable_list,
field_args=validators)
return wrap_fields_in_fieldlist(self.form_base_class,
form_class,
custom_fieldlist)
return create_editable_list_form(self.form_base_class, form_class,
widget)
def scaffold_inline_form_models(self, form_class):
converter = self.model_form_converter(self)
......
......@@ -14,7 +14,7 @@ except ImportError:
from .tools import get_primary_key
from flask_admin._compat import text_type, string_types, iteritems
from flask_admin.form import FormOpts, BaseForm
from flask_admin.form import FormOpts, BaseForm, Select2Widget
from flask_admin.model.fields import InlineFieldList, InlineModelFormField
from flask_admin.babel import lazy_gettext
......@@ -55,7 +55,7 @@ class QuerySelectField(SelectFieldBase):
being `None`. The label for this blank choice can be set by specifying the
`blank_text` parameter.
"""
widget = widgets.Select()
widget = Select2Widget()
def __init__(self, label=None, validators=None, query_factory=None,
get_pk=None, get_label=None, allow_blank=False,
......@@ -136,7 +136,7 @@ class QuerySelectMultipleField(QuerySelectField):
If any of the items in the data list or submitted form data cannot be
found in the query, this will result in a validation error.
"""
widget = widgets.Select(multiple=True)
widget = Select2Widget(multiple=True)
def __init__(self, label=None, validators=None, default=None, **kwargs):
if default is None:
......
......@@ -80,12 +80,6 @@ class AdminModelConverter(ModelConverterBase):
if 'query_factory' not in kwargs:
kwargs['query_factory'] = lambda: self.session.query(remote_model)
if 'widget' not in kwargs:
if multiple:
kwargs['widget'] = form.Select2Widget(multiple=True)
else:
kwargs['widget'] = form.Select2Widget()
if multiple:
return QuerySelectMultipleField(**kwargs)
else:
......
......@@ -15,9 +15,7 @@ from flask import flash
from flask_admin._compat import string_types, text_type
from flask_admin.babel import gettext, ngettext, lazy_gettext
from flask_admin.model import BaseModelView
from flask_admin.model.form import wrap_fields_in_fieldlist
from flask_admin.model.fields import ListEditableFieldList
from flask_admin.model.form import create_editable_list_form
from flask_admin.actions import action
from flask_admin._backwards import ObsoleteAttr
......@@ -675,17 +673,16 @@ class ModelView(BaseModelView):
return form_class
def scaffold_list_form(self, custom_fieldlist=ListEditableFieldList,
validators=None):
def scaffold_list_form(self, widget=None, validators=None):
"""
Create form for the `index_view` using only the columns from
`self.column_editable_list`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
:param validators:
`form_args` dict with only validators
{'name': {'validators': [required()]}}
:param custom_fieldlist:
A WTForm FieldList class. By default, `ListEditableFieldList`.
"""
converter = self.model_form_converter(self.session, self)
form_class = form.get_form(self.model, converter,
......@@ -693,9 +690,8 @@ class ModelView(BaseModelView):
only=self.column_editable_list,
field_args=validators)
return wrap_fields_in_fieldlist(self.form_base_class,
form_class,
custom_fieldlist)
return create_editable_list_form(self.form_base_class, form_class,
widget)
def scaffold_inline_form_models(self, form_class):
"""
......
......@@ -26,7 +26,6 @@ from flask_admin._compat import (iteritems, itervalues, OrderedDict,
as_unicode, csv_encode, text_type)
from .helpers import prettify_name, get_mdict_item_or_list
from .ajax import AjaxModelLoader
from .fields import ListEditableFieldList
# Used to generate filter query string name
filter_char_re = re.compile('[^a-z0-9 ]')
......@@ -1069,17 +1068,16 @@ class BaseModelView(BaseView, ActionsMixin):
"""
raise NotImplementedError('Please implement scaffold_form method')
def scaffold_list_form(self, custom_fieldlist=ListEditableFieldList,
validators=None):
def scaffold_list_form(self, widget=None, validators=None):
"""
Create form for the `index_view` using only the columns from
`self.column_editable_list`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
:param validators:
`form_args` dict with only validators
{'name': {'validators': [DataRequired()]}}
:param custom_fieldlist:
A WTForm FieldList class. By default, `ListEditableFieldList`.
Must be implemented in the child class.
"""
......@@ -1107,7 +1105,6 @@ class BaseModelView(BaseView, ActionsMixin):
Allows overriding the editable list view field/widget. For example::
from flask_admin.model.fields import ListEditableFieldList
from flask_admin.model.widgets import XEditableWidget
class CustomWidget(XEditableWidget):
......@@ -1119,12 +1116,9 @@ class BaseModelView(BaseView, ActionsMixin):
return kwargs
class CustomFieldList(ListEditableFieldList):
widget = CustomWidget()
class MyModelView(BaseModelView):
def get_list_form(self):
return self.scaffold_list_form(CustomFieldList)
return self.scaffold_list_form(widget=CustomWidget)
"""
if self.form_args:
# get only validators, other form_args can break FieldList wrapper
......@@ -1716,7 +1710,7 @@ class BaseModelView(BaseView, ActionsMixin):
List view
"""
if self.column_editable_list:
form = self.list_form()
form = self.list_form
else:
form = None
......@@ -2074,24 +2068,23 @@ class BaseModelView(BaseView, ActionsMixin):
if not self.column_editable_list:
abort(404)
record = None
form = self.list_form()
# prevent validation issues due to submitting a single field
# delete all fields except the field being submitted
# delete all fields except the submitted fields and csrf token
for field in form:
# only the submitted field has a positive last_index
if getattr(field, 'last_index', 0):
record = self.get_one(str(field.last_index))
elif field.name == 'csrf_token':
if (field.name in request.form) or (field.name == 'csrf_token'):
pass
else:
form.__delitem__(field.name)
if record is None:
return gettext('Failed to update record. %(error)s', error=''), 500
if self.validate_form(form):
pk = form.list_form_pk.data
record = self.get_one(pk)
if record is None:
return gettext('Record does not exist.'), 500
if self.update_model(form, record):
# Success
return gettext('Record was successfully saved.')
......@@ -2105,6 +2098,8 @@ class BaseModelView(BaseView, ActionsMixin):
for error in field.errors:
# return validation error to x-editable
if isinstance(error, list):
return ", ".join(error), 500
return gettext('Failed to update record. %(error)s',
error=", ".join(error)), 500
else:
return error, 500
return gettext('Failed to update record. %(error)s',
error=error), 500
......@@ -10,7 +10,7 @@ except ImportError:
from flask_admin._compat import iteritems
from .widgets import (InlineFieldListWidget, InlineFormWidget,
AjaxSelect2Widget, XEditableWidget)
AjaxSelect2Widget)
class InlineFieldList(FieldList):
......@@ -126,58 +126,6 @@ class InlineModelFormField(FormField):
field.populate_obj(obj, name)
class ListEditableFieldList(FieldList):
"""
Modified FieldList to allow for alphanumeric primary keys.
Used in the editable list view.
"""
widget = XEditableWidget()
def __init__(self, *args, **kwargs):
super(ListEditableFieldList, self).__init__(*args, **kwargs)
# min_entries = 1 is required for the widget to determine the type
self.min_entries = 1
def _extract_indices(self, prefix, formdata):
offset = len(prefix) + 1
for name in formdata:
# selects only relevant field (not CSRF, other fields, etc)
if name.startswith(prefix):
# exclude offset (prefix-), remaining text is the index
yield name[offset:]
def _add_entry(self, formdata=None, data=unset_value, index=None):
assert not self.max_entries or len(self.entries) < self.max_entries, \
'You cannot have more than max_entries entries in this FieldList'
if index is None:
index = self.last_index + 1
self.last_index = index
# '%s-%s' instead of '%s-%d' to allow alphanumeric
name = '%s-%s' % (self.short_name, index)
id = '%s-%s' % (self.id, index)
# support both wtforms 1 and 2
meta = getattr(self, 'meta', None)
if meta:
field = self.unbound_field.bind(
form=None, name=name, prefix=self._prefix, id=id, _meta=meta
)
else:
field = self.unbound_field.bind(
form=None, name=name, prefix=self._prefix, id=id
)
field.process(formdata, data)
self.entries.append(field)
return field
def populate_obj(self, obj, name):
# return data from first item, instead of a list of items
setattr(obj, name, self.data.pop())
class AjaxSelectField(SelectFieldBase):
"""
Ajax Model Select Field
......
......@@ -3,7 +3,10 @@ import inspect
from flask_admin.form import BaseForm, rules
from flask_admin._compat import iteritems
from wtforms.fields import HiddenField
from wtforms.fields.core import UnboundField
from wtforms.validators import InputRequired
from .widgets import XEditableWidget
def converts(*args):
......@@ -13,7 +16,7 @@ def converts(*args):
return _inner
def wrap_fields_in_fieldlist(form_base_class, form_class, CustomFieldList):
def create_editable_list_form(form_base_class, form_class, widget=None):
"""
Create a form class with all the fields wrapped in a FieldList.
......@@ -26,20 +29,25 @@ def wrap_fields_in_fieldlist(form_base_class, form_class, CustomFieldList):
WTForms form class, by default `form_base_class` from base.
:param form_class:
WTForms form class generated by `form.get_form`.
:param CustomFieldList:
WTForms FieldList class.
By default, `CustomFieldList` is `ListEditableFieldList`.
:param widget:
WTForms widget class. Defaults to `XEditableWidget`.
"""
class FieldListForm(form_base_class):
pass
if widget is None:
widget = XEditableWidget
class ListForm(form_base_class):
list_form_pk = HiddenField(validators=[InputRequired()])
# iterate FormMeta to get unbound fields
# iterate FormMeta to get unbound fields, replace widget, copy to ListForm
for name, obj in iteritems(form_class.__dict__):
if isinstance(obj, UnboundField):
# wrap field in a WTForms FieldList
setattr(FieldListForm, name, CustomFieldList(obj))
obj.kwargs['widget'] = XEditableWidget()
setattr(ListForm, name, obj)
if name == "list_form_pk":
raise Exception('Form already has a list_form_pk column.')
return FieldListForm
return ListForm
class InlineBaseFormAdmin(object):
......
......@@ -72,7 +72,7 @@ class XEditableWidget(object):
field inside of the FieldList (StringField, IntegerField, etc).
"""
def __call__(self, field, **kwargs):
kwargs.setdefault('data-value', kwargs.pop('value', ''))
kwargs.setdefault('data-value', kwargs.pop('display_value', ''))
kwargs.setdefault('data-role', 'x-editable')
kwargs.setdefault('data-url', './ajax/update/')
......@@ -87,26 +87,23 @@ class XEditableWidget(object):
kwargs['data-csrf'] = kwargs.pop("csrf", "")
# subfield is the first entry (subfield) from FieldList (field)
subfield = field.entries[0]
kwargs = self.get_kwargs(subfield, kwargs)
kwargs = self.get_kwargs(field, kwargs)
return HTMLString(
'<a %s>%s</a>' % (html_params(**kwargs),
escape(kwargs['data-value']))
)
def get_kwargs(self, subfield, kwargs):
def get_kwargs(self, field, kwargs):
"""
Return extra kwargs based on the subfield type.
Return extra kwargs based on the field type.
"""
if subfield.type == 'StringField':
if field.type == 'StringField':
kwargs['data-type'] = 'text'
elif subfield.type == 'TextAreaField':
elif field.type == 'TextAreaField':
kwargs['data-type'] = 'textarea'
kwargs['data-rows'] = '5'
elif subfield.type == 'BooleanField':
elif field.type == 'BooleanField':
kwargs['data-type'] = 'select'
# data-source = dropdown options
kwargs['data-source'] = json.dumps([
......@@ -114,53 +111,64 @@ class XEditableWidget(object):
{'value': '1', 'text': gettext('Yes')}
])
kwargs['data-role'] = 'x-editable-boolean'
elif subfield.type == 'Select2Field':
elif field.type == 'Select2Field':
kwargs['data-type'] = 'select'
choices = [{'value': x, 'text': y} for x, y in subfield.choices]
choices = [{'value': x, 'text': y} for x, y in field.choices]
# prepend a blank field to choices if allow_blank = True
if getattr(subfield, 'allow_blank', False):
if getattr(field, 'allow_blank', False):
choices.insert(0, {'value': '__None', 'text': ''})
# json.dumps fixes issue with unicode strings not loading correctly
kwargs['data-source'] = json.dumps(choices)
elif subfield.type == 'DateField':
elif field.type == 'DateField':
kwargs['data-type'] = 'combodate'
kwargs['data-format'] = 'YYYY-MM-DD'
kwargs['data-template'] = 'YYYY-MM-DD'
elif subfield.type == 'DateTimeField':
elif field.type == 'DateTimeField':
kwargs['data-type'] = 'combodate'
kwargs['data-format'] = 'YYYY-MM-DD HH:mm:ss'
kwargs['data-template'] = 'YYYY-MM-DD HH:mm:ss'
# x-editable-combodate uses 1 minute increments
kwargs['data-role'] = 'x-editable-combodate'
elif subfield.type == 'TimeField':
elif field.type == 'TimeField':
kwargs['data-type'] = 'combodate'
kwargs['data-format'] = 'HH:mm:ss'
kwargs['data-template'] = 'HH:mm:ss'
kwargs['data-role'] = 'x-editable-combodate'
elif subfield.type == 'IntegerField':
elif field.type == 'IntegerField':
kwargs['data-type'] = 'number'
elif subfield.type in ['FloatField', 'DecimalField']:
elif field.type in ['FloatField', 'DecimalField']:
kwargs['data-type'] = 'number'
kwargs['data-step'] = 'any'
elif subfield.type in ['QuerySelectField', 'ModelSelectField']:
elif field.type in ['QuerySelectField', 'ModelSelectField',
'QuerySelectMultipleField']:
# QuerySelectField and ModelSelectField are for relations
kwargs['data-type'] = 'select'
choices = []
for choice in subfield:
selected_ids = []
for value, label, selected in field.iter_choices():
try:
choices.append({'value': text_type(choice._value()),
'text': text_type(choice.label.text)})
label = text_type(label)
except TypeError:
# unable to display text value
choices.append({'value': text_type(choice._value()),
'text': ''})
label = ''
choices.append({'value': text_type(value), 'text': label})
if selected:
selected_ids.append(value)
# blank field is already included if allow_blank
kwargs['data-source'] = json.dumps(choices)
if field.type == 'QuerySelectMultipleField':
kwargs['data-type'] = 'select2'
kwargs['data-role'] = 'x-editable-select2-multiple'
# must use id instead of text or prefilled values won't work
separator = getattr(field, 'separator', ',')
kwargs['data-value'] = separator.join(selected_ids)
else:
raise Exception('Unsupported field type: %s' % (type(subfield),))
raise Exception('Unsupported field type: %s' % (type(field),))
return kwargs
......@@ -272,11 +272,12 @@
return true;
}
// make x-editable's POST act like a normal FieldList field
// make x-editable's POST compatible with WTForms
// for x-editable, x-editable-combodate, and x-editable-boolean cases
var overrideXeditableParams = function(params) {
var newParams = {};
newParams[params.name + '-' + params.pk] = params.value;
newParams['list_form_pk'] = params.pk;
newParams[params.name] = params.value;
if ($(this).data('csrf')) {
newParams['csrf_token'] = $(this).data('csrf');
}
......@@ -451,6 +452,30 @@
}
});
return true;
case 'x-editable-select2-multiple':
$el.editable({
params: overrideXeditableParams,
ajaxOptions: {
// prevents keys with the same value from getting converted into arrays
traditional: true
},
select2: {
multiple: true
},
display: function(value) {
// override to display text instead of ids on list view
var html = [];
var data = $.fn.editableutils.itemsByValue(value, $el.data('source'), 'id');
if(data.length) {
$.each(data, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
$(this).html(html.join(', '));
} else {
$(this).empty();
}
}
});
return true;
case 'x-editable-boolean':
$el.editable({
params: overrideXeditableParams,
......
......@@ -249,5 +249,5 @@
{% if editable_columns %}
<script src="{{ admin_static.url(filename='vendor/x-editable/js/bootstrap2-editable.min.js', v='1.5.1') }}"></script>
{% endif %}
<script src="{{ admin_static.url(filename='admin/js/form.js', v='1.0.0') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js', v='1.0.1') }}"></script>
{% endmacro %}
......@@ -158,9 +158,9 @@
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% if form.csrf_token %}
{{ form[c](pk=get_pk_value(row), value=get_value(row, c), csrf=form.csrf_token._value()) }}
{{ form(obj=row)[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), value=get_value(row, c)) }}
{{ form(obj=row)[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
{% endif %}
{% else %}
{{ get_value(row, c) }}
......
......@@ -233,5 +233,5 @@
{% if editable_columns %}
<script src="{{ admin_static.url(filename='vendor/x-editable/js/bootstrap3-editable.min.js', v='1.5.1') }}"></script>
{% endif %}
<script src="{{ admin_static.url(filename='admin/js/form.js', v='1.0.0') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js', v='1.0.1') }}"></script>
{% endmacro %}
......@@ -157,9 +157,9 @@
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% if form.csrf_token %}
{{ form[c](pk=get_pk_value(row), value=get_value(row, c), csrf=form.csrf_token._value()) }}
{{ form(obj=row)[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), value=get_value(row, c)) }}
{{ form(obj=row)[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
{% endif %}
{% else %}
{{ get_value(row, c) }}
......
......@@ -148,8 +148,7 @@ def test_column_editable_list():
Model1, Model2 = create_models(db)
view = CustomModelView(Model1,
column_editable_list=[
'test1', 'datetime_field'])
column_editable_list=['test1', 'datetime_field'])
admin.add_view(view)
fill_db(Model1, Model2)
......@@ -164,7 +163,8 @@ def test_column_editable_list():
# Form - Test basic in-line edit functionality
obj1 = Model1.objects.get(test1 = 'test1_val_3')
rv = client.post('/admin/model1/ajax/update/', data={
'test1-' + str(obj1.id): 'change-success-1',
'list_form_pk': str(obj1.id),
'test1': 'change-success-1',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......@@ -177,33 +177,35 @@ def test_column_editable_list():
# Test validation error
obj2 = Model1.objects.get(test1 = 'datetime_obj1')
rv = client.post('/admin/model1/ajax/update/', data={
'datetime_field-' + str(obj2.id): 'problematic-input',
'list_form_pk': str(obj2.id),
'datetime_field': 'problematic-input',
})
eq_(rv.status_code, 500)
# Test invalid primary key
rv = client.post('/admin/model1/ajax/update/', data={
'test1-1000': 'problematic-input',
'list_form_pk': '1000',
'test1': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
# Test editing column not in column_editable_list
rv = client.post('/admin/model1/ajax/update/', data={
'test2-1': 'problematic-input',
'list_form_pk': '1',
'test2': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
ok_('problematic-input' not in data)
# Test in-line editing for relations
view = CustomModelView(Model2,
column_editable_list=[
'model1'])
view = CustomModelView(Model2, column_editable_list=['model1'])
admin.add_view(view)
obj3 = Model2.objects.get(string_field = 'string_field_val_1')
rv = client.post('/admin/model2/ajax/update/', data={
'model1-' + str(obj3.id): str(obj1.id),
'list_form_pk': str(obj3.id),
'model1': str(obj1.id),
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......
......@@ -185,9 +185,10 @@ def test_column_editable_list():
Model1, Model2 = create_models(db)
view = CustomModelView(Model1,
column_editable_list=[
'test1', 'enum_field'])
# wtf-peewee doesn't automatically add length validators for max_length
form_args = {'test1': {'validators': [validators.Length(max=20)]}}
view = CustomModelView(Model1, column_editable_list=['test1'],
form_args=form_args)
admin.add_view(view)
fill_db(Model1, Model2)
......@@ -201,7 +202,8 @@ def test_column_editable_list():
# Form - Test basic in-line edit functionality
rv = client.post('/admin/model1/ajax/update/', data={
'test1-1': 'change-success-1',
'list_form_pk': '1',
'test1': 'change-success-1',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......@@ -213,32 +215,35 @@ def test_column_editable_list():
# Test validation error
rv = client.post('/admin/model1/ajax/update/', data={
'enum_field-1': 'problematic-input',
'list_form_pk': '1',
'test1': 'longerthantwentycharacterslongerthantwentycharacterslongerthantwentycharacterslongerthantwentycharacters',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
# Test invalid primary key
rv = client.post('/admin/model1/ajax/update/', data={
'test1-1000': 'problematic-input',
'list_form_pk': '1000',
'test1': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
# Test editing column not in column_editable_list
rv = client.post('/admin/model1/ajax/update/', data={
'test2-1': 'problematic-input',
'list_form_pk': '1',
'test2': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
ok_('problematic-input' not in data)
# Test in-line editing for relations
view = CustomModelView(Model2,
column_editable_list=[
'model1'])
view = CustomModelView(Model2, column_editable_list=['model1'])
admin.add_view(view)
rv = client.post('/admin/model2/ajax/update/', data={
'model1-1': '3',
'list_form_pk': '1',
'model1': '3',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......
......@@ -354,8 +354,7 @@ def test_column_editable_list():
Model1, Model2 = create_models(db)
view = CustomModelView(Model1, db.session,
column_editable_list=[
'test1', 'enum_field'])
column_editable_list=['test1', 'enum_field'])
admin.add_view(view)
fill_db(db, Model1, Model2)
......@@ -369,7 +368,8 @@ def test_column_editable_list():
# Form - Test basic in-line edit functionality
rv = client.post('/admin/model1/ajax/update/', data={
'test1-1': 'change-success-1',
'list_form_pk': '1',
'test1': 'change-success-1',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......@@ -381,32 +381,34 @@ def test_column_editable_list():
# Test validation error
rv = client.post('/admin/model1/ajax/update/', data={
'enum_field-1': 'problematic-input',
'list_form_pk': '1',
'enum_field': 'problematic-input',
})
eq_(rv.status_code, 500)
# Test invalid primary key
rv = client.post('/admin/model1/ajax/update/', data={
'test1-1000': 'problematic-input',
'list_form_pk': '1000',
'test1': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
# Test editing column not in column_editable_list
rv = client.post('/admin/model1/ajax/update/', data={
'test2-1': 'problematic-input',
'list_form_pk': '1',
'test2': 'problematic-input',
})
data = rv.data.decode('utf-8')
eq_(rv.status_code, 500)
ok_('problematic-input' not in data)
# Test in-line editing for relations
view = CustomModelView(Model2, db.session,
column_editable_list=[
'model1'])
view = CustomModelView(Model2, db.session, column_editable_list=['model1'])
admin.add_view(view)
rv = client.post('/admin/model2/ajax/update/', data={
'model1-1': '3',
'list_form_pk': '1',
'model1': '3',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......@@ -495,7 +497,8 @@ def test_editable_list_special_pks():
# Form - Test basic in-line edit functionality
rv = client.post('/admin/model1/ajax/update/', data={
'val1-1-1': 'change-success-1',
'list_form_pk': '1-1',
'val1': 'change-success-1',
})
data = rv.data.decode('utf-8')
ok_('Record was successfully saved.' == data)
......@@ -1511,8 +1514,8 @@ def test_form_onetoone():
eq_(model1.model2, model2)
eq_(model2.model1, model1)
eq_(view1._create_form_class.model2.kwargs['widget'].multiple, False)
eq_(view2._create_form_class.model1.kwargs['widget'].multiple, False)
eq_(view1._create_form_class.model2.field_class.widget.multiple, False)
eq_(view2._create_form_class.model1.field_class.widget.multiple, False)
def test_relations():
......
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