mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-16 22:20:24 +00:00
parent
0d401c2b9a
commit
7f2ae25920
17 changed files with 311 additions and 665 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
253
djadmin2/contrib/floppyforms.py
Normal file
253
djadmin2/contrib/floppyforms.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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." %}
|
||||
|
|
|
|||
|
|
@ -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." %}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -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
10
tox.ini
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue