Removing django-crispy-forms and django-floppyforms

Resolve #433 #343
This commit is contained in:
arthur 2016-05-26 19:14:39 +02:00
parent 0d401c2b9a
commit 7f2ae25920
17 changed files with 311 additions and 665 deletions

View file

@ -4,7 +4,8 @@ History
0.6.2 (?)
* Fix Django 1.8 issues and add 1.9 compatibility
* Update all dependancies (DRF, floppyforms, filters, ...)
* Update django-rest-framework
* Remove django-crispy-forms and django-floppyforms
* Regenerate example project to make it django 1.9 compatible
* Update tox and travis and add flake8
* Rename AdminModel2Mixin to Admin2ModelMixin

View file

@ -44,18 +44,16 @@ Screenshots
Requirements
============
* Django 1.6+
* Django 1.7+
* Python 2.7+ or Python 3.3+
* django-braces_
* django-extra-views_
* django-floppyforms_
* django-rest-framework_
* django-filter_
* Sphinx_ (for documentation)
.. _django-braces: https://github.com/brack3t/django-braces
.. _django-extra-views: https://github.com/AndrewIngram/django-extra-views
.. _django-floppyforms: https://github.com/brutasse/django-floppyforms
.. _django-rest-framework: https://github.com/tomchristie/django-rest-framework
.. _django-filter: https://github.com/alex/django-filter
.. _Sphinx: http://sphinx-doc.org/
@ -79,8 +77,6 @@ Add djadmin2 and rest_framework to your settings file:
...
'djadmin2',
'rest_framework', # for the browsable API templates
'floppyforms', # For HTML5 form fields
'crispy_forms', # Required for the default theme's layout
...
)
@ -94,7 +90,6 @@ Add setting for apps and the default theme in your settings file:
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
CRISPY_TEMPLATE_PACK = "bootstrap3"
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"
Add djadmin2 urls to your URLconf:
@ -154,7 +149,6 @@ Themes are a new default theme based on bootstrap3 and also some new settings to
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
CRISPY_TEMPLATE_PACK = "bootstrap3"
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"

View file

@ -0,0 +1,253 @@
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)

View file

@ -1,272 +1,16 @@
# -*- 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 import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.core.exceptions import ValidationError
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 = _(
@ -284,8 +28,8 @@ class AdminAuthenticationForm(AuthenticationForm):
error_messages = {
'required': _("Please log in again, because your session has expired."),
}
this_is_the_login_form = django.forms.BooleanField(
widget=floppyforms.HiddenInput,
this_is_the_login_form = forms.BooleanField(
widget=forms.HiddenInput,
initial=1,
error_messages=error_messages
)
@ -298,16 +42,20 @@ class AdminAuthenticationForm(AuthenticationForm):
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise floppyforms.ValidationError(message % {
raise 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 % {
raise ValidationError(message % {
'username': self.username_field.verbose_name
})
return self.cleaned_data
class Admin2UserCreationForm(UserCreationForm):
pass
class Admin2UserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs):
@ -319,6 +67,3 @@ class Admin2UserChangeForm(UserChangeForm):
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)

View file

@ -1,10 +1,9 @@
from django import forms
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import RequestFactory
import floppyforms
from djadmin2.site import djadmin2_site
from ..admin2 import UserAdmin2
@ -23,7 +22,7 @@ class UserAdminTest(TestCase):
form = UserAdmin2.create_form_class()
self.assertTrue(
isinstance(form.fields['username'].widget,
floppyforms.TextInput))
forms.TextInput))
request = self.factory.get(reverse('admin2:auth_user_create'))
request.user = self.user
@ -34,16 +33,16 @@ class UserAdminTest(TestCase):
form = response.context_data['form']
self.assertTrue(
isinstance(form.fields['username'].widget,
floppyforms.TextInput))
forms.TextInput))
def test_update_form_uses_floppyform_widgets(self):
form = UserAdmin2.update_form_class()
self.assertTrue(
isinstance(form.fields['username'].widget,
floppyforms.TextInput))
forms.TextInput))
self.assertTrue(
isinstance(form.fields['date_joined'].widget,
floppyforms.DateTimeInput))
forms.DateTimeInput))
request = self.factory.get(
reverse('admin2:auth_user_update', args=(self.user.pk,)))
@ -55,7 +54,7 @@ class UserAdminTest(TestCase):
form = response.context_data['form']
self.assertTrue(
isinstance(form.fields['username'].widget,
floppyforms.TextInput))
forms.TextInput))
self.assertTrue(
isinstance(form.fields['date_joined'].widget,
floppyforms.DateTimeInput))
forms.DateTimeInput))

