Fixed #19733 - deprecated ModelForms without 'fields' or 'exclude', and added '__all__' shortcut

This also updates all dependent functionality, including modelform_factory
 and modelformset_factory, and the generic views `ModelFormMixin`,
 `CreateView` and `UpdateView` which gain a new `fields` attribute.
This commit is contained in:
Luke Plant 2013-02-21 21:56:55 +00:00
parent 1e37cb37ce
commit f026a519ae
34 changed files with 578 additions and 201 deletions

View File

@ -5,7 +5,7 @@ from django import forms
from django.conf import settings
from django.forms.formsets import all_valid, DELETION_FIELD_NAME
from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet)
inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
@ -488,6 +488,10 @@ class ModelAdmin(BaseModelAdmin):
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
defaults['fields'] = forms.ALL_FIELDS
try:
return modelform_factory(self.model, **defaults)
except FieldError as e:
@ -523,6 +527,10 @@ class ModelAdmin(BaseModelAdmin):
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
if (defaults.get('fields') is None
and not modelform_defines_fields(defaults.get('form'))):
defaults['fields'] = forms.ALL_FIELDS
return modelform_factory(self.model, **defaults)
def get_changelist_formset(self, request, **kwargs):
@ -1527,6 +1535,10 @@ class InlineModelAdmin(BaseModelAdmin):
return result
defaults['form'] = DeleteProtectedModelForm
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
defaults['fields'] = forms.ALL_FIELDS
return inlineformset_factory(self.parent_model, self.model, **defaults)
def get_fieldsets(self, request, obj=None):

View File

@ -130,6 +130,7 @@ class UserChangeForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)

View File

@ -13,8 +13,9 @@ from django.db.models import signals
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
from django.db.models.related import PathInfo
from django.db.models.sql.where import Constraint
from django.forms import ModelForm
from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
from django.forms import ModelForm, ALL_FIELDS
from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance,
modelform_defines_fields)
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
from django.contrib.contenttypes.models import ContentType
from django.utils import six
@ -480,6 +481,10 @@ class GenericInlineModelAdmin(InlineModelAdmin):
"exclude": exclude
}
defaults.update(kwargs)
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
defaults['fields'] = ALL_FIELDS
return generic_inlineformset_factory(self.model, **defaults)
class GenericStackedInline(GenericInlineModelAdmin):

View File

@ -12,6 +12,7 @@ class FlatpageForm(forms.ModelForm):
class Meta:
model = FlatPage
fields = '__all__'
def clean_url(self):
url = self.cleaned_data['url']

View File

@ -60,9 +60,11 @@ class TestModel(models.Model):
class TestModelForm(forms.ModelForm):
class Meta:
model = TestModel
fields = '__all__'
TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2)
TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2,
fields='__all__')
class TestWizard(WizardView):

View File

@ -15,6 +15,7 @@ from django.utils._os import upath
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
UserFormSet = forms.models.modelformset_factory(User, form=UserForm, extra=2)

View File

@ -5,6 +5,8 @@ and database field objects.
from __future__ import absolute_import, unicode_literals
import warnings
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
from django.forms.fields import Field, ChoiceField
from django.forms.forms import BaseForm, get_declared_fields
@ -22,8 +24,12 @@ from django.utils.translation import ugettext_lazy as _, ugettext
__all__ = (
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
'ALL_FIELDS',
)
ALL_FIELDS = '__all__'
def construct_instance(form, instance, fields=None, exclude=None):
"""
Constructs and returns a model instance from the bound ``form``'s
@ -211,7 +217,7 @@ class ModelFormMetaclass(type):
# of ('foo',)
for opt in ['fields', 'exclude']:
value = getattr(opts, opt)
if isinstance(value, six.string_types):
if isinstance(value, six.string_types) and value != ALL_FIELDS:
msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
"Did you mean to type: ('%(value)s',)?" % {
'model': new_class.__name__,
@ -222,6 +228,20 @@ class ModelFormMetaclass(type):
if opts.model:
# If a model is defined, extract form fields from it.
if opts.fields is None and opts.exclude is None:
# This should be some kind of assertion error once deprecation
# cycle is complete.
warnings.warn("Creating a ModelForm without either the 'fields' attribute "
"or the 'exclude' attribute is deprecated - form %s "
"needs updating" % name,
PendingDeprecationWarning)
if opts.fields == ALL_FIELDS:
# sentinel for fields_for_model to indicate "get the list of
# fields from the model"
opts.fields = None
fields = fields_for_model(opts.model, opts.fields,
opts.exclude, opts.widgets, formfield_callback)
# make sure opts.fields doesn't specify an invalid field
@ -394,7 +414,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
Returns a ModelForm containing form fields for the given model.
``fields`` is an optional list of field names. If provided, only the named
fields will be included in the returned fields.
fields will be included in the returned fields. If omitted or '__all__',
all fields will be used.
``exclude`` is an optional list of field names. If provided, the named
fields will be excluded from the returned fields, even if they are listed
@ -434,6 +455,15 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
'formfield_callback': formfield_callback
}
# The ModelFormMetaclass will trigger a similar warning/error, but this will
# be difficult to debug for code that needs updating, so we produce the
# warning here too.
if (getattr(Meta, 'fields', None) is None and
getattr(Meta, 'exclude', None) is None):
warnings.warn("Calling modelform_factory without defining 'fields' or "
"'exclude' explicitly is deprecated",
PendingDeprecationWarning, stacklevel=2)
# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)
@ -701,6 +731,21 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
"""
Returns a FormSet class for the given Django model class.
"""
# modelform_factory will produce the same warning/error, but that will be
# difficult to debug for code that needs upgrading, so we produce the
# warning here too. This logic is reproducing logic inside
# modelform_factory, but it can be removed once the deprecation cycle is
# complete, since the validation exception will produce a helpful
# stacktrace.
meta = getattr(form, 'Meta', None)
if meta is None:
meta = type(str('Meta'), (object,), {})
if (getattr(meta, 'fields', fields) is None and
getattr(meta, 'exclude', exclude) is None):
warnings.warn("Calling modelformset_factory without defining 'fields' or "
"'exclude' explicitly is deprecated",
PendingDeprecationWarning, stacklevel=2)
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback,
widgets=widgets)
@ -1091,3 +1136,11 @@ class ModelMultipleChoiceField(ModelChoiceField):
initial_set = set([force_text(value) for value in self.prepare_value(initial)])
data_set = set([force_text(value) for value in data])
return data_set != initial_set
def modelform_defines_fields(form_class):
return (form_class is not None and (
hasattr(form_class, '_meta') and
(form_class._meta.fields is not None or
form_class._meta.exclude is not None)
))

View File

@ -1,3 +1,5 @@
import warnings
from django.forms import models as model_forms
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
@ -95,7 +97,14 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
return model_forms.modelform_factory(model)
fields = getattr(self, 'fields', None)
if fields is None:
warnings.warn("Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is deprecated." % self.__class__.__name__,
PendingDeprecationWarning)
return model_forms.modelform_factory(model, fields=fields)
def get_form_kwargs(self):
"""

View File

@ -110,6 +110,7 @@ CreateView
class AuthorCreate(CreateView):
model = Author
fields = ['name']
UpdateView
----------
@ -152,6 +153,7 @@ UpdateView
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
DeleteView
----------

View File

