Commit fba873d5 authored by Sergey Markelov's avatar Sergey Markelov

Merge remote-tracking branch 'upstream/master'

parents d3dddbf6 19d6dbd7
...@@ -5,6 +5,7 @@ from wtforms import form, fields, validators ...@@ -5,6 +5,7 @@ from wtforms import form, fields, validators
from flask.ext import admin, login from flask.ext import admin, login
from flask.ext.admin.contrib import sqla from flask.ext.admin.contrib import sqla
from flask.ext.admin import helpers, expose from flask.ext.admin import helpers, expose
from werkzeug.security import generate_password_hash, check_password_hash
# Create Flask application # Create Flask application
...@@ -59,7 +60,10 @@ class LoginForm(form.Form): ...@@ -59,7 +60,10 @@ class LoginForm(form.Form):
if user is None: if user is None:
raise validators.ValidationError('Invalid user') raise validators.ValidationError('Invalid user')
if user.password != self.password.data: # we're comparing the plaintext pw with the the hash from the db
if not check_password_hash(user.password, self.password.data):
# to compare plain text passwords use
# if user.password != self.password.data:
raise validators.ValidationError('Invalid password') raise validators.ValidationError('Invalid password')
def get_user(self): def get_user(self):
...@@ -125,6 +129,9 @@ class MyAdminIndexView(admin.AdminIndexView): ...@@ -125,6 +129,9 @@ class MyAdminIndexView(admin.AdminIndexView):
user = User() user = User()
form.populate_obj(user) form.populate_obj(user)
# we hash the users password to avoid saving it as plaintext in the db,
# remove to use plain text:
user.password = generate_password_hash(form.password.data)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
...@@ -188,7 +195,9 @@ def build_sample_db(): ...@@ -188,7 +195,9 @@ def build_sample_db():
user.last_name = last_names[i] user.last_name = last_names[i]
user.login = user.first_name.lower() user.login = user.first_name.lower()
user.email = user.login + "@example.com" user.email = user.login + "@example.com"
user.password = ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10)) user.password = generate_password_hash(''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10)))
# passwords are hashed, to use plaintext passwords use:
# user.password = ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10))
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
Authentication Authentication
</p> </p>
<p> <p>
This example shows how you can use Flask-Login for authentication. It is only intended as a basic demonstration, so please don't freak out when you see passwords being stored as plain text. This example shows how you can use Flask-Login for authentication. It is only intended as a basic demonstration.
</p> </p>
{% else %} {% else %}
<form method="POST" action=""> <form method="POST" action="">
......
__version__ = '1.0.8' __version__ = '1.0.9.dev0'
__author__ = 'Serge S. Koval' __author__ = 'Serge S. Koval'
__email__ = 'serge.koval+github@gmail.com' __email__ = 'serge.koval+github@gmail.com'
......
...@@ -148,7 +148,8 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)): ...@@ -148,7 +148,8 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
return args return args
def __init__(self, name=None, category=None, endpoint=None, url=None, def __init__(self, name=None, category=None, endpoint=None, url=None,
static_folder=None, static_url_path=None): static_folder=None, static_url_path=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
""" """
Constructor. Constructor.
...@@ -168,9 +169,16 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)): ...@@ -168,9 +169,16 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
and '/admin/' prefix won't be applied. and '/admin/' prefix won't be applied.
:param static_url_path: :param static_url_path:
Static URL Path. If provided, this specifies the path to the static url directory. Static URL Path. If provided, this specifies the path to the static url directory.
:param debug: :param menu_class_name:
Optional debug flag. If set to `True`, will rethrow exceptions in some cases, so Werkzeug Optional class name for the menu item.
debugger can catch them. :param menu_icon_type:
Optional icon. Possible icon types:
- `flask.ext.admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon
- `flask.ext.admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory
- `flask.ext.admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL
:param menu_icon_value:
Icon glyph name or URL, depending on `menu_icon_type` setting
""" """
self.name = name self.name = name
self.category = category self.category = category
...@@ -178,6 +186,11 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)): ...@@ -178,6 +186,11 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
self.url = url self.url = url
self.static_folder = static_folder self.static_folder = static_folder
self.static_url_path = static_url_path self.static_url_path = static_url_path
self.menu = None
self.menu_class_name = menu_class_name
self.menu_icon_type = menu_icon_type
self.menu_icon_value = menu_icon_value
# Initialized from create_blueprint # Initialized from create_blueprint
self.admin = None self.admin = None
...@@ -483,6 +496,9 @@ class Admin(object): ...@@ -483,6 +496,9 @@ class Admin(object):
def _add_view_to_menu(self, view): def _add_view_to_menu(self, view):
self._add_menu_item(MenuView(view.name, view), view.category) self._add_menu_item(MenuView(view.name, view), view.category)
def get_category_menu_item(self, name):
return self._menu_categories.get(name)
def init_app(self, app): def init_app(self, app):
""" """
Register all views with the Flask application. Register all views with the Flask application.
......
# bootstrap glyph icon
ICON_TYPE_GLYPH = 'glyph'
# image relative to Flask static folder
ICON_TYPE_IMAGE = 'image'
# external image
ICON_TYPE_IMAGE_URL = 'image-url'
...@@ -199,7 +199,8 @@ class ModelView(BaseModelView): ...@@ -199,7 +199,8 @@ class ModelView(BaseModelView):
""" """
def __init__(self, model, name=None, def __init__(self, model, name=None,
category=None, endpoint=None, url=None): category=None, endpoint=None, url=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
""" """
Constructor Constructor
...@@ -213,10 +214,24 @@ class ModelView(BaseModelView): ...@@ -213,10 +214,24 @@ class ModelView(BaseModelView):
Endpoint Endpoint
:param url: :param url:
Custom URL Custom URL
:param menu_class_name:
Optional class name for the menu item.
:param menu_icon_type:
Optional icon. Possible icon types:
- `flask.ext.admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon
- `flask.ext.admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory
- `flask.ext.admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL
:param menu_icon_value:
Icon glyph name or URL, depending on `menu_icon_type` setting
""" """
self._search_fields = [] self._search_fields = []
super(ModelView, self).__init__(model, name, category, endpoint, url) super(ModelView, self).__init__(model, name, category, endpoint, url,
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._primary_key = self.scaffold_pk() self._primary_key = self.scaffold_pk()
...@@ -493,12 +508,11 @@ class ModelView(BaseModelView): ...@@ -493,12 +508,11 @@ class ModelView(BaseModelView):
model.save() model.save()
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to create model. %(error)s', flash(gettext('Failed to create model. %(error)s',
error=format_error(ex)), error=format_error(ex)),
'error') 'error')
log.exception('Failed to create model') log.exception('Failed to create model')
return False return False
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
...@@ -520,12 +534,11 @@ class ModelView(BaseModelView): ...@@ -520,12 +534,11 @@ class ModelView(BaseModelView):
model.save() model.save()
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to update model. %(error)s', flash(gettext('Failed to update model. %(error)s',
error=format_error(ex)), error=format_error(ex)),
'error') 'error')
log.exception('Failed to update model') log.exception('Failed to update model')
return False return False
else: else:
self.after_model_change(form, model, False) self.after_model_change(form, model, False)
...@@ -545,12 +558,11 @@ class ModelView(BaseModelView): ...@@ -545,12 +558,11 @@ class ModelView(BaseModelView):
return True return True
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to delete model. %(error)s', flash(gettext('Failed to delete model. %(error)s',
error=format_error(ex)), error=format_error(ex)),
'error') 'error')
log.exception('Failed to delete model') log.exception('Failed to delete model')
return False return False
# FileField access API # FileField access API
...@@ -600,7 +612,5 @@ class ModelView(BaseModelView): ...@@ -600,7 +612,5 @@ class ModelView(BaseModelView):
count=count)) count=count))
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), flash(gettext('Failed to delete models. %(error)s', error=str(ex)),
'error') 'error')
...@@ -132,10 +132,14 @@ class ModelView(BaseModelView): ...@@ -132,10 +132,14 @@ class ModelView(BaseModelView):
""" """
def __init__(self, model, name=None, def __init__(self, model, name=None,
category=None, endpoint=None, url=None): category=None, endpoint=None, url=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
self._search_fields = [] self._search_fields = []
super(ModelView, self).__init__(model, name, category, endpoint, url) super(ModelView, self).__init__(model, name, category, endpoint, url,
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self._primary_key = self.scaffold_pk() self._primary_key = self.scaffold_pk()
...@@ -350,10 +354,9 @@ class ModelView(BaseModelView): ...@@ -350,10 +354,9 @@ class ModelView(BaseModelView):
save_inline(form, model) save_inline(form, model)
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to create model') log.exception('Failed to create model')
return False return False
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
...@@ -370,10 +373,9 @@ class ModelView(BaseModelView): ...@@ -370,10 +373,9 @@ class ModelView(BaseModelView):
save_inline(form, model) save_inline(form, model)
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to update model') log.exception('Failed to update model')
return False return False
else: else:
self.after_model_change(form, model, False) self.after_model_change(form, model, False)
...@@ -387,10 +389,9 @@ class ModelView(BaseModelView): ...@@ -387,10 +389,9 @@ class ModelView(BaseModelView):
return True return True
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to delete model') log.exception('Failed to delete model')
return False return False
# Default model actions # Default model actions
...@@ -425,6 +426,4 @@ class ModelView(BaseModelView): ...@@ -425,6 +426,4 @@ class ModelView(BaseModelView):
count=count)) count=count))
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error')
...@@ -39,7 +39,8 @@ class ModelView(BaseModelView): ...@@ -39,7 +39,8 @@ class ModelView(BaseModelView):
""" """
def __init__(self, coll, def __init__(self, coll,
name=None, category=None, endpoint=None, url=None): name=None, category=None, endpoint=None, url=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
""" """
Constructor Constructor
...@@ -53,6 +54,16 @@ class ModelView(BaseModelView): ...@@ -53,6 +54,16 @@ class ModelView(BaseModelView):
Endpoint Endpoint
:param url: :param url:
Custom URL Custom URL
:param menu_class_name:
Optional class name for the menu item.
:param menu_icon_type:
Optional icon. Possible icon types:
- `flask.ext.admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon
- `flask.ext.admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory
- `flask.ext.admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL
:param menu_icon_value:
Icon glyph name or URL, depending on `menu_icon_type` setting
""" """
self._search_fields = [] self._search_fields = []
...@@ -62,7 +73,10 @@ class ModelView(BaseModelView): ...@@ -62,7 +73,10 @@ class ModelView(BaseModelView):
if endpoint is None: if endpoint is None:
endpoint = ('%sview' % coll.name).lower() endpoint = ('%sview' % coll.name).lower()
super(ModelView, self).__init__(None, name, category, endpoint, url) super(ModelView, self).__init__(None, name, category, endpoint, url,
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self.coll = coll self.coll = coll
......
...@@ -242,7 +242,8 @@ class ModelView(BaseModelView): ...@@ -242,7 +242,8 @@ class ModelView(BaseModelView):
""" """
def __init__(self, model, session, def __init__(self, model, session,
name=None, category=None, endpoint=None, url=None): name=None, category=None, endpoint=None, url=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
""" """
Constructor. Constructor.
...@@ -258,6 +259,16 @@ class ModelView(BaseModelView): ...@@ -258,6 +259,16 @@ class ModelView(BaseModelView):
Endpoint name. If not set, defaults to the model name Endpoint name. If not set, defaults to the model name
:param url: :param url:
Base URL. If not set, defaults to '/admin/' + endpoint Base URL. If not set, defaults to '/admin/' + endpoint
:param menu_class_name:
Optional class name for the menu item.
:param menu_icon_type:
Optional icon. Possible icon types:
- `flask.ext.admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon
- `flask.ext.admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory
- `flask.ext.admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL
:param menu_icon_value:
Icon glyph name or URL, depending on `menu_icon_type` setting
""" """
self.session = session self.session = session
...@@ -271,7 +282,10 @@ class ModelView(BaseModelView): ...@@ -271,7 +282,10 @@ class ModelView(BaseModelView):
if self.form_choices is None: if self.form_choices is None:
self.form_choices = {} self.form_choices = {}
super(ModelView, self).__init__(model, name, category, endpoint, url) super(ModelView, self).__init__(model, name, category, endpoint, url,
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
# Primary key # Primary key
self._primary_key = self.scaffold_pk() self._primary_key = self.scaffold_pk()
...@@ -831,11 +845,11 @@ class ModelView(BaseModelView): ...@@ -831,11 +845,11 @@ class ModelView(BaseModelView):
self.session.commit() self.session.commit()
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to create model') log.exception('Failed to create model')
self.session.rollback() self.session.rollback()
return False return False
else: else:
self.after_model_change(form, model, True) self.after_model_change(form, model, True)
...@@ -857,10 +871,9 @@ class ModelView(BaseModelView): ...@@ -857,10 +871,9 @@ class ModelView(BaseModelView):
self.session.commit() self.session.commit()
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to update model') log.exception('Failed to update model')
self.session.rollback() self.session.rollback()
return False return False
...@@ -884,11 +897,11 @@ class ModelView(BaseModelView): ...@@ -884,11 +897,11 @@ class ModelView(BaseModelView):
return True return True
except Exception as ex: except Exception as ex:
if not self.handle_view_exception(ex): if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error')
log.exception('Failed to delete model') log.exception('Failed to delete model')
self.session.rollback() self.session.rollback()
return False return False
# Default model actions # Default model actions
......
...@@ -5,6 +5,12 @@ from flask.ext.admin import helpers as h ...@@ -5,6 +5,12 @@ from flask.ext.admin import helpers as h
__all__ = ['Select2Widget', 'DatePickerWidget', 'DateTimePickerWidget', 'RenderTemplateWidget', 'Select2TagsWidget', ] __all__ = ['Select2Widget', 'DatePickerWidget', 'DateTimePickerWidget', 'RenderTemplateWidget', 'Select2TagsWidget', ]
def _is_bootstrap3():
view = h.get_current_view()
return view and view.admin.template_mode == 'bootstrap3'
class Select2Widget(widgets.Select): class Select2Widget(widgets.Select):
""" """
`Select2 <https://github.com/ivaynberg/select2>`_ styled select widget. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
...@@ -41,7 +47,12 @@ class DatePickerWidget(widgets.TextInput): ...@@ -41,7 +47,12 @@ class DatePickerWidget(widgets.TextInput):
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'datepicker') kwargs.setdefault('data-role', u'datepicker')
if _is_bootstrap3():
kwargs.setdefault('data-date-format', u'YYYY-MM-DD')
else:
kwargs.setdefault('data-date-format', u'yyyy-mm-dd') kwargs.setdefault('data-date-format', u'yyyy-mm-dd')
kwargs.setdefault('data-date-autoclose', u'true') kwargs.setdefault('data-date-autoclose', u'true')
self.date_format = kwargs['data-date-format'] self.date_format = kwargs['data-date-format']
return super(DatePickerWidget, self).__call__(field, **kwargs) return super(DatePickerWidget, self).__call__(field, **kwargs)
...@@ -55,10 +66,15 @@ class DateTimePickerWidget(widgets.TextInput): ...@@ -55,10 +66,15 @@ class DateTimePickerWidget(widgets.TextInput):
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'datetimepicker') kwargs.setdefault('data-role', u'datetimepicker')
if _is_bootstrap3():
kwargs.setdefault('data-date-format', u'YYYY-MM-DD hh:mm:ss')
else:
kwargs.setdefault('data-date-format', u'yyyy-mm-dd hh:ii:ss') kwargs.setdefault('data-date-format', u'yyyy-mm-dd hh:ii:ss')
kwargs.setdefault('data-date-autoclose', u'true')
kwargs.setdefault('data-date-today-btn', u'linked') kwargs.setdefault('data-date-today-btn', u'linked')
kwargs.setdefault('data-date-today-highlight', u'true') kwargs.setdefault('data-date-today-highlight', u'true')
kwargs.setdefault('data-date-autoclose', u'true')
return super(DateTimePickerWidget, self).__call__(field, **kwargs) return super(DateTimePickerWidget, self).__call__(field, **kwargs)
...@@ -70,7 +86,12 @@ class TimePickerWidget(widgets.TextInput): ...@@ -70,7 +86,12 @@ class TimePickerWidget(widgets.TextInput):
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'timepicker') kwargs.setdefault('data-role', u'timepicker')
if _is_bootstrap3():
kwargs.setdefault('data-date-format', u'hh:mm:ss')
else:
kwargs.setdefault('data-date-format', u'hh:ii:ss') kwargs.setdefault('data-date-format', u'hh:ii:ss')
kwargs.setdefault('data-date-autoclose', u'true') kwargs.setdefault('data-date-autoclose', u'true')
return super(TimePickerWidget, self).__call__(field, **kwargs) return super(TimePickerWidget, self).__call__(field, **kwargs)
......
...@@ -5,11 +5,18 @@ class BaseMenu(object): ...@@ -5,11 +5,18 @@ class BaseMenu(object):
""" """
Base menu item Base menu item
""" """
def __init__(self, name): def __init__(self, name, class_name=None, icon_type=None, icon_value=None):
self.name = name self.name = name
self.class_name = class_name
self.icon_type = icon_type
self.icon_value = icon_value
self.parent = None
self._children = [] self._children = []
def add_child(self, menu): def add_child(self, menu):
# TODO: Check if menu item is already assigned to some parent
menu.parent = self
self._children.append(menu) self._children.append(menu)
def get_url(self): def get_url(self):
...@@ -25,6 +32,15 @@ class BaseMenu(object): ...@@ -25,6 +32,15 @@ class BaseMenu(object):
return False return False
def get_class_name(self):
return self.class_name
def get_icon_type(self):
return self.icon_type
def get_icon_value(self):
return self.icon_value
def is_visible(self): def is_visible(self):
return True return True
...@@ -65,11 +81,16 @@ class MenuView(BaseMenu): ...@@ -65,11 +81,16 @@ class MenuView(BaseMenu):
Admin view menu item Admin view menu item
""" """
def __init__(self, name, view=None): def __init__(self, name, view=None):
super(MenuView, self).__init__(name) super(MenuView, self).__init__(name,
class_name=view.menu_class_name,
icon_type=view.menu_icon_type,
icon_value=view.menu_icon_value)
self._view = view self._view = view
self._cached_url = None self._cached_url = None
view.menu = self
def get_url(self): def get_url(self):
if self._view is None: if self._view is None:
return None return None
...@@ -103,8 +124,8 @@ class MenuLink(BaseMenu): ...@@ -103,8 +124,8 @@ class MenuLink(BaseMenu):
""" """
Link item Link item
""" """
def __init__(self, name, url=None, endpoint=None, category=None): def __init__(self, name, url=None, endpoint=None, category=None, class_name=None, icon_type=None, icon_value=None):
super(MenuLink, self).__init__(name) super(MenuLink, self).__init__(name, class_name, icon_type, icon_value)
self.category = category self.category = category
......
...@@ -484,7 +484,8 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -484,7 +484,8 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
def __init__(self, model, def __init__(self, model,
name=None, category=None, endpoint=None, url=None): name=None, category=None, endpoint=None, url=None,
menu_class_name=None, menu_icon_type=None, menu_icon_value=None):
""" """
Constructor. Constructor.
...@@ -500,9 +501,16 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -500,9 +501,16 @@ class BaseModelView(BaseView, ActionsMixin):
'userview' 'userview'
:param url: :param url:
Base URL. If not provided, will use endpoint as a URL. Base URL. If not provided, will use endpoint as a URL.
:param debug: :param menu_class_name:
Enable debugging mode. Won't catch exceptions on model Optional class name for the menu item.
save failures. :param menu_icon_type:
Optional icon. Possible icon types:
- `flask.ext.admin.consts.ICON_TYPE_GLYPH` - Bootstrap glyph icon
- `flask.ext.admin.consts.ICON_TYPE_IMAGE` - Image relative to Flask static directory
- `flask.ext.admin.consts.ICON_TYPE_IMAGE_URL` - Image with full URL
:param menu_icon_value:
Icon glyph name or URL, depending on `menu_icon_type` setting
""" """
# If name not provided, it is model name # If name not provided, it is model name
...@@ -513,7 +521,10 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -513,7 +521,10 @@ class BaseModelView(BaseView, ActionsMixin):
if endpoint is None: if endpoint is None:
endpoint = model.__name__.lower() endpoint = model.__name__.lower()
super(BaseModelView, self).__init__(name, category, endpoint, url) super(BaseModelView, self).__init__(name, category, endpoint, url,
menu_class_name=menu_class_name,
menu_icon_type=menu_icon_type,
menu_icon_value=menu_icon_value)
self.model = model self.model = model
...@@ -890,9 +901,9 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -890,9 +901,9 @@ class BaseModelView(BaseView, ActionsMixin):
# Exception handler # Exception handler
def handle_view_exception(self, exc): def handle_view_exception(self, exc):
if self._debug: if self._debug:
return False raise
return True return False
# Model event handlers # Model event handlers
def on_model_change(self, form, model, is_created): def on_model_change(self, form, model, is_created):
...@@ -955,6 +966,29 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -955,6 +966,29 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
pass pass
def on_form_prefill (self, form, id):
"""
Perform additional actions to pre-fill the edit form.
Called from edit_view, if the current action is rendering
the form rather than receiving client side input, after
default pre-filling has been performed.
By default does nothing.
You only need to override this if you have added custom
fields that depend on the database contents in a way that
Flask-admin can't figure out by itself. Fields that were
added by name of a normal column or relationship should
work out of the box.
:param form:
Form instance
:param id:
id of the object that is going to be edited
"""
pass
def create_model(self, form): def create_model(self, form):
""" """
Create model from the form. Create model from the form.
...@@ -1296,6 +1330,9 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1296,6 +1330,9 @@ class BaseModelView(BaseView, ActionsMixin):
else: else:
return redirect(return_url) return redirect(return_url)
if request.method == 'GET':
self.on_form_prefill(form, id)
form_opts = FormOpts(widget_args=self.form_widget_args, form_opts = FormOpts(widget_args=self.form_widget_args,
form_rules=self._form_edit_rules) form_rules=self._form_edit_rules)
......
...@@ -20,6 +20,7 @@ form.icon button { ...@@ -20,6 +20,7 @@ form.icon button {
a.icon, button span.glyphicon { a.icon, button span.glyphicon {
text-decoration: none; text-decoration: none;
margin-left: 10px; margin-left: 10px;
color: #333;
} }
/* Model search form */ /* Model search form */
......
...@@ -32,6 +32,7 @@ var AdminFilters = function(element, filtersElement, filterGroups) { ...@@ -32,6 +32,7 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
if($('.filters tr').length == 0) { if($('.filters tr').length == 0) {
$('button', $root).hide(); $('button', $root).hide();
$('a[class=btn]', $root).hide(); $('a[class=btn]', $root).hide();
$('.filters tbody').remove();
} else { } else {
$('button', $root).show(); $('button', $root).show();
} }
...@@ -92,6 +93,9 @@ var AdminFilters = function(element, filtersElement, filterGroups) { ...@@ -92,6 +93,9 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
if (filter.type) { if (filter.type) {
$field.attr('data-role', filter.type); $field.attr('data-role', filter.type);
if (filter.type == "datepicker") {
$field.attr('data-date-format', "YYYY-MM-DD");
}
faForm.applyStyle($field, filter.type); faForm.applyStyle($field, filter.type);
} }
} }
......
...@@ -109,17 +109,25 @@ ...@@ -109,17 +109,25 @@
return true; return true;
case 'datepicker': case 'datepicker':
$el.datetimepicker({ $el.datetimepicker({
minView: 'month' // TODO: Have separate converters for bs2 and bs3
// Bootstrap 2 option
minView: 'month',
// Bootstrap 3 option
pickTime: false
}); });
return true; return true;
case 'datetimepicker': case 'datetimepicker':
$el.datetimepicker(); $el.datetimepicker({
});
return true; return true;
case 'timepicker': case 'timepicker':
$el.datetimepicker({ $el.datetimepicker({
// Bootstrap 2 option
startView: 'day', startView: 'day',
maxView: 'day', maxView: 'day',
formatViewType: 'time' formatViewType: 'time',
// Bootstrap 3 option
pickDate: false
}); });
return true; return true;
} }
......
/*!
* Datetimepicker for Bootstrap v3
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget{top:0;left:0;width:250px;padding:4px;margin-top:1px;z-index:99999!important;border-radius:4px}.bootstrap-datetimepicker-widget.timepicker-sbs{width:600px}.bootstrap-datetimepicker-widget.bottom:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:7px}.bootstrap-datetimepicker-widget.bottom:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:8px}.bootstrap-datetimepicker-widget.top:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.top:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;position:absolute;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget .dow{width:14.2857%}.bootstrap-datetimepicker-widget.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget>ul{list-style-type:none;margin:0}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:100%;font-weight:bold;font-size:1.2em}.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator{width:4px;padding:0;margin:0}.bootstrap-datetimepicker-widget .datepicker>div{display:none}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget td,.bootstrap-datetimepicker-widget th{text-align:center;width:20px;height:20px;border-radius:4px}.bootstrap-datetimepicker-widget td.day:hover,.bootstrap-datetimepicker-widget td.hour:hover,.bootstrap-datetimepicker-widget td.minute:hover,.bootstrap-datetimepicker-widget td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget td.old,.bootstrap-datetimepicker-widget td.new{color:#999}.bootstrap-datetimepicker-widget td.today{position:relative}.bootstrap-datetimepicker-widget td.today:before{content:'';display:inline-block;border-left:7px solid transparent;border-bottom:7px solid #428bca;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget td.active,.bootstrap-datetimepicker-widget td.active:hover{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget td.disabled,.bootstrap-datetimepicker-widget td.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget td span{display:block;width:47px;height:54px;line-height:54px;float:left;margin:2px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget td span:hover{background:#eee}.bootstrap-datetimepicker-widget td span.active{background-color:#428bca;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget td span.old{color:#999}.bootstrap-datetimepicker-widget td span.disabled,.bootstrap-datetimepicker-widget td span.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget th.switch{width:145px}.bootstrap-datetimepicker-widget th.next,.bootstrap-datetimepicker-widget th.prev{font-size:21px}.bootstrap-datetimepicker-widget th.disabled,.bootstrap-datetimepicker-widget th.disabled:hover{background:none;color:#999;cursor:not-allowed}.bootstrap-datetimepicker-widget thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget thead tr:first-child th:hover{background:#eee}.input-group.date .input-group-addon span{display:block;cursor:pointer;width:16px;height:16px}.bootstrap-datetimepicker-widget.left-oriented:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.left-oriented:after{left:auto;right:7px}.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody>tr>td{padding:0!important}
\ No newline at end of file
This diff is collapsed.
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
{% block head_css %} {% block head_css %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap2/css/bootstrap.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='bootstrap/bootstrap2/css/bootstrap.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap2/css/bootstrap-responsive.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='bootstrap/bootstrap2/css/bootstrap-responsive.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap2/admin.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='admin/css/bootstrap2/admin.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block head %} {% block head %}
...@@ -56,7 +55,8 @@ ...@@ -56,7 +55,8 @@
{% block tail_js %} {% block tail_js %}
<script src="{{ admin_static.url(filename='vendor/jquery-2.0.3.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='vendor/jquery-2.0.3.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap2/js/bootstrap.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='bootstrap/bootstrap2/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='select2/select2.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='vendor/moment-2.7.0.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
......
{% macro menu_icon(item) -%}
{% set icon_type = item.get_icon_type() %}
{% if icon_type %}
{% set icon_value = item.get_icon_value() %}
{% if icon_type == 'glyph' %}
<i class="{{ icon_value }}"></i>
{% elif icon_type == 'image' %}
<img src="{{ url_for('static', filename=icon_value) }}" alt="menu image"></img>
{% elif icon_type == 'image-url' %}
<img src="item.icon_value" alt="menu image"></img>
{% endif %}
{% endif %}
{%- endmacro %}
{% macro menu() %} {% macro menu() %}
{% for item in admin_view.admin.menu() %} {% for item in admin_view.admin.menu() %}
{% if item.is_category() %} {% if item.is_category() %}
{% set children = item.get_children() %} {% set children = item.get_children() %}
{% if children %} {% if children %}
{% if item.is_active(admin_view) %}<li class="active dropdown">{% else %}<li class="dropdown">{% endif %} {% set class_name = item.get_class_name() %}
<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">{{ item.name }}<b class="caret"></b></a> {% if item.is_active(admin_view) %}
<li class="active dropdown{% if class_name %} {{class_name}}{% endif %}">
{% else %}
<li class="dropdown{% if class_name %} {{class_name}}{% endif %}">
{% endif %}
<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
{{ menu_icon(item) }}{{ item.name }}<b class="caret"></b>
</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for child in children %} {% for child in children %}
{% if child.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} {% set class_name = child.get_class_name() %}
<a href="{{ child.get_url() }}">{{ child.name }}</a> {% if child.is_active(admin_view) %}
<li class="active{% if class_name %} {{class_name}}{% endif %}">
{% else %}
<li{% if class_name %} class="{{class_name}}"{% endif %}>
{% endif %}
<a href="{{ child.get_url() }}">{{ menu_icon(item) }}{{ child.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
...@@ -16,8 +42,13 @@ ...@@ -16,8 +42,13 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if item.is_accessible() and item.is_visible() %} {% if item.is_accessible() and item.is_visible() %}
{% if item.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} {% set class_name = item.get_class_name() %}
<a href="{{ item.get_url() }}">{{ item.name }}</a> {% if item.is_active(admin_view) %}
<li class="active{% if class_name %} {{class_name}}{% endif %}">
{% else %}
<li{% if class_name %} class="{{class_name}}"{% endif %}>
{% endif %}
<a href="{{ item.get_url() }}">{{ menu_icon(item) }}{{ item.name }}</a>
</li> </li>
{% endif %} {% endif %}
{% endif %} {% endif %}
...@@ -28,7 +59,7 @@ ...@@ -28,7 +59,7 @@
{% for item in admin_view.admin.menu_links() %} {% for item in admin_view.admin.menu_links() %}
{% if item.is_accessible() and item.is_visible() %} {% if item.is_accessible() and item.is_visible() %}
<li> <li>
<a href="{{ item.get_url() }}">{{ item.name }}</a> <a href="{{ item.get_url() }}">{{ menu_icon(item) }}{{ item.name }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
......
{% import 'admin/static.html' as admin_static with context %}
{# ---------------------- Pager -------------------------- #} {# ---------------------- Pager -------------------------- #}
{% macro pager(page, pages, generator) -%} {% macro pager(page, pages, generator) -%}
{% if pages > 1 %} {% if pages > 1 %}
...@@ -75,7 +77,7 @@ ...@@ -75,7 +77,7 @@
{%- endmacro %} {%- endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
<div class="control-group{{ ' error' if direct_error else '' }}"> <div class="control-group{{ ' error' if direct_error else '' }}">
<div class="control-label"> <div class="control-label">
...@@ -102,6 +104,9 @@ ...@@ -102,6 +104,9 @@
</ul> </ul>
{% endif %} {% endif %}
</div> </div>
{% if caller %}
{{ caller(form, field, direct_error, kwargs) }}
{% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
...@@ -165,3 +170,13 @@ ...@@ -165,3 +170,13 @@
{{ render_form_buttons(cancel_url, extra) }} {{ render_form_buttons(cancel_url, extra) }}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
{% macro form_css() %}
<link href="{{ admin_static.url(filename='vendor/select2/select2.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/datetimepicker-bs2/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endmacro %}
{% macro form_js() %}
<script src="{{ admin_static.url(filename='vendor/datetimepicker-bs2/bootstrap-datetimepicker.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endmacro %}
{% extends 'admin/master.html' %} {% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% import 'admin/static.html' as admin_static with context %}
{% macro extra() %} {% macro extra() %}
<input name="_add_another" type="submit" class="btn btn-large" value="{{ _gettext('Save and Add') }}" /> <input name="_add_another" type="submit" class="btn btn-large" value="{{ _gettext('Save and Add') }}" />
...@@ -8,8 +7,7 @@ ...@@ -8,8 +7,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -30,6 +28,5 @@ ...@@ -30,6 +28,5 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script> {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endblock %} {% endblock %}
{% extends 'admin/master.html' %} {% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% import 'admin/static.html' as admin_static with context %}
{% macro extra() %} {% macro extra() %}
<input name="_continue_editing" type="submit" class="btn btn-large" value="{{ _gettext('Save and Continue') }}" /> <input name="_continue_editing" type="submit" class="btn btn-large" value="{{ _gettext('Save and Continue') }}" />
...@@ -8,8 +7,7 @@ ...@@ -8,8 +7,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -21,6 +19,5 @@ ...@@ -21,6 +19,5 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script> {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endblock %} {% endblock %}
...@@ -6,8 +6,7 @@ ...@@ -6,8 +6,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -103,7 +102,7 @@ ...@@ -103,7 +102,7 @@
<td> <td>
{% block list_row_actions scoped %} {% block list_row_actions scoped %}
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}" title="Edit record"> <a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}">
<i class="icon-pencil"></i> <i class="icon-pencil"></i>
</a> </a>
{%- endif -%} {%- endif -%}
...@@ -112,7 +111,7 @@ ...@@ -112,7 +111,7 @@
{% if csrf_token %} {% if csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %} {% endif %}
<button onclick="return confirm('{{ _gettext('You sure you want to delete this item?') }}');" title="Delete record"> <button onclick="return confirm('{{ _gettext('You sure you want to delete this item?') }}');" title="{{ _gettext('Delete record') }}">
<i class="icon-trash"></i> <i class="icon-trash"></i>
</button> </button>
</form> </form>
...@@ -145,8 +144,7 @@ ...@@ -145,8 +144,7 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script> {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script>
{{ actionlib.script(_gettext('Please select at least one model.'), {{ actionlib.script(_gettext('Please select at least one model.'),
......
...@@ -66,7 +66,8 @@ ...@@ -66,7 +66,8 @@
{% block tail_js %} {% block tail_js %}
<script src="{{ admin_static.url(filename='vendor/jquery-2.0.3.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='vendor/jquery-2.0.3.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='bootstrap/bootstrap3/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='select2/select2.min.js') }}" type="text/javascript"></script> <script src="{{ admin_static.url(filename='vendor/moment-2.7.0.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js') }}" type="text/javascript"></script>
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
......
{% macro menu_icon(item) -%}
{% set icon_type = item.get_icon_type() %}
{% if icon_type %}
{% set icon_value = item.get_icon_value() %}
{% if icon_type == 'glyph' %}
<i class="glyphicon {{ icon_value }}"></i>
{% elif icon_type == 'image' %}
<img src="{{ url_for('static', filename=icon_value) }}" alt="menu image"></img>
{% elif icon_type == 'image-url' %}
<img src="item.icon_value" alt="menu image"></img>
{% endif %}
{% endif %}
{%- endmacro %}
{% macro menu() %} {% macro menu() %}
{% for item in admin_view.admin.menu() %} {% for item in admin_view.admin.menu() %}
{% if item.is_category() %} {% if item.is_category() %}
{% set children = item.get_children() %} {% set children = item.get_children() %}
{% if children %} {% if children %}
{% if item.is_active(admin_view) %}<li class="active dropdown">{% else %}<li class="dropdown">{% endif %} {% if item.is_active(admin_view) %}
<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">{{ item.name }}<b class="caret"></b></a> <li class="active dropdown{% if class_name %} {{class_name}}{% endif %}">
{% else %}
<li class="dropdown{% if class_name %} {{class_name}}{% endif %}">
{% endif %}
<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">{{ menu_icon(item) }}{{ item.name }}<b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for child in children %} {% for child in children %}
{% if child.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} {% set class_name = child.get_class_name() %}
<a href="{{ child.get_url() }}">{{ child.name }}</a> {% if child.is_active(admin_view) %}
<li class="active"{% if class_name %} {{class_name}}{% endif %}>
{% else %}
<li{% if class_name %} class="{{class_name}}"{% endif %}>
{% endif %}
<a href="{{ child.get_url() }}">{{ menu_icon(item) }}{{ child.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
...@@ -16,8 +39,13 @@ ...@@ -16,8 +39,13 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if item.is_accessible() and item.is_visible() %} {% if item.is_accessible() and item.is_visible() %}
{% if item.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} {% set class_name = item.get_class_name() %}
<a href="{{ item.get_url() }}">{{ item.name }}</a> {% if item.is_active(admin_view) %}
<li class="active"{% if class_name %} {{class_name}}{% endif %}>
{% else %}
<li{% if class_name %} class="{{class_name}}"{% endif %}>
{% endif %}
<a href="{{ item.get_url() }}">{{ menu_icon(item) }}{{ item.name }}</a>
</li> </li>
{% endif %} {% endif %}
{% endif %} {% endif %}
...@@ -28,7 +56,7 @@ ...@@ -28,7 +56,7 @@
{% for item in admin_view.admin.menu_links() %} {% for item in admin_view.admin.menu_links() %}
{% if item.is_accessible() and item.is_visible() %} {% if item.is_accessible() and item.is_visible() %}
<li> <li>
<a href="{{ item.get_url() }}">{{ item.name }}</a> <a href="{{ item.get_url() }}">{{ menu_icon(item) }}{{ item.name }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
......
{% import 'admin/static.html' as admin_static with context %}
{# ---------------------- Pager -------------------------- #} {# ---------------------- Pager -------------------------- #}
{% macro pager(page, pages, generator) -%} {% macro pager(page, pages, generator) -%}
{% if pages > 1 %} {% if pages > 1 %}
...@@ -73,7 +75,7 @@ ...@@ -73,7 +75,7 @@
{%- endmacro %} {%- endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
<div class="form-group{{ ' error' if direct_error else '' }}"> <div class="form-group{{ ' error' if direct_error else '' }}">
<label for="{{ field.id }}" class="col-md-2 control-label">{{ field.label.text }} <label for="{{ field.id }}" class="col-md-2 control-label">{{ field.label.text }}
...@@ -83,7 +85,7 @@ ...@@ -83,7 +85,7 @@
&nbsp; &nbsp;
{%- endif %} {%- endif %}
</label> </label>
<div class="{{ kwargs.get('column_class', 'col-md-4') }}"> <div class="{{ kwargs.get('column_class', 'col-md-10') }}">
{% set _dummy = kwargs.setdefault('class', 'form-control') %} {% set _dummy = kwargs.setdefault('class', 'form-control') %}
{{ field(**kwargs)|safe }} {{ field(**kwargs)|safe }}
</div> </div>
...@@ -97,6 +99,9 @@ ...@@ -97,6 +99,9 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% if caller %}
{{ caller(form, field, direct_error, kwargs) }}
{% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
...@@ -158,3 +163,13 @@ ...@@ -158,3 +163,13 @@
{{ render_form_buttons(cancel_url, extra) }} {{ render_form_buttons(cancel_url, extra) }}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
{% macro form_css() %}
<link href="{{ admin_static.url(filename='vendor/select2/select2.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/datetimepicker-bs3/bootstrap-datetimepicker.min.css') }}" rel="stylesheet">
{% endmacro %}
{% macro form_js() %}
<script src="{{ admin_static.url(filename='vendor/datetimepicker-bs3/bootstrap-datetimepicker.min.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endmacro %}
{% extends 'admin/master.html' %} {% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% import 'admin/static.html' as admin_static with context %}
{% macro extra() %} {% macro extra() %}
<input name="_add_another" type="submit" class="btn btn-large" value="{{ _gettext('Save and Add') }}" /> <input name="_add_another" type="submit" class="btn btn-large" value="{{ _gettext('Save and Add') }}" />
...@@ -8,8 +7,7 @@ ...@@ -8,8 +7,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -32,6 +30,5 @@ ...@@ -32,6 +30,5 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script> {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endblock %} {% endblock %}
{% extends 'admin/master.html' %} {% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% import 'admin/static.html' as admin_static with context %}
{% macro extra() %} {% macro extra() %}
<input name="_continue_editing" type="submit" class="btn" value="{{ _gettext('Save and Continue') }}" /> <input name="_continue_editing" type="submit" class="btn" value="{{ _gettext('Save and Continue') }}" />
...@@ -8,8 +7,7 @@ ...@@ -8,8 +7,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -21,6 +19,5 @@ ...@@ -21,6 +19,5 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script> {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
{% endblock %} {% endblock %}
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
{%- endfor %} {%- endfor %}
</select> </select>
{%- else -%} {%- else -%}
<input name="flt{{n}}_{{ filter_arg }}" type="text" value="{{ value or '' }}" class="filter-val form-control"{% if filter.data_type %} data-role="{{ filter.data_type }}"{% endif %}></input> <input name="flt{{n}}_{{ filter_arg }}" type="text" value="{{ value or '' }}" class="filter-val form-control"{% if filter.data_type %} data-role="{{ filter.data_type }}"{% endif %} {% if filter.data_type == "datepicker" %}data-date-format="YYYY-MM-DD"{% endif %}></input>
{%- endif -%} {%- endif -%}
</td> </td>
</tr> </tr>
......
...@@ -6,8 +6,7 @@ ...@@ -6,8 +6,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="{{ admin_static.url(filename='select2/select2.css') }}" rel="stylesheet"> {{ lib.form_css() }}
<link href="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.css') }}" rel="stylesheet">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
...@@ -103,7 +102,7 @@ ...@@ -103,7 +102,7 @@
<td> <td>
{% block list_row_actions scoped %} {% block list_row_actions scoped %}
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}" title="Edit record"> <a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}">
<span class="glyphicon glyphicon-pencil"></span> <span class="glyphicon glyphicon-pencil"></span>
</a> </a>
{%- endif -%} {%- endif -%}
...@@ -145,9 +144,8 @@ ...@@ -145,9 +144,8 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='datetimepicker/bootstrap-datetimepicker.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script>
{{ lib.form_js() }}
{{ actionlib.script(_gettext('Please select at least one model.'), {{ actionlib.script(_gettext('Please select at least one model.'),
actions, actions,
......
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