View file

@ -1,5 +1,5 @@
{% extends "djadmin2theme_bootstrap3/base.html" %}
{% load i18n staticfiles admin2_tags crispy_forms_tags %}
{% load i18n staticfiles admin2_tags %}
{% block navbar %}{% endblock navbar %}
{% block breacrumbs %}{% endblock breacrumbs %}
@ -17,7 +17,7 @@
<div class="panel-body">
<form method="post" class="">
{% csrf_token %}
{{ form|crispy }}
{{ form }}
<input type="hidden" name="this_is_the_login_form" value="1"/>
<input type="hidden" name="next" value="{{ next }}"/>
<button class="btn btn-lg btn-success btn-block" type="submit">

View file

@ -1,5 +1,5 @@
{% extends "djadmin2theme_bootstrap3/base.html" %}
{% load i18n admin2_tags crispy_forms_tags %}
{% load i18n admin2_tags %}
{% block page_title %}{% trans "Password change" %}: {{ form.user }}{% endblock page_title %}
@ -24,7 +24,7 @@
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{{ form }}
<button class="btn btn-sm btn-success" type="submit" name="_save">{% trans "Change my password" %}</button>
</form>
</div>

View file

@ -1,8 +1,8 @@
{% load i18n admin2_tags crispy_forms_tags %}
{% load i18n admin2_tags %}
{% for inline_form in formset %}
<div class="change_form space-below">
{{ inline_form|crispy }}
{{ inline_form }}
{% if not inline_form.visible_fields %}
<p class="alert alert-warning empty-form">
{% trans "This form doesn't have visible fields. This doesn't mean there are no hidden fields." %}

View file

@ -1,6 +1,6 @@
{% extends "djadmin2theme_bootstrap3/base.html" %}
{% load i18n admin2_tags crispy_forms_tags %}
{% load i18n admin2_tags %}
{# Translators : examples : Add post, Change object #}
{% block title %}{% blocktrans with action=action model_name=model_name %}{{ action_name }} {{ model_name }}
@ -53,7 +53,7 @@
<div class="panel-body">
<div class="change_form">
{% csrf_token %}
{{ form|crispy }}
{{ form.as_p }}
{% if not form.visible_fields %}
<p class="alert alert-warning empty-form">
{% trans "This form doesn't have visible fields. This doesn't mean there are no hidden fields." %}

View file

@ -9,6 +9,7 @@ from collections import namedtuple
import extra_views
from django.conf.urls import url
from django.core.urlresolvers import reverse
from django.forms import modelform_factory
from django.utils.six import with_metaclass
from . import actions
@ -16,7 +17,6 @@ from . import apiviews
from . import settings
from . import utils
from . import views
from .forms import modelform_factory
logger = logging.getLogger('djadmin2')

View file

@ -23,8 +23,6 @@ Add djadmin2 and rest_framework to your settings file:
'djadmin2',
'djadmin2.themes.djadmin2theme_bootstrap3', # for the default theme
'rest_framework', # for the browsable API templates
'floppyforms', # For HTML5 form fields
'crispy_forms', # Required for the default theme's layout
...
)
@ -32,7 +30,6 @@ Add djadmin2 and rest_framework to your settings file:
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
CRISPY_TEMPLATE_PACK = "bootstrap3"
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"
Add djadmin2 urls to your URLconf:
@ -70,5 +67,4 @@ Themes are a new default theme based on bootstrap3 and also some new settings to
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
CRISPY_TEMPLATE_PACK = "bootstrap3"
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"

View file

