diff --git a/lower-constraints.txt b/lower-constraints.txt index b85afeb94..4965710b7 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -20,7 +20,6 @@ Django==1.11 django-appconf==1.0.2 django-babel==0.6.2 django-compressor==2.0 -django-floppyforms==1.7.0 django-formtools==2.0 django-pyscss==2.0.2 docutils==0.14 diff --git a/muranodashboard/common/utils.py b/muranodashboard/common/utils.py index 479bac119..15baf1fb2 100644 --- a/muranodashboard/common/utils.py +++ b/muranodashboard/common/utils.py @@ -25,6 +25,8 @@ import pytz import six import yaql +from django.template import Context + from horizon.utils import functions as utils # WrappingColumn is only available in N-horizon @@ -35,6 +37,18 @@ except ImportError: from horizon.tables import Column as Column # noqa +REQUIRED_CONTEXT_ATTRIBTUES = ( + '_form_config', + '_form_render', +) + + +# We need a custom subclass of dict here in order to allow setting attributes +# on it like _form_config and _form_render. +class DictContext(dict): + pass + + def parse_api_error(api_error_html): error_html = bs4.BeautifulSoup(api_error_html, "html.parser") body = error_html.find('body') @@ -131,3 +145,24 @@ class CustomUnpickler(object): return yaql_expression.YAQL else: raise pickle.UnpicklingError('Invalid persistent id') + + +def flatten_context(context): + if isinstance(context, Context): + flat = {} + for d in context.dicts: + flat.update(d) + return flat + else: + return context + + +def flatten_contexts(*contexts): + new_context = DictContext() + for context in contexts: + if context is not None: + new_context.update(flatten_context(context)) + for attr in REQUIRED_CONTEXT_ATTRIBTUES: + if hasattr(context, attr): + setattr(new_context, attr, getattr(context, attr)) + return new_context diff --git a/muranodashboard/common/widgets.py b/muranodashboard/common/widgets.py index dd14d932e..4b6318539 100644 --- a/muranodashboard/common/widgets.py +++ b/muranodashboard/common/widgets.py @@ -14,10 +14,103 @@ import itertools as it -import floppyforms as floppy +from django import forms +from django.template import loader +from django.utils.encoding import force_text +from django.utils import formats + +from muranodashboard.common import utils -class TriStateCheckboxSelectMultiple(floppy.widgets.Input): +# Widge and Input code is copied from https://github.com/gregmuellegger/ +# django-floppyforms/blob/master/floppyforms/widgets.py to remove the +# dependency of django-floppyforms due to not very well supported anymore +# and was dropped from Ubuntu in Xenial. +class Widget(forms.Widget): + is_required = False + + # Backported from Django 1.7 + @property + def is_hidden(self): + return self.input_type == 'hidden' \ + if hasattr(self, 'input_type') else False + + # Backported from Django 1.9 + if not hasattr(forms.Widget, 'format_value'): + def format_value(self, value): + return self._format_value(value) + + +class Input(Widget): + template_name = 'common/tri_state_checkbox/base.html' + input_type = None + datalist = None + + def __init__(self, *args, **kwargs): + datalist = kwargs.pop('datalist', None) + if datalist is not None: + self.datalist = datalist + template_name = kwargs.pop('template_name', None) + if template_name is not None: + self.template_name = template_name + super(Input, self).__init__(*args, **kwargs) + # This attribute is used to inject a surrounding context in the + # floppyforms templatetags, when rendered inside a complete form. + self.context_instance = None + + def get_context_data(self): + return {} + + def _format_value(self, value): + if self.is_localized: + value = formats.localize_input(value) + return force_text(value) + + def get_context(self, name, value, attrs=None): + context = { + 'type': self.input_type, + 'name': name, + 'hidden': self.is_hidden, + 'required': self.is_required, + 'True': True, + } + # True is injected in the context to allow stricter comparisons + # for widget attrs. See #25. + if self.is_hidden: + context['hidden'] = True + + if value is None: + value = '' + + if value != '': + # Only add the value if it is non-empty + context['value'] = self.format_value(value) + + context.update(self.get_context_data()) + context['attrs'] = self.build_attrs(attrs) + + for key, attr in context['attrs'].items(): + if attr == 1: + # 1 == True so 'key="1"' will show up only as 'key' + # Casting to a string so that it doesn't equal to True + # See #25. + if not isinstance(attr, bool): + context['attrs'][key] = str(attr) + + if self.datalist is not None: + context['datalist'] = self.datalist + return context + + def render(self, name, value, attrs=None, **kwargs): + template_name = kwargs.pop('template_name', None) + if template_name is None: + template_name = self.template_name + context = self.get_context(name, value, attrs=attrs or {}) + context = utils.flatten_contexts(self.context_instance, context) + return loader.render_to_string(template_name, context) + + +class TriStateCheckboxSelectMultiple(Input): """Renders tri-state multi-selectable checkbox. .. note:: Subclassed from ``CheckboxSelectMultiple`` and not from @@ -28,7 +121,6 @@ class TriStateCheckboxSelectMultiple(floppy.widgets.Input): Otherwise template ``horizon/common/_form_field.html`` would render this widget slightly incorrectly. """ - template_name = 'common/tri_state_checkbox/base.html' VALUES_MAP = { 'True': True, diff --git a/requirements.txt b/requirements.txt index 93bcde09b..070ed9e51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,6 @@ pytz>=2013.6 # MIT PyYAML>=3.12 # MIT yaql>=1.1.3 # Apache 2.0 License castellan>=0.18.0 # Apache-2.0 -django-floppyforms>=1.7.0,<2 # BSD oslo.log>=3.36.0 # Apache-2.0 semantic-version>=2.3.1 # BSD