@ -116,6 +116,18 @@ ModelFormMixin
by examining ``self.object`` or
:attr:`~django.views.generic.detail.SingleObjectMixin.queryset`.
.. attribute:: fields
.. versionadded:: 1.6
A list of names of fields. This is interpreted the same way as the
``Meta.fields`` attribute of :class:`~django.forms.ModelForm`.
This is a required attribute if you are generating the form class
automatically (e.g. using ``model``). Omitting this attribute will
result in all fields being used, but this behaviour is deprecated
and will be removed in Django 1.8.
.. attribute:: success_url
The URL to redirect to when the form is successfully processed.

View File

@ -335,6 +335,22 @@ subclass::
For an example see the section `Adding custom validation to the admin`_.
.. admonition:: Note
.. versionchanged:: 1.6
If you define the ``Meta.model`` attribute on a
:class:`~django.forms.ModelForm`, you must also define the
``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
since the admin has its own way of defining fields, the ``Meta.fields``
attribute will be ignored.
If the ``ModelForm`` is only going to be used for the admin, the easiest
solution is to omit the ``Meta.model`` attribute, since ``ModelAdmin``
will provide the correct model to use. Alternatively, you can set
``fields = []`` in the ``Meta`` class to satisfy the validation on the
``ModelForm``.
.. admonition:: Note
If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude``
@ -1283,13 +1299,24 @@ templates used by the :class:`ModelAdmin` views:
on the changelist page. To use a custom form, for example::
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
pass
class MyModelAdmin(admin.ModelAdmin):
def get_changelist_form(self, request, **kwargs):
return MyForm
.. admonition:: Note
.. versionchanged:: 1.6
If you define the ``Meta.model`` attribute on a
:class:`~django.forms.ModelForm`, you must also define the
``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
``ModelAdmin`` ignores this value, overriding it with the
:attr:`ModelAdmin.list_editable` attribute. The easiest solution is to
omit the ``Meta.model`` attribute, since ``ModelAdmin`` will provide the
correct model to use.
.. method:: ModelAdmin.get_changelist_formset(self, request, **kwargs)
Returns a :ref:`ModelFormSet <model-formsets>` class for use on the
@ -1490,9 +1517,6 @@ needed. Now within your form you can add your own custom validation for
any field::
class MyArticleAdminForm(forms.ModelForm):
class Meta:
model = Article
def clean_name(self):
# do something that validates your data
return self.cleaned_data["name"]

View File

@ -25,6 +25,14 @@ Model Form Functions
See :ref:`modelforms-factory` for example usage.
.. versionchanged:: 1.6
You must provide the list of fields explicitly, either via keyword arguments
``fields`` or ``exclude``, or the corresponding attributes on the form's
inner ``Meta`` class. See :ref:`modelforms-selecting-fields` for more
information. Omitting any definition of the fields to use will result in all
fields being used, but this behaviour is deprecated.
.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False)
Returns a ``FormSet`` class for the given ``model`` class.

View File

@ -532,3 +532,55 @@ including it in an URLconf, simply replace::
with::
(r'^prefix/(?P<content_type_id>\d+)/(?P<object_id>.*)/$', 'django.contrib.contenttypes.views.shortcut'),
``ModelForm`` without ``fields`` or ``exclude``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, if you wanted a :class:`~django.forms.ModelForm` to use all fields on
the model, you could simply omit the ``Meta.fields`` attribute, and all fields
would be used.
This can lead to security problems where fields are added to the model and,
unintentionally, automatically become editable by end users. In some cases,
particular with boolean fields, it is possible for this problem to be completely
invisible. This is a form of `Mass assignment vulnerability
<http://en.wikipedia.org/wiki/Mass_assignment_vulnerability>`_.
For this reason, this behaviour is deprecated, and using the ``Meta.exclude``
option is strongly discouraged. Instead, all fields that are intended for
inclusion in the form should be listed explicitly in the ``fields`` attribute.
If this security concern really does not apply in your case, there is a shortcut
to explicitly indicate that all fields should be used - use the special value
``"__all__"`` for the fields attribute::
class MyModelForm(ModelForm):
class Meta:
fields = "__all__"
model = MyModel
If you have custom ``ModelForms`` that only need to be used in the admin, there
is another option. The admin has its own methods for defining fields
(``fieldsets`` etc.), and so adding a list of fields to the ``ModelForm`` is
redundant. Instead, simply omit the ``Meta`` inner class of the ``ModelForm``,
or omit the ``Meta.model`` attribute. Since the ``ModelAdmin`` subclass knows
which model it is for, it can add the necessary attributes to derive a
functioning ``ModelForm``. This behaviour also works for earlier Django
versions.
``UpdateView`` and ``CreateView`` without explicit fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The generic views :class:`~django.views.generic.edit.CreateView` and
:class:`~django.views.generic.edit.UpdateView`, and anything else derived from
:class:`~django.views.generic.edit.ModelFormMixin`, are vulnerable to the
security problem described in the section above, because they can automatically
create a ``ModelForm`` that uses all fields for a model.
For this reason, if you use these views for editing models, you must also supply
the ``fields`` attribute, which is a list of model fields and works in the same
way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively,
you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly
defines the fields to be used. Defining an ``UpdateView`` or ``CreateView``
subclass to be used with a model but without an explicit list of fields is
deprecated.

View File

@ -1051,6 +1051,7 @@ code would be required in the app's ``admin.py`` file::
class Meta:
model = MyUser
fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin']
def clean_password(self):
# Regardless of what the user provides, return the initial value.

View File

@ -114,9 +114,11 @@ here; we don't have to write any logic ourselves::
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
@ -126,6 +128,17 @@ here; we don't have to write any logic ourselves::
We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
just ``reverse`` as the urls are not loaded when the file is imported.
.. versionchanged:: 1.6
In Django 1.6, the ``fields`` attribute was added, which works the same way as
the ``fields`` attribute on the inner ``Meta`` class on
:class:`~django.forms.ModelForm`.
Omitting the fields attribute will work as previously, but is deprecated and
this attribute will be required from 1.8 (unless you define the form class in
another way).
Finally, we hook these new views into the URLconf::
# urls.py
@ -177,33 +190,17 @@ the foreign key relation to the model::
# ...
Create a custom :class:`~django.forms.ModelForm` in order to exclude the
``created_by`` field and prevent the user from editing it:
.. code-block:: python
# forms.py
from django import forms
from myapp.models import Author
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
exclude = ('created_by',)
In the view, use the custom
:attr:`~django.views.generic.edit.FormMixin.form_class` and override
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the
user::
In the view, ensure that you exclude ``created_by`` in the list of fields to
edit, and override
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
# views.py
from django.views.generic.edit import CreateView
from myapp.models import Author
from myapp.forms import AuthorForm
class AuthorCreate(CreateView):
form_class = AuthorForm
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user

View File

@ -28,6 +28,7 @@ For example::
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ['pub_date', 'headline', 'content', 'reporter']
# Creating a form to add an article.
>>> form = ArticleForm()
@ -39,11 +40,13 @@ For example::
Field types
-----------
The generated ``Form`` class will have a form field for every model field. Each
model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A
model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
the full list of conversions:
The generated ``Form`` class will have a form field for every model field
specified, in the order specified in the ``fields`` attribute.
Each model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A model
``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is the
full list of conversions:
=============================== ========================================
Model field Form field
@ -168,10 +171,13 @@ Consider this set of models::
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']
With these models, the ``ModelForm`` subclasses above would be roughly
equivalent to this (the only difference being the ``save()`` method, which
@ -288,47 +294,66 @@ method is used to determine whether a form requires multipart file upload (and
hence whether ``request.FILES`` must be passed to the form), etc. See
:ref:`binding-uploaded-files` for more information.
Using a subset of fields on the form
------------------------------------
.. _modelforms-selecting-fields:
In some cases, you may not want all the model fields to appear on the generated
form. There are three ways of telling ``ModelForm`` to use only a subset of the
model fields:
Selecting the fields to use
---------------------------
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``ModelForm`` will not include that
field.
It is strongly recommended that you explicitly set all fields that should be
edited in the form using the ``fields`` attribute. Failure to do so can easily
lead to security problems when a form unexpectedly allows a user to set certain
fields, especially when new fields are added to a model. Depending on how the
form is rendered, the problem may not even be visible on the web page.
2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta``
class. This attribute, if given, should be a list of field names
to include in the form. The order in which the fields names are specified
in that list is respected when the form renders them.
The alternative approach would be to include all fields automatically, or
blacklist only some. This fundamental approach is known to be much less secure
and has led to serious exploits on major websites (e.g. `GitHub
<https://github.com/blog/1068-public-key-security-vulnerability-and-mitigation>`_).
3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta``
class. This attribute, if given, should be a list of field names
to exclude from the form.
There are, however, two shortcuts available for cases where you can guarantee
these security concerns do not apply to you:
For example, if you want a form for the ``Author`` model (defined
above) that includes only the ``name`` and ``birth_date`` fields, you would
specify ``fields`` or ``exclude`` like this::
1. Set the ``fields`` attribute to the special value ``'__all__'`` to indicate
that all fields in the model should be used. For example::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'birth_date')
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('title',)
2. Set the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class to
a list of fields to be excluded from the form.
For example::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
Since the ``Author`` model has the 3 fields ``name``, ``title`` and
``birth_date``, this will result in the fields ``name`` and ``birth_date``
being present on the form.
If either of these are used, the order the fields appear in the form will be the
order the fields are defined in the model, with ``ManyToManyField`` instances
appearing last.
In addition, Django applies the following rule: if you set ``editable=False`` on
the model field, *any* form created from the model via ``ModelForm`` will not
include that field.
.. versionchanged:: 1.6
Before version 1.6, the ``'__all__'`` shortcut did not exist, but omitting
the ``fields`` attribute had the same effect. Omitting both ``fields`` and
``exclude`` is now deprecated, but will continue to work as before until
version 1.8
Since the Author model has only 3 fields, 'name', 'title', and
'birth_date', the forms above will contain exactly the same fields.
.. note::
If you specify ``fields`` or ``exclude`` when creating a form with
``ModelForm``, then the fields that are not in the resulting form
Any fields not included in a form by the above logic
will not be set by the form's ``save()`` method. Also, if you
manually add the excluded fields back to the form, they will not
be initialized from the model instance.
@ -401,15 +426,19 @@ field, you could do the following::
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
If you want to override a field's default label, then specify the ``label``
parameter when declaring the form field::
>>> class ArticleForm(ModelForm):
... pub_date = DateField(label='Publication date')
...
... class Meta:
... model = Article
class ArticleForm(ModelForm):
pub_date = DateField(label='Publication date')
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
.. note::
@ -436,6 +465,7 @@ parameter when declaring the form field::
class Meta:
model = Article
fields = ['headline', 'content']
You must ensure that the type of the form field can be used to set the
contents of the corresponding model field. When they are not compatible,
@ -444,30 +474,6 @@ parameter when declaring the form field::
See the :doc:`form field documentation </ref/forms/fields>` for more information
on fields and their arguments.
Changing the order of fields
----------------------------
By default, a ``ModelForm`` will render fields in the same order that they are
defined on the model, with ``ManyToManyField`` instances appearing last. If
you want to change the order in which fields are rendered, you can use the
``fields`` attribute on the ``Meta`` class.
The ``fields`` attribute defines the subset of model fields that will be
rendered, and the order in which they will be rendered. For example given this
model::
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
the ``author`` field would be rendered first. If we wanted the title field
to be rendered first, we could specify the following ``ModelForm``::
>>> class BookForm(ModelForm):
... class Meta:
... model = Book
... fields = ('title', 'author')
.. _overriding-modelform-clean-method:
Overriding the clean() method
@ -550,21 +556,19 @@ definition. This may be more convenient if you do not have many customizations
to make::
>>> from django.forms.models import modelform_factory
>>> BookForm = modelform_factory(Book)
>>> BookForm = modelform_factory(Book, fields=("author", "title"))
This can also be used to make simple modifications to existing forms, for
example by specifying which fields should be displayed::
>>> Form = modelform_factory(Book, form=BookForm, fields=("author",))
... or which fields should be excluded::
>>> Form = modelform_factory(Book, form=BookForm, exclude=("title",))
You can also specify the widgets to be used for a given field::
example by specifying the widgets to be used for a given field::
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
>>> Form = modelform_factory(Book, form=BookForm,
widgets={"title": Textarea()})
The fields to include can be specified using the ``fields`` and ``exclude``
keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
documentation.
.. _model-formsets:
@ -688,11 +692,10 @@ database. If a given instance's data didn't change in the bound data, the
instance won't be saved to the database and won't be included in the return
value (``instances``, in the above example).
When fields are missing from the form (for example because they have
been excluded), these fields will not be set by the ``save()``
method. You can find more information about this restriction, which
also holds for regular ``ModelForms``, in `Using a subset of fields on
the form`_.
When fields are missing from the form (for example because they have been
excluded), these fields will not be set by the ``save()`` method. You can find
more information about this restriction, which also holds for regular
``ModelForms``, in `Selecting the fields to use`_.
Pass ``commit=False`` to return the unsaved model instances::

View File

@ -285,6 +285,8 @@ class ValidationTestCase(TestCase):
extra_data = forms.CharField()
class Meta:
model = Song
fields = '__all__'
class FieldsOnFormOnlyAdmin(admin.ModelAdmin):
form = SongForm

View File

@ -25,3 +25,4 @@ class Photo(models.Model):
class PhotoForm(ModelForm):
class Meta:
model = Photo
fields = '__all__'

View File

@ -322,6 +322,7 @@ class FormsTests(TestCase):
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
def test_foreign_object_form(self):
# A very crude test checking that the non-concrete fields do not get form fields.

View File

@ -139,6 +139,7 @@ class FormsRegressionsTestCase(TestCase):
class CheeseForm(ModelForm):
class Meta:
model = Cheese
fields = '__all__'
form = CheeseForm({
'name': 'Brie',

View File

@ -17,11 +17,13 @@ from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
class ChoiceFieldForm(ModelForm):
class Meta:
model = ChoiceFieldModel
fields = '__all__'
class OptionalMultiChoiceModelForm(ModelForm):
class Meta:
model = OptionalMultiChoiceModel
fields = '__all__'
class FileForm(Form):
@ -139,6 +141,7 @@ class FormsModelTestCase(TestCase):
class BoundaryForm(ModelForm):
class Meta:
model = BoundaryModel
fields = '__all__'
f = BoundaryForm({'positive_integer': 100})
self.assertTrue(f.is_valid())
@ -154,6 +157,7 @@ class FormsModelTestCase(TestCase):
class DefaultsForm(ModelForm):
class Meta:
model = Defaults
fields = '__all__'
self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value')
self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))

View File

@ -247,6 +247,7 @@ class CustomWidget(forms.TextInput):
class TaggedItemForm(forms.ModelForm):
class Meta:
model = TaggedItem
fields = '__all__'
widgets = {'tag': CustomWidget}
class GenericInlineFormsetTest(TestCase):

View File

