From 77fa84418468935c665849130c7fc7d072797fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Wed, 22 May 2013 22:16:03 +0200 Subject: [PATCH] Also take form field into account when floppifying widgets. That way all specific floppyform widgets will be used for modelforms (like EmailInput, NumberInput, etc.). --- djadmin2/forms.py | 62 ++++++++-- example/blog/tests/test_modelforms.py | 170 ++++++++++++++++++++++++-- 2 files changed, 216 insertions(+), 16 deletions(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index f481ad4..77711cc 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -71,7 +71,7 @@ def _create_multiwidget(widget_class, copy_attributes=(), init_arguments=()): def create_new_multiwidget(original): multiwidget = create_new_widget(original) multiwidget.widgets = [ - get_floppyform_widget(widget) + floppify_widget(widget) for widget in multiwidget.widgets] return multiwidget return create_new_multiwidget @@ -155,8 +155,27 @@ _django_to_floppyforms_widget = { init_arguments=('years', 'required')), } +_django_field_to_floppyform_widget = { + django.forms.fields.FloatField: + _create_widget(floppyforms.widgets.NumberInput), + django.forms.fields.DecimalField: + _create_widget(floppyforms.widgets.NumberInput), + django.forms.fields.IntegerField: + _create_widget(floppyforms.widgets.NumberInput), + django.forms.fields.EmailField: + _create_widget(floppyforms.widgets.EmailInput), + django.forms.fields.URLField: + _create_widget(floppyforms.widgets.URLInput), + django.forms.fields.SlugField: + _create_widget(floppyforms.widgets.SlugInput), + django.forms.fields.IPAddressField: + _create_widget(floppyforms.widgets.IPAddressInput), + django.forms.fields.SplitDateTimeField: + _create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget), +} -def get_floppyform_widget(widget): + +def floppify_widget(widget, field=None): ''' Get an instance of django.forms.widgets.Widget and return a new widget instance but using the corresponding floppyforms widget class. @@ -164,14 +183,41 @@ def get_floppyform_widget(widget): Only original django widgets will be replaced with a floppyforms version. The widget will be returned unaltered if it is not known, e.g. if it's a custom widget from a third-party app. + + The optional parameter ``field`` can be used to influence the widget + creation further. This is useful since floppyforms supports more widgets + than django does. For example is django using a ``TextInput`` for a + ``EmailField``, but floppyforms has a better suiting widget called + ``EmailInput``. If a widget is found specifically for the passed in + ``field``, it will take precendence to the first parameter ``widget`` + which will effectively be ignored. ''' - create_widget_class = _django_to_floppyforms_widget.get( - widget.__class__, None) - if create_widget_class is not None: - return create_widget_class(widget) + if field is not None: + create_widget = _django_field_to_floppyform_widget.get( + field.__class__) + if create_widget is not None: + # check if the default widget was replaced by a different one, in + # that case we cannot create the field specific floppyforms + # widget. + if field.widget.__class__ is field.__class__.widget: + return create_widget(widget) + create_widget = _django_to_floppyforms_widget.get(widget.__class__) + if create_widget is not None: + return create_widget(widget) return widget +def floppify_form(form_class): + ''' + Take a normal form and return a subclass of that form that replaces all + django widgets with the corresponding floppyforms widgets. + ''' + new_form_class = type(form_class.__name__, (form_class,), {}) + for field in new_form_class.base_fields.values(): + field.widget = floppify_widget(field.widget, field=field) + return new_form_class + + def modelform_factory(model, form=django.forms.models.ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None): form_class = django.forms.models.modelform_factory( @@ -181,6 +227,4 @@ def modelform_factory(model, form=django.forms.models.ModelForm, fields=None, exclude=exclude, formfield_callback=formfield_callback, widgets=widgets) - for field in form_class.base_fields.values(): - field.widget = get_floppyform_widget(field.widget) - return form_class + return floppify_form(form_class) diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index cd6c7aa..ca003aa 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -1,7 +1,7 @@ import floppyforms from django import forms from django.test import TestCase -from djadmin2.forms import get_floppyform_widget, modelform_factory +from djadmin2.forms import floppify_widget, floppify_form, modelform_factory from ..models import Post @@ -16,7 +16,7 @@ class ModelFormFactoryTest(TestCase): class GetFloppyformWidgetTest(TestCase): def assertExpectWidget(self, instance, new_class_, equal_attributes=None): - new_instance = get_floppyform_widget(instance) + new_instance = floppify_widget(instance) self.assertEqual(new_instance.__class__, new_class_) if equal_attributes: for attribute in equal_attributes: @@ -38,7 +38,7 @@ class GetFloppyformWidgetTest(TestCase): widget = forms.TextInput() widget.is_required = True widget.attrs = {'placeholder': 'Search ...'} - new_widget = get_floppyform_widget(widget) + new_widget = floppify_widget(widget) self.assertFalse(widget.__dict__ is new_widget.__dict__) new_widget.is_required = False self.assertEqual(widget.is_required, True) @@ -177,7 +177,7 @@ class GetFloppyformWidgetTest(TestCase): check_test = lambda v: False widget = forms.widgets.CheckboxInput(check_test=check_test) - new_widget = get_floppyform_widget(widget) + new_widget = floppify_widget(widget) self.assertEqual(widget.check_test, new_widget.check_test) self.assertTrue(new_widget.check_test is check_test) @@ -235,7 +235,7 @@ class GetFloppyformWidgetTest(TestCase): text_input = forms.widgets.TextInput() widget = forms.widgets.MultiWidget([text_input]) - new_widget = get_floppyform_widget(widget) + new_widget = floppify_widget(widget) self.assertEqual(widget.widgets, new_widget.widgets) self.assertTrue(new_widget.widgets[0] is text_input) @@ -247,7 +247,7 @@ class GetFloppyformWidgetTest(TestCase): widget = forms.widgets.SplitDateTimeWidget( date_format='DATE_FORMAT', time_format='TIME_FORMAT') - new_widget = get_floppyform_widget(widget) + new_widget = floppify_widget(widget) self.assertTrue(isinstance( new_widget.widgets[0], floppyforms.widgets.DateInput)) self.assertTrue(isinstance( @@ -263,7 +263,7 @@ class GetFloppyformWidgetTest(TestCase): widget = forms.widgets.SplitHiddenDateTimeWidget( date_format='DATE_FORMAT', time_format='TIME_FORMAT') - new_widget = get_floppyform_widget(widget) + new_widget = floppify_widget(widget) self.assertTrue(isinstance( new_widget.widgets[0], floppyforms.widgets.DateInput)) self.assertTrue(isinstance( @@ -286,3 +286,159 @@ class GetFloppyformWidgetTest(TestCase): widget, floppyforms.widgets.SelectDateWidget, ('attrs', 'years', 'required')) + + +class ModelFormTest(TestCase): + def test_custom_base_form(self): + class MyForm(forms.ModelForm): + pass + + form_class = modelform_factory(model=Post, form=MyForm) + form = form_class() + self.assertTrue(isinstance( + form.fields['title'].widget, + floppyforms.widgets.TextInput)) + + def test_declared_fields(self): + class MyForm(forms.ModelForm): + subtitle = forms.CharField() + + form_class = modelform_factory(model=Post, form=MyForm) + self.assertTrue(isinstance( + form_class.base_fields['subtitle'].widget, + floppyforms.widgets.TextInput)) + self.assertTrue(isinstance( + form_class.declared_fields['subtitle'].widget, + floppyforms.widgets.TextInput)) + + self.assertTrue(isinstance( + form_class.base_fields['title'].widget, + floppyforms.widgets.TextInput)) + # title is not defined in declared fields + + def test_additional_form_fields(self): + class MyForm(forms.ModelForm): + subtitle = forms.CharField() + + form_class = modelform_factory(model=Post, form=MyForm) + form = form_class() + self.assertTrue(isinstance( + form.fields['subtitle'].widget, + floppyforms.widgets.TextInput)) + + def test_subclassing_forms(self): + class MyForm(forms.ModelForm): + subtitle = forms.CharField() + + class Meta: + model = Post + + class ChildForm(MyForm): + created = forms.DateField() + + form_class = modelform_factory(model=Post, form=ChildForm) + form = form_class() + self.assertTrue(isinstance( + form.fields['title'].widget, + floppyforms.widgets.TextInput)) + self.assertTrue(isinstance( + form.fields['subtitle'].widget, + floppyforms.widgets.TextInput)) + self.assertTrue(isinstance( + form.fields['created'].widget, + floppyforms.widgets.DateInput)) + + +class FieldWidgetTest(TestCase): + def test_dont_overwrite_none_default_widget(self): + # we don't create the floppyform EmailInput for the email field here + # since we have overwritten the default widget. However we replace the + # django textarea with a floppyforms Textarea + email_input = forms.widgets.Textarea() + + class MyForm(forms.ModelForm): + email = forms.EmailField(widget=email_input) + + class Meta: + model = Post + + form_class = floppify_form(MyForm) + widget = form_class().fields['email'].widget + self.assertFalse(isinstance(widget, floppyforms.widgets.EmailInput)) + self.assertTrue(isinstance(widget, floppyforms.widgets.Textarea)) + + def test_float_field(self): + class MyForm(forms.ModelForm): + float = forms.FloatField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['float'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput)) + self.assertEqual(widget.input_type, 'number') + + def test_decimal_field(self): + class MyForm(forms.ModelForm): + decimal = forms.DecimalField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['decimal'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput)) + self.assertEqual(widget.input_type, 'number') + + def test_integer_field(self): + class MyForm(forms.ModelForm): + integer = forms.IntegerField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['integer'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput)) + self.assertEqual(widget.input_type, 'number') + + def test_email_field(self): + class MyForm(forms.ModelForm): + email = forms.EmailField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['email'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.EmailInput)) + self.assertEqual(widget.input_type, 'email') + + def test_url_field(self): + class MyForm(forms.ModelForm): + url = forms.URLField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['url'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.URLInput)) + self.assertEqual(widget.input_type, 'url') + + def test_slug_field(self): + class MyForm(forms.ModelForm): + slug = forms.SlugField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['slug'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput)) + self.assertEqual(widget.input_type, 'text') + + def test_ipaddress_field(self): + class MyForm(forms.ModelForm): + ipaddress = forms.IPAddressField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['ipaddress'].widget + self.assertTrue(isinstance(widget, floppyforms.widgets.IPAddressInput)) + self.assertEqual(widget.input_type, 'text') + + def test_splitdatetime_field(self): + class MyForm(forms.ModelForm): + splitdatetime = forms.SplitDateTimeField() + + form_class = modelform_factory(model=Post, form=MyForm) + widget = form_class().fields['splitdatetime'].widget + self.assertTrue(isinstance( + widget, floppyforms.widgets.SplitDateTimeWidget)) + self.assertTrue(isinstance( + widget.widgets[0], floppyforms.widgets.DateInput)) + self.assertTrue(isinstance( + widget.widgets[1], floppyforms.widgets.TimeInput))