2013-07-07 14:13:48 +00:00
# -*- coding: utf-8 -*-
from __future__ import division , absolute_import , unicode_literals
2013-05-22 17:12:09 +00:00
from copy import deepcopy
2013-11-28 11:02:43 +00:00
import django
2013-05-22 17:12:09 +00:00
import django . forms
import django . forms . extras . widgets
2016-05-07 23:31:16 +00:00
import django . forms . models
2013-05-31 06:49:24 +00:00
import floppyforms
2016-05-07 23:31:16 +00:00
from django . contrib . auth import authenticate
from django . contrib . auth . forms import AuthenticationForm
from django . contrib . auth . forms import UserCreationForm , UserChangeForm
2016-05-19 16:50:56 +00:00
from django . core . urlresolvers import reverse_lazy
from django . utils . translation import ugettext_lazy as _
2013-05-22 17:12:09 +00:00
_WIDGET_COMMON_ATTRIBUTES = (
' is_hidden ' ,
' needs_multipart_form ' ,
' is_localized ' ,
' is_required ' )
_WIDGET_COMMON_ARGUMENTS = ( ' attrs ' , )
def _copy_attributes ( original , new_widget , attributes ) :
2013-07-06 16:33:44 +00:00
for attr in attributes :
original_value = getattr ( original , attr )
original_value = deepcopy ( original_value )
2013-11-28 11:15:52 +00:00
# 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 )
2013-05-22 17:12:09 +00:00
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 ,
2013-06-04 20:31:59 +00:00
( ' choices ' , ' allow_multiple_selected ' , ) )
2013-05-22 17:12:09 +00:00
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 = [
2013-05-22 20:16:03 +00:00
floppify_widget ( widget )
2013-05-22 17:12:09 +00:00
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 ,
2013-06-04 20:31:59 +00:00
( ' choices ' , ' allow_multiple_selected ' , ) ) ,
2013-05-22 17:12:09 +00:00
django . forms . widgets . NullBooleanSelect :
_create_widget (
floppyforms . widgets . NullBooleanSelect ,
2013-06-04 20:31:59 +00:00
( ' choices ' , ' allow_multiple_selected ' , ) ) ,
2013-05-22 17:12:09 +00:00
django . forms . widgets . SelectMultiple :
_create_widget (
floppyforms . widgets . SelectMultiple ,
2013-06-04 20:31:59 +00:00
( ' choices ' , ' allow_multiple_selected ' , ) ) ,
2013-05-22 17:12:09 +00:00
django . forms . widgets . RadioSelect :
_create_radioselect ,
django . forms . widgets . CheckboxSelectMultiple :
2013-06-04 20:31:59 +00:00
_create_widget (
floppyforms . widgets . CheckboxSelectMultiple ,
( ' choices ' , ' allow_multiple_selected ' , ) ) ,
2013-05-22 17:12:09 +00:00
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 ,
2016-05-07 20:59:15 +00:00
init_arguments = ( ' years ' , ) if django . VERSION > = ( 1 , 7 ) else ( ' years ' , ' required ' ) ) ,
2013-05-22 17:12:09 +00:00
}
2013-05-22 20:16:03 +00:00
_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 ) ,
2016-05-19 15:05:21 +00:00
django . forms . fields . GenericIPAddressField :
2016-05-19 16:16:01 +00:00
_create_widget ( floppyforms . widgets . TextInput ) ,
2013-05-22 20:16:03 +00:00
django . forms . fields . SplitDateTimeField :
_create_splitdatetimewidget ( floppyforms . widgets . SplitDateTimeWidget ) ,
}
2013-05-22 17:12:09 +00:00
2013-11-28 11:02:43 +00:00
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:
2016-05-07 21:26:48 +00:00
# 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
2013-11-28 11:02:43 +00:00
# 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
2013-05-22 20:16:03 +00:00
def floppify_widget ( widget , field = None ) :
2013-05-22 17:12:09 +00:00
'''
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 .
2013-05-22 20:16:03 +00:00
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 .
2013-05-22 17:12:09 +00:00
'''
2013-05-22 20:16:03 +00:00
if field is not None :
create_widget = _django_field_to_floppyform_widget . get (
field . __class__ )
if create_widget is not None :
2013-11-28 11:02:43 +00:00
if allow_floppify_widget_for_field ( field ) :
2013-05-22 20:16:03 +00:00
return create_widget ( widget )
create_widget = _django_to_floppyforms_widget . get ( widget . __class__ )
if create_widget is not None :
return create_widget ( widget )
2013-05-22 17:12:09 +00:00
return widget
2013-05-22 20:16:03 +00:00
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
2013-05-22 17:12:09 +00:00
def modelform_factory ( model , form = django . forms . models . ModelForm , fields = None ,
2013-07-06 07:37:25 +00:00
exclude = None , formfield_callback = None , widgets = None ) :
2013-05-22 17:12:09 +00:00
form_class = django . forms . models . modelform_factory (
model = model ,
form = form ,
fields = fields ,
exclude = exclude ,
formfield_callback = formfield_callback ,
widgets = widgets )
2013-05-22 20:16:03 +00:00
return floppify_form ( form_class )
2013-05-31 06:49:24 +00:00
2013-07-07 08:15:22 +00:00
# Translators : %(username)s will be replaced by the username_field name
# (default : username, but could be email, or something else)
2016-05-19 16:50:56 +00:00
ERROR_MESSAGE = _ (
2016-05-07 20:59:15 +00:00
" Please enter the correct %(username)s and password "
" for a staff account. Note that both fields may be case-sensitive. "
)
2013-05-31 06:49:24 +00:00
class AdminAuthenticationForm ( AuthenticationForm ) :
"""
A custom authentication form used in the admin app .
Liberally copied from django . contrib . admin . forms . AdminAuthenticationForm
"""
2013-07-06 07:37:25 +00:00
error_messages = {
2016-05-19 16:50:56 +00:00
' required ' : _ ( " Please log in again, because your session has expired. " ) ,
2013-07-06 07:37:25 +00:00
}
2016-05-07 20:59:15 +00:00
this_is_the_login_form = django . forms . BooleanField (
widget = floppyforms . HiddenInput ,
initial = 1 ,
error_messages = error_messages
)
2013-05-31 06:49:24 +00:00
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
2013-06-01 16:02:01 +00:00
2016-05-19 16:50:56 +00:00
class Admin2UserChangeForm ( UserChangeForm ) :
def __init__ ( self , * args , * * kwargs ) :
super ( Admin2UserChangeForm , self ) . __init__ ( * args , * * kwargs )
print ( self . fields [ ' password ' ] . help_text )
2016-05-19 17:21:56 +00:00
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 ( ) )
2016-05-19 16:50:56 +00:00
def get_update_password_url ( self ) :
2016-05-19 17:21:56 +00:00
if self . instance and self . instance . pk :
return reverse_lazy ( ' admin2:password_change ' , args = [ self . instance . pk ] )
return ' password/ '
2016-05-19 17:27:02 +00:00
2013-06-01 16:02:01 +00:00
UserCreationForm = floppify_form ( UserCreationForm )
2016-05-19 16:50:56 +00:00
UserChangeForm = floppify_form ( Admin2UserChangeForm )