django-admin2/djadmin2/forms.py

324 lines
13 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from copy import deepcopy
import django
import django.forms
import django.forms.extras.widgets
import django.forms.models
import floppyforms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
_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)
# Translators : %(username)s will be replaced by the username_field name
# (default : username, but could be email, or something else)
ERROR_MESSAGE = _(
"Please enter the correct %(username)s and password "
"for a staff account. Note that both fields may be case-sensitive."
)
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
Liberally copied from django.contrib.admin.forms.AdminAuthenticationForm
"""
error_messages = {
'required': _("Please log in again, because your session has expired."),
}
this_is_the_login_form = django.forms.BooleanField(
widget=floppyforms.HiddenInput,
initial=1,
error_messages=error_messages
)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
message = ERROR_MESSAGE
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise floppyforms.ValidationError(message % {
'username': self.username_field.verbose_name
})
elif not self.user_cache.is_active or not self.user_cache.is_staff:
raise floppyforms.ValidationError(message % {
'username': self.username_field.verbose_name
})
return self.cleaned_data
class Admin2UserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs):
super(Admin2UserChangeForm, self).__init__(*args, **kwargs)
print(self.fields['password'].help_text)
self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using <a href=\"%s\">this form</a>." % self.get_update_password_url())
def get_update_password_url(self):
if self.instance and self.instance.pk:
return reverse_lazy('admin2:password_change', args=[self.instance.pk])
return 'password/'
UserCreationForm = floppify_form(UserCreationForm)
UserChangeForm = floppify_form(Admin2UserChangeForm)