@ -1,10 +1,9 @@
from __future__ import unicode_literals
import floppyforms
from django import forms
from django.forms import modelform_factory
from django.test import TestCase
from djadmin2.forms import floppify_widget, floppify_form, modelform_factory
from ..models import Post
@ -14,340 +13,7 @@ class ModelFormFactoryTest(TestCase):
form_class = modelform_factory(Post, exclude=[])
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_attributes=None):
new_instance = floppify_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))
if new_attributes:
for attribute, value in new_attributes.items():
self.assertTrue(
hasattr(new_instance, attribute),
'Cannot check new attribute %r, not available on '
'generated widget %r' % (attribute, new_instance))
new_attr = getattr(new_instance, attribute)
self.assertEqual(new_attr, value,
'Generated widget\'s attribute is not as expected: '
'%r != %r' % (new_attr, value))
def test_created_widget_doesnt_leak_attributes_into_original_widget(self):
widget = forms.TextInput()
widget.is_required = True
widget.attrs = {'placeholder': 'Search ...'}
new_widget = floppify_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,
['input_type'],
{'input_type': 'text'})
def test_passwordinput_widget(self):
self.assertExpectWidget(
forms.widgets.PasswordInput(),
floppyforms.widgets.PasswordInput,
['input_type'],
{'input_type': 'password'})
def test_hiddeninput_widget(self):
self.assertExpectWidget(
forms.widgets.HiddenInput(),
floppyforms.widgets.HiddenInput)
widget = forms.widgets.HiddenInput()
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='%Y-%m-%d')
self.assertExpectWidget(
widget,
floppyforms.widgets.DateInput,
['format'],
{'input_type': 'date'})
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'],
{'input_type': 'datetime'})
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'],
{'input_type': 'time'})
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 = floppify_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):
choices = (
('draft', 'Draft'),
('public', 'Public'),
)
self.assertExpectWidget(
forms.widgets.Select(),
floppyforms.widgets.Select)
widget = forms.widgets.Select(choices=choices)
widget.allow_multiple_selected = True
self.assertExpectWidget(
widget,
floppyforms.widgets.Select,
('choices', 'allow_multiple_selected',))
def test_nullbooleanselect_widget(self):
self.assertExpectWidget(
forms.widgets.NullBooleanSelect(),
floppyforms.widgets.NullBooleanSelect,
('choices', 'allow_multiple_selected',))
widget = forms.widgets.NullBooleanSelect()
widget.choices = list(widget.choices)
value, label = widget.choices[0]
widget.choices[0] = value, 'Maybe'
self.assertExpectWidget(
widget,
floppyforms.widgets.NullBooleanSelect,
('choices', 'allow_multiple_selected',))
def test_selectmultiple_widget(self):
choices = (
('draft', 'Draft'),
('public', 'Public'),
)
self.assertExpectWidget(
forms.widgets.SelectMultiple(),
floppyforms.widgets.SelectMultiple)
widget = forms.widgets.SelectMultiple(choices=choices)
widget.allow_multiple_selected = False
self.assertExpectWidget(
widget,
floppyforms.widgets.SelectMultiple,
('choices', 'allow_multiple_selected',))
def test_radioselect_widget(self):
choices = (
('draft', 'Draft'),
('public', 'Public'),
)
self.assertExpectWidget(
forms.widgets.RadioSelect(),
floppyforms.widgets.RadioSelect)
self.assertExpectWidget(
forms.widgets.RadioSelect(choices=choices),
floppyforms.widgets.RadioSelect,
('choices', 'allow_multiple_selected',))
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):
choices = (
('draft', 'Draft'),
('public', 'Public'),
)
self.assertExpectWidget(
forms.widgets.CheckboxSelectMultiple(),
floppyforms.widgets.CheckboxSelectMultiple)
self.assertExpectWidget(
forms.widgets.CheckboxSelectMultiple(choices=choices),
floppyforms.widgets.CheckboxSelectMultiple,
('choices', 'allow_multiple_selected',))
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 = floppify_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='%Y-%m-%d', time_format='TIME_FORMAT')
new_widget = floppify_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, '%Y-%m-%d')
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='%Y-%m-%d', time_format='TIME_FORMAT')
new_widget = floppify_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, '%Y-%m-%d')
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])
self.assertExpectWidget(
widget,
floppyforms.widgets.SelectDateWidget,
('attrs', 'years'))
self.assertTrue(isinstance(field.widget, forms.TextInput))
class ModelFormTest(TestCase):
@ -360,7 +26,7 @@ class ModelFormTest(TestCase):
form = form_class()
self.assertTrue(isinstance(
form.fields['title'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
def test_declared_fields(self):
class MyForm(forms.ModelForm):
@ -369,14 +35,14 @@ class ModelFormTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
self.assertTrue(isinstance(
form_class.base_fields['subtitle'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
self.assertTrue(isinstance(
form_class.declared_fields['subtitle'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
self.assertTrue(isinstance(
form_class.base_fields['title'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
# title is not defined in declared fields
def test_additional_form_fields(self):
@ -387,7 +53,7 @@ class ModelFormTest(TestCase):
form = form_class()
self.assertTrue(isinstance(
form.fields['subtitle'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
def test_subclassing_forms(self):
class MyForm(forms.ModelForm):
@ -404,13 +70,13 @@ class ModelFormTest(TestCase):
form = form_class()
self.assertTrue(isinstance(
form.fields['title'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
self.assertTrue(isinstance(
form.fields['subtitle'].widget,
floppyforms.widgets.TextInput))
forms.TextInput))
self.assertTrue(isinstance(
form.fields['created'].widget,
floppyforms.widgets.DateInput))
forms.DateInput))
class FieldWidgetTest(TestCase):
@ -428,10 +94,9 @@ class FieldWidgetTest(TestCase):
model = Post
exclude = []
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))
widget = MyForm().fields['email'].widget
self.assertFalse(isinstance(widget, forms.EmailInput))
self.assertTrue(isinstance(widget, forms.Textarea))
def test_float_field(self):
class MyForm(forms.ModelForm):
@ -439,7 +104,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['float'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput))
self.assertTrue(isinstance(widget, forms.NumberInput))
self.assertEqual(widget.input_type, 'number')
def test_decimal_field(self):
@ -448,7 +113,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['decimal'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput))
self.assertTrue(isinstance(widget, forms.NumberInput))
self.assertEqual(widget.input_type, 'number')
def test_integer_field(self):
@ -457,7 +122,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['integer'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.NumberInput))
self.assertTrue(isinstance(widget, forms.NumberInput))
self.assertEqual(widget.input_type, 'number')
def test_email_field(self):
@ -466,7 +131,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['email'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.EmailInput))
self.assertTrue(isinstance(widget, forms.EmailInput))
self.assertEqual(widget.input_type, 'email')
def test_url_field(self):
@ -475,7 +140,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['url'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.URLInput))
self.assertTrue(isinstance(widget, forms.URLInput))
self.assertEqual(widget.input_type, 'url')
def test_slug_field(self):
@ -484,7 +149,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['slug'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput))
self.assertTrue(isinstance(widget, forms.TextInput))
self.assertEqual(widget.input_type, 'text')
def test_genericipaddress_field(self):
@ -493,7 +158,7 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['ipaddress'].widget
self.assertTrue(isinstance(widget, floppyforms.widgets.TextInput))
self.assertTrue(isinstance(widget, forms.TextInput))
self.assertEqual(widget.input_type, 'text')
def test_splitdatetime_field(self):
@ -503,8 +168,8 @@ class FieldWidgetTest(TestCase):
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
widget = form_class().fields['splitdatetime'].widget
self.assertTrue(isinstance(
widget, floppyforms.widgets.SplitDateTimeWidget))
widget, forms.SplitDateTimeWidget))
self.assertTrue(isinstance(
widget.widgets[0], floppyforms.widgets.DateInput))
widget.widgets[0], forms.DateInput))
self.assertTrue(isinstance(
widget.widgets[1], floppyforms.widgets.TimeInput))
widget.widgets[1], forms.TimeInput))

Binary file not shown.

View file

@ -40,9 +40,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'floppyforms',
'rest_framework',
'crispy_forms',
'djadmin2',
'djadmin2.tests',
'djadmin2.themes.djadmin2theme_bootstrap3',
@ -149,5 +147,4 @@ REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
CRISPY_TEMPLATE_PACK = "bootstrap3"
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"

View file

@ -1,9 +1,7 @@
django-extra-views>=0.6.5
django-braces>=1.3.0
djangorestframework>=3.3.3
django-floppyforms>=1.6.2
django-filter>=0.13.0
django-crispy-forms>=1.3.2
django-debug-toolbar>=0.9.4
future>=0.15.2
pytz==2016.4

View file

@ -131,9 +131,7 @@ setup(
'django-extra-views>=0.6.5',
'django-braces>=1.3.0',
'djangorestframework>=3.3.3',
'django-floppyforms>=1.6.2',
'django-filter>=0.13.0',
'django-crispy-forms>=1.3.2',
'pytz==2014.7',
'future>=0.15.2',
],

10
tox.ini
View file

@ -6,11 +6,11 @@ exclude = migrations/*,docs/*
[tox]
envlist =
py27-{1.8,1.9,master},
py33-{1.8},
py34-{1.8,1.9,master},
py35-{1.8,1.9,master},
; py27-{1.8,1.9,master},
; py33-{1.8},
; py34-{1.8,1.9,master},
; py35-{1.8,1.9,master},
py35-1.9
[testenv]
commands =
flake8 djadmin2