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

Merge branch 'master' of github.com:mrjoes/flask-admin

parents 2e823c72 d12a6e12
Flask-Admin includes some bundled software to ease installation.
Select2
=======
Distributed under `APLv2 <http://www.apache.org/licenses/LICENSE-2.0>`_.
Twitter Bootstrap
=================
Distributed under `APLv2 <http://www.apache.org/licenses/LICENSE-2.0>`_.
...@@ -33,7 +33,7 @@ Flask-Admin is extensively documented, you can find `documentation here <http:// ...@@ -33,7 +33,7 @@ Flask-Admin is extensively documented, you can find `documentation here <http://
3rd Party Stuff 3rd Party Stuff
--------------- ---------------
Flask-Admin is built with help of `Twitter Bootstrap <http://twitter.github.com/bootstrap/>`_ and `Chosen <http://harvesthq.github.com/chosen/>`_. Flask-Admin is built with help of `Twitter Bootstrap <http://twitter.github.com/bootstrap/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_.
Kudos Kudos
----- -----
......
...@@ -17,6 +17,8 @@ import sys, os ...@@ -17,6 +17,8 @@ import sys, os
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
import flask_admin
from flask_admin import __version__
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
...@@ -48,9 +50,9 @@ copyright = u'2012, Serge S. Koval' ...@@ -48,9 +50,9 @@ copyright = u'2012, Serge S. Koval'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.0.1' version = __version__
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.0.1' release = version
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
......
__version__ = '1.0.2'
from .base import expose, Admin, BaseView, AdminIndexView from .base import expose, Admin, BaseView, AdminIndexView
...@@ -96,7 +96,17 @@ def contribute_inline(model, form_class, inline_models): ...@@ -96,7 +96,17 @@ def contribute_inline(model, form_class, inline_models):
elif isinstance(p, BaseModel): elif isinstance(p, BaseModel):
info = InlineFormAdmin(p) info = InlineFormAdmin(p)
else: else:
raise Exception('Unknown inline model admin: %s' % repr(p)) model = getattr(p, 'model', None)
if model is None:
raise Exception('Unknown inline model admin: %s' % repr(p))
attrs = dict()
for attr in dir(p):
if not attr.startswith('_') and attr != model:
attrs[attr] = getattr(p, attr)
info = InlineFormAdmin(model, **attrs)
# Find property from target model to current model # Find property from target model to current model
reverse_field = None reverse_field = None
......
...@@ -70,23 +70,23 @@ class ModelView(BaseModelView): ...@@ -70,23 +70,23 @@ class ModelView(BaseModelView):
Accept enumerable with one of the values: Accept enumerable with one of the values:
1. Child model class 1. Child model class::
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = (Post,) inline_models = (Post,)
2. Child model class and additional options 2. Child model class and additional options::
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = [(Post, dict(form_columns=['title']))] inline_models = [(Post, dict(form_columns=['title']))]
3. Django-like ``InlineFormAdmin`` class instance 3. Django-like ``InlineFormAdmin`` class instance::
class MyInlineForm(InlineFormAdmin): class MyInlineModelForm(InlineFormAdmin):
forum_columns = ('title', 'date') form_columns = ('title', 'date')
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = (MyInlineForm,) inline_models = (MyInlineModelForm(MyInlineModel),)
""" """
def __init__(self, model, name=None, def __init__(self, model, name=None,
...@@ -340,7 +340,7 @@ class ModelView(BaseModelView): ...@@ -340,7 +340,7 @@ class ModelView(BaseModelView):
count += 1 count += 1
flash(ngettext('Model was successfully deleted.', flash(ngettext('Model was successfully deleted.',
'%(count)s models were sucessfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception, ex:
......
...@@ -64,13 +64,13 @@ class AdminModelConverter(ModelConverterBase): ...@@ -64,13 +64,13 @@ class AdminModelConverter(ModelConverterBase):
return override(**kwargs) return override(**kwargs)
# Contribute model-related parameters # Contribute model-related parameters
kwargs.update({ if 'allow_blank' not in kwargs:
'allow_blank': local_column.nullable, kwargs['allow_blank'] = local_column.nullable,
'query_factory': lambda: self.session.query(remote_model) if 'query_factory' not in kwargs:
}) kwargs['query_factory'] = lambda: self.session.query(remote_model)
if prop.direction.name == 'MANYTOONE': if prop.direction.name == 'MANYTOONE':
return QuerySelectField(widget=form.ChosenSelectWidget(), return QuerySelectField(widget=form.Select2Widget(),
**kwargs) **kwargs)
elif prop.direction.name == 'ONETOMANY': elif prop.direction.name == 'ONETOMANY':
# Skip backrefs # Skip backrefs
...@@ -78,11 +78,11 @@ class AdminModelConverter(ModelConverterBase): ...@@ -78,11 +78,11 @@ class AdminModelConverter(ModelConverterBase):
return None return None
return QuerySelectMultipleField( return QuerySelectMultipleField(
widget=form.ChosenSelectWidget(multiple=True), widget=form.Select2Widget(multiple=True),
**kwargs) **kwargs)
elif prop.direction.name == 'MANYTOMANY': elif prop.direction.name == 'MANYTOMANY':
return QuerySelectMultipleField( return QuerySelectMultipleField(
widget=form.ChosenSelectWidget(multiple=True), widget=form.Select2Widget(multiple=True),
**kwargs) **kwargs)
else: else:
# Ignore pk/fk # Ignore pk/fk
...@@ -348,7 +348,17 @@ def contribute_inline(session, model, form_class, inline_models): ...@@ -348,7 +348,17 @@ def contribute_inline(session, model, form_class, inline_models):
elif hasattr(p, '_sa_class_manager'): elif hasattr(p, '_sa_class_manager'):
info = InlineFormAdmin(p) info = InlineFormAdmin(p)
else: else:
raise Exception('Unknown inline model admin: %s' % repr(p)) model = getattr(p, 'model', None)
if model is None:
raise Exception('Unknown inline model admin: %s' % repr(p))
attrs = dict()
for attr in dir(p):
if not attr.startswith('_') and attr != 'model':
attrs[attr] = getattr(p, attr)
info = InlineFormAdmin(model, **attrs)
# Find property from target model to current model # Find property from target model to current model
target_mapper = info.model._sa_class_manager.mapper target_mapper = info.model._sa_class_manager.mapper
......
...@@ -138,34 +138,34 @@ class ModelView(BaseModelView): ...@@ -138,34 +138,34 @@ class ModelView(BaseModelView):
giving SQLAlchemy chance to manually cleanup any dependencies (many-to-many giving SQLAlchemy chance to manually cleanup any dependencies (many-to-many
relationships, etc). relationships, etc).
If set to True, will run DELETE statement which is somewhat faster, but If set to `True`, will run `DELETE` statement which is somewhat faster,
might leave corrupted data if you forget to configure DELETE CASCADE but might leave corrupted data if you forget to configure `DELETE
for your model. CASCADE` for your model.
""" """
inline_models = None inline_models = None
""" """
Inline related-model editing for models with parent to child relation. Inline related-model editing for models with parent-child relations.
Accept enumerable with one of the values: Accepts enumerable with one of the following possible values:
1. Child model class 1. Child model class::
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = (Post,) inline_models = (Post,)
2. Child model class and additional options 2. Child model class and additional options::
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = [(Post, dict(form_columns=['title']))] inline_models = [(Post, dict(form_columns=['title']))]
3. Django-like ``InlineFormAdmin`` class instance 3. Django-like ``InlineFormAdmin`` class instance::
class MyInlineForm(InlineFormAdmin): class MyInlineModelForm(InlineFormAdmin):
forum_columns = ('title', 'date') form_columns = ('title', 'date')
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = (MyInlineForm,) inline_models = (MyInlineModelForm(MyInlineModel),)
""" """
def __init__(self, model, session, def __init__(self, model, session,
...@@ -657,7 +657,7 @@ class ModelView(BaseModelView): ...@@ -657,7 +657,7 @@ class ModelView(BaseModelView):
self.session.commit() self.session.commit()
flash(ngettext('Model was successfully deleted.', flash(ngettext('Model was successfully deleted.',
'%(count)s models were sucessfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception, ex:
......
...@@ -83,28 +83,30 @@ class TimeField(fields.Field): ...@@ -83,28 +83,30 @@ class TimeField(fields.Field):
raise ValueError(gettext('Invalid time format')) raise ValueError(gettext('Invalid time format'))
class ChosenSelectWidget(widgets.Select): class Select2Widget(widgets.Select):
""" """
`Chosen <http://harvesthq.github.com/chosen/>`_ styled select widget. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
You must include chosen.js and form.js for styling to work. You must include select2.js, form.js and select2 stylesheet for it to
work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
if field.allow_blank and not self.multiple: if field.allow_blank and not self.multiple:
kwargs['data-role'] = u'chosenblank' kwargs['data-role'] = u'select2blank'
else: else:
kwargs['data-role'] = u'chosen' kwargs['data-role'] = u'select2'
return super(ChosenSelectWidget, self).__call__(field, **kwargs) return super(Select2Widget, self).__call__(field, **kwargs)
class ChosenSelectField(fields.SelectField): class Select2Field(fields.SelectField):
""" """
`Chosen <http://harvesthq.github.com/chosen/>`_ styled select field. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
You must include chosen.js and form.js for styling to work. You must include select2.js, form.js and select2 stylesheet for it to
work.
""" """
widget = ChosenSelectWidget widget = Select2Widget
class DatePickerWidget(widgets.TextInput): class DatePickerWidget(widgets.TextInput):
......
...@@ -20,6 +20,8 @@ class InlineFormAdmin(object): ...@@ -20,6 +20,8 @@ class InlineFormAdmin(object):
class MyUserInfoForm(InlineFormAdmin): class MyUserInfoForm(InlineFormAdmin):
form_columns = ('name', 'email') form_columns = ('name', 'email')
""" """
_defaults = ['form_columns', 'excluded_form_columns', 'form_args']
def __init__(self, model, **kwargs): def __init__(self, model, **kwargs):
""" """
Constructor Constructor
...@@ -31,13 +33,11 @@ class InlineFormAdmin(object): ...@@ -31,13 +33,11 @@ class InlineFormAdmin(object):
""" """
self.model = model self.model = model
defaults = dict(form_columns=None, for k in self._defaults:
excluded_form_columns=None, if not hasattr(self, k):
form_args=None) setattr(self, k, None)
defaults.update(kwargs)
for k, v in defaults.iteritems(): for k, v in kwargs.iteritems():
setattr(self, k, v) setattr(self, k, v)
......
This diff is collapsed.
This diff is collapsed.
...@@ -41,7 +41,7 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -41,7 +41,7 @@ var AdminFilters = function(element, filters_element, operations, options, types
$select.append($('<option/>').attr('value', this[0]).text(this[1])); $select.append($('<option/>').attr('value', this[0]).text(this[1]));
}); });
$select.chosen(); $select.select2();
var optId = op[0][0]; var optId = op[0][0];
...@@ -58,7 +58,7 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -58,7 +58,7 @@ var AdminFilters = function(element, filters_element, operations, options, types
.appendTo($el); .appendTo($el);
}); });
$field.chosen(); $field.select2();
} else } else
{ {
$field = $('<input type="text" class="filter-val" />') $field = $('<input type="text" class="filter-val" />')
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
var AdminForm = function() { var AdminForm = function() {
this.applyStyle = function(el, name) { this.applyStyle = function(el, name) {
switch (name) { switch (name) {
case 'chosen': case 'select2':
$(el).chosen(); $(el).select2({width: 'resolve'});
break; break;
case 'chosenblank': case 'select2blank':
$(el).chosen({allow_single_deselect: true}); $(el).select2({allowClear: true, width: 'resolve'});
break; break;
case 'datepicker': case 'datepicker':
$(el).datepicker(); $(el).datepicker();
...@@ -49,8 +49,8 @@ ...@@ -49,8 +49,8 @@
}; };
this.applyGlobalStyles = function(parent) { this.applyGlobalStyles = function(parent) {
$('[data-role=chosen]', parent).chosen(); $('[data-role=select2]', parent).select2({width: 'resolve'});
$('[data-role=chosenblank]', parent).chosen({allow_single_deselect: true}); $('[data-role=select2blank]', parent).select2({allowClear: true, width: 'resolve'});
$('[data-role=datepicker]', parent).datepicker(); $('[data-role=datepicker]', parent).datepicker();
$('[data-role=datetimepicker]', parent).datepicker({displayTime: true}); $('[data-role=datetimepicker]', parent).datepicker({displayTime: true});
}; };
......
Copyright 2012 Igor Vaynberg
Version: @@ver@@ Timestamp: @@timestamp@@
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in
compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script> <script src="{{ url_for('admin.static', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='chosen/chosen.jquery.min.js') }}" type="text/javascript"></script> <script src="{{ url_for('admin.static', filename='select2/select2.min.js') }}" type="text/javascript"></script>
{% block tail %} {% block tail %}
{% endblock %} {% endblock %}
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% block head %} {% block head %}
<link href="{{ url_for('admin.static', filename='chosen/chosen.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% block head %} {% block head %}
<link href="{{ url_for('admin.static', filename='chosen/chosen.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% import 'admin/actions.html' as actionlib with context %} {% import 'admin/actions.html' as actionlib with context %}
{% block head %} {% block head %}
<link href="{{ url_for('admin.static', filename='chosen/chosen.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='select2/select2.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet"> <link href="{{ url_for('admin.static', filename='css/datepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
...@@ -73,14 +73,14 @@ ...@@ -73,14 +73,14 @@
{% set filter = admin_view._filters[flt[0]] %} {% set filter = admin_view._filters[flt[0]] %}
<a href="#" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}"> <a href="#" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}">
<span class="close-icon">&times;</span>&nbsp;{{ filters[flt[0]] }} <span class="close-icon">&times;</span>&nbsp;{{ filters[flt[0]] }}
</a><select class="filter-op" data-role="chosen"> </a><select class="filter-op" data-role="select2">
{% for op in admin_view._filter_dict[filter.name] %} {% for op in admin_view._filter_dict[filter.name] %}
<option value="{{ op[0] }}"{% if flt[0] == op[0] %} selected="selected"{% endif %}>{{ op[1] }}</option> <option value="{{ op[0] }}"{% if flt[0] == op[0] %} selected="selected"{% endif %}>{{ op[1] }}</option>
{% endfor %} {% endfor %}
</select> </select>
{%- set data = filter_data.get(flt[0]) -%} {%- set data = filter_data.get(flt[0]) -%}
{%- if data -%} {%- if data -%}
<select name="flt{{ i }}_{{ flt[0] }}" class="filter-val" data-role="chosen"> <select name="flt{{ i }}_{{ flt[0] }}" class="filter-val" data-role="select2">
{%- for d in data %} {%- for d in data %}
<option value="{{ d[0] }}"{% if flt[1] == d[0] %} selected{% endif %}>{{ d[1] }}</option> <option value="{{ d[0] }}"{% if flt[1] == d[0] %} selected{% endif %}>{{ d[1] }}</option>
{%- endfor %} {%- endfor %}
......
# Fix for older setuptools # Fix for older setuptools
import multiprocessing, logging, os import multiprocessing, logging, os
import flask_admin
from setuptools import setup, find_packages from setuptools import setup, find_packages
...@@ -17,7 +19,7 @@ def desc(): ...@@ -17,7 +19,7 @@ def desc():
setup( setup(
name='Flask-Admin', name='Flask-Admin',
version='1.0.2', version=flask_admin.__version__,
url='https://github.com/mrjoes/flask-admin/', url='https://github.com/mrjoes/flask-admin/',
license='BSD', license='BSD',
author='Serge S. Koval', author='Serge S. Koval',
......
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