mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-04-30 19:44:46 +00:00
Implementing a modelform_factory that takes care of converting all widgets on the modelform to floppyform widgets if possible.
This commit is contained in:
parent
af96990275
commit
188c12d647
5 changed files with 478 additions and 1 deletions
186
djadmin2/forms.py
Normal file
186
djadmin2/forms.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import floppyforms
|
||||
from copy import deepcopy
|
||||
|
||||
import django.forms
|
||||
import django.forms.models
|
||||
import django.forms.extras.widgets
|
||||
|
||||
|
||||
_WIDGET_COMMON_ATTRIBUTES = (
|
||||
'is_hidden',
|
||||
'needs_multipart_form',
|
||||
'is_localized',
|
||||
'is_required')
|
||||
|
||||
_WIDGET_COMMON_ARGUMENTS = ('attrs',)
|
||||
|
||||
|
||||
def _copy_attributes(original, new_widget, attributes):
|
||||
for attr in attributes:
|
||||
original_value = getattr(original, attr)
|
||||
original_value = deepcopy(original_value)
|
||||
setattr(new_widget, attr, original_value)
|
||||
|
||||
|
||||
def _create_widget(widget_class, copy_attributes=(), init_arguments=()):
|
||||
# attach defaults that apply for all widgets
|
||||
copy_attributes = tuple(copy_attributes) + _WIDGET_COMMON_ATTRIBUTES
|
||||
init_arguments = tuple(init_arguments) + _WIDGET_COMMON_ARGUMENTS
|
||||
|
||||
def create_new_widget(original):
|
||||
kwargs = {}
|
||||
for argname in init_arguments:
|
||||
kwargs[argname] = getattr(original, argname)
|
||||
new_widget = widget_class(**kwargs)
|
||||
_copy_attributes(
|
||||
original,
|
||||
new_widget,
|
||||
copy_attributes)
|
||||
return new_widget
|
||||
return create_new_widget
|
||||
|
||||
|
||||
def _create_radioselect(original):
|
||||
# return original widget if the renderer is something else than what
|
||||
# django ships with by default. This means if this condition evaluates to
|
||||
# true, then a custom renderer was specified. We cannot emulate its
|
||||
# behaviour so we shouldn't guess and just return the original widget
|
||||
if original.renderer is not django.forms.widgets.RadioFieldRenderer:
|
||||
return original
|
||||
create_new_widget = _create_widget(
|
||||
floppyforms.widgets.RadioSelect,
|
||||
('allow_multiple_selected',))
|
||||
return create_new_widget(original)
|
||||
|
||||
|
||||
def _create_splitdatetimewidget(widget_class):
|
||||
def create_new_widget(original):
|
||||
new_widget = widget_class(
|
||||
attrs=original.attrs,
|
||||
date_format=original.widgets[0].format,
|
||||
time_format=original.widgets[1].format)
|
||||
_copy_attributes(original, new_widget, _WIDGET_COMMON_ARGUMENTS)
|
||||
return new_widget
|
||||
return create_new_widget
|
||||
|
||||
|
||||
def _create_multiwidget(widget_class, copy_attributes=(), init_arguments=()):
|
||||
create_new_widget = _create_widget(widget_class, copy_attributes,
|
||||
init_arguments)
|
||||
|
||||
def create_new_multiwidget(original):
|
||||
multiwidget = create_new_widget(original)
|
||||
multiwidget.widgets = [
|
||||
get_floppyform_widget(widget)
|
||||
for widget in multiwidget.widgets]
|
||||
return multiwidget
|
||||
return create_new_multiwidget
|
||||
|
||||
|
||||
# this dictionary keeps a mapping from django's widget classes to a callable
|
||||
# that will accept an instance of this class. It will return a new instance of
|
||||
# a corresponding floppyforms widget, with the same semantics -- all relevant
|
||||
# attributes will be copied to the new widget.
|
||||
_django_to_floppyforms_widget = {
|
||||
django.forms.widgets.Input:
|
||||
_create_widget(floppyforms.widgets.Input, ('input_type',)),
|
||||
django.forms.widgets.TextInput:
|
||||
_create_widget(floppyforms.widgets.TextInput, ('input_type',)),
|
||||
django.forms.widgets.PasswordInput:
|
||||
_create_widget(floppyforms.widgets.PasswordInput, ('input_type',)),
|
||||
django.forms.widgets.HiddenInput:
|
||||
_create_widget(floppyforms.widgets.HiddenInput, ('input_type',)),
|
||||
django.forms.widgets.MultipleHiddenInput:
|
||||
_create_widget(
|
||||
floppyforms.widgets.MultipleHiddenInput,
|
||||
('input_type',),
|
||||
init_arguments=('choices',)),
|
||||
django.forms.widgets.FileInput:
|
||||
_create_widget(floppyforms.widgets.FileInput, ('input_type',)),
|
||||
django.forms.widgets.ClearableFileInput:
|
||||
_create_widget(
|
||||
floppyforms.widgets.ClearableFileInput,
|
||||
(
|
||||
'input_type', 'initial_text', 'input_text',
|
||||
'clear_checkbox_label', 'template_with_initial',
|
||||
'template_with_clear')),
|
||||
django.forms.widgets.Textarea:
|
||||
_create_widget(floppyforms.widgets.Textarea),
|
||||
django.forms.widgets.DateInput:
|
||||
_create_widget(
|
||||
floppyforms.widgets.DateInput,
|
||||
('input_type',),
|
||||
init_arguments=('format',)),
|
||||
django.forms.widgets.DateTimeInput:
|
||||
_create_widget(
|
||||
floppyforms.widgets.DateTimeInput,
|
||||
('input_type',),
|
||||
init_arguments=('format',)),
|
||||
django.forms.widgets.TimeInput:
|
||||
_create_widget(
|
||||
floppyforms.widgets.TimeInput,
|
||||
('input_type',),
|
||||
init_arguments=('format',)),
|
||||
django.forms.widgets.CheckboxInput:
|
||||
_create_widget(floppyforms.widgets.CheckboxInput, ('check_test',)),
|
||||
django.forms.widgets.Select:
|
||||
_create_widget(
|
||||
floppyforms.widgets.Select,
|
||||
('allow_multiple_selected',)),
|
||||
django.forms.widgets.NullBooleanSelect:
|
||||
_create_widget(
|
||||
floppyforms.widgets.NullBooleanSelect,
|
||||
('allow_multiple_selected',)),
|
||||
django.forms.widgets.SelectMultiple:
|
||||
_create_widget(
|
||||
floppyforms.widgets.SelectMultiple,
|
||||
('allow_multiple_selected',)),
|
||||
django.forms.widgets.RadioSelect:
|
||||
_create_radioselect,
|
||||
django.forms.widgets.CheckboxSelectMultiple:
|
||||
_create_widget(floppyforms.widgets.CheckboxSelectMultiple),
|
||||
django.forms.widgets.MultiWidget:
|
||||
_create_widget(
|
||||
floppyforms.widgets.MultiWidget,
|
||||
init_arguments=('widgets',)),
|
||||
django.forms.widgets.SplitDateTimeWidget:
|
||||
_create_splitdatetimewidget(
|
||||
floppyforms.widgets.SplitDateTimeWidget),
|
||||
django.forms.widgets.SplitHiddenDateTimeWidget:
|
||||
_create_splitdatetimewidget(
|
||||
floppyforms.widgets.SplitHiddenDateTimeWidget),
|
||||
django.forms.extras.widgets.SelectDateWidget:
|
||||
_create_widget(
|
||||
floppyforms.widgets.SelectDateWidget,
|
||||
init_arguments=('years', 'required')),
|
||||
}
|
||||
|
||||
|
||||
def get_floppyform_widget(widget):
|
||||
'''
|
||||
Get an instance of django.forms.widgets.Widget and return a new widget
|
||||
instance but using the corresponding floppyforms widget class.
|
||||
|
||||
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.
|
||||
'''
|
||||
create_widget_class = _django_to_floppyforms_widget.get(
|
||||
widget.__class__, None)
|
||||
if create_widget_class is not None:
|
||||
return create_widget_class(widget)
|
||||
return widget
|
||||
|
||||
|
||||
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(
|
||||
model=model,
|
||||
form=form,
|
||||
fields=fields,
|
||||
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
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
from test_views import *
|
||||
from test_apiviews import *
|
||||
from test_builtin_api_resources import *
|
||||
from test_views import *
|
||||
from test_modelforms import *
|
||||
|
|
|
|||
288
example/blog/tests/test_modelforms.py
Normal file
288
example/blog/tests/test_modelforms.py
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
import floppyforms
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
from djadmin2.forms import get_floppyform_widget, modelform_factory
|
||||
from ..models import Post
|
||||
|
||||
|
||||
class ModelFormFactoryTest(TestCase):
|
||||
def test_modelform_factory(self):
|
||||
form_class = modelform_factory(Post)
|
||||
self.assertTrue(form_class)
|
||||
field = form_class.base_fields['title']
|
||||
self.assertTrue(isinstance(field.widget, floppyforms.TextInput))
|
||||
|
||||
|
||||
class GetFloppyformWidgetTest(TestCase):
|
||||
def assertExpectWidget(self, instance, new_class_,
|
||||
equal_attributes=None):
|
||||
new_instance = get_floppyform_widget(instance)
|
||||
self.assertEqual(new_instance.__class__, new_class_)
|
||||
if equal_attributes:
|
||||
for attribute in equal_attributes:
|
||||
self.assertTrue(
|
||||
hasattr(instance, attribute),
|
||||
'Cannot check attribute %r, not available on original '
|
||||
'widget %r' % (attribute, instance))
|
||||
self.assertTrue(
|
||||
hasattr(new_instance, attribute),
|
||||
'Cannot check attribute %r, not available on generated '
|
||||
'widget %r' % (attribute, new_instance))
|
||||
old_attr = getattr(instance, attribute)
|
||||
new_attr = getattr(new_instance, attribute)
|
||||
self.assertEqual(old_attr, new_attr,
|
||||
'Original widget\'s attribute was not copied: %r != %r' %
|
||||
(old_attr, new_attr))
|
||||
|
||||
def test_created_widget_doesnt_leak_attributes_into_original_widget(self):
|
||||
widget = forms.TextInput()
|
||||
widget.is_required = True
|
||||
widget.attrs = {'placeholder': 'Search ...'}
|
||||
new_widget = get_floppyform_widget(widget)
|
||||
self.assertFalse(widget.__dict__ is new_widget.__dict__)
|
||||
new_widget.is_required = False
|
||||
self.assertEqual(widget.is_required, True)
|
||||
new_widget.attrs['placeholder'] = 'Enter name'
|
||||
self.assertEqual(widget.attrs['placeholder'], 'Search ...')
|
||||
|
||||
def test_copy_attribute_is_required(self):
|
||||
widget = forms.TextInput()
|
||||
widget.is_required = True
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.TextInput,
|
||||
equal_attributes=['is_required'])
|
||||
|
||||
# Test individual widgets
|
||||
|
||||
def test_input_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.Input(),
|
||||
floppyforms.widgets.Input)
|
||||
|
||||
widget = forms.widgets.Input()
|
||||
widget.input_type = 'email'
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.Input,
|
||||
['input_type'])
|
||||
|
||||
def test_textinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.TextInput(),
|
||||
floppyforms.widgets.TextInput)
|
||||
|
||||
def test_passwordinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.PasswordInput(),
|
||||
floppyforms.widgets.PasswordInput)
|
||||
|
||||
def test_hiddeninput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.HiddenInput(),
|
||||
floppyforms.widgets.HiddenInput)
|
||||
|
||||
widget = forms.widgets.HiddenInput()
|
||||
widget.is_hidden = False
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.HiddenInput,
|
||||
['input_type'])
|
||||
|
||||
def test_multiplehiddeninput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.MultipleHiddenInput(),
|
||||
floppyforms.widgets.MultipleHiddenInput)
|
||||
|
||||
widget = forms.widgets.MultipleHiddenInput(choices=(
|
||||
('no', 'Please, No!'),
|
||||
('yes', 'Ok, why not.'),
|
||||
))
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.MultipleHiddenInput,
|
||||
['choices'])
|
||||
|
||||
def test_fileinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.FileInput(),
|
||||
floppyforms.widgets.FileInput)
|
||||
|
||||
widget = forms.widgets.FileInput()
|
||||
widget.needs_multipart_form = False
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.FileInput,
|
||||
['needs_multipart_form'])
|
||||
|
||||
def test_clearablefileinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.ClearableFileInput(),
|
||||
floppyforms.widgets.ClearableFileInput)
|
||||
|
||||
widget = forms.widgets.ClearableFileInput()
|
||||
widget.initial_text = 'some random text 1'
|
||||
widget.input_text = 'some random text 2'
|
||||
widget.clear_checkbox_label = 'some random text 3'
|
||||
widget.template_with_initial = 'some random text 4'
|
||||
widget.template_with_clear = 'some random text 5'
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.ClearableFileInput,
|
||||
['initial_text', 'input_text', 'clear_checkbox_label',
|
||||
'template_with_initial', 'template_with_clear'])
|
||||
|
||||
def test_textarea_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.Textarea(),
|
||||
floppyforms.widgets.Textarea)
|
||||
|
||||
def test_dateinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.DateInput(),
|
||||
floppyforms.DateInput)
|
||||
|
||||
widget = forms.widgets.DateInput(format='DATE_FORMAT')
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.DateInput,
|
||||
['format'])
|
||||
|
||||
def test_datetimeinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.DateTimeInput(),
|
||||
floppyforms.widgets.DateTimeInput)
|
||||
|
||||
widget = forms.widgets.DateTimeInput(format='DATETIME_FORMAT')
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.DateTimeInput,
|
||||
['format'])
|
||||
|
||||
def test_timeinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.TimeInput(),
|
||||
floppyforms.widgets.TimeInput)
|
||||
|
||||
widget = forms.widgets.TimeInput(format='TIME_FORMAT')
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.TimeInput,
|
||||
['format'])
|
||||
|
||||
def test_checkboxinput_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.CheckboxInput(),
|
||||
floppyforms.widgets.CheckboxInput)
|
||||
|
||||
check_test = lambda v: False
|
||||
widget = forms.widgets.CheckboxInput(check_test=check_test)
|
||||
new_widget = get_floppyform_widget(widget)
|
||||
self.assertEqual(widget.check_test, new_widget.check_test)
|
||||
self.assertTrue(new_widget.check_test is check_test)
|
||||
|
||||
def test_select_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.Select(),
|
||||
floppyforms.widgets.Select)
|
||||
|
||||
widget = forms.widgets.Select()
|
||||
widget.allow_multiple_selected = True
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.Select,
|
||||
('allow_multiple_selected',))
|
||||
|
||||
def test_nullbooleanselect_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.NullBooleanSelect(),
|
||||
floppyforms.widgets.NullBooleanSelect)
|
||||
|
||||
def test_selectmultiple_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.SelectMultiple(),
|
||||
floppyforms.widgets.SelectMultiple)
|
||||
|
||||
widget = forms.widgets.SelectMultiple()
|
||||
widget.allow_multiple_selected = False
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.SelectMultiple,
|
||||
('allow_multiple_selected',))
|
||||
|
||||
def test_radioselect_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.RadioSelect(),
|
||||
floppyforms.widgets.RadioSelect)
|
||||
|
||||
widget = forms.widgets.RadioSelect(renderer='custom renderer')
|
||||
# don't overwrite widget with floppyform widget if a custom renderer
|
||||
# was used. We cannot copy this over since floppyform doesn't use the
|
||||
# renderer.
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
forms.widgets.RadioSelect)
|
||||
|
||||
def test_checkboxselectmultiple_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.CheckboxSelectMultiple(),
|
||||
floppyforms.widgets.CheckboxSelectMultiple)
|
||||
|
||||
def test_multi_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.widgets.MultiWidget([]),
|
||||
floppyforms.widgets.MultiWidget)
|
||||
|
||||
text_input = forms.widgets.TextInput()
|
||||
widget = forms.widgets.MultiWidget([text_input])
|
||||
new_widget = get_floppyform_widget(widget)
|
||||
self.assertEqual(widget.widgets, new_widget.widgets)
|
||||
self.assertTrue(new_widget.widgets[0] is text_input)
|
||||
|
||||
def test_splitdatetime_widget(self):
|
||||
widget = forms.widgets.SplitDateTimeWidget()
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.SplitDateTimeWidget)
|
||||
|
||||
widget = forms.widgets.SplitDateTimeWidget(
|
||||
date_format='DATE_FORMAT', time_format='TIME_FORMAT')
|
||||
new_widget = get_floppyform_widget(widget)
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
||||
self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT')
|
||||
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
||||
|
||||
def test_splithiddendatetime_widget(self):
|
||||
widget = forms.widgets.SplitHiddenDateTimeWidget()
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.SplitHiddenDateTimeWidget)
|
||||
|
||||
widget = forms.widgets.SplitHiddenDateTimeWidget(
|
||||
date_format='DATE_FORMAT', time_format='TIME_FORMAT')
|
||||
new_widget = get_floppyform_widget(widget)
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
||||
self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT')
|
||||
self.assertEqual(new_widget.widgets[0].is_hidden, True)
|
||||
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
||||
self.assertEqual(new_widget.widgets[1].is_hidden, True)
|
||||
|
||||
def test_selectdate_widget(self):
|
||||
self.assertExpectWidget(
|
||||
forms.extras.widgets.SelectDateWidget(),
|
||||
floppyforms.widgets.SelectDateWidget)
|
||||
|
||||
widget = forms.extras.widgets.SelectDateWidget(
|
||||
attrs={'attribute': 'value'},
|
||||
years=[2010, 2011, 2012, 2013],
|
||||
required=False)
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.SelectDateWidget,
|
||||
('attrs', 'years', 'required'))
|
||||
|
|
@ -121,6 +121,7 @@ INSTALLED_APPS = (
|
|||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
'django_coverage',
|
||||
'floppyforms',
|
||||
'rest_framework',
|
||||
'djadmin2',
|
||||
'blog',
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ django-debug-toolbar==0.9.4
|
|||
coverage==3.6
|
||||
django-coverage==1.2.2
|
||||
django-extra-views==0.6.2
|
||||
django-floppyforms==1.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue