Code

Ticket #17642: 17642.patch

File 17642.patch, 11.4 KB (added by michal@…, 2 years ago)

Added support for min_num in admin inlines

  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index ee4ff97..f40074a 100644
    a b class InlineModelAdmin(BaseModelAdmin): 
    13561356    formset = BaseInlineFormSet 
    13571357    extra = 3 
    13581358    max_num = None 
     1359    min_num = None 
    13591360    template = None 
    13601361    verbose_name = None 
    13611362    verbose_name_plural = None 
    class InlineModelAdmin(BaseModelAdmin): 
    14091410            "formfield_callback": partial(self.formfield_for_dbfield, request=request), 
    14101411            "extra": self.extra, 
    14111412            "max_num": self.max_num, 
     1413            "min_num": self.min_num, 
    14121414            "can_delete": can_delete, 
    14131415        } 
    14141416        defaults.update(kwargs) 
  • django/contrib/contenttypes/generic.py

    diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
    index c513787..619f50d 100644
    a b def generic_inlineformset_factory(model, form=ModelForm, 
    424424                                  ct_field="content_type", fk_field="object_id", 
    425425                                  fields=None, exclude=None, 
    426426                                  extra=3, can_order=False, can_delete=True, 
    427                                   max_num=None, 
     427                                  max_num=None, min_num=None, 
    428428                                  formfield_callback=None): 
    429429    """ 
    430430    Returns an ``GenericInlineFormSet`` for the given kwargs. 
    def generic_inlineformset_factory(model, form=ModelForm, 
    449449                                   formfield_callback=formfield_callback, 
    450450                                   formset=formset, 
    451451                                   extra=extra, can_delete=can_delete, can_order=can_order, 
    452                                    fields=fields, exclude=exclude, max_num=max_num) 
     452                                   fields=fields, exclude=exclude, max_num=max_num, min_num=min_num) 
    453453    FormSet.ct_field = ct_field 
    454454    FormSet.ct_fk_field = fk_field 
    455455    return FormSet 
    class GenericInlineModelAdmin(InlineModelAdmin): 
    486486            "can_order": False, 
    487487            "fields": fields, 
    488488            "max_num": self.max_num, 
     489            "min_num": self.min_num, 
    489490            "exclude": exclude 
    490491        } 
    491492        defaults.update(kwargs) 
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index cd8f027..12cef23 100644
    a b class BaseModelFormSet(BaseFormSet): 
    664664def modelformset_factory(model, form=ModelForm, formfield_callback=None, 
    665665                         formset=BaseModelFormSet, 
    666666                         extra=1, can_delete=False, can_order=False, 
    667                          max_num=None, fields=None, exclude=None): 
     667                         max_num=None, min_num=None, fields=None, exclude=None): 
    668668    """ 
    669669    Returns a FormSet class for the given Django model class. 
    670670    """ 
    671671    form = modelform_factory(model, form=form, fields=fields, exclude=exclude, 
    672672                             formfield_callback=formfield_callback) 
    673     FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, 
     673    FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, min_num=min_num, 
    674674                              can_order=can_order, can_delete=can_delete) 
    675675    FormSet.model = model 
    676676    return FormSet 
    def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): 
    806806def inlineformset_factory(parent_model, model, form=ModelForm, 
    807807                          formset=BaseInlineFormSet, fk_name=None, 
    808808                          fields=None, exclude=None, 
    809                           extra=3, can_order=False, can_delete=True, max_num=None, 
     809                          extra=3, can_order=False, can_delete=True, max_num=None, min_num=None, 
    810810                          formfield_callback=None): 
    811811    """ 
    812812    Returns an ``InlineFormSet`` for the given kwargs. 
    def inlineformset_factory(parent_model, model, form=ModelForm, 
    828828        'fields': fields, 
    829829        'exclude': exclude, 
    830830        'max_num': max_num, 
     831        'min_num': min_num, 
    831832    } 
    832833    FormSet = modelformset_factory(model, **kwargs) 
    833834    FormSet.fk = fk 
  • docs/ref/contrib/admin/index.txt

    diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
    index 76ea780..ebd9ae3 100644
    a b The ``InlineModelAdmin`` class adds: 
    14651465    doesn't directly correlate to the number of objects, but can if the value 
    14661466    is small enough. See :ref:`model-formsets-max-num` for more information. 
    14671467 
     1468.. attribute:: InlineModelAdmin.min_num 
     1469 
     1470    .. versionadded:: 1.4 
     1471 
     1472    This controls the minumum number of forms to show in the inline. 
     1473    See :ref:`formsets-min-num` for more information. 
     1474 
     1475    .. _ref-contrib-admin-inline-min-num: 
     1476 
    14681477.. attribute:: InlineModelAdmin.raw_id_fields 
    14691478 
    14701479    By default, Django's admin uses a select-box interface (<select>) for 
  • docs/topics/forms/formsets.txt

    diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
    index 9a91182..0646c60 100644
    a b from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value. 
    117117Ensuring the minimum number of forms 
    118118------------------------------------ 
    119119 
     120.. versionadded:: 1.4 
     121 
    120122The ``min_num`` parameter to ``formset_factory`` gives you the ability to make 
    121123sure that at least a certain number of forms will be displayed:: 
    122124 
    sure that at least a certain number of forms will be displayed:: 
    133135    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr> 
    134136    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr> 
    135137 
    136 .. versionchanged:: 1.4 
    137138 
    138139If the value of ``min_num`` is greater than the number of elements that would 
    139140be displayed normally (including extra forms), additional blank forms will be 
  • tests/regressiontests/generic_inline_admin/admin.py

    diff --git a/tests/regressiontests/generic_inline_admin/admin.py b/tests/regressiontests/generic_inline_admin/admin.py
    index 73cac7f..9b12c70 100644
    a b from django.contrib import admin 
    44from django.contrib.contenttypes import generic 
    55 
    66from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact, 
    7     Category, EpisodePermanent, EpisodeMaxNum) 
     7    Category, EpisodePermanent, EpisodeMaxNum, EpisodeMinNum) 
    88 
    99 
    1010site = admin.AdminSite(name="admin") 
    class MediaMaxNumInline(generic.GenericTabularInline): 
    2929    extra = 5 
    3030    max_num = 2 
    3131 
     32class MediaMinNumInline(generic.GenericTabularInline): 
     33    model = Media 
     34    extra = 1 
     35    min_num = 3 
    3236 
    3337class PhoneNumberInline(generic.GenericTabularInline): 
    3438    model = PhoneNumber 
    class MediaPermanentInline(generic.GenericTabularInline): 
    4246site.register(Episode, EpisodeAdmin) 
    4347site.register(EpisodeExtra, inlines=[MediaExtraInline]) 
    4448site.register(EpisodeMaxNum, inlines=[MediaMaxNumInline]) 
     49site.register(EpisodeMinNum, inlines=[MediaMinNumInline]) 
    4550site.register(Contact, inlines=[PhoneNumberInline]) 
    4651site.register(Category) 
    4752site.register(EpisodePermanent, inlines=[MediaPermanentInline]) 
  • tests/regressiontests/generic_inline_admin/models.py

    diff --git a/tests/regressiontests/generic_inline_admin/models.py b/tests/regressiontests/generic_inline_admin/models.py
    index b9426b4..65d9df9 100644
    a b class EpisodeExtra(Episode): 
    4242class EpisodeMaxNum(Episode): 
    4343    pass 
    4444 
     45# 
     46# Generic inline with extra and min_num 
     47# 
     48class EpisodeMinNum(Episode): 
     49    pass 
    4550 
    4651# 
    4752# Generic inline with unique_together 
  • tests/regressiontests/generic_inline_admin/tests.py

    diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/regressiontests/generic_inline_admin/tests.py
    index db81eec..829a793 100644
    a b from django.test import TestCase 
    1212 
    1313# local test models 
    1414from .admin import MediaInline, MediaPermanentInline 
    15 from .models import (Episode, EpisodeExtra, EpisodeMaxNum, Media, 
     15from .models import (Episode, EpisodeExtra, EpisodeMaxNum, EpisodeMinNum, Media, 
    1616    EpisodePermanent, Category) 
    1717 
    1818 
    class GenericInlineAdminParametersTest(TestCase): 
    174174        With extra=5 and max_num=2, there should be only 2 forms. 
    175175        """ 
    176176        e = self._create_object(EpisodeMaxNum) 
    177         inline_form_data = '<input type="hidden" name="generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" value="2" id="id_generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" value="1" id="id_generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" />' 
     177        inline_form_data = """<input id="id_generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" name="generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" type="hidden" value="2" /><input id="id_generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" name="generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" type="hidden" value="1" /><input id="id_generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS" name="generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS" type="hidden" value="2" /><input id="id_generic_inline_admin-media-content_type-object_id-MIN_NUM_FORMS" name="generic_inline_admin-media-content_type-object_id-MIN_NUM_FORMS" type="hidden" />""" 
     178 
    178179        response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodemaxnum/%s/' % e.pk) 
    179180        formset = response.context['inline_admin_formsets'][0].formset 
     181 
     182        self.assertHTMLEqual( str(formset.management_form), inline_form_data ) 
    180183        self.assertEqual(formset.total_form_count(), 2) 
    181184        self.assertEqual(formset.initial_form_count(), 1) 
    182185 
     186    def testMinNumParam(self): 
     187        """ 
     188        With extra=1 and min_num=3, there should be exactly 3 forms, one filled and 2 blank. 
     189        """ 
     190        e = self._create_object(EpisodeMinNum) 
     191        inline_form_data =  """<input type="hidden" name="generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" value="3" id="id_generic_inline_admin-media-content_type-object_id-TOTAL_FORMS" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" value="1" id="id_generic_inline_admin-media-content_type-object_id-INITIAL_FORMS" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS" id="id_generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-MIN_NUM_FORMS" value="3" id="id_generic_inline_admin-media-content_type-object_id-MIN_NUM_FORMS" />""" 
     192 
     193        response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeminnum/%s/' % e.pk) 
     194        formset = response.context['inline_admin_formsets'][0].formset 
     195 
     196        self.assertHTMLEqual( str(formset.management_form), inline_form_data ) 
     197        self.assertEqual(formset.total_form_count(), 3) 
     198        self.assertEqual(formset.initial_form_count(), 1) 
    183199 
    184200class GenericInlineAdminWithUniqueTogetherTest(TestCase): 
    185201    urls = "regressiontests.generic_inline_admin.urls"