@ -1,12 +1,14 @@
from __future__ import absolute_import
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django import forms
from django.test import TestCase
from django.utils.unittest import expectedFailure
from django.views.generic.base import View
from django.views.generic.edit import FormMixin
from django.views.generic.edit import FormMixin, CreateView, UpdateView
from . import views
from .models import Artist, Author
@ -34,6 +36,7 @@ class ModelFormMixinTests(TestCase):
form_class = views.AuthorGetQuerySetFormView().get_form_class()
self.assertEqual(form_class._meta.model, Author)
class CreateViewTests(TestCase):
urls = 'generic_views.urls'
@ -112,6 +115,45 @@ class CreateViewTests(TestCase):
self.assertEqual(res.status_code, 302)
self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')
def test_create_view_with_restricted_fields(self):
class MyCreateView(CreateView):
model = Author
fields = ['name']
self.assertEqual(list(MyCreateView().get_form_class().base_fields),
['name'])
def test_create_view_all_fields(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", PendingDeprecationWarning)
class MyCreateView(CreateView):
model = Author
fields = '__all__'
self.assertEqual(list(MyCreateView().get_form_class().base_fields),
['name', 'slug'])
self.assertEqual(len(w), 0)
def test_create_view_without_explicit_fields(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", PendingDeprecationWarning)
class MyCreateView(CreateView):
model = Author
# Until end of the deprecation cycle, should still create the form
# as before:
self.assertEqual(list(MyCreateView().get_form_class().base_fields),
['name', 'slug'])
# but with a warning:
self.assertEqual(w[0].category, PendingDeprecationWarning)
class UpdateViewTests(TestCase):
urls = 'generic_views.urls'

View File

@ -11,6 +11,7 @@ class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ['name', 'slug']
class ContactForm(forms.Form):

View File

@ -85,15 +85,18 @@ class ContactView(generic.FormView):
class ArtistCreate(generic.CreateView):
model = Artist
fields = '__all__'
class NaiveAuthorCreate(generic.CreateView):
queryset = Author.objects.all()
fields = '__all__'
class AuthorCreate(generic.CreateView):
model = Author
success_url = '/list/authors/'
fields = '__all__'
class SpecializedAuthorCreate(generic.CreateView):
@ -112,19 +115,23 @@ class AuthorCreateRestricted(AuthorCreate):
class ArtistUpdate(generic.UpdateView):
model = Artist
fields = '__all__'
class NaiveAuthorUpdate(generic.UpdateView):
queryset = Author.objects.all()
fields = '__all__'
class AuthorUpdate(generic.UpdateView):
model = Author
success_url = '/list/authors/'
fields = '__all__'
class OneAuthorUpdate(generic.UpdateView):
success_url = '/list/authors/'
fields = '__all__'
def get_object(self):
return Author.objects.get(pk=1)
@ -184,6 +191,8 @@ class BookDetail(BookConfig, generic.DateDetailView):
pass
class AuthorGetQuerySetFormView(generic.edit.ModelFormMixin):
fields = '__all__'
def get_queryset(self):
return Author.objects.all()

View File

@ -24,3 +24,4 @@ class CompanyForm(forms.ModelForm):
class Meta:
model = Company
fields = '__all__'

View File

@ -10,7 +10,7 @@ from .models import Poet, Poem, School, Parent, Child
class DeletionTests(TestCase):
def test_deletion(self):
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
poet = Poet.objects.create(name='test')
poem = poet.poem_set.create(name='test poem')
data = {
@ -32,7 +32,7 @@ class DeletionTests(TestCase):
Make sure that an add form that is filled out, but marked for deletion
doesn't cause validation errors.
"""
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
poet = Poet.objects.create(name='test')
data = {
'poem_set-TOTAL_FORMS': '1',
@ -60,7 +60,7 @@ class DeletionTests(TestCase):
Make sure that a change form that is filled out, but marked for deletion
doesn't cause validation errors.
"""
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
poet = Poet.objects.create(name='test')
poem = poet.poem_set.create(name='test poem')
data = {
@ -115,8 +115,8 @@ class InlineFormsetFactoryTest(TestCase):
"""
These should both work without a problem.
"""
inlineformset_factory(Parent, Child, fk_name='mother')
inlineformset_factory(Parent, Child, fk_name='father')
inlineformset_factory(Parent, Child, fk_name='mother', fields="__all__")
inlineformset_factory(Parent, Child, fk_name='father', fields="__all__")
def test_exception_on_unspecified_foreign_key(self):
"""

View File

@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals
import datetime
import os
from decimal import Decimal
import warnings
from django import forms
from django.core.exceptions import FieldError
@ -30,19 +31,25 @@ if test_images:
class ImageFileForm(forms.ModelForm):
class Meta:
model = ImageFile
fields = '__all__'
class OptionalImageFileForm(forms.ModelForm):
class Meta:
model = OptionalImageFile
fields = '__all__'
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
class PriceForm(forms.ModelForm):
class Meta:
model = Price
fields = '__all__'
class BookForm(forms.ModelForm):
@ -66,11 +73,13 @@ class ExplicitPKForm(forms.ModelForm):
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'
class DerivedPostForm(forms.ModelForm):
class Meta:
model = DerivedPost
fields = '__all__'
class CustomAuthorForm(forms.ModelForm):
@ -78,61 +87,79 @@ class CustomAuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = '__all__'
class FlexDatePostForm(forms.ModelForm):
class Meta:
model = FlexibleDatePost
fields = '__all__'
class BaseCategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = '__all__'
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class PartialArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('headline','pub_date')
class RoykoForm(forms.ModelForm):
class Meta:
model = Author
fields = '__all__'
class TestArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class PartialArticleFormWithSlug(forms.ModelForm):
class Meta:
model = Article
fields=('headline', 'slug', 'pub_date')
fields = ('headline', 'slug', 'pub_date')
class ArticleStatusForm(forms.ModelForm):
class Meta:
model = ArticleStatus
fields = '__all__'
class InventoryForm(forms.ModelForm):
class Meta:
model = Inventory
fields = '__all__'
class SelectInventoryForm(forms.Form):
items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
class CustomFieldForExclusionForm(forms.ModelForm):
class Meta:
model = CustomFieldForExclusionModel
fields = ['name', 'markup']
class ShortCategory(forms.ModelForm):
name = forms.CharField(max_length=5)
slug = forms.CharField(max_length=5)
@ -140,30 +167,44 @@ class ShortCategory(forms.ModelForm):
class Meta:
model = Category
fields = '__all__'
class ImprovedArticleForm(forms.ModelForm):
class Meta:
model = ImprovedArticle
fields = '__all__'
class ImprovedArticleWithParentLinkForm(forms.ModelForm):
class Meta:
model = ImprovedArticleWithParentLink
fields = '__all__'
class BetterAuthorForm(forms.ModelForm):
class Meta:
model = BetterAuthor
fields = '__all__'
class AuthorProfileForm(forms.ModelForm):
class Meta:
model = AuthorProfile
fields = '__all__'
class TextFileForm(forms.ModelForm):
class Meta:
model = TextFile
fields = '__all__'
class BigIntForm(forms.ModelForm):
class Meta:
model = BigInt
fields = '__all__'
class ModelFormWithMedia(forms.ModelForm):
class Media:
@ -173,19 +214,25 @@ class ModelFormWithMedia(forms.ModelForm):
}
class Meta:
model = TextFile
fields = '__all__'
class CommaSeparatedIntegerForm(forms.ModelForm):
class Meta:
model = CommaSeparatedInteger
class Meta:
model = CommaSeparatedInteger
fields = '__all__'
class PriceFormWithoutQuantity(forms.ModelForm):
class Meta:
model = Price
exclude = ('quantity',)
class ColourfulItemForm(forms.ModelForm):
class Meta:
model = ColourfulItem
fields = '__all__'
class ModelFormBaseTest(TestCase):
@ -193,6 +240,25 @@ class ModelFormBaseTest(TestCase):
self.assertEqual(list(BaseCategoryForm.base_fields),
['name', 'slug', 'url'])
def test_missing_fields_attribute(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", PendingDeprecationWarning)
class MissingFieldsForm(forms.ModelForm):
class Meta:
model = Category
# There is some internal state in warnings module which means that
# if a warning has been seen already, the catch_warnings won't
# have recorded it. The following line therefore will not work reliably:
# self.assertEqual(w[0].category, PendingDeprecationWarning)
# Until end of the deprecation cycle, should still create the
# form as before:
self.assertEqual(list(MissingFieldsForm.base_fields),
['name', 'slug', 'url'])
def test_extra_fields(self):
class ExtraFields(BaseCategoryForm):
some_extra_field = forms.BooleanField()
@ -206,6 +272,33 @@ class ModelFormBaseTest(TestCase):
class Meta:
model = Category
fields = '__all__'
self.assertTrue(isinstance(ReplaceField.base_fields['url'],
forms.fields.BooleanField))
def test_replace_field_variant_2(self):
# Should have the same result as before,
# but 'fields' attribute specified differently
class ReplaceField(forms.ModelForm):
url = forms.BooleanField()
class Meta:
model = Category
fields = ['url']
self.assertTrue(isinstance(ReplaceField.base_fields['url'],
forms.fields.BooleanField))
def test_replace_field_variant_3(self):
# Should have the same result as before,
# but 'fields' attribute specified differently
class ReplaceField(forms.ModelForm):
url = forms.BooleanField()
class Meta:
model = Category
fields = [] # url will still appear, since it is explicit above
self.assertTrue(isinstance(ReplaceField.base_fields['url'],
forms.fields.BooleanField))
@ -216,19 +309,11 @@ class ModelFormBaseTest(TestCase):
class Meta:
model = Author
fields = '__all__'
wf = AuthorForm({'name': 'Richard Lockridge'})
self.assertTrue(wf.is_valid())
def test_limit_fields(self):
class LimitFields(forms.ModelForm):
class Meta:
model = Category
fields = ['url']
self.assertEqual(list(LimitFields.base_fields),
['url'])
def test_limit_nonexistent_field(self):
expected_msg = 'Unknown field(s) (nonexistent) specified for Category'
with self.assertRaisesMessage(FieldError, expected_msg):
@ -294,6 +379,7 @@ class ModelFormBaseTest(TestCase):
"""
class Meta:
model = Article
fields = '__all__'
# MixModelForm is now an Article-related thing, because MixModelForm.Meta
# overrides BaseCategoryForm.Meta.
@ -348,6 +434,7 @@ class ModelFormBaseTest(TestCase):
class Meta:
model = Category
fields = '__all__'
class SubclassMeta(SomeCategoryForm):
""" We can also subclass the Meta inner class to change the fields

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import, unicode_literals
from datetime import date
import warnings
from django import forms
from django.core.exceptions import FieldError, ValidationError
@ -43,9 +44,12 @@ class ModelMultipleChoiceFieldTests(TestCase):
f.clean([p.pk for p in Person.objects.all()[8:9]])
self.assertTrue(self._validator_run)
class TripleForm(forms.ModelForm):
class Meta:
model = Triple
fields = '__all__'
class UniqueTogetherTests(TestCase):
def test_multiple_field_unique_together(self):
@ -63,15 +67,18 @@ class UniqueTogetherTests(TestCase):
form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
self.assertTrue(form.is_valid())
class TripleFormWithCleanOverride(forms.ModelForm):
class Meta:
model = Triple
fields = '__all__'
def clean(self):
if not self.cleaned_data['left'] == self.cleaned_data['right']:
raise forms.ValidationError('Left and right should be equal')
return self.cleaned_data
class OverrideCleanTests(TestCase):
def test_override_clean(self):
"""
@ -84,6 +91,7 @@ class OverrideCleanTests(TestCase):
# by form.full_clean().
self.assertEqual(form.instance.left, 1)
# Regression test for #12960.
# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
# model instance.
@ -95,6 +103,8 @@ class PublicationForm(forms.ModelForm):
class Meta:
model = Publication
fields = '__all__'
class ModelFormCleanTest(TestCase):
def test_model_form_clean_applies_to_model(self):
@ -103,9 +113,12 @@ class ModelFormCleanTest(TestCase):
publication = form.save()
self.assertEqual(publication.title, 'TEST')
class FPForm(forms.ModelForm):
class Meta:
model = FilePathModel
fields = '__all__'
class FilePathFieldTests(TestCase):
def test_file_path_field_blank(self):
@ -133,7 +146,8 @@ class ManyToManyCallableInitialTests(TestCase):
book3 = Publication.objects.create(title="Third Book", date_published=date(2009,1,1))
# Create a ModelForm, instantiate it, and check that the output is as expected
ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
ModelForm = modelform_factory(Article, fields="__all__",
formfield_callback=formfield_for_dbfield)
form = ModelForm()
self.assertHTMLEqual(form.as_ul(), """<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
<li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
@ -143,9 +157,12 @@ class ManyToManyCallableInitialTests(TestCase):
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
% (book1.pk, book2.pk, book3.pk))
class CFFForm(forms.ModelForm):
class Meta:
model = CustomFF
fields = '__all__'
class CustomFieldSaveTests(TestCase):
def test_save(self):
@ -168,9 +185,12 @@ class ModelChoiceIteratorTests(TestCase):
f = Form()
self.assertEqual(len(f.fields["publications"].choices), 1)
class RealPersonForm(forms.ModelForm):
class Meta:
model = RealPerson
fields = '__all__'
class CustomModelFormSaveMethod(TestCase):
def test_string_message(self):
@ -230,9 +250,12 @@ class TestTicket11183(TestCase):
self.assertTrue(field1 is not ModelChoiceForm.base_fields['person'])
self.assertTrue(field1.widget.choices.field is field1)
class HomepageForm(forms.ModelForm):
class Meta:
model = Homepage
fields = '__all__'
class URLFieldTests(TestCase):
def test_url_on_modelform(self):
@ -274,6 +297,7 @@ class FormFieldCallbackTests(TestCase):
class Meta:
model = Person
widgets = {'name': widget}
fields = "__all__"
Form = modelform_factory(Person, form=BaseForm)
self.assertTrue(Form.base_fields['name'].widget is widget)
@ -285,11 +309,11 @@ class FormFieldCallbackTests(TestCase):
widget = forms.Textarea()
# Without a widget should not set the widget to textarea
Form = modelform_factory(Person)
Form = modelform_factory(Person, fields="__all__")
self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
# With a widget should not set the widget to textarea
Form = modelform_factory(Person, widgets={'name':widget})
Form = modelform_factory(Person, fields="__all__", widgets={'name':widget})
self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
def test_custom_callback(self):
@ -307,6 +331,7 @@ class FormFieldCallbackTests(TestCase):
class Meta:
model = Person
widgets = {'name': widget}
fields = "__all__"
_ = modelform_factory(Person, form=BaseForm,
formfield_callback=callback)
@ -317,7 +342,7 @@ class FormFieldCallbackTests(TestCase):
def test_bad_callback(self):
# A bad callback provided by user still gives an error
self.assertRaises(TypeError, modelform_factory, Person,
self.assertRaises(TypeError, modelform_factory, Person, fields="__all__",
formfield_callback='not a function or callable')
@ -362,6 +387,8 @@ class InvalidFieldAndFactory(TestCase):
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = '__all__'
class FileFieldTests(unittest.TestCase):
def test_clean_false(self):
@ -425,6 +452,7 @@ class FileFieldTests(unittest.TestCase):
self.assertTrue('something.txt' in rendered)
self.assertTrue('myfile-clear' in rendered)
class EditionForm(forms.ModelForm):
author = forms.ModelChoiceField(queryset=Person.objects.all())
publication = forms.ModelChoiceField(queryset=Publication.objects.all())
@ -433,6 +461,8 @@ class EditionForm(forms.ModelForm):
class Meta:
model = Edition
fields = '__all__'
class UniqueErrorsTests(TestCase):
def setUp(self):
@ -473,7 +503,7 @@ class EmptyFieldsTestCase(TestCase):
def test_empty_fields_to_construct_instance(self):
"No fields should be set on a model instance if construct_instance receives fields=()"
form = modelform_factory(Person)({'name': 'John Doe'})
form = modelform_factory(Person, fields="__all__")({'name': 'John Doe'})
self.assertTrue(form.is_valid())
instance = construct_instance(form, Person(), fields=())
self.assertEqual(instance.name, '')
@ -485,10 +515,25 @@ class CustomMetaclass(ModelFormMetaclass):
new.base_fields = {}
return new
class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
pass
class CustomMetaclassTestCase(TestCase):
def test_modelform_factory_metaclass(self):
new_cls = modelform_factory(Person, form=CustomMetaclassForm)
new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm)
self.assertEqual(new_cls.base_fields, {})
class TestTicket19733(TestCase):
def test_modelform_factory_without_fields(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", PendingDeprecationWarning)
# This should become an error once deprecation cycle is complete.
form = modelform_factory(Person)
self.assertEqual(w[0].category, PendingDeprecationWarning)
def test_modelform_factory_with_all_fields(self):
form = modelform_factory(Person, fields="__all__")
self.assertEqual(form.base_fields.keys(), ["name"])

View File

@ -21,7 +21,7 @@ from .models import (Author, BetterAuthor, Book, BookWithCustomPK,
class DeletionTests(TestCase):
def test_deletion(self):
PoetFormSet = modelformset_factory(Poet, can_delete=True)
PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
poet = Poet.objects.create(name='test')
data = {
'form-TOTAL_FORMS': '1',
@ -41,7 +41,7 @@ class DeletionTests(TestCase):
Make sure that an add form that is filled out, but marked for deletion
doesn't cause validation errors.
"""
PoetFormSet = modelformset_factory(Poet, can_delete=True)
PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
poet = Poet.objects.create(name='test')
# One existing untouched and two new unvalid forms
data = {
@ -75,7 +75,7 @@ class DeletionTests(TestCase):
Make sure that a change form that is filled out, but marked for deletion
doesn't cause validation errors.
"""
PoetFormSet = modelformset_factory(Poet, can_delete=True)
PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
poet = Poet.objects.create(name='test')
data = {
'form-TOTAL_FORMS': '1',
@ -100,7 +100,7 @@ class DeletionTests(TestCase):
class ModelFormsetTest(TestCase):
def test_simple_save(self):
qs = Author.objects.all()
AuthorFormSet = modelformset_factory(Author, extra=3)
AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=3)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 3)
@ -138,7 +138,7 @@ class ModelFormsetTest(TestCase):
# we'll use it to display them in alphabetical order by name.
qs = Author.objects.order_by('name')
AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False)
AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=False)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 3)
@ -176,7 +176,7 @@ class ModelFormsetTest(TestCase):
# marked for deletion, make sure we don't save that form.
qs = Author.objects.order_by('name')
AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True)
AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=True)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 4)
@ -256,7 +256,7 @@ class ModelFormsetTest(TestCase):
author4 = Author.objects.create(name='John Steinbeck')
AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True)
AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, fields="__all__", extra=1, can_delete=True)
data = {
'form-TOTAL_FORMS': '2', # the number of forms rendered
'form-INITIAL_FORMS': '1', # the number of forms with initial data
@ -294,22 +294,22 @@ class ModelFormsetTest(TestCase):
qs = Author.objects.order_by('name')
AuthorFormSet = modelformset_factory(Author, max_num=None, extra=3)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None, extra=3)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 6)
self.assertEqual(len(formset.extra_forms), 3)
AuthorFormSet = modelformset_factory(Author, max_num=4, extra=3)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4, extra=3)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 4)
self.assertEqual(len(formset.extra_forms), 1)
AuthorFormSet = modelformset_factory(Author, max_num=0, extra=3)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0, extra=3)
formset = AuthorFormSet(queryset=qs)
self.assertEqual(len(formset.forms), 3)
self.assertEqual(len(formset.extra_forms), 0)
AuthorFormSet = modelformset_factory(Author, max_num=None)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None)
formset = AuthorFormSet(queryset=qs)
self.assertQuerysetEqual(formset.get_queryset(), [
'<Author: Charles Baudelaire>',
@ -317,7 +317,7 @@ class ModelFormsetTest(TestCase):
'<Author: Walt Whitman>',
])
AuthorFormSet = modelformset_factory(Author, max_num=0)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0)
formset = AuthorFormSet(queryset=qs)
self.assertQuerysetEqual(formset.get_queryset(), [
'<Author: Charles Baudelaire>',
@ -325,7 +325,7 @@ class ModelFormsetTest(TestCase):
'<Author: Walt Whitman>',
])
AuthorFormSet = modelformset_factory(Author, max_num=4)
AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4)
formset = AuthorFormSet(queryset=qs)
self.assertQuerysetEqual(formset.get_queryset(), [
'<Author: Charles Baudelaire>',
@ -343,7 +343,7 @@ class ModelFormsetTest(TestCase):
author.save()
return author
PoetFormSet = modelformset_factory(Poet, form=PoetForm)
PoetFormSet = modelformset_factory(Poet, fields="__all__", form=PoetForm)
data = {
'form-TOTAL_FORMS': '3', # the number of forms rendered
@ -387,7 +387,7 @@ class ModelFormsetTest(TestCase):
self.assertFalse("subtitle" in formset.forms[0].fields)
def test_model_inheritance(self):
BetterAuthorFormSet = modelformset_factory(BetterAuthor)
BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__")
formset = BetterAuthorFormSet()
self.assertEqual(len(formset.forms), 1)
self.assertHTMLEqual(formset.forms[0].as_p(),
@ -440,7 +440,7 @@ class ModelFormsetTest(TestCase):
# We can also create a formset that is tied to a parent model. This is
# how the admin system's edit inline functionality works.
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3)
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3, fields="__all__")
author = Author.objects.create(name='Charles Baudelaire')
formset = AuthorBooksFormSet(instance=author)
@ -474,7 +474,7 @@ class ModelFormsetTest(TestCase):
# another one. This time though, an edit form will be available for
# every existing book.
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
author = Author.objects.get(name='Charles Baudelaire')
formset = AuthorBooksFormSet(instance=author)
@ -514,7 +514,7 @@ class ModelFormsetTest(TestCase):
def test_inline_formsets_save_as_new(self):
# The save_as_new parameter lets you re-associate the data to a new
# instance. This is used in the admin for save_as functionality.
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
author = Author.objects.create(name='Charles Baudelaire')
data = {
@ -553,7 +553,7 @@ class ModelFormsetTest(TestCase):
# primary key that is not the fk to the parent object.
self.maxDiff = 1024
AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1)
AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1, fields="__all__")
author = Author.objects.create(pk=1, name='Charles Baudelaire')
formset = AuthorBooksFormSet2(instance=author)
@ -585,7 +585,7 @@ class ModelFormsetTest(TestCase):
# Test inline formsets where the inline-edited object uses multi-table
# inheritance, thus has a non AutoField yet auto-created primary key.
AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1)
AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1, fields="__all__")
author = Author.objects.create(pk=1, name='Charles Baudelaire')
formset = AuthorBooksFormSet3(instance=author)
@ -616,7 +616,7 @@ class ModelFormsetTest(TestCase):
# Test inline formsets where the inline-edited object has a
# unique_together constraint with a nullable member
AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2)
AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2, fields="__all__")
author = Author.objects.create(pk=1, name='Charles Baudelaire')
data = {
@ -640,7 +640,7 @@ class ModelFormsetTest(TestCase):
self.assertEqual(book2.title, 'Les Fleurs du Mal')
def test_inline_formsets_with_custom_save_method(self):
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
author = Author.objects.create(pk=1, name='Charles Baudelaire')
book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
@ -655,7 +655,7 @@ class ModelFormsetTest(TestCase):
poem.save()
return poem
PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm)
PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm, fields="__all__")
data = {
'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
@ -732,7 +732,7 @@ class ModelFormsetTest(TestCase):
def test_custom_pk(self):
# We need to ensure that it is displayed
CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey, fields="__all__")
formset = CustomPrimaryKeyFormSet()
self.assertEqual(len(formset.forms), 1)
self.assertHTMLEqual(formset.forms[0].as_p(),
@ -743,7 +743,7 @@ class ModelFormsetTest(TestCase):
place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False)
FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False, fields="__all__")
formset = FormSet(instance=place)
self.assertEqual(len(formset.forms), 2)
self.assertHTMLEqual(formset.forms[0].as_p(),
@ -799,7 +799,7 @@ class ModelFormsetTest(TestCase):
# Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose.
FormSet = modelformset_factory(OwnerProfile)
FormSet = modelformset_factory(OwnerProfile, fields="__all__")
formset = FormSet()
self.assertHTMLEqual(formset.forms[0].as_p(),
'<p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner">\n'
@ -811,7 +811,7 @@ class ModelFormsetTest(TestCase):
% (owner1.auto_id, owner2.auto_id))
owner1 = Owner.objects.get(name='Joe Perry')
FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False, fields="__all__")
self.assertEqual(FormSet.max_num, 1)
formset = FormSet(instance=owner1)
@ -861,7 +861,7 @@ class ModelFormsetTest(TestCase):
place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
FormSet = inlineformset_factory(Place, Location, can_delete=False)
FormSet = inlineformset_factory(Place, Location, can_delete=False, fields="__all__")
self.assertEqual(FormSet.max_num, 1)
formset = FormSet(instance=place)
@ -875,7 +875,7 @@ class ModelFormsetTest(TestCase):
self.assertEqual(type(_get_foreign_key(MexicanRestaurant, Owner)), models.ForeignKey)
def test_unique_validation(self):
FormSet = modelformset_factory(Product, extra=1)
FormSet = modelformset_factory(Product, fields="__all__", extra=1)
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '0',
@ -915,19 +915,19 @@ class ModelFormsetTest(TestCase):
'form-1-quantity': '2',
}
FormSet = modelformset_factory(Price, extra=1, max_num=1, validate_max=True)
FormSet = modelformset_factory(Price, fields="__all__", extra=1, max_num=1, validate_max=True)
formset = FormSet(data)
self.assertFalse(formset.is_valid())
self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
# Now test the same thing without the validate_max flag to ensure
# default behavior is unchanged
FormSet = modelformset_factory(Price, extra=1, max_num=1)
FormSet = modelformset_factory(Price, fields="__all__", extra=1, max_num=1)
formset = FormSet(data)
self.assertTrue(formset.is_valid())
def test_unique_together_validation(self):
FormSet = modelformset_factory(Price, extra=1)
FormSet = modelformset_factory(Price, fields="__all__", extra=1)
data = {
'form-TOTAL_FORMS': '1',
'form-INITIAL_FORMS': '0',
@ -958,7 +958,7 @@ class ModelFormsetTest(TestCase):
# Also see bug #8882.
repository = Repository.objects.create(name='Test Repo')
FormSet = inlineformset_factory(Repository, Revision, extra=1)
FormSet = inlineformset_factory(Repository, Revision, extra=1, fields="__all__")
data = {
'revision_set-TOTAL_FORMS': '1',
'revision_set-INITIAL_FORMS': '0',
@ -1007,7 +1007,7 @@ class ModelFormsetTest(TestCase):
# Use of callable defaults (see bug #7975).
person = Person.objects.create(name='Ringo')
FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1)
FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1, fields="__all__")
formset = FormSet(instance=person)
# Django will render a hidden field for model fields that have a callable
@ -1057,11 +1057,12 @@ class ModelFormsetTest(TestCase):
date_joined = forms.SplitDateTimeField(initial=now)
class Meta:
model = Membership
fields = "__all__"
def __init__(self, **kwargs):
super(MembershipForm, self).__init__(**kwargs)
self.fields['date_joined'].widget = forms.SplitDateTimeWidget()
FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1)
FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1, fields="__all__")
data = {
'membership_set-TOTAL_FORMS': '1',
'membership_set-INITIAL_FORMS': '0',
@ -1081,7 +1082,7 @@ class ModelFormsetTest(TestCase):
Player(name="Timmy").save()
Player(name="Bobby", team=team).save()
PlayerInlineFormSet = inlineformset_factory(Team, Player)
PlayerInlineFormSet = inlineformset_factory(Team, Player, fields="__all__")
formset = PlayerInlineFormSet()
self.assertQuerysetEqual(formset.get_queryset(), [])
@ -1101,7 +1102,7 @@ class ModelFormsetTest(TestCase):
def test_model_formset_with_initial_model_instance(self):
# has_changed should compare model instance and primary key
# see #18898
FormSet = modelformset_factory(Poem)
FormSet = modelformset_factory(Poem, fields='__all__')
john_milton = Poet(name="John Milton")
john_milton.save()
data = {
@ -1117,7 +1118,7 @@ class ModelFormsetTest(TestCase):
def test_model_formset_with_initial_queryset(self):
# has_changed should work with queryset and list of pk's
# see #18898
FormSet = modelformset_factory(AuthorMeeting)
FormSet = modelformset_factory(AuthorMeeting, fields='__all__')
author = Author.objects.create(pk=1, name='Charles Baudelaire')
data = {
'form-TOTAL_FORMS': 1,
@ -1131,7 +1132,7 @@ class ModelFormsetTest(TestCase):
self.assertFalse(formset.extra_forms[0].has_changed())
def test_prevent_duplicates_from_with_the_same_formset(self):
FormSet = modelformset_factory(Product, extra=2)
FormSet = modelformset_factory(Product, fields="__all__", extra=2)
data = {
'form-TOTAL_FORMS': 2,
'form-INITIAL_FORMS': 0,
@ -1144,7 +1145,7 @@ class ModelFormsetTest(TestCase):
self.assertEqual(formset._non_form_errors,
['Please correct the duplicate data for slug.'])
FormSet = modelformset_factory(Price, extra=2)
FormSet = modelformset_factory(Price, fields="__all__", extra=2)
data = {
'form-TOTAL_FORMS': 2,
'form-INITIAL_FORMS': 0,
@ -1172,7 +1173,7 @@ class ModelFormsetTest(TestCase):
formset = FormSet(data)
self.assertTrue(formset.is_valid())
FormSet = inlineformset_factory(Author, Book, extra=0)
FormSet = inlineformset_factory(Author, Book, extra=0, fields="__all__")
author = Author.objects.create(pk=1, name='Charles Baudelaire')
book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
@ -1199,7 +1200,7 @@ class ModelFormsetTest(TestCase):
self.assertEqual(formset.errors,
[{}, {'__all__': ['Please correct the duplicate values below.']}])
FormSet = modelformset_factory(Post, extra=2)
FormSet = modelformset_factory(Post, fields="__all__", extra=2)
data = {
'form-TOTAL_FORMS': '2',
'form-INITIAL_FORMS': '0',
@ -1265,7 +1266,7 @@ class TestModelFormsetWidgets(TestCase):
widgets = {
'name': forms.TextInput(attrs={'class': 'poet'})
}
PoetFormSet = modelformset_factory(Poet, widgets=widgets)
PoetFormSet = modelformset_factory(Poet, fields="__all__", widgets=widgets)
form = PoetFormSet.form()
self.assertHTMLEqual(
"%s" % form['name'],
@ -1276,7 +1277,7 @@ class TestModelFormsetWidgets(TestCase):
widgets = {
'title': forms.TextInput(attrs={'class': 'book'})
}
BookFormSet = inlineformset_factory(Author, Book, widgets=widgets)
BookFormSet = inlineformset_factory(Author, Book, widgets=widgets, fields="__all__")
form = BookFormSet.form()
self.assertHTMLEqual(
"%s" % form['title'],

View File

@ -13,8 +13,8 @@ from .models import User, UserSite, Restaurant, Manager, Network, Host
class InlineFormsetTests(TestCase):
def test_formset_over_to_field(self):
"A formset over a ForeignKey with a to_field can be saved. Regression for #10243"
Form = modelform_factory(User)
FormSet = inlineformset_factory(User, UserSite)
Form = modelform_factory(User, fields="__all__")
FormSet = inlineformset_factory(User, UserSite, fields="__all__")
# Instantiate the Form and FormSet to prove
# you can create a form with no data
@ -89,8 +89,8 @@ class InlineFormsetTests(TestCase):
def test_formset_over_inherited_model(self):
"A formset over a ForeignKey with a to_field can be saved. Regression for #11120"
Form = modelform_factory(Restaurant)
FormSet = inlineformset_factory(Restaurant, Manager)
Form = modelform_factory(Restaurant, fields="__all__")
FormSet = inlineformset_factory(Restaurant, Manager, fields="__all__")
# Instantiate the Form and FormSet to prove
# you can create a form with no data
@ -156,8 +156,8 @@ class InlineFormsetTests(TestCase):
def test_formset_with_none_instance(self):
"A formset with instance=None can be created. Regression for #11872"
Form = modelform_factory(User)
FormSet = inlineformset_factory(User, UserSite)
Form = modelform_factory(User, fields="__all__")
FormSet = inlineformset_factory(User, UserSite, fields="__all__")
# Instantiate the Form and FormSet to prove
# you can create a formset with an instance of None
@ -182,7 +182,7 @@ class InlineFormsetTests(TestCase):
efnet = Network.objects.create(name="EFNet")
host1 = Host.objects.create(hostname="irc.he.net", network=efnet)
HostFormSet = inlineformset_factory(Network, Host)
HostFormSet = inlineformset_factory(Network, Host, fields="__all__")
# Add a new host, modify previous host, and save-as-new
data = {
@ -208,7 +208,7 @@ class InlineFormsetTests(TestCase):
def test_initial_data(self):
user = User.objects.create(username="bibi", serial=1)
UserSite.objects.create(user=user, data=7)
FormSet = inlineformset_factory(User, UserSite, extra=2)
FormSet = inlineformset_factory(User, UserSite, extra=2, fields="__all__")
formset = FormSet(instance=user, initial=[{'data': 41}, {'data': 42}])
self.assertEqual(formset.forms[0].initial['data'], 7)
@ -221,7 +221,7 @@ class FormsetTests(TestCase):
'''
Test the type of Formset and Form error attributes
'''
Formset = modelformset_factory(User)
Formset = modelformset_factory(User, fields="__all__")
data = {
'form-TOTAL_FORMS': '2',
'form-INITIAL_FORMS': '0',
@ -244,14 +244,14 @@ class FormsetTests(TestCase):
def test_initial_data(self):
User.objects.create(username="bibi", serial=1)
Formset = modelformset_factory(User, extra=2)
Formset = modelformset_factory(User, fields="__all__", extra=2)
formset = Formset(initial=[{'username': 'apollo11'}, {'username': 'apollo12'}])
self.assertEqual(formset.forms[0].initial['username'], "bibi")
self.assertEqual(formset.extra_forms[0].initial['username'], "apollo11")
self.assertTrue('value="apollo12"' in formset.extra_forms[1].as_p())
def test_extraneous_query_is_not_run(self):
Formset = modelformset_factory(Network)
Formset = modelformset_factory(Network, fields="__all__")
data = {'test-TOTAL_FORMS': '1',
'test-INITIAL_FORMS': '0',
'test-MAX_NUM_FORMS': '',
@ -268,6 +268,7 @@ class CustomWidget(forms.widgets.TextInput):
class UserSiteForm(forms.ModelForm):
class Meta:
model = UserSite
fields = "__all__"
widgets = {
'id': CustomWidget,
'data': CustomWidget,
@ -292,7 +293,7 @@ class FormfieldCallbackTests(TestCase):
"""
def test_inlineformset_factory_default(self):
Formset = inlineformset_factory(User, UserSite, form=UserSiteForm)
Formset = inlineformset_factory(User, UserSite, form=UserSiteForm, fields="__all__")
form = Formset().forms[0]
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
@ -315,7 +316,7 @@ class FormfieldCallbackTests(TestCase):
def test_inlineformset_custom_callback(self):
callback = Callback()
inlineformset_factory(User, UserSite, form=UserSiteForm,
formfield_callback=callback)
formfield_callback=callback, fields="__all__")
self.assertCallbackCalled(callback)
def test_modelformset_custom_callback(self):
@ -353,6 +354,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
""" A model form with a 'should_delete' method """
class Meta:
model = User
fields = "__all__"
def should_delete(self):
""" delete form if odd PK """

View File

@ -418,6 +418,8 @@ class ModelInheritanceTest(TestCase):
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = '__all__'
User.objects.create(username="user_only")
p = Profile.objects.create(username="user_with_profile")
form = ProfileForm({'username': "user_with_profile", 'extra': "hello"},

View File

@ -229,9 +229,6 @@ class ModelAdminTests(TestCase):
class AdminBandForm(forms.ModelForm):
delete = forms.BooleanField()
class Meta:
model = Band
class BandAdmin(ModelAdmin):
form = AdminBandForm
@ -319,8 +316,7 @@ class ModelAdminTests(TestCase):
'</select>' % (band2.id, self.band.id))
class AdminConcertForm(forms.ModelForm):
class Meta:
model = Concert
pass
def __init__(self, *args, **kwargs):
super(AdminConcertForm, self).__init__(*args, **kwargs)
@ -685,9 +681,6 @@ class ValidationTests(unittest.TestCase):
class AdminBandForm(forms.ModelForm):
delete = forms.BooleanField()
class Meta:
model = Band
class BandAdmin(ModelAdmin):
form = AdminBandForm

View File

@ -11,3 +11,4 @@ class EventSplitForm(forms.Form):
class EventModelForm(forms.ModelForm):
class Meta:
model = Event
fields = '__all__'