mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-17 06:30:25 +00:00
254 lines
10 KiB
Python
254 lines
10 KiB
Python
import django
|
|
import floppyforms
|
|
from copy import deepcopy
|
|
|
|
_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)
|
|
|
|
# Don't set the attribute if it is a property. In that case we can be
|
|
# sure that the widget class is taking care of the calculation for that
|
|
# property.
|
|
old_value_on_new_widget = getattr(new_widget.__class__, attr, None)
|
|
if not isinstance(old_value_on_new_widget, property):
|
|
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,
|
|
('choices', '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 = [
|
|
floppify_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,
|
|
init_arguments=('format',)),
|
|
django.forms.widgets.DateTimeInput:
|
|
_create_widget(
|
|
floppyforms.widgets.DateTimeInput,
|
|
init_arguments=('format',)),
|
|
django.forms.widgets.TimeInput:
|
|
_create_widget(
|
|
floppyforms.widgets.TimeInput,
|
|
init_arguments=('format',)),
|
|
django.forms.widgets.CheckboxInput:
|
|
_create_widget(floppyforms.widgets.CheckboxInput, ('check_test',)),
|
|
django.forms.widgets.Select:
|
|
_create_widget(
|
|
floppyforms.widgets.Select,
|
|
('choices', 'allow_multiple_selected',)),
|
|
django.forms.widgets.NullBooleanSelect:
|
|
_create_widget(
|
|
floppyforms.widgets.NullBooleanSelect,
|
|
('choices', 'allow_multiple_selected',)),
|
|
django.forms.widgets.SelectMultiple:
|
|
_create_widget(
|
|
floppyforms.widgets.SelectMultiple,
|
|
('choices', 'allow_multiple_selected',)),
|
|
django.forms.widgets.RadioSelect:
|
|
_create_radioselect,
|
|
django.forms.widgets.CheckboxSelectMultiple:
|
|
_create_widget(
|
|
floppyforms.widgets.CheckboxSelectMultiple,
|
|
('choices', 'allow_multiple_selected',)),
|
|
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',) if django.VERSION >= (1, 7) else ('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.GenericIPAddressField:
|
|
_create_widget(floppyforms.widgets.TextInput),
|
|
django.forms.fields.SplitDateTimeField:
|
|
_create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget),
|
|
}
|
|
|
|
|
|
def allow_floppify_widget_for_field(field):
|
|
'''
|
|
We only allow to replace a widget with the floppyform counterpart if the
|
|
original, by django determined widget is still in place. We don't want to
|
|
override custom widgets that a user specified.
|
|
'''
|
|
# There is a special case for IntegerFields (and all subclasses) that
|
|
# replaces the default TextInput with a NumberInput, if localization is
|
|
# turned off. That applies for Django 1.6 upwards.
|
|
# See the relevant source code in django:
|
|
# https://github.com/django/django/blob/1.9.6/django/forms/fields.py#L261-264
|
|
if isinstance(field, django.forms.IntegerField) and not field.localize:
|
|
if field.widget.__class__ is django.forms.NumberInput:
|
|
return True
|
|
|
|
# We can check if the widget was replaced by comparing the class of the
|
|
# specified widget with the default widget that is specified on the field
|
|
# class.
|
|
if field.widget.__class__ is field.__class__.widget:
|
|
return True
|
|
|
|
# At that point we are assuming that the user replaced the original widget
|
|
# with a custom one. So we don't allow to overwrite it.
|
|
return False
|
|
|
|
|
|
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.
|
|
|
|
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.
|
|
'''
|
|
if field is not None:
|
|
create_widget = _django_field_to_floppyform_widget.get(
|
|
field.__class__)
|
|
if create_widget is not None:
|
|
if allow_floppify_widget_for_field(field):
|
|
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(
|
|
model=model,
|
|
form=form,
|
|
fields=fields,
|
|
exclude=exclude,
|
|
formfield_callback=formfield_callback,
|
|
widgets=widgets)
|
|
return floppify_form(form_class)
|