from flask import json from jinja2 import escape from wtforms.widgets import HTMLString, html_params from flask_admin._compat import as_unicode, text_type from flask_admin.babel import gettext from flask_admin.helpers import get_url from flask_admin.form import RenderTemplateWidget class InlineFieldListWidget(RenderTemplateWidget): def __init__(self): super(InlineFieldListWidget, self).__init__('admin/model/inline_field_list.html') class InlineFormWidget(RenderTemplateWidget): def __init__(self): super(InlineFormWidget, self).__init__('admin/model/inline_form.html') def __call__(self, field, **kwargs): kwargs.setdefault('form_opts', getattr(field, 'form_opts', None)) return super(InlineFormWidget, self).__call__(field, **kwargs) class AjaxSelect2Widget(object): def __init__(self, multiple=False): self.multiple = multiple def __call__(self, field, **kwargs): kwargs.setdefault('data-role', 'select2-ajax') kwargs.setdefault('data-url', get_url('.ajax_lookup', name=field.loader.name)) allow_blank = getattr(field, 'allow_blank', False) if allow_blank and not self.multiple: kwargs['data-allow-blank'] = u'1' kwargs.setdefault('id', field.id) kwargs.setdefault('type', 'hidden') if self.multiple: result = [] ids = [] for value in field.data: data = field.loader.format(value) result.append(data) ids.append(as_unicode(data[0])) separator = getattr(field, 'separator', ',') kwargs['value'] = separator.join(ids) kwargs['data-json'] = json.dumps(result) kwargs['data-multiple'] = u'1' else: data = field.loader.format(field.data) if data: kwargs['value'] = data[0] kwargs['data-json'] = json.dumps(data) placeholder = field.loader.options.get('placeholder', gettext('Please select model')) kwargs.setdefault('data-placeholder', placeholder) return HTMLString('' % html_params(name=field.name, **kwargs)) class XEditableWidget(object): """ WTForms widget that provides in-line editing for the list view. Determines how to display the x-editable/ajax form based on the field inside of the FieldList (StringField, IntegerField, etc). """ def __call__(self, field, **kwargs): kwargs.setdefault('data-value', kwargs.pop('display_value', '')) kwargs.setdefault('data-role', 'x-editable') kwargs.setdefault('data-url', './ajax/update/') kwargs.setdefault('id', field.id) kwargs.setdefault('name', field.name) kwargs.setdefault('href', '#') if not kwargs.get('pk'): raise Exception('pk required') kwargs['data-pk'] = str(kwargs.pop("pk")) kwargs['data-csrf'] = kwargs.pop("csrf", "") kwargs = self.get_kwargs(field, kwargs) return HTMLString( '%s' % (html_params(**kwargs), escape(kwargs['data-value'])) ) def get_kwargs(self, field, kwargs): """ Return extra kwargs based on the field type. """ if field.type == 'StringField': kwargs['data-type'] = 'text' elif field.type == 'TextAreaField': kwargs['data-type'] = 'textarea' kwargs['data-rows'] = '5' elif field.type == 'BooleanField': kwargs['data-type'] = 'select' # data-source = dropdown options kwargs['data-source'] = json.dumps([ {'value': '', 'text': gettext('No')}, {'value': '1', 'text': gettext('Yes')} ]) kwargs['data-role'] = 'x-editable-boolean' elif field.type in ['Select2Field', 'SelectField']: kwargs['data-type'] = 'select' choices = [{'value': x, 'text': y} for x, y in field.choices] # prepend a blank field to choices if allow_blank = True if getattr(field, 'allow_blank', False): choices.insert(0, {'value': '__None', 'text': ''}) # json.dumps fixes issue with unicode strings not loading correctly kwargs['data-source'] = json.dumps(choices) elif field.type == 'DateField': kwargs['data-type'] = 'combodate' kwargs['data-format'] = 'YYYY-MM-DD' kwargs['data-template'] = 'YYYY-MM-DD' elif field.type == 'DateTimeField': kwargs['data-type'] = 'combodate' kwargs['data-format'] = 'YYYY-MM-DD HH:mm:ss' kwargs['data-template'] = 'YYYY-MM-DD HH:mm:ss' # x-editable-combodate uses 1 minute increments kwargs['data-role'] = 'x-editable-combodate' elif field.type == 'TimeField': kwargs['data-type'] = 'combodate' kwargs['data-format'] = 'HH:mm:ss' kwargs['data-template'] = 'HH:mm:ss' kwargs['data-role'] = 'x-editable-combodate' elif field.type == 'IntegerField': kwargs['data-type'] = 'number' elif field.type in ['FloatField', 'DecimalField']: kwargs['data-type'] = 'number' kwargs['data-step'] = 'any' elif field.type in ['QuerySelectField', 'ModelSelectField', 'QuerySelectMultipleField', 'KeyPropertyField']: # QuerySelectField and ModelSelectField are for relations kwargs['data-type'] = 'select' choices = [] selected_ids = [] for value, label, selected in field.iter_choices(): try: label = text_type(label) except TypeError: # unable to display text value label = '' choices.append({'value': text_type(value), 'text': label}) if selected: selected_ids.append(value) # blank field is already included if allow_blank kwargs['data-source'] = json.dumps(choices) if field.type == 'QuerySelectMultipleField': kwargs['data-type'] = 'select2' kwargs['data-role'] = 'x-editable-select2-multiple' # must use id instead of text or prefilled values won't work separator = getattr(field, 'separator', ',') kwargs['data-value'] = separator.join(selected_ids) else: raise Exception('Unsupported field type: %s' % (type(field),)) return kwargs