mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
[1.7.x] Fixed #17642 -- Added min_num support to modelformsets, inlines, and the admin.
Thanks Stephen Burrows for work on the patch as well.
This commit is contained in:
parent
93d5b0d5b6
commit
2914f66983
@ -850,6 +850,7 @@ class InlineModelAdminChecks(BaseModelAdminChecks):
|
|||||||
errors.extend(self._check_exclude_of_parent_model(cls, parent_model))
|
errors.extend(self._check_exclude_of_parent_model(cls, parent_model))
|
||||||
errors.extend(self._check_extra(cls))
|
errors.extend(self._check_extra(cls))
|
||||||
errors.extend(self._check_max_num(cls))
|
errors.extend(self._check_max_num(cls))
|
||||||
|
errors.extend(self._check_min_num(cls))
|
||||||
errors.extend(self._check_formset(cls))
|
errors.extend(self._check_formset(cls))
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@ -909,12 +910,22 @@ class InlineModelAdminChecks(BaseModelAdminChecks):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _check_min_num(self, cls):
|
||||||
|
""" Check that min_num is an integer. """
|
||||||
|
|
||||||
|
if cls.min_num is None:
|
||||||
|
return []
|
||||||
|
elif not isinstance(cls.min_num, int):
|
||||||
|
return must_be('an integer', option='min_num', obj=cls, id='admin.E205')
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
def _check_formset(self, cls):
|
def _check_formset(self, cls):
|
||||||
""" Check formset is a subclass of BaseModelFormSet. """
|
""" Check formset is a subclass of BaseModelFormSet. """
|
||||||
|
|
||||||
if not issubclass(cls.formset, BaseModelFormSet):
|
if not issubclass(cls.formset, BaseModelFormSet):
|
||||||
return must_inherit_from(parent='BaseModelFormSet', option='formset',
|
return must_inherit_from(parent='BaseModelFormSet', option='formset',
|
||||||
obj=cls, id='admin.E205')
|
obj=cls, id='admin.E206')
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -1707,6 +1707,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||||||
fk_name = None
|
fk_name = None
|
||||||
formset = BaseInlineFormSet
|
formset = BaseInlineFormSet
|
||||||
extra = 3
|
extra = 3
|
||||||
|
min_num = None
|
||||||
max_num = None
|
max_num = None
|
||||||
template = None
|
template = None
|
||||||
verbose_name = None
|
verbose_name = None
|
||||||
@ -1739,6 +1740,10 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||||||
"""Hook for customizing the number of extra inline forms."""
|
"""Hook for customizing the number of extra inline forms."""
|
||||||
return self.extra
|
return self.extra
|
||||||
|
|
||||||
|
def get_min_num(self, request, obj=None, **kwargs):
|
||||||
|
"""Hook for customizing the min number of inline forms."""
|
||||||
|
return self.min_num
|
||||||
|
|
||||||
def get_max_num(self, request, obj=None, **kwargs):
|
def get_max_num(self, request, obj=None, **kwargs):
|
||||||
"""Hook for customizing the max number of extra inline forms."""
|
"""Hook for customizing the max number of extra inline forms."""
|
||||||
return self.max_num
|
return self.max_num
|
||||||
@ -1770,6 +1775,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||||||
"exclude": exclude,
|
"exclude": exclude,
|
||||||
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
||||||
"extra": self.get_extra(request, obj, **kwargs),
|
"extra": self.get_extra(request, obj, **kwargs),
|
||||||
|
"min_num": self.get_min_num(request, obj, **kwargs),
|
||||||
"max_num": self.get_max_num(request, obj, **kwargs),
|
"max_num": self.get_max_num(request, obj, **kwargs),
|
||||||
"can_delete": can_delete,
|
"can_delete": can_delete,
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,7 @@ class GenericInlineModelAdmin(InlineModelAdmin):
|
|||||||
"can_delete": can_delete,
|
"can_delete": can_delete,
|
||||||
"can_order": False,
|
"can_order": False,
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
|
"min_num": self.min_num,
|
||||||
"max_num": self.max_num,
|
"max_num": self.max_num,
|
||||||
"exclude": exclude
|
"exclude": exclude
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,9 @@ def generic_inlineformset_factory(model, form=ModelForm,
|
|||||||
ct_field="content_type", fk_field="object_id",
|
ct_field="content_type", fk_field="object_id",
|
||||||
fields=None, exclude=None,
|
fields=None, exclude=None,
|
||||||
extra=3, can_order=False, can_delete=True,
|
extra=3, can_order=False, can_delete=True,
|
||||||
max_num=None,
|
max_num=None, formfield_callback=None,
|
||||||
formfield_callback=None, validate_max=False,
|
validate_max=False, for_concrete_model=True,
|
||||||
for_concrete_model=True):
|
min_num=None, validate_min=False):
|
||||||
"""
|
"""
|
||||||
Returns a ``GenericInlineFormSet`` for the given kwargs.
|
Returns a ``GenericInlineFormSet`` for the given kwargs.
|
||||||
|
|
||||||
@ -81,7 +81,8 @@ def generic_inlineformset_factory(model, form=ModelForm,
|
|||||||
formset=formset,
|
formset=formset,
|
||||||
extra=extra, can_delete=can_delete, can_order=can_order,
|
extra=extra, can_delete=can_delete, can_order=can_order,
|
||||||
fields=fields, exclude=exclude, max_num=max_num,
|
fields=fields, exclude=exclude, max_num=max_num,
|
||||||
validate_max=validate_max)
|
validate_max=validate_max, min_num=min_num,
|
||||||
|
validate_min=validate_min)
|
||||||
FormSet.ct_field = ct_field
|
FormSet.ct_field = ct_field
|
||||||
FormSet.ct_fk_field = fk_field
|
FormSet.ct_fk_field = fk_field
|
||||||
FormSet.for_concrete_model = for_concrete_model
|
FormSet.for_concrete_model = for_concrete_model
|
||||||
|
@ -810,7 +810,8 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
|
|||||||
formset=BaseModelFormSet, extra=1, can_delete=False,
|
formset=BaseModelFormSet, extra=1, can_delete=False,
|
||||||
can_order=False, max_num=None, fields=None, exclude=None,
|
can_order=False, max_num=None, fields=None, exclude=None,
|
||||||
widgets=None, validate_max=False, localized_fields=None,
|
widgets=None, validate_max=False, localized_fields=None,
|
||||||
labels=None, help_texts=None, error_messages=None):
|
labels=None, help_texts=None, error_messages=None,
|
||||||
|
min_num=None, validate_min=False):
|
||||||
"""
|
"""
|
||||||
Returns a FormSet class for the given Django model class.
|
Returns a FormSet class for the given Django model class.
|
||||||
"""
|
"""
|
||||||
@ -833,9 +834,9 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
|
|||||||
formfield_callback=formfield_callback,
|
formfield_callback=formfield_callback,
|
||||||
widgets=widgets, localized_fields=localized_fields,
|
widgets=widgets, localized_fields=localized_fields,
|
||||||
labels=labels, help_texts=help_texts, error_messages=error_messages)
|
labels=labels, help_texts=help_texts, error_messages=error_messages)
|
||||||
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
|
FormSet = formset_factory(form, formset, extra=extra, min_num=min_num, max_num=max_num,
|
||||||
can_order=can_order, can_delete=can_delete,
|
can_order=can_order, can_delete=can_delete,
|
||||||
validate_max=validate_max)
|
validate_min=validate_min, validate_max=validate_max)
|
||||||
FormSet.model = model
|
FormSet.model = model
|
||||||
return FormSet
|
return FormSet
|
||||||
|
|
||||||
@ -979,7 +980,8 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
|
|||||||
fields=None, exclude=None, extra=3, can_order=False,
|
fields=None, exclude=None, extra=3, can_order=False,
|
||||||
can_delete=True, max_num=None, formfield_callback=None,
|
can_delete=True, max_num=None, formfield_callback=None,
|
||||||
widgets=None, validate_max=False, localized_fields=None,
|
widgets=None, validate_max=False, localized_fields=None,
|
||||||
labels=None, help_texts=None, error_messages=None):
|
labels=None, help_texts=None, error_messages=None,
|
||||||
|
min_num=None, validate_min=False):
|
||||||
"""
|
"""
|
||||||
Returns an ``InlineFormSet`` for the given kwargs.
|
Returns an ``InlineFormSet`` for the given kwargs.
|
||||||
|
|
||||||
@ -999,8 +1001,10 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
|
|||||||
'can_order': can_order,
|
'can_order': can_order,
|
||||||
'fields': fields,
|
'fields': fields,
|
||||||
'exclude': exclude,
|
'exclude': exclude,
|
||||||
|
'min_num': min_num,
|
||||||
'max_num': max_num,
|
'max_num': max_num,
|
||||||
'widgets': widgets,
|
'widgets': widgets,
|
||||||
|
'validate_min': validate_min,
|
||||||
'validate_max': validate_max,
|
'validate_max': validate_max,
|
||||||
'localized_fields': localized_fields,
|
'localized_fields': localized_fields,
|
||||||
'labels': labels,
|
'labels': labels,
|
||||||
|
@ -204,7 +204,8 @@ inline on a :class:`~django.contrib.admin.ModelAdmin`.
|
|||||||
* **admin.E202**: ``<model>`` has no ForeignKey to ``<parent model>``./``<model>`` has more than one ForeignKey to ``<parent model>``.
|
* **admin.E202**: ``<model>`` has no ForeignKey to ``<parent model>``./``<model>`` has more than one ForeignKey to ``<parent model>``.
|
||||||
* **admin.E203**: The value of ``extra`` must be an integer.
|
* **admin.E203**: The value of ``extra`` must be an integer.
|
||||||
* **admin.E204**: The value of ``max_num`` must be an integer.
|
* **admin.E204**: The value of ``max_num`` must be an integer.
|
||||||
* **admin.E205**: The value of ``formset`` must inherit from ``BaseModelFormSet``.
|
* **admin.E205**: The value of ``min_num`` must be an integer.
|
||||||
|
* **admin.E206**: The value of ``formset`` must inherit from ``BaseModelFormSet``.
|
||||||
|
|
||||||
GenericInlineModelAdmin
|
GenericInlineModelAdmin
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -1994,6 +1994,16 @@ The ``InlineModelAdmin`` class adds:
|
|||||||
:meth:`InlineModelAdmin.get_max_num` also allows you to customize the
|
:meth:`InlineModelAdmin.get_max_num` also allows you to customize the
|
||||||
maximum number of extra forms.
|
maximum number of extra forms.
|
||||||
|
|
||||||
|
.. attribute:: InlineModelAdmin.min_num
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
This controls the minimum number of forms to show in the inline.
|
||||||
|
See :func:`~django.forms.models.modelformset_factory` for more information.
|
||||||
|
|
||||||
|
:meth:`InlineModelAdmin.get_min_num` also allows you to customize the
|
||||||
|
minimum number of displayed forms.
|
||||||
|
|
||||||
.. attribute:: InlineModelAdmin.raw_id_fields
|
.. attribute:: InlineModelAdmin.raw_id_fields
|
||||||
|
|
||||||
By default, Django's admin uses a select-box interface (<select>) for
|
By default, Django's admin uses a select-box interface (<select>) for
|
||||||
@ -2074,6 +2084,16 @@ The ``InlineModelAdmin`` class adds:
|
|||||||
return max_num - 5
|
return max_num - 5
|
||||||
return max_num
|
return max_num
|
||||||
|
|
||||||
|
.. method:: InlineModelAdmin.get_min_num(request, obj=None, **kwargs)
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Returns the minimum number of inline forms to use. By default,
|
||||||
|
returns the :attr:`InlineModelAdmin.min_num` attribute.
|
||||||
|
|
||||||
|
Override this method to programmatically determine the minimum number of
|
||||||
|
inline forms. For example, this may be based on the model instance
|
||||||
|
(passed as the keyword argument ``obj``).
|
||||||
|
|
||||||
Working with a model with two or more foreign keys to the same parent model
|
Working with a model with two or more foreign keys to the same parent model
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
@ -500,7 +500,7 @@ The :mod:`django.contrib.contenttypes.forms` module provides:
|
|||||||
|
|
||||||
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True)
|
.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False)
|
||||||
|
|
||||||
Returns a ``GenericInlineFormSet`` using
|
Returns a ``GenericInlineFormSet`` using
|
||||||
:func:`~django.forms.models.modelformset_factory`.
|
:func:`~django.forms.models.modelformset_factory`.
|
||||||
@ -521,6 +521,10 @@ The :mod:`django.contrib.contenttypes.forms` module provides:
|
|||||||
|
|
||||||
This function used to be defined in ``django.contrib.contenttypes.generic``.
|
This function used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
``min_num`` and ``validate_min`` were added.
|
||||||
|
|
||||||
|
|
||||||
.. module:: django.contrib.contenttypes.admin
|
.. module:: django.contrib.contenttypes.admin
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ Model Form Functions
|
|||||||
The ``localized_fields``, ``labels``, ``help_texts``, and
|
The ``localized_fields``, ``labels``, ``help_texts``, and
|
||||||
``error_messages`` parameters were added.
|
``error_messages`` parameters were added.
|
||||||
|
|
||||||
.. 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, localized_fields=None, labels=None, help_texts=None, error_messages=None)
|
.. 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, localized_fields=None, labels=None, help_texts=None, error_messages=None, min_num=None, validate_min=False)
|
||||||
|
|
||||||
Returns a ``FormSet`` class for the given ``model`` class.
|
Returns a ``FormSet`` class for the given ``model`` class.
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ Model Form Functions
|
|||||||
The ``widgets``, ``validate_max``, ``localized_fields``, ``labels``,
|
The ``widgets``, ``validate_max``, ``localized_fields``, ``labels``,
|
||||||
``help_texts``, and ``error_messages`` parameters were added.
|
``help_texts``, and ``error_messages`` parameters were added.
|
||||||
|
|
||||||
.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None, labels=None, help_texts=None, error_messages=None)
|
.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None, labels=None, help_texts=None, error_messages=None, min_num=None, validate_min=False)
|
||||||
|
|
||||||
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
|
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
|
||||||
defaults of ``formset=``:class:`~django.forms.models.BaseInlineFormSet`,
|
defaults of ``formset=``:class:`~django.forms.models.BaseInlineFormSet`,
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.contrib.admin import TabularInline, ModelAdmin
|
||||||
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
|
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
|
||||||
from django.contrib.admin.helpers import InlineAdminForm
|
from django.contrib.admin.helpers import InlineAdminForm
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings, RequestFactory
|
||||||
|
|
||||||
# local test models
|
# local test models
|
||||||
from .admin import InnerInline
|
from .admin import InnerInline, site as admin_site
|
||||||
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
|
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
|
||||||
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
|
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
|
||||||
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
|
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
|
||||||
@ -28,6 +29,7 @@ class TestInline(TestCase):
|
|||||||
|
|
||||||
result = self.client.login(username='super', password='secret')
|
result = self.client.login(username='super', password='secret')
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
@ -221,6 +223,62 @@ class TestInline(TestCase):
|
|||||||
self.assertContains(response, max_forms_input % 2)
|
self.assertContains(response, max_forms_input % 2)
|
||||||
self.assertContains(response, total_forms_hidden)
|
self.assertContains(response, total_forms_hidden)
|
||||||
|
|
||||||
|
def test_min_num(self):
|
||||||
|
"""
|
||||||
|
Ensure that min_num and extra determine number of forms.
|
||||||
|
"""
|
||||||
|
class MinNumInline(TabularInline):
|
||||||
|
model = BinaryTree
|
||||||
|
min_num = 2
|
||||||
|
extra = 3
|
||||||
|
|
||||||
|
modeladmin = ModelAdmin(BinaryTree, admin_site)
|
||||||
|
modeladmin.inlines = [MinNumInline]
|
||||||
|
|
||||||
|
min_forms = '<input id="id_binarytree_set-MIN_NUM_FORMS" name="binarytree_set-MIN_NUM_FORMS" type="hidden" value="2" />'
|
||||||
|
total_forms = '<input id="id_binarytree_set-TOTAL_FORMS" name="binarytree_set-TOTAL_FORMS" type="hidden" value="5" />'
|
||||||
|
|
||||||
|
request = self.factory.get('/admin/admin_inlines/binarytree/add/')
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request)
|
||||||
|
self.assertContains(response, min_forms)
|
||||||
|
self.assertContains(response, total_forms)
|
||||||
|
|
||||||
|
def test_custom_min_num(self):
|
||||||
|
"""
|
||||||
|
Ensure that get_min_num is called and used correctly.
|
||||||
|
See #22628 - this will change when that's fixed.
|
||||||
|
"""
|
||||||
|
bt_head = BinaryTree.objects.create(name="Tree Head")
|
||||||
|
BinaryTree.objects.create(name="First Child", parent=bt_head)
|
||||||
|
|
||||||
|
class MinNumInline(TabularInline):
|
||||||
|
model = BinaryTree
|
||||||
|
extra = 3
|
||||||
|
|
||||||
|
def get_min_num(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 5
|
||||||
|
return 2
|
||||||
|
|
||||||
|
modeladmin = ModelAdmin(BinaryTree, admin_site)
|
||||||
|
modeladmin.inlines = [MinNumInline]
|
||||||
|
|
||||||
|
min_forms = '<input id="id_binarytree_set-MIN_NUM_FORMS" name="binarytree_set-MIN_NUM_FORMS" type="hidden" value="%d" />'
|
||||||
|
total_forms = '<input id="id_binarytree_set-TOTAL_FORMS" name="binarytree_set-TOTAL_FORMS" type="hidden" value="%d" />'
|
||||||
|
|
||||||
|
request = self.factory.get('/admin/admin_inlines/binarytree/add/')
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request)
|
||||||
|
self.assertContains(response, min_forms % 2)
|
||||||
|
self.assertContains(response, total_forms % 5)
|
||||||
|
|
||||||
|
request = self.factory.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id)
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request, object_id=str(bt_head.id))
|
||||||
|
self.assertContains(response, min_forms % 5)
|
||||||
|
self.assertContains(response, total_forms % 9)
|
||||||
|
|
||||||
def test_inline_nonauto_noneditable_pk(self):
|
def test_inline_nonauto_noneditable_pk(self):
|
||||||
response = self.client.get('/admin/admin_inlines/author/add/')
|
response = self.client.get('/admin/admin_inlines/author/add/')
|
||||||
self.assertContains(response,
|
self.assertContains(response,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes.admin import GenericTabularInline
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
|
||||||
from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact,
|
from .models import (Media, PhoneNumber, Episode, Contact,
|
||||||
Category, EpisodePermanent, EpisodeMaxNum)
|
Category, EpisodePermanent)
|
||||||
|
|
||||||
|
|
||||||
site = admin.AdminSite(name="admin")
|
site = admin.AdminSite(name="admin")
|
||||||
@ -18,17 +18,6 @@ class EpisodeAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MediaExtraInline(GenericTabularInline):
|
|
||||||
model = Media
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class MediaMaxNumInline(GenericTabularInline):
|
|
||||||
model = Media
|
|
||||||
extra = 5
|
|
||||||
max_num = 2
|
|
||||||
|
|
||||||
|
|
||||||
class PhoneNumberInline(GenericTabularInline):
|
class PhoneNumberInline(GenericTabularInline):
|
||||||
model = PhoneNumber
|
model = PhoneNumber
|
||||||
|
|
||||||
@ -39,8 +28,6 @@ class MediaPermanentInline(GenericTabularInline):
|
|||||||
|
|
||||||
|
|
||||||
site.register(Episode, EpisodeAdmin)
|
site.register(Episode, EpisodeAdmin)
|
||||||
site.register(EpisodeExtra, inlines=[MediaExtraInline])
|
|
||||||
site.register(EpisodeMaxNum, inlines=[MediaMaxNumInline])
|
|
||||||
site.register(Contact, inlines=[PhoneNumberInline])
|
site.register(Contact, inlines=[PhoneNumberInline])
|
||||||
site.register(Category)
|
site.register(Category)
|
||||||
site.register(EpisodePermanent, inlines=[MediaPermanentInline])
|
site.register(EpisodePermanent, inlines=[MediaPermanentInline])
|
||||||
|
@ -27,26 +27,6 @@ class Media(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
#
|
|
||||||
# These models let us test the different GenericInline settings at
|
|
||||||
# different urls in the admin site.
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generic inline with extra = 0
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class EpisodeExtra(Episode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generic inline with extra and max_num
|
|
||||||
#
|
|
||||||
class EpisodeMaxNum(Episode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Generic inline with unique_together
|
# Generic inline with unique_together
|
||||||
|
@ -4,16 +4,16 @@ import warnings
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.admin import GenericTabularInline
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
from django.contrib.contenttypes.forms import generic_inlineformset_factory
|
from django.contrib.contenttypes.forms import generic_inlineformset_factory
|
||||||
from django.forms.formsets import DEFAULT_MAX_NUM
|
from django.forms.formsets import DEFAULT_MAX_NUM
|
||||||
from django.forms.models import ModelForm
|
from django.forms.models import ModelForm
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings, RequestFactory
|
||||||
|
|
||||||
# local test models
|
# local test models
|
||||||
from .admin import MediaInline, MediaPermanentInline
|
from .admin import MediaInline, MediaPermanentInline, site as admin_site
|
||||||
from .models import (Episode, EpisodeExtra, EpisodeMaxNum, Media,
|
from .models import Episode, Media, EpisodePermanent, Category
|
||||||
EpisodePermanent, Category)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
|
||||||
@ -136,6 +136,7 @@ class GenericInlineAdminParametersTest(TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username='super', password='secret')
|
self.client.login(username='super', password='secret')
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
@ -165,9 +166,18 @@ class GenericInlineAdminParametersTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
With extra=0, there should be one form.
|
With extra=0, there should be one form.
|
||||||
"""
|
"""
|
||||||
e = self._create_object(EpisodeExtra)
|
class ExtraInline(GenericTabularInline):
|
||||||
response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeextra/%s/' % e.pk)
|
model = Media
|
||||||
formset = response.context['inline_admin_formsets'][0].formset
|
extra = 0
|
||||||
|
|
||||||
|
modeladmin = admin.ModelAdmin(Episode, admin_site)
|
||||||
|
modeladmin.inlines = [ExtraInline]
|
||||||
|
|
||||||
|
e = self._create_object(Episode)
|
||||||
|
request = self.factory.get('/generic_inline_admin/admin/generic_inline_admin/episode/%s/' % e.pk)
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request, object_id=str(e.pk))
|
||||||
|
formset = response.context_data['inline_admin_formsets'][0].formset
|
||||||
self.assertEqual(formset.total_form_count(), 1)
|
self.assertEqual(formset.total_form_count(), 1)
|
||||||
self.assertEqual(formset.initial_form_count(), 1)
|
self.assertEqual(formset.initial_form_count(), 1)
|
||||||
|
|
||||||
@ -175,12 +185,43 @@ class GenericInlineAdminParametersTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
With extra=5 and max_num=2, there should be only 2 forms.
|
With extra=5 and max_num=2, there should be only 2 forms.
|
||||||
"""
|
"""
|
||||||
e = self._create_object(EpisodeMaxNum)
|
class MaxNumInline(GenericTabularInline):
|
||||||
response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodemaxnum/%s/' % e.pk)
|
model = Media
|
||||||
formset = response.context['inline_admin_formsets'][0].formset
|
extra = 5
|
||||||
|
max_num = 2
|
||||||
|
|
||||||
|
modeladmin = admin.ModelAdmin(Episode, admin_site)
|
||||||
|
modeladmin.inlines = [MaxNumInline]
|
||||||
|
|
||||||
|
e = self._create_object(Episode)
|
||||||
|
request = self.factory.get('/generic_inline_admin/admin/generic_inline_admin/episode/%s/' % e.pk)
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request, object_id=str(e.pk))
|
||||||
|
formset = response.context_data['inline_admin_formsets'][0].formset
|
||||||
self.assertEqual(formset.total_form_count(), 2)
|
self.assertEqual(formset.total_form_count(), 2)
|
||||||
self.assertEqual(formset.initial_form_count(), 1)
|
self.assertEqual(formset.initial_form_count(), 1)
|
||||||
|
|
||||||
|
def testMinNumParam(self):
|
||||||
|
"""
|
||||||
|
With extra=3 and min_num=2, there should be six forms.
|
||||||
|
See #22628 - this will change when that's fixed.
|
||||||
|
"""
|
||||||
|
class MinNumInline(GenericTabularInline):
|
||||||
|
model = Media
|
||||||
|
extra = 3
|
||||||
|
min_num = 2
|
||||||
|
|
||||||
|
modeladmin = admin.ModelAdmin(Episode, admin_site)
|
||||||
|
modeladmin.inlines = [MinNumInline]
|
||||||
|
|
||||||
|
e = self._create_object(Episode)
|
||||||
|
request = self.factory.get('/generic_inline_admin/admin/generic_inline_admin/episode/%s/' % e.pk)
|
||||||
|
request.user = User(username='super', is_superuser=True)
|
||||||
|
response = modeladmin.changeform_view(request, object_id=str(e.pk))
|
||||||
|
formset = response.context_data['inline_admin_formsets'][0].formset
|
||||||
|
self.assertEqual(formset.total_form_count(), 6)
|
||||||
|
self.assertEqual(formset.initial_form_count(), 1)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
class GenericInlineAdminWithUniqueTogetherTest(TestCase):
|
class GenericInlineAdminWithUniqueTogetherTest(TestCase):
|
||||||
|
@ -366,6 +366,33 @@ class ModelFormsetTest(TestCase):
|
|||||||
'<Author: Walt Whitman>',
|
'<Author: Walt Whitman>',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_min_num(self):
|
||||||
|
# Test the behavior of min_num with model formsets. It should be
|
||||||
|
# added to extra.
|
||||||
|
qs = Author.objects.none()
|
||||||
|
|
||||||
|
AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=0)
|
||||||
|
formset = AuthorFormSet(queryset=qs)
|
||||||
|
self.assertEqual(len(formset.forms), 0)
|
||||||
|
|
||||||
|
AuthorFormSet = modelformset_factory(Author, fields="__all__", min_num=1, extra=0)
|
||||||
|
formset = AuthorFormSet(queryset=qs)
|
||||||
|
self.assertEqual(len(formset.forms), 1)
|
||||||
|
|
||||||
|
AuthorFormSet = modelformset_factory(Author, fields="__all__", min_num=1, extra=1)
|
||||||
|
formset = AuthorFormSet(queryset=qs)
|
||||||
|
self.assertEqual(len(formset.forms), 2)
|
||||||
|
|
||||||
|
def test_min_num_with_existing(self):
|
||||||
|
# Test the behavior of min_num with existing objects.
|
||||||
|
# See #22628 - this will change when that's fixed.
|
||||||
|
Author.objects.create(name='Charles Baudelaire')
|
||||||
|
qs = Author.objects.all()
|
||||||
|
|
||||||
|
AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=0, min_num=1)
|
||||||
|
formset = AuthorFormSet(queryset=qs)
|
||||||
|
self.assertEqual(len(formset.forms), 2)
|
||||||
|
|
||||||
def test_custom_save_method(self):
|
def test_custom_save_method(self):
|
||||||
class PoetForm(forms.ModelForm):
|
class PoetForm(forms.ModelForm):
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
@ -1444,6 +1444,33 @@ class MaxNumCheckTests(CheckTestCase):
|
|||||||
self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
|
self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
|
||||||
|
|
||||||
|
|
||||||
|
class MinNumCheckTests(CheckTestCase):
|
||||||
|
|
||||||
|
def test_not_integer(self):
|
||||||
|
class ValidationTestInline(TabularInline):
|
||||||
|
model = ValidationTestInlineModel
|
||||||
|
min_num = "hello"
|
||||||
|
|
||||||
|
class ValidationTestModelAdmin(ModelAdmin):
|
||||||
|
inlines = [ValidationTestInline]
|
||||||
|
|
||||||
|
self.assertIsInvalid(
|
||||||
|
ValidationTestModelAdmin, ValidationTestModel,
|
||||||
|
"The value of 'min_num' must be an integer.",
|
||||||
|
'admin.E205',
|
||||||
|
invalid_obj=ValidationTestInline)
|
||||||
|
|
||||||
|
def test_valid_case(self):
|
||||||
|
class ValidationTestInline(TabularInline):
|
||||||
|
model = ValidationTestInlineModel
|
||||||
|
min_num = 2
|
||||||
|
|
||||||
|
class ValidationTestModelAdmin(ModelAdmin):
|
||||||
|
inlines = [ValidationTestInline]
|
||||||
|
|
||||||
|
self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
|
||||||
|
|
||||||
|
|
||||||
class FormsetCheckTests(CheckTestCase):
|
class FormsetCheckTests(CheckTestCase):
|
||||||
|
|
||||||
def test_invalid_type(self):
|
def test_invalid_type(self):
|
||||||
@ -1460,7 +1487,7 @@ class FormsetCheckTests(CheckTestCase):
|
|||||||
self.assertIsInvalid(
|
self.assertIsInvalid(
|
||||||
ValidationTestModelAdmin, ValidationTestModel,
|
ValidationTestModelAdmin, ValidationTestModel,
|
||||||
"The value of 'formset' must inherit from 'BaseModelFormSet'.",
|
"The value of 'formset' must inherit from 'BaseModelFormSet'.",
|
||||||
'admin.E205',
|
'admin.E206',
|
||||||
invalid_obj=ValidationTestInline)
|
invalid_obj=ValidationTestInline)
|
||||||
|
|
||||||
def test_valid_case(self):
|
def test_valid_case(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user