Commit 2f280a80 authored by Petrus J.v.Rensburg's avatar Petrus J.v.Rensburg

Merge branch 'master' into examples

parents 6f628968 ff878092
...@@ -56,7 +56,7 @@ class NdbModelView(BaseModelView): ...@@ -56,7 +56,7 @@ class NdbModelView(BaseModelView):
model = self.model() model = self.model()
form.populate_obj(model) form.populate_obj(model)
model.put() model.put()
return True return model
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
#flash(gettext('Failed to create record. %(error)s', #flash(gettext('Failed to create record. %(error)s',
...@@ -137,7 +137,7 @@ class DbModelView(BaseModelView): ...@@ -137,7 +137,7 @@ class DbModelView(BaseModelView):
model = self.model() model = self.model()
form.populate_obj(model) form.populate_obj(model)
model.put() model.put()
return True return model
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
#flash(gettext('Failed to create record. %(error)s', #flash(gettext('Failed to create record. %(error)s',
......
...@@ -221,9 +221,9 @@ class DateTimeNotBetweenFilter(DateTimeBetweenFilter): ...@@ -221,9 +221,9 @@ class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
return lazy_gettext('not between') return lazy_gettext('not between')
# Base peewee filter field converter # Base MongoEngine filter field converter
class FilterConverter(filters.BaseFilterConverter): class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike, strings = (FilterLike, FilterNotLike, FilterEqual, FilterNotEqual,
FilterEmpty, FilterInList, FilterNotInList) FilterEmpty, FilterInList, FilterNotInList)
int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter, int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter,
IntSmallerFilter, FilterEmpty, IntInListFilter, IntSmallerFilter, FilterEmpty, IntInListFilter,
......
from mongoengine import ReferenceField from mongoengine import ReferenceField, ListField
from mongoengine.base import BaseDocument, DocumentMetaclass, get_document from mongoengine.base import BaseDocument, DocumentMetaclass, get_document
from wtforms import fields, validators from wtforms import fields, validators
...@@ -72,7 +72,7 @@ class CustomModelConverter(orm.ModelConverter): ...@@ -72,7 +72,7 @@ class CustomModelConverter(orm.ModelConverter):
if field.required: if field.required:
kwargs['validators'].append(validators.Required()) kwargs['validators'].append(validators.Required())
else: elif not isinstance(field, ListField):
kwargs['validators'].append(validators.Optional()) kwargs['validators'].append(validators.Optional())
ftype = type(field).__name__ ftype = type(field).__name__
......
...@@ -544,7 +544,7 @@ class ModelView(BaseModelView): ...@@ -544,7 +544,7 @@ class ModelView(BaseModelView):
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
return True return model
def update_model(self, form, model): def update_model(self, form, model):
""" """
......
...@@ -284,7 +284,7 @@ class TimeNotBetweenFilter(TimeBetweenFilter): ...@@ -284,7 +284,7 @@ class TimeNotBetweenFilter(TimeBetweenFilter):
# Base peewee filter field converter # Base peewee filter field converter
class FilterConverter(filters.BaseFilterConverter): class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike, strings = (FilterLike, FilterNotLike, FilterEqual, FilterNotEqual,
FilterEmpty, FilterInList, FilterNotInList) FilterEmpty, FilterInList, FilterNotInList)
int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter, int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter,
IntSmallerFilter, FilterEmpty, IntInListFilter, IntSmallerFilter, FilterEmpty, IntInListFilter,
......
...@@ -384,7 +384,7 @@ class ModelView(BaseModelView): ...@@ -384,7 +384,7 @@ class ModelView(BaseModelView):
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
return True return model
def update_model(self, form, model): def update_model(self, form, model):
try: try:
......
...@@ -288,7 +288,7 @@ class ModelView(BaseModelView): ...@@ -288,7 +288,7 @@ class ModelView(BaseModelView):
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
return True return model
def update_model(self, form, model): def update_model(self, form, model):
""" """
......
...@@ -285,7 +285,7 @@ class TimeNotBetweenFilter(TimeBetweenFilter): ...@@ -285,7 +285,7 @@ class TimeNotBetweenFilter(TimeBetweenFilter):
# Base SQLA filter field converter # Base SQLA filter field converter
class FilterConverter(filters.BaseFilterConverter): class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike, strings = (FilterLike, FilterNotLike, FilterEqual, FilterNotEqual,
FilterEmpty, FilterInList, FilterNotInList) FilterEmpty, FilterInList, FilterNotInList)
int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter, int_filters = (IntEqualFilter, IntNotEqualFilter, IntGreaterFilter,
IntSmallerFilter, FilterEmpty, IntInListFilter, IntSmallerFilter, FilterEmpty, IntInListFilter,
......
...@@ -894,7 +894,7 @@ class ModelView(BaseModelView): ...@@ -894,7 +894,7 @@ class ModelView(BaseModelView):
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
return True return model
def update_model(self, form, model): def update_model(self, form, model):
""" """
......
...@@ -87,9 +87,10 @@ def is_field_error(errors): ...@@ -87,9 +87,10 @@ def is_field_error(errors):
:param errors: :param errors:
Errors list. Errors list.
""" """
for e in errors: if isinstance(errors, (list, tuple)):
if isinstance(e, string_types): for e in errors:
return True if isinstance(e, string_types):
return True
return False return False
......
...@@ -1243,7 +1243,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1243,7 +1243,7 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
Create model from the form. Create model from the form.
Returns `True` if operation succeeded. Returns the model instance if operation succeeded.
Must be implemented in the child class. Must be implemented in the child class.
...@@ -1550,12 +1550,20 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1550,12 +1550,20 @@ class BaseModelView(BaseView, ActionsMixin):
self._validate_form_instance(ruleset=self._form_create_rules, form=form) self._validate_form_instance(ruleset=self._form_create_rules, form=form)
if self.validate_form(form): if self.validate_form(form):
if self.create_model(form): # in versions 1.1.0 and before, this returns a boolean
# in later versions, this is the model itself
model = self.create_model(form)
if model:
flash(gettext('Record was successfully created.')) flash(gettext('Record was successfully created.'))
if '_add_another' in request.form: if '_add_another' in request.form:
return redirect(request.url) return redirect(request.url)
else: else:
return redirect(return_url) # if we have a valid model, try to go to the edit view
if model is not True:
url = self.get_url('.edit_view', id=self.get_pk_value(model), url=return_url)
else:
url = return_url
return redirect(url)
form_opts = FormOpts(widget_args=self.form_widget_args, form_opts = FormOpts(widget_args=self.form_widget_args,
form_rules=self._form_create_rules) form_rules=self._form_create_rules)
......
...@@ -266,7 +266,7 @@ def convert(*args): ...@@ -266,7 +266,7 @@ def convert(*args):
See :mod:`flask_admin.contrib.sqla.filters` for usage example. See :mod:`flask_admin.contrib.sqla.filters` for usage example.
""" """
def _inner(func): def _inner(func):
func._converter_for = list(map(str.lower, args)) func._converter_for = list(map(lambda x: x.lower(), args))
return func return func
return _inner return _inner
......
...@@ -194,6 +194,36 @@ ...@@ -194,6 +194,36 @@
var drawControl = new L.Control.Draw(drawOptions); var drawControl = new L.Control.Draw(drawOptions);
map.addControl(drawControl); map.addControl(drawControl);
if (window.google) {
var geocoder = new google.maps.Geocoder();
function googleGeocoding(text, callResponse) {
geocoder.geocode({address: text}, callResponse);
}
function filterJSONCall(rawjson) {
var json = {}, key, loc, disp = [];
for (var i in rawjson) {
key = rawjson[i].formatted_address;
loc = L.latLng(rawjson[i].geometry.location.lat(),
rawjson[i].geometry.location.lng());
json[key] = loc;
}
return json;
}
map.addControl(new L.Control.Search({
callData: googleGeocoding,
filterJSON: filterJSONCall,
markerLocation: true,
autoType: false,
autoCollapse: true,
minLength: 2,
zoom: 10
}));
}
// save when the editableLayers are edited // save when the editableLayers are edited
var saveToTextArea = function() { var saveToTextArea = function() {
var geo = editableLayers.toGeoJSON(); var geo = editableLayers.toGeoJSON();
......
/*
* Leaflet Control Search v1.5.7 - 2015-05-07
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-container .leaflet-control-search{position:relative;float:left;background:#fff;color:#1978cf;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;background-color:rgba(255,255,255,.8);z-index:1000;box-shadow:0 1px 7px rgba(0,0,0,.65);margin-left:10px;margin-top:10px}.leaflet-control-search.search-exp{box-shadow:0 1px 7px #999;background:#fff}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:18px;padding:0 18px 0 2px;margin:3px 0 3px 3px}.leaflet-control-search.search-load .search-input{background:url(./images/loader.gif) no-repeat center right #fff}.leaflet-control-search.search-load .search-cancel{visibility:hidden}.leaflet-control-search .search-cancel{display:block;width:22px;height:18px;position:absolute;right:22px;margin:3px 0;background:url(./images/search-icon.png) no-repeat 0 -46px;text-decoration:none;filter:alpha(opacity=80);opacity:.8}.leaflet-control-search .search-cancel:hover{filter:alpha(opacity=100);opacity:1}.leaflet-control-search .search-cancel span{display:none;font-size:18px;line-height:20px;color:#ccc;font-weight:700}.leaflet-control-search .search-cancel:hover span{color:#aaa}.leaflet-control-search .search-button{display:block;float:left;width:26px;height:26px;background:url(./images/search-icon.png) no-repeat 2px 2px;border-radius:4px}.leaflet-control-search .search-button:hover{background:url(./images/search-icon.png) no-repeat 2px -22px}.leaflet-control-search .search-tooltip{position:absolute;top:100%;left:0;float:left;min-width:120px;max-height:122px;box-shadow:1px 1px 6px rgba(0,0,0,.4);background-color:rgba(0,0,0,.25);z-index:1010;overflow-y:auto;overflow-x:hidden}.leaflet-control-search .search-tip{margin:2px;padding:2px 4px;display:block;color:#000;background:#eee;border-radius:.25em;text-decoration:none;white-space:nowrap;vertical-align:center}.leaflet-control-search .search-button:hover,.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{cursor:pointer;clear:both;font-size:.75em;margin-bottom:5px;padding:0 .25em;color:#e00;font-weight:700;border-radius:.25em}
This diff is collapsed.
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
<p class="help-block">{{ field.description }}</p> <p class="help-block">{{ field.description }}</p>
{% endif %} {% endif %}
{% if direct_error %} {% if direct_error %}
<ul{% if direct_error %} class="input-errors"{% endif %}> <ul class="input-errors">
{% for e in field.errors if e is string %} {% for e in field.errors if e is string %}
<li>{{ e }}</li> <li>{{ e }}</li>
{% endfor %} {% endfor %}
...@@ -177,6 +177,9 @@ ...@@ -177,6 +177,9 @@
{% if config.MAPBOX_MAP_ID %} {% if config.MAPBOX_MAP_ID %}
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.css') }}" rel="stylesheet">
{% if config.MAPBOX_SEARCH %}
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.search.css') }}" rel="stylesheet">
{% endif %}
{% endif %} {% endif %}
{% if editable_columns %} {% if editable_columns %}
<link href="{{ admin_static.url(filename='vendor/x-editable/css/bootstrap2-editable-1.5.1.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/x-editable/css/bootstrap2-editable-1.5.1.css') }}" rel="stylesheet">
...@@ -193,6 +196,10 @@ ...@@ -193,6 +196,10 @@
</script> </script>
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.js') }}"></script>
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script>
{% if config.MAPBOX_SEARCH %}
<script src="https://maps.googleapis.com/maps/api/js?v=3&sensor=false"></script>
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.search.js') }}"></script>
{% endif %}
{% endif %} {% endif %}
<script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script>
{% if editable_columns %} {% if editable_columns %}
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
{% macro render_field(field) %} {% macro render_field(field) %}
{{ field }} {{ field }}
{% if h.is_field_error(field.errors) %}
<ul class="input-errors">
{% for e in field.errors if e is string %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %} {% endmacro %}
{{ base.render_inline_fields(field, template, render_field, check) }} {{ base.render_inline_fields(field, template, render_field, check) }}
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
{% endblock %} {% endblock %}
{% block menu_links %} {% block menu_links %}
<ul class="nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{{ layout.menu_links() }} {{ layout.menu_links() }}
</ul> </ul>
{% endblock %} {% endblock %}
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
{% macro render_field(field) %} {% macro render_field(field) %}
{{ field }} {{ field }}
{% if h.is_field_error(field.errors) %}
<ul class="help-block input-errors">
{% for e in field.errors if e is string %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %} {% endmacro %}
{{ base.render_inline_fields(field, template, render_field, check) }} {{ base.render_inline_fields(field, template, render_field, check) }}
...@@ -229,10 +229,10 @@ def test_column_filters(): ...@@ -229,10 +229,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[ [
(0, 'equals'), (0, 'contains'),
(1, 'not equal'), (1, 'not contains'),
(2, 'contains'), (2, 'equals'),
(3, 'not contains'), (3, 'not equal'),
(4, 'empty'), (4, 'empty'),
(5, 'in list'), (5, 'in list'),
(6, 'not in list'), (6, 'not in list'),
...@@ -791,6 +791,30 @@ def test_nested_list_subdocument(): ...@@ -791,6 +791,30 @@ def test_nested_list_subdocument():
ok_('value' not in dir(inline_form)) ok_('value' not in dir(inline_form))
def test_list_subdocument_validation():
app, db, admin = setup()
class Comment(db.EmbeddedDocument):
name = db.StringField(max_length=20, required=True)
value = db.StringField(max_length=20)
class Model1(db.Document):
test1 = db.StringField(max_length=20)
subdoc = db.ListField(db.EmbeddedDocumentField(Comment))
view = CustomModelView(Model1)
admin.add_view(view)
client = app.test_client()
rv = client.post('/admin/model1/new/',
data={'test1': 'test1large', 'subdoc-0-name': 'comment', 'subdoc-0-value': 'test'})
eq_(rv.status_code, 302)
rv = client.post('/admin/model1/new/',
data={'test1': 'test1large', 'subdoc-0-name': '', 'subdoc-0-value': 'test'})
eq_(rv.status_code, 200)
ok_('This field is required' in rv.data)
def test_ajax_fk(): def test_ajax_fk():
app, db, admin = setup() app, db, admin = setup()
......
...@@ -263,10 +263,10 @@ def test_column_filters(): ...@@ -263,10 +263,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[ [
(0, 'equals'), (0, 'contains'),
(1, 'not equal'), (1, 'not contains'),
(2, 'contains'), (2, 'equals'),
(3, 'not contains'), (3, 'not equal'),
(4, 'empty'), (4, 'empty'),
(5, 'in list'), (5, 'in list'),
(6, 'not in list'), (6, 'not in list'),
......
from nose.tools import eq_, ok_, raises from nose.tools import eq_, ok_, raises, assert_true
from wtforms import fields from wtforms import fields
...@@ -421,10 +421,10 @@ def test_column_filters(): ...@@ -421,10 +421,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[ [
(0, u'equals'), (0, u'contains'),
(1, u'not equal'), (1, u'not contains'),
(2, u'contains'), (2, u'equals'),
(3, u'not contains'), (3, u'not equal'),
(4, u'empty'), (4, u'empty'),
(5, u'in list'), (5, u'in list'),
(6, u'not in list'), (6, u'not in list'),
...@@ -436,10 +436,10 @@ def test_column_filters(): ...@@ -436,10 +436,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test1']],
[ [
(0, u'equals'), (0, u'contains'),
(1, u'not equal'), (1, u'not contains'),
(2, u'contains'), (2, u'equals'),
(3, u'not contains'), (3, u'not equal'),
(4, u'empty'), (4, u'empty'),
(5, u'in list'), (5, u'in list'),
(6, u'not in list'), (6, u'not in list'),
...@@ -447,10 +447,10 @@ def test_column_filters(): ...@@ -447,10 +447,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test2']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test2']],
[ [
(7, u'equals'), (7, u'contains'),
(8, u'not equal'), (8, u'not contains'),
(9, u'contains'), (9, u'equals'),
(10, u'not contains'), (10, u'not equal'),
(11, u'empty'), (11, u'empty'),
(12, u'in list'), (12, u'in list'),
(13, u'not in list'), (13, u'not in list'),
...@@ -458,10 +458,10 @@ def test_column_filters(): ...@@ -458,10 +458,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test3']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test3']],
[ [
(14, u'equals'), (14, u'contains'),
(15, u'not equal'), (15, u'not contains'),
(16, u'contains'), (16, u'equals'),
(17, u'not contains'), (17, u'not equal'),
(18, u'empty'), (18, u'empty'),
(19, u'in list'), (19, u'in list'),
(20, u'not in list'), (20, u'not in list'),
...@@ -469,10 +469,10 @@ def test_column_filters(): ...@@ -469,10 +469,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test4']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test4']],
[ [
(21, u'equals'), (21, u'contains'),
(22, u'not equal'), (22, u'not contains'),
(23, u'contains'), (23, u'equals'),
(24, u'not contains'), (24, u'not equal'),
(25, u'empty'), (25, u'empty'),
(26, u'in list'), (26, u'in list'),
(27, u'not in list'), (27, u'not in list'),
...@@ -538,10 +538,10 @@ def test_column_filters(): ...@@ -538,10 +538,10 @@ def test_column_filters():
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[ [
(0, 'equals'), (0, 'contains'),
(1, 'not equal'), (1, 'not contains'),
(2, 'contains'), (2, 'equals'),
(3, 'not contains'), (3, 'not equal'),
(4, 'empty'), (4, 'empty'),
(5, 'in list'), (5, 'in list'),
(6, 'not in list'), (6, 'not in list'),
...@@ -1669,10 +1669,14 @@ def test_safe_redirect(): ...@@ -1669,10 +1669,14 @@ def test_safe_redirect():
data=dict(test1='test1large', test2='test2')) data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302) eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model2view/') assert_true(rv.location.startswith('http://localhost/admin/model1/edit/'))
assert_true('url=http%3A%2F%2Flocalhost%2Fadmin%2Fmodel2view%2F' in rv.location)
assert_true('id=1' in rv.location)
rv = client.post('/admin/model1/new/?url=http://google.com/evil/', rv = client.post('/admin/model1/new/?url=http://google.com/evil/',
data=dict(test1='test1large', test2='test2')) data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302) eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model1/') assert_true(rv.location.startswith('http://localhost/admin/model1/edit/'))
assert_true('url=%2Fadmin%2Fmodel1%2F' in rv.location)
assert_true('id=2' in rv.location)
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