Ejemplo n.º 1
0
 def lazy_registration(self):
     # First, directly handle models we don't want at all,
     # as per the ``ADMIN_REMOVAL`` setting.
     for model in getattr(settings, "ADMIN_REMOVAL", []):
         try:
             model = tuple(model.rsplit(".", 1))
             exec("from %s import %s" % model)
         except ImportError:
             pass
         else:
             try:
                 AdminSite.unregister(self, eval(model[1]))
             except NotRegistered:
                 pass
     # Call register/unregister.
     for name, deferred_args, deferred_kwargs in self._deferred:
         getattr(AdminSite, name)(self, *deferred_args, **deferred_kwargs)
Ejemplo n.º 2
0
 def lazy_registration(self):
     # First, directly handle models we don't want at all,
     # as per the ``ADMIN_REMOVAL`` setting.
     for model in getattr(settings, "ADMIN_REMOVAL", []):
         try:
             model = tuple(model.rsplit(".", 1))
             exec("from %s import %s" % model)
         except ImportError:
             pass
         else:
             try:
                 AdminSite.unregister(self, eval(model[1]))
             except NotRegistered:
                 pass
     # Call register/unregister.
     for name, deferred_args, deferred_kwargs in self._deferred:
         getattr(AdminSite, name)(self, *deferred_args, **deferred_kwargs)
Ejemplo n.º 3
0
 def lazy_registration(self):
     # First pick up any admin classes registered via decorator to the
     # default admin site.
     for model, admin in default_site._registry.items():
         self._deferred.append(("register", (model, admin.__class__), {}))
     # Call register/unregister.
     for name, args, kwargs in self._deferred:
         try:
             getattr(AdminSite, name)(self, *args, **kwargs)
         except (AlreadyRegistered, NotRegistered):
             pass
     # Then handle models we don't want at all,
     # as per the ``ADMIN_REMOVAL`` setting.
     for model in getattr(settings, "ADMIN_REMOVAL", []):
         try:
             model = tuple(model.rsplit(".", 1))
             exec("from %s import %s" % model)
         except ImportError:
             pass
         else:
             try:
                 AdminSite.unregister(self, eval(model[1]))
             except NotRegistered:
                 pass
Ejemplo n.º 4
0
 def lazy_registration(self):
     # First pick up any admin classes registered via decorator to the
     # default admin site.
     for model, admin in default_site._registry.items():
         self._deferred.append(("register", (model, admin.__class__), {}))
     # Call register/unregister.
     for name, args, kwargs in self._deferred:
         try:
             getattr(AdminSite, name)(self, *args, **kwargs)
         except (AlreadyRegistered, NotRegistered):
             pass
     # Then handle models we don't want at all,
     # as per the ``ADMIN_REMOVAL`` setting.
     for model in getattr(settings, "ADMIN_REMOVAL", []):
         try:
             model = tuple(model.rsplit(".", 1))
             exec("from %s import %s" % model)
         except ImportError:
             pass
         else:
             try:
                 AdminSite.unregister(self, eval(model[1]))
             except NotRegistered:
                 pass
Ejemplo n.º 5
0
class ModelAdminTests(TestCase):

    def setUp(self):
        self.band = Band.objects.create(
            name='The Doors',
            bio='',
            sign_date=date(1965, 1, 1),
        )
        self.site = AdminSite()

    def test_modeladmin_str(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(str(ma), 'modeladmin.ModelAdmin')

    # form/fields/fieldsets interaction ##############################

    def test_default_fields(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request)), ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request, self.band)), ['name', 'bio', 'sign_date'])
        self.assertIsNone(ma.get_exclude(request, self.band))

    def test_default_fieldsets(self):
        # fieldsets_add and fieldsets_change should return a special data structure that
        # is used in the templates. They should generate the "right thing" whether we
        # have specified a custom form, the fields argument, or nothing at all.
        #
        # Here's the default case. There are no custom form_add/form_change methods,
        # no fields argument, and no fieldsets argument.
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(ma.get_fieldsets(request), [(None, {'fields': ['name', 'bio', 'sign_date']})])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {'fields': ['name', 'bio', 'sign_date']})])

    def test_get_fieldsets(self):
        # get_fieldsets() is called when figuring out form fields (#18681).
        class BandAdmin(ModelAdmin):
            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['name', 'bio']})]

        ma = BandAdmin(Band, self.site)
        form = ma.get_form(None)
        self.assertEqual(form._meta.fields, ['name', 'bio'])

        class InlineBandAdmin(TabularInline):
            model = Concert
            fk_name = 'main_band'
            can_delete = False

            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['day', 'transport']})]

        ma = InlineBandAdmin(Band, self.site)
        form = ma.get_formset(None).form
        self.assertEqual(form._meta.fields, ['day', 'transport'])

    def test_lookup_allowed_allows_nonexistent_lookup(self):
        """
        A lookup_allowed allows a parameter whose field lookup doesn't exist.
        (#21129).
        """
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertTrue(ma.lookup_allowed('name__nonexistent', 'test_value'))

    @isolate_apps('modeladmin')
    def test_lookup_allowed_onetoone(self):
        class Department(models.Model):
            code = models.CharField(max_length=4, unique=True)

        class Employee(models.Model):
            department = models.ForeignKey(Department, models.CASCADE, to_field="code")

        class EmployeeProfile(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)

        class EmployeeInfo(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)
            description = models.CharField(max_length=100)

        class EmployeeProfileAdmin(ModelAdmin):
            list_filter = [
                'employee__employeeinfo__description',
                'employee__department__code',
            ]

        ma = EmployeeProfileAdmin(EmployeeProfile, self.site)
        # Reverse OneToOneField
        self.assertIs(ma.lookup_allowed('employee__employeeinfo__description', 'test_value'), True)
        # OneToOneField and ForeignKey
        self.assertIs(ma.lookup_allowed('employee__department__code', 'test_value'), True)

    def test_field_arguments(self):
        # If fields is specified, fieldsets_add and fieldsets_change should
        # just stick the fields into a formsets structure and return it.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)

        self.assertEqual(list(ma.get_fields(request)), ['name'])
        self.assertEqual(list(ma.get_fields(request, self.band)), ['name'])
        self.assertEqual(ma.get_fieldsets(request), [(None, {'fields': ['name']})])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {'fields': ['name']})])

    def test_field_arguments_restricted_on_form(self):
        # If fields or fieldsets is specified, it should exclude fields on the
        # Form class to the fields specified. This may cause errors to be
        # raised in the db layer if required model fields aren't in fields/
        # fieldsets, but that's preferable to ghost errors where a field in the
        # Form class isn't being displayed because it's not in fields/fieldsets.

        # Using `fields`.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields), ['name'])

        # Using `fieldsets`.
        class BandAdmin(ModelAdmin):
            fieldsets = [(None, {'fields': ['name']})]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields), ['name'])

        # Using `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

        # You can also pass a tuple to `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ('bio',)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

        # Using `fields` and `exclude`.
        class BandAdmin(ModelAdmin):
            fields = ['name', 'bio']
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])

    def test_custom_form_meta_exclude_with_readonly(self):
        """
        The custom ModelForm's `Meta.exclude` is respected when used in
        conjunction with `ModelAdmin.readonly_fields` and when no
        `ModelAdmin.exclude` is defined (#14496).
        """
        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            readonly_fields = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['sign_date'])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            readonly_fields = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'opening_band', 'id', 'DELETE'])

    def test_custom_formfield_override_readonly(self):
        class AdminBandForm(forms.ModelForm):
            name = forms.CharField()

            class Meta:
                exclude = ()
                model = Band

        class BandAdmin(ModelAdmin):
            form = AdminBandForm
            readonly_fields = ['name']

        ma = BandAdmin(Band, self.site)

        # `name` shouldn't appear in base_fields because it's part of
        # readonly_fields.
        self.assertEqual(
            list(ma.get_form(request).base_fields),
            ['bio', 'sign_date']
        )
        # But it should appear in get_fields()/fieldsets() so it can be
        # displayed as read-only.
        self.assertEqual(
            list(ma.get_fields(request)),
            ['bio', 'sign_date', 'name']
        )
        self.assertEqual(
            list(ma.get_fieldsets(request)),
            [(None, {'fields': ['bio', 'sign_date', 'name']})]
        )

    def test_custom_form_meta_exclude(self):
        """
        The custom ModelForm's `Meta.exclude` is overridden if
        `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined (#14496).
        """
        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            exclude = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['bio', 'sign_date'])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'opening_band', 'day', 'id', 'DELETE']
        )

    def test_overriding_get_exclude(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                return ['name']

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ['bio', 'sign_date']
        )

    def test_get_exclude_overrides_exclude(self):
        class BandAdmin(ModelAdmin):
            exclude = ['bio']

            def get_exclude(self, request, obj=None):
                return ['name']

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ['bio', 'sign_date']
        )

    def test_get_exclude_takes_obj(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                if obj:
                    return ['sign_date']
                return ['name']

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request, self.band).base_fields),
            ['name', 'bio']
        )

    def test_custom_form_validation(self):
        # If a form is specified, it should use it allowing custom validation
        # to work properly. This won't break any of the admin widgets or media.
        class AdminBandForm(forms.ModelForm):
            delete = forms.BooleanField()

        class BandAdmin(ModelAdmin):
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'bio', 'sign_date', 'delete'])
        self.assertEqual(type(ma.get_form(request).base_fields['sign_date'].widget), AdminDateWidget)

    def test_form_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `ModelAdmin.get_form()` overrides all
        other declarations (#8999).
        """
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['name']

        class BandAdmin(ModelAdmin):
            exclude = ['sign_date']
            form = AdminBandForm

            def get_form(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['bio']
                return super().get_form(request, obj, **kwargs)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'sign_date'])

    def test_formset_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `InlineModelAdmin.get_formset()`
        overrides all other declarations (#8999).
        """
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_formset(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['opening_band']
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE']
        )

    def test_formset_overriding_get_exclude_with_form_fields(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ['main_band', 'opening_band', 'day', 'transport']

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_exclude(self, request, obj=None):
                return ['opening_band']

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE']
        )

    def test_formset_overriding_get_exclude_with_form_exclude(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_exclude(self, request, obj=None):
                return ['opening_band']

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE']
        )

    def test_queryset_override(self):
        # If the queryset of a ModelChoiceField in a custom form is overridden,
        # RelatedFieldWidgetWrapper doesn't mess that up.
        band2 = Band.objects.create(name='The Beatles', bio='', sign_date=date(1962, 1, 1))

        ma = ModelAdmin(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '<div class="related-widget-wrapper">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Beatles</option>'
            '<option value="%d">The Doors</option>'
            '</select></div>' % (band2.id, self.band.id)
        )

        class AdminConcertForm(forms.ModelForm):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.fields["main_band"].queryset = Band.objects.filter(name='The Doors')

        class ConcertAdminWithForm(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdminWithForm(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '<div class="related-widget-wrapper">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Doors</option>'
            '</select></div>' % self.band.id
        )

    def test_regression_for_ticket_15820(self):
        """
        `obj` is passed from `InlineModelAdmin.get_fieldsets()` to
        `InlineModelAdmin.get_formset()`.
        """
        class CustomConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ['day']

        class ConcertInline(TabularInline):
            model = Concert
            fk_name = 'main_band'

            def get_formset(self, request, obj=None, **kwargs):
                if obj:
                    kwargs['form'] = CustomConcertForm
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        Concert.objects.create(main_band=self.band, opening_band=self.band, day=1)
        ma = BandAdmin(Band, self.site)
        inline_instances = ma.get_inline_instances(request)
        fieldsets = list(inline_instances[0].get_fieldsets(request))
        self.assertEqual(fieldsets[0][1]['fields'], ['main_band', 'opening_band', 'day', 'transport'])
        fieldsets = list(inline_instances[0].get_fieldsets(request, inline_instances[0].model))
        self.assertEqual(fieldsets[0][1]['fields'], ['day'])

    # radio_fields behavior ###########################################

    def test_default_foreign_key_widget(self):
        # First, without any radio_fields specified, the widgets for ForeignKey
        # and fields with choices specified ought to be a basic Select widget.
        # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
        # they need to be handled properly when type checking. For Select fields, all of
        # the choices lists have a first entry of dashes.
        cma = ModelAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['main_band'].widget.choices),
            [('', '---------'), (self.band.id, 'The Doors')])

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', '---------'), (self.band.id, 'The Doors')]
        )
        self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['day'].widget.choices),
            [('', '---------'), (1, 'Fri'), (2, 'Sat')]
        )
        self.assertEqual(type(cmafa.base_fields['transport'].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields['transport'].widget.choices),
            [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')])

    def test_foreign_key_as_radio_field(self):
        # Now specify all the fields as radio_fields.  Widgets should now be
        # RadioSelect, and the choices list should have a first entry of 'None' if
        # blank=True for the model field.  Finally, the widget should have the
        # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
        class ConcertAdmin(ModelAdmin):
            radio_fields = {
                'main_band': HORIZONTAL,
                'opening_band': VERTICAL,
                'day': VERTICAL,
                'transport': HORIZONTAL,
            }

        cma = ConcertAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['main_band'].widget.attrs, {'class': 'radiolist inline'})
        self.assertEqual(
            list(cmafa.base_fields['main_band'].widget.choices),
            [(self.band.id, 'The Doors')]
        )

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs, {'class': 'radiolist'})
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', 'None'), (self.band.id, 'The Doors')]
        )
        self.assertEqual(type(cmafa.base_fields['day'].widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['day'].widget.attrs, {'class': 'radiolist'})
        self.assertEqual(list(cmafa.base_fields['day'].widget.choices), [(1, 'Fri'), (2, 'Sat')])

        self.assertEqual(type(cmafa.base_fields['transport'].widget), AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['transport'].widget.attrs, {'class': 'radiolist inline'})
        self.assertEqual(
            list(cmafa.base_fields['transport'].widget.choices),
            [('', 'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]
        )

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ('transport',)

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['main_band', 'opening_band', 'day'])

        class AdminConcertForm(forms.ModelForm):
            extra = forms.CharField()

            class Meta:
                model = Concert
                fields = ['extra', 'transport']

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['extra', 'transport'])

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            model = Concert
            fk_name = 'main_band'
            can_delete = True

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
            ['extra', 'transport', 'id', 'DELETE', 'main_band']
        )

    def test_log_actions(self):
        ma = ModelAdmin(Band, self.site)
        mock_request = MockRequest()
        mock_request.user = User.objects.create(username='******')
        content_type = get_content_type_for_model(self.band)
        tests = (
            (ma.log_addition, ADDITION, {'added': {}}),
            (ma.log_change, CHANGE, {'changed': {'fields': ['name', 'bio']}}),
            (ma.log_deletion, DELETION, str(self.band)),
        )
        for method, flag, message in tests:
            with self.subTest(name=method.__name__):
                created = method(mock_request, self.band, message)
                fetched = LogEntry.objects.filter(action_flag=flag).latest('id')
                self.assertEqual(created, fetched)
                self.assertEqual(fetched.action_flag, flag)
                self.assertEqual(fetched.content_type, content_type)
                self.assertEqual(fetched.object_id, str(self.band.pk))
                self.assertEqual(fetched.user, mock_request.user)
                if flag == DELETION:
                    self.assertEqual(fetched.change_message, '')
                    self.assertEqual(fetched.object_repr, message)
                else:
                    self.assertEqual(fetched.change_message, str(message))
                    self.assertEqual(fetched.object_repr, str(self.band))

    def test_get_autocomplete_fields(self):
        class NameAdmin(ModelAdmin):
            search_fields = ['name']

        class SongAdmin(ModelAdmin):
            autocomplete_fields = ['featuring']
            fields = ['featuring', 'band']

        class OtherSongAdmin(SongAdmin):
            def get_autocomplete_fields(self, request):
                return ['band']

        self.site.register(Band, NameAdmin)
        try:
            # Uses autocomplete_fields if not overridden.
            model_admin = SongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields['featuring'].widget.widget, AutocompleteSelectMultiple)
            # Uses overridden get_autocomplete_fields
            model_admin = OtherSongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields['band'].widget.widget, AutocompleteSelect)
        finally:
            self.site.unregister(Band)

    def test_get_deleted_objects(self):
        mock_request = MockRequest()
        mock_request.user = User.objects.create_superuser(username='******', email='*****@*****.**', password='******')
        self.site.register(Band, ModelAdmin)
        ma = self.site._registry[Band]
        deletable_objects, model_count, perms_needed, protected = ma.get_deleted_objects([self.band], request)
        self.assertEqual(deletable_objects, ['Band: The Doors'])
        self.assertEqual(model_count, {'bands': 1})
        self.assertEqual(perms_needed, set())
        self.assertEqual(protected, [])

    def test_get_deleted_objects_with_custom_has_delete_permission(self):
        """
        ModelAdmin.get_deleted_objects() uses ModelAdmin.has_delete_permission()
        for permissions checking.
        """
        mock_request = MockRequest()
        mock_request.user = User.objects.create_superuser(username='******', email='*****@*****.**', password='******')

        class TestModelAdmin(ModelAdmin):
            def has_delete_permission(self, request, obj=None):
                return False

        self.site.register(Band, TestModelAdmin)
        ma = self.site._registry[Band]
        deletable_objects, model_count, perms_needed, protected = ma.get_deleted_objects([self.band], request)
        self.assertEqual(deletable_objects, ['Band: The Doors'])
        self.assertEqual(model_count, {'bands': 1})
        self.assertEqual(perms_needed, {'band'})
        self.assertEqual(protected, [])
Ejemplo n.º 6
0
class ModelAdminTests(TestCase):
    def setUp(self):
        self.band = Band.objects.create(
            name='The Doors',
            bio='',
            sign_date=date(1965, 1, 1),
        )
        self.site = AdminSite()

    def test_modeladmin_str(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(str(ma), 'modeladmin.ModelAdmin')

    # form/fields/fieldsets interaction ##############################

    def test_default_fields(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request)),
                         ['name', 'bio', 'sign_date'])
        self.assertEqual(list(ma.get_fields(request, self.band)),
                         ['name', 'bio', 'sign_date'])
        self.assertIsNone(ma.get_exclude(request, self.band))

    def test_default_fieldsets(self):
        # fieldsets_add and fieldsets_change should return a special data structure that
        # is used in the templates. They should generate the "right thing" whether we
        # have specified a custom form, the fields argument, or nothing at all.
        #
        # Here's the default case. There are no custom form_add/form_change methods,
        # no fields argument, and no fieldsets argument.
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(ma.get_fieldsets(request), [(None, {
            'fields': ['name', 'bio', 'sign_date']
        })])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {
            'fields': ['name', 'bio', 'sign_date']
        })])

    def test_get_fieldsets(self):
        # get_fieldsets() is called when figuring out form fields (#18681).
        class BandAdmin(ModelAdmin):
            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['name', 'bio']})]

        ma = BandAdmin(Band, self.site)
        form = ma.get_form(None)
        self.assertEqual(form._meta.fields, ['name', 'bio'])

        class InlineBandAdmin(TabularInline):
            model = Concert
            fk_name = 'main_band'
            can_delete = False

            def get_fieldsets(self, request, obj=None):
                return [(None, {'fields': ['day', 'transport']})]

        ma = InlineBandAdmin(Band, self.site)
        form = ma.get_formset(None).form
        self.assertEqual(form._meta.fields, ['day', 'transport'])

    def test_lookup_allowed_allows_nonexistent_lookup(self):
        """
        A lookup_allowed allows a parameter whose field lookup doesn't exist.
        (#21129).
        """
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertTrue(ma.lookup_allowed('name__nonexistent', 'test_value'))

    @isolate_apps('modeladmin')
    def test_lookup_allowed_onetoone(self):
        class Department(models.Model):
            code = models.CharField(max_length=4, unique=True)

        class Employee(models.Model):
            department = models.ForeignKey(Department,
                                           models.CASCADE,
                                           to_field="code")

        class EmployeeProfile(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)

        class EmployeeInfo(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)
            description = models.CharField(max_length=100)

        class EmployeeProfileAdmin(ModelAdmin):
            list_filter = [
                'employee__employeeinfo__description',
                'employee__department__code',
            ]

        ma = EmployeeProfileAdmin(EmployeeProfile, self.site)
        # Reverse OneToOneField
        self.assertIs(
            ma.lookup_allowed('employee__employeeinfo__description',
                              'test_value'), True)
        # OneToOneField and ForeignKey
        self.assertIs(
            ma.lookup_allowed('employee__department__code', 'test_value'),
            True)

    def test_field_arguments(self):
        # If fields is specified, fieldsets_add and fieldsets_change should
        # just stick the fields into a formsets structure and return it.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)

        self.assertEqual(list(ma.get_fields(request)), ['name'])
        self.assertEqual(list(ma.get_fields(request, self.band)), ['name'])
        self.assertEqual(ma.get_fieldsets(request), [(None, {
            'fields': ['name']
        })])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {
            'fields': ['name']
        })])

    def test_field_arguments_restricted_on_form(self):
        # If fields or fieldsets is specified, it should exclude fields on the
        # Form class to the fields specified. This may cause errors to be
        # raised in the db layer if required model fields aren't in fields/
        # fieldsets, but that's preferable to ghost errors where a field in the
        # Form class isn't being displayed because it's not in fields/fieldsets.

        # Using `fields`.
        class BandAdmin(ModelAdmin):
            fields = ['name']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields),
                         ['name'])

        # Using `fieldsets`.
        class BandAdmin(ModelAdmin):
            fieldsets = [(None, {'fields': ['name']})]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields),
                         ['name'])

        # Using `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['name', 'sign_date'])

        # You can also pass a tuple to `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ('bio', )

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['name', 'sign_date'])

        # Using `fields` and `exclude`.
        class BandAdmin(ModelAdmin):
            fields = ['name', 'bio']
            exclude = ['bio']

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['name'])

    def test_custom_form_meta_exclude_with_readonly(self):
        """
        The custom ModelForm's `Meta.exclude` is respected when used in
        conjunction with `ModelAdmin.readonly_fields` and when no
        `ModelAdmin.exclude` is defined (#14496).
        """

        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            readonly_fields = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ['sign_date'])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            readonly_fields = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['main_band', 'opening_band', 'id', 'DELETE'])

    def test_custom_formfield_override_readonly(self):
        class AdminBandForm(forms.ModelForm):
            name = forms.CharField()

            class Meta:
                exclude = ()
                model = Band

        class BandAdmin(ModelAdmin):
            form = AdminBandForm
            readonly_fields = ['name']

        ma = BandAdmin(Band, self.site)

        # `name` shouldn't appear in base_fields because it's part of
        # readonly_fields.
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['bio', 'sign_date'])
        # But it should appear in get_fields()/fieldsets() so it can be
        # displayed as read-only.
        self.assertEqual(list(ma.get_fields(request)),
                         ['bio', 'sign_date', 'name'])
        self.assertEqual(list(ma.get_fieldsets(request)), [(None, {
            'fields': ['bio', 'sign_date', 'name']
        })])

    def test_custom_form_meta_exclude(self):
        """
        The custom ModelForm's `Meta.exclude` is overridden if
        `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined (#14496).
        """

        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['bio']

        class BandAdmin(ModelAdmin):
            exclude = ['name']
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['bio', 'sign_date'])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['main_band', 'opening_band', 'day', 'id', 'DELETE'])

    def test_overriding_get_exclude(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                return ['name']

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ['bio', 'sign_date'])

    def test_get_exclude_overrides_exclude(self):
        class BandAdmin(ModelAdmin):
            exclude = ['bio']

            def get_exclude(self, request, obj=None):
                return ['name']

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ['bio', 'sign_date'])

    def test_get_exclude_takes_obj(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                if obj:
                    return ['sign_date']
                return ['name']

        self.assertEqual(
            list(
                BandAdmin(Band, self.site).get_form(request,
                                                    self.band).base_fields),
            ['name', 'bio'])

    def test_custom_form_validation(self):
        # If a form is specified, it should use it allowing custom validation
        # to work properly. This won't break any of the admin widgets or media.
        class AdminBandForm(forms.ModelForm):
            delete = forms.BooleanField()

        class BandAdmin(ModelAdmin):
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['name', 'bio', 'sign_date', 'delete'])
        self.assertEqual(
            type(ma.get_form(request).base_fields['sign_date'].widget),
            AdminDateWidget)

    def test_form_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `ModelAdmin.get_form()` overrides all
        other declarations (#8999).
        """
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ['name']

        class BandAdmin(ModelAdmin):
            exclude = ['sign_date']
            form = AdminBandForm

            def get_form(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['bio']
                return super().get_form(request, obj, **kwargs)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['name', 'sign_date'])

    def test_formset_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `InlineModelAdmin.get_formset()`
        overrides all other declarations (#8999).
        """
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            exclude = ['transport']
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_formset(self, request, obj=None, **kwargs):
                kwargs['exclude'] = ['opening_band']
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE'])

    def test_formset_overriding_get_exclude_with_form_fields(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ['main_band', 'opening_band', 'day', 'transport']

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_exclude(self, request, obj=None):
                return ['opening_band']

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE'])

    def test_formset_overriding_get_exclude_with_form_exclude(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ['day']

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = 'main_band'
            model = Concert

            def get_exclude(self, request, obj=None):
                return ['opening_band']

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['main_band', 'day', 'transport', 'id', 'DELETE'])

    def test_queryset_override(self):
        # If the queryset of a ModelChoiceField in a custom form is overridden,
        # RelatedFieldWidgetWrapper doesn't mess that up.
        band2 = Band.objects.create(name='The Beatles',
                                    bio='',
                                    sign_date=date(1962, 1, 1))

        ma = ModelAdmin(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]), '<div class="related-widget-wrapper">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Beatles</option>'
            '<option value="%d">The Doors</option>'
            '</select></div>' % (band2.id, self.band.id))

        class AdminConcertForm(forms.ModelForm):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.fields["main_band"].queryset = Band.objects.filter(
                    name='The Doors')

        class ConcertAdminWithForm(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdminWithForm(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]), '<div class="related-widget-wrapper">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Doors</option>'
            '</select></div>' % self.band.id)

    def test_regression_for_ticket_15820(self):
        """
        `obj` is passed from `InlineModelAdmin.get_fieldsets()` to
        `InlineModelAdmin.get_formset()`.
        """
        class CustomConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ['day']

        class ConcertInline(TabularInline):
            model = Concert
            fk_name = 'main_band'

            def get_formset(self, request, obj=None, **kwargs):
                if obj:
                    kwargs['form'] = CustomConcertForm
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        Concert.objects.create(main_band=self.band,
                               opening_band=self.band,
                               day=1)
        ma = BandAdmin(Band, self.site)
        inline_instances = ma.get_inline_instances(request)
        fieldsets = list(inline_instances[0].get_fieldsets(request))
        self.assertEqual(fieldsets[0][1]['fields'],
                         ['main_band', 'opening_band', 'day', 'transport'])
        fieldsets = list(inline_instances[0].get_fieldsets(
            request, inline_instances[0].model))
        self.assertEqual(fieldsets[0][1]['fields'], ['day'])

    # radio_fields behavior ###########################################

    def test_default_foreign_key_widget(self):
        # First, without any radio_fields specified, the widgets for ForeignKey
        # and fields with choices specified ought to be a basic Select widget.
        # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
        # they need to be handled properly when type checking. For Select fields, all of
        # the choices lists have a first entry of dashes.
        cma = ModelAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
                         Select)
        self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices),
                         [('', '---------'), (self.band.id, 'The Doors')])

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget),
                         Select)
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', '---------'), (self.band.id, 'The Doors')])
        self.assertEqual(type(cmafa.base_fields['day'].widget), Select)
        self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
                         [('', '---------'), (1, 'Fri'), (2, 'Sat')])
        self.assertEqual(type(cmafa.base_fields['transport'].widget), Select)
        self.assertEqual(list(cmafa.base_fields['transport'].widget.choices),
                         [('', '---------'), (1, 'Plane'), (2, 'Train'),
                          (3, 'Bus')])

    def test_foreign_key_as_radio_field(self):
        # Now specify all the fields as radio_fields.  Widgets should now be
        # RadioSelect, and the choices list should have a first entry of 'None' if
        # blank=True for the model field.  Finally, the widget should have the
        # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
        class ConcertAdmin(ModelAdmin):
            radio_fields = {
                'main_band': HORIZONTAL,
                'opening_band': VERTICAL,
                'day': VERTICAL,
                'transport': HORIZONTAL,
            }

        cma = ConcertAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['main_band'].widget.attrs,
                         {'class': 'radiolist inline'})
        self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices),
                         [(self.band.id, 'The Doors')])

        self.assertEqual(type(cmafa.base_fields['opening_band'].widget.widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs,
                         {'class': 'radiolist'})
        self.assertEqual(
            list(cmafa.base_fields['opening_band'].widget.choices),
            [('', 'None'), (self.band.id, 'The Doors')])
        self.assertEqual(type(cmafa.base_fields['day'].widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['day'].widget.attrs,
                         {'class': 'radiolist'})
        self.assertEqual(list(cmafa.base_fields['day'].widget.choices),
                         [(1, 'Fri'), (2, 'Sat')])

        self.assertEqual(type(cmafa.base_fields['transport'].widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields['transport'].widget.attrs,
                         {'class': 'radiolist inline'})
        self.assertEqual(list(cmafa.base_fields['transport'].widget.choices),
                         [('', 'None'), (1, 'Plane'), (2, 'Train'),
                          (3, 'Bus')])

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ('transport', )

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['main_band', 'opening_band', 'day'])

        class AdminConcertForm(forms.ModelForm):
            extra = forms.CharField()

            class Meta:
                model = Concert
                fields = ['extra', 'transport']

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ['extra', 'transport'])

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            model = Concert
            fk_name = 'main_band'
            can_delete = True

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ['extra', 'transport', 'id', 'DELETE', 'main_band'])

    def test_log_actions(self):
        ma = ModelAdmin(Band, self.site)
        mock_request = MockRequest()
        mock_request.user = User.objects.create(username='******')
        content_type = get_content_type_for_model(self.band)
        tests = (
            (ma.log_addition, ADDITION, {
                'added': {}
            }),
            (ma.log_change, CHANGE, {
                'changed': {
                    'fields': ['name', 'bio']
                }
            }),
            (ma.log_deletion, DELETION, str(self.band)),
        )
        for method, flag, message in tests:
            with self.subTest(name=method.__name__):
                created = method(mock_request, self.band, message)
                fetched = LogEntry.objects.filter(
                    action_flag=flag).latest('id')
                self.assertEqual(created, fetched)
                self.assertEqual(fetched.action_flag, flag)
                self.assertEqual(fetched.content_type, content_type)
                self.assertEqual(fetched.object_id, str(self.band.pk))
                self.assertEqual(fetched.user, mock_request.user)
                if flag == DELETION:
                    self.assertEqual(fetched.change_message, '')
                    self.assertEqual(fetched.object_repr, message)
                else:
                    self.assertEqual(fetched.change_message, str(message))
                    self.assertEqual(fetched.object_repr, str(self.band))

    def test_get_autocomplete_fields(self):
        class NameAdmin(ModelAdmin):
            search_fields = ['name']

        class SongAdmin(ModelAdmin):
            autocomplete_fields = ['featuring']
            fields = ['featuring', 'band']

        class OtherSongAdmin(SongAdmin):
            def get_autocomplete_fields(self, request):
                return ['band']

        self.site.register(Band, NameAdmin)
        try:
            # Uses autocomplete_fields if not overridden.
            model_admin = SongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields['featuring'].widget.widget,
                                  AutocompleteSelectMultiple)
            # Uses overridden get_autocomplete_fields
            model_admin = OtherSongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields['band'].widget.widget,
                                  AutocompleteSelect)
        finally:
            self.site.unregister(Band)
Ejemplo n.º 7
0
class ModelAdminTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.band = Band.objects.create(
            name="The Doors",
            bio="",
            sign_date=date(1965, 1, 1),
        )

    def setUp(self):
        self.site = AdminSite()

    def test_modeladmin_str(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(str(ma), "modeladmin.ModelAdmin")

    def test_default_attributes(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(ma.actions, ())
        self.assertEqual(ma.inlines, ())

    # form/fields/fieldsets interaction ##############################

    def test_default_fields(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["name", "bio", "sign_date"])
        self.assertEqual(list(ma.get_fields(request)),
                         ["name", "bio", "sign_date"])
        self.assertEqual(list(ma.get_fields(request, self.band)),
                         ["name", "bio", "sign_date"])
        self.assertIsNone(ma.get_exclude(request, self.band))

    def test_default_fieldsets(self):
        # fieldsets_add and fieldsets_change should return a special data structure that
        # is used in the templates. They should generate the "right thing" whether we
        # have specified a custom form, the fields argument, or nothing at all.
        #
        # Here's the default case. There are no custom form_add/form_change methods,
        # no fields argument, and no fieldsets argument.
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(
            ma.get_fieldsets(request),
            [(None, {
                "fields": ["name", "bio", "sign_date"]
            })],
        )
        self.assertEqual(
            ma.get_fieldsets(request, self.band),
            [(None, {
                "fields": ["name", "bio", "sign_date"]
            })],
        )

    def test_get_fieldsets(self):
        # get_fieldsets() is called when figuring out form fields (#18681).
        class BandAdmin(ModelAdmin):
            def get_fieldsets(self, request, obj=None):
                return [(None, {"fields": ["name", "bio"]})]

        ma = BandAdmin(Band, self.site)
        form = ma.get_form(None)
        self.assertEqual(form._meta.fields, ["name", "bio"])

        class InlineBandAdmin(TabularInline):
            model = Concert
            fk_name = "main_band"
            can_delete = False

            def get_fieldsets(self, request, obj=None):
                return [(None, {"fields": ["day", "transport"]})]

        ma = InlineBandAdmin(Band, self.site)
        form = ma.get_formset(None).form
        self.assertEqual(form._meta.fields, ["day", "transport"])

    def test_lookup_allowed_allows_nonexistent_lookup(self):
        """
        A lookup_allowed allows a parameter whose field lookup doesn't exist.
        (#21129).
        """
        class BandAdmin(ModelAdmin):
            fields = ["name"]

        ma = BandAdmin(Band, self.site)
        self.assertTrue(ma.lookup_allowed("name__nonexistent", "test_value"))

    @isolate_apps("modeladmin")
    def test_lookup_allowed_onetoone(self):
        class Department(models.Model):
            code = models.CharField(max_length=4, unique=True)

        class Employee(models.Model):
            department = models.ForeignKey(Department,
                                           models.CASCADE,
                                           to_field="code")

        class EmployeeProfile(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)

        class EmployeeInfo(models.Model):
            employee = models.OneToOneField(Employee, models.CASCADE)
            description = models.CharField(max_length=100)

        class EmployeeProfileAdmin(ModelAdmin):
            list_filter = [
                "employee__employeeinfo__description",
                "employee__department__code",
            ]

        ma = EmployeeProfileAdmin(EmployeeProfile, self.site)
        # Reverse OneToOneField
        self.assertIs(
            ma.lookup_allowed("employee__employeeinfo__description",
                              "test_value"), True)
        # OneToOneField and ForeignKey
        self.assertIs(
            ma.lookup_allowed("employee__department__code", "test_value"),
            True)

    def test_field_arguments(self):
        # If fields is specified, fieldsets_add and fieldsets_change should
        # just stick the fields into a formsets structure and return it.
        class BandAdmin(ModelAdmin):
            fields = ["name"]

        ma = BandAdmin(Band, self.site)

        self.assertEqual(list(ma.get_fields(request)), ["name"])
        self.assertEqual(list(ma.get_fields(request, self.band)), ["name"])
        self.assertEqual(ma.get_fieldsets(request), [(None, {
            "fields": ["name"]
        })])
        self.assertEqual(ma.get_fieldsets(request, self.band), [(None, {
            "fields": ["name"]
        })])

    def test_field_arguments_restricted_on_form(self):
        # If fields or fieldsets is specified, it should exclude fields on the
        # Form class to the fields specified. This may cause errors to be
        # raised in the db layer if required model fields aren't in fields/
        # fieldsets, but that's preferable to ghost errors where a field in the
        # Form class isn't being displayed because it's not in fields/fieldsets.

        # Using `fields`.
        class BandAdmin(ModelAdmin):
            fields = ["name"]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ["name"])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields),
                         ["name"])

        # Using `fieldsets`.
        class BandAdmin(ModelAdmin):
            fieldsets = [(None, {"fields": ["name"]})]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ["name"])
        self.assertEqual(list(ma.get_form(request, self.band).base_fields),
                         ["name"])

        # Using `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ["bio"]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["name", "sign_date"])

        # You can also pass a tuple to `exclude`.
        class BandAdmin(ModelAdmin):
            exclude = ("bio", )

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["name", "sign_date"])

        # Using `fields` and `exclude`.
        class BandAdmin(ModelAdmin):
            fields = ["name", "bio"]
            exclude = ["bio"]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ["name"])

    def test_custom_form_meta_exclude_with_readonly(self):
        """
        The custom ModelForm's `Meta.exclude` is respected when used in
        conjunction with `ModelAdmin.readonly_fields` and when no
        `ModelAdmin.exclude` is defined (#14496).
        """

        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ["bio"]

        class BandAdmin(ModelAdmin):
            readonly_fields = ["name"]
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields), ["sign_date"])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ["day"]

        class ConcertInline(TabularInline):
            readonly_fields = ["transport"]
            form = AdminConcertForm
            fk_name = "main_band"
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["main_band", "opening_band", "id", "DELETE"],
        )

    def test_custom_formfield_override_readonly(self):
        class AdminBandForm(forms.ModelForm):
            name = forms.CharField()

            class Meta:
                exclude = ()
                model = Band

        class BandAdmin(ModelAdmin):
            form = AdminBandForm
            readonly_fields = ["name"]

        ma = BandAdmin(Band, self.site)

        # `name` shouldn't appear in base_fields because it's part of
        # readonly_fields.
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["bio", "sign_date"])
        # But it should appear in get_fields()/fieldsets() so it can be
        # displayed as read-only.
        self.assertEqual(list(ma.get_fields(request)),
                         ["bio", "sign_date", "name"])
        self.assertEqual(
            list(ma.get_fieldsets(request)),
            [(None, {
                "fields": ["bio", "sign_date", "name"]
            })],
        )

    def test_custom_form_meta_exclude(self):
        """
        The custom ModelForm's `Meta.exclude` is overridden if
        `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined (#14496).
        """

        # With ModelAdmin
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ["bio"]

        class BandAdmin(ModelAdmin):
            exclude = ["name"]
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["bio", "sign_date"])

        # With InlineModelAdmin
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ["day"]

        class ConcertInline(TabularInline):
            exclude = ["transport"]
            form = AdminConcertForm
            fk_name = "main_band"
            model = Concert

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["main_band", "opening_band", "day", "id", "DELETE"],
        )

    def test_overriding_get_exclude(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                return ["name"]

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ["bio", "sign_date"],
        )

    def test_get_exclude_overrides_exclude(self):
        class BandAdmin(ModelAdmin):
            exclude = ["bio"]

            def get_exclude(self, request, obj=None):
                return ["name"]

        self.assertEqual(
            list(BandAdmin(Band, self.site).get_form(request).base_fields),
            ["bio", "sign_date"],
        )

    def test_get_exclude_takes_obj(self):
        class BandAdmin(ModelAdmin):
            def get_exclude(self, request, obj=None):
                if obj:
                    return ["sign_date"]
                return ["name"]

        self.assertEqual(
            list(
                BandAdmin(Band, self.site).get_form(request,
                                                    self.band).base_fields),
            ["name", "bio"],
        )

    def test_custom_form_validation(self):
        # If a form is specified, it should use it allowing custom validation
        # to work properly. This won't break any of the admin widgets or media.
        class AdminBandForm(forms.ModelForm):
            delete = forms.BooleanField()

        class BandAdmin(ModelAdmin):
            form = AdminBandForm

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(ma.get_form(request).base_fields),
            ["name", "bio", "sign_date", "delete"],
        )
        self.assertEqual(
            type(ma.get_form(request).base_fields["sign_date"].widget),
            AdminDateWidget)

    def test_form_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `ModelAdmin.get_form()` overrides all
        other declarations (#8999).
        """
        class AdminBandForm(forms.ModelForm):
            class Meta:
                model = Band
                exclude = ["name"]

        class BandAdmin(ModelAdmin):
            exclude = ["sign_date"]
            form = AdminBandForm

            def get_form(self, request, obj=None, **kwargs):
                kwargs["exclude"] = ["bio"]
                return super().get_form(request, obj, **kwargs)

        ma = BandAdmin(Band, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["name", "sign_date"])

    def test_formset_exclude_kwarg_override(self):
        """
        The `exclude` kwarg passed to `InlineModelAdmin.get_formset()`
        overrides all other declarations (#8999).
        """
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ["day"]

        class ConcertInline(TabularInline):
            exclude = ["transport"]
            form = AdminConcertForm
            fk_name = "main_band"
            model = Concert

            def get_formset(self, request, obj=None, **kwargs):
                kwargs["exclude"] = ["opening_band"]
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["main_band", "day", "transport", "id", "DELETE"],
        )

    def test_formset_overriding_get_exclude_with_form_fields(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ["main_band", "opening_band", "day", "transport"]

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = "main_band"
            model = Concert

            def get_exclude(self, request, obj=None):
                return ["opening_band"]

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["main_band", "day", "transport", "id", "DELETE"],
        )

    def test_formset_overriding_get_exclude_with_form_exclude(self):
        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ["day"]

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            fk_name = "main_band"
            model = Concert

            def get_exclude(self, request, obj=None):
                return ["opening_band"]

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["main_band", "day", "transport", "id", "DELETE"],
        )

    def test_raw_id_fields_widget_override(self):
        """
        The autocomplete_fields, raw_id_fields, and radio_fields widgets may
        overridden by specifying a widget in get_formset().
        """
        class ConcertInline(TabularInline):
            model = Concert
            fk_name = "main_band"
            raw_id_fields = ("opening_band", )

            def get_formset(self, request, obj=None, **kwargs):
                kwargs["widgets"] = {"opening_band": Select}
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        band_widget = (list(ma.get_formsets_with_inlines(request))[0][0]
                       ().forms[0].fields["opening_band"].widget)
        # Without the override this would be ForeignKeyRawIdWidget.
        self.assertIsInstance(band_widget, Select)

    def test_queryset_override(self):
        # If the queryset of a ModelChoiceField in a custom form is overridden,
        # RelatedFieldWidgetWrapper doesn't mess that up.
        band2 = Band.objects.create(name="The Beatles",
                                    bio="",
                                    sign_date=date(1962, 1, 1))

        ma = ModelAdmin(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '<div class="related-widget-wrapper" data-model-ref="band">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Beatles</option>'
            '<option value="%d">The Doors</option>'
            "</select></div>" % (band2.id, self.band.id),
        )

        class AdminConcertForm(forms.ModelForm):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.fields["main_band"].queryset = Band.objects.filter(
                    name="The Doors")

        class ConcertAdminWithForm(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdminWithForm(Concert, self.site)
        form = ma.get_form(request)()

        self.assertHTMLEqual(
            str(form["main_band"]),
            '<div class="related-widget-wrapper" data-model-ref="band">'
            '<select name="main_band" id="id_main_band" required>'
            '<option value="" selected>---------</option>'
            '<option value="%d">The Doors</option>'
            "</select></div>" % self.band.id,
        )

    def test_regression_for_ticket_15820(self):
        """
        `obj` is passed from `InlineModelAdmin.get_fieldsets()` to
        `InlineModelAdmin.get_formset()`.
        """
        class CustomConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                fields = ["day"]

        class ConcertInline(TabularInline):
            model = Concert
            fk_name = "main_band"

            def get_formset(self, request, obj=None, **kwargs):
                if obj:
                    kwargs["form"] = CustomConcertForm
                return super().get_formset(request, obj, **kwargs)

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        Concert.objects.create(main_band=self.band,
                               opening_band=self.band,
                               day=1)
        ma = BandAdmin(Band, self.site)
        inline_instances = ma.get_inline_instances(request)
        fieldsets = list(inline_instances[0].get_fieldsets(request))
        self.assertEqual(fieldsets[0][1]["fields"],
                         ["main_band", "opening_band", "day", "transport"])
        fieldsets = list(inline_instances[0].get_fieldsets(
            request, inline_instances[0].model))
        self.assertEqual(fieldsets[0][1]["fields"], ["day"])

    # radio_fields behavior ###########################################

    def test_default_foreign_key_widget(self):
        # First, without any radio_fields specified, the widgets for ForeignKey
        # and fields with choices specified ought to be a basic Select widget.
        # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so
        # they need to be handled properly when type checking. For Select fields, all of
        # the choices lists have a first entry of dashes.
        cma = ModelAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields["main_band"].widget.widget),
                         Select)
        self.assertEqual(
            list(cmafa.base_fields["main_band"].widget.choices),
            [("", "---------"), (self.band.id, "The Doors")],
        )

        self.assertEqual(type(cmafa.base_fields["opening_band"].widget.widget),
                         Select)
        self.assertEqual(
            list(cmafa.base_fields["opening_band"].widget.choices),
            [("", "---------"), (self.band.id, "The Doors")],
        )
        self.assertEqual(type(cmafa.base_fields["day"].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields["day"].widget.choices),
            [("", "---------"), (1, "Fri"), (2, "Sat")],
        )
        self.assertEqual(type(cmafa.base_fields["transport"].widget), Select)
        self.assertEqual(
            list(cmafa.base_fields["transport"].widget.choices),
            [("", "---------"), (1, "Plane"), (2, "Train"), (3, "Bus")],
        )

    def test_foreign_key_as_radio_field(self):
        # Now specify all the fields as radio_fields.  Widgets should now be
        # RadioSelect, and the choices list should have a first entry of 'None' if
        # blank=True for the model field.  Finally, the widget should have the
        # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL.
        class ConcertAdmin(ModelAdmin):
            radio_fields = {
                "main_band": HORIZONTAL,
                "opening_band": VERTICAL,
                "day": VERTICAL,
                "transport": HORIZONTAL,
            }

        cma = ConcertAdmin(Concert, self.site)
        cmafa = cma.get_form(request)

        self.assertEqual(type(cmafa.base_fields["main_band"].widget.widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields["main_band"].widget.attrs,
                         {"class": "radiolist inline"})
        self.assertEqual(
            list(cmafa.base_fields["main_band"].widget.choices),
            [(self.band.id, "The Doors")],
        )

        self.assertEqual(type(cmafa.base_fields["opening_band"].widget.widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields["opening_band"].widget.attrs,
                         {"class": "radiolist"})
        self.assertEqual(
            list(cmafa.base_fields["opening_band"].widget.choices),
            [("", "None"), (self.band.id, "The Doors")],
        )
        self.assertEqual(type(cmafa.base_fields["day"].widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields["day"].widget.attrs,
                         {"class": "radiolist"})
        self.assertEqual(list(cmafa.base_fields["day"].widget.choices),
                         [(1, "Fri"), (2, "Sat")])

        self.assertEqual(type(cmafa.base_fields["transport"].widget),
                         AdminRadioSelect)
        self.assertEqual(cmafa.base_fields["transport"].widget.attrs,
                         {"class": "radiolist inline"})
        self.assertEqual(
            list(cmafa.base_fields["transport"].widget.choices),
            [("", "None"), (1, "Plane"), (2, "Train"), (3, "Bus")],
        )

        class AdminConcertForm(forms.ModelForm):
            class Meta:
                model = Concert
                exclude = ("transport", )

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["main_band", "opening_band", "day"])

        class AdminConcertForm(forms.ModelForm):
            extra = forms.CharField()

            class Meta:
                model = Concert
                fields = ["extra", "transport"]

        class ConcertAdmin(ModelAdmin):
            form = AdminConcertForm

        ma = ConcertAdmin(Concert, self.site)
        self.assertEqual(list(ma.get_form(request).base_fields),
                         ["extra", "transport"])

        class ConcertInline(TabularInline):
            form = AdminConcertForm
            model = Concert
            fk_name = "main_band"
            can_delete = True

        class BandAdmin(ModelAdmin):
            inlines = [ConcertInline]

        ma = BandAdmin(Band, self.site)
        self.assertEqual(
            list(
                list(ma.get_formsets_with_inlines(request))[0][0]
                ().forms[0].fields),
            ["extra", "transport", "id", "DELETE", "main_band"],
        )

    def test_log_actions(self):
        ma = ModelAdmin(Band, self.site)
        mock_request = MockRequest()
        mock_request.user = User.objects.create(username="******")
        content_type = get_content_type_for_model(self.band)
        tests = (
            (ma.log_addition, ADDITION, {
                "added": {}
            }),
            (ma.log_change, CHANGE, {
                "changed": {
                    "fields": ["name", "bio"]
                }
            }),
            (ma.log_deletion, DELETION, str(self.band)),
        )
        for method, flag, message in tests:
            with self.subTest(name=method.__name__):
                created = method(mock_request, self.band, message)
                fetched = LogEntry.objects.filter(
                    action_flag=flag).latest("id")
                self.assertEqual(created, fetched)
                self.assertEqual(fetched.action_flag, flag)
                self.assertEqual(fetched.content_type, content_type)
                self.assertEqual(fetched.object_id, str(self.band.pk))
                self.assertEqual(fetched.user, mock_request.user)
                if flag == DELETION:
                    self.assertEqual(fetched.change_message, "")
                    self.assertEqual(fetched.object_repr, message)
                else:
                    self.assertEqual(fetched.change_message, str(message))
                    self.assertEqual(fetched.object_repr, str(self.band))

    def test_get_autocomplete_fields(self):
        class NameAdmin(ModelAdmin):
            search_fields = ["name"]

        class SongAdmin(ModelAdmin):
            autocomplete_fields = ["featuring"]
            fields = ["featuring", "band"]

        class OtherSongAdmin(SongAdmin):
            def get_autocomplete_fields(self, request):
                return ["band"]

        self.site.register(Band, NameAdmin)
        try:
            # Uses autocomplete_fields if not overridden.
            model_admin = SongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields["featuring"].widget.widget,
                                  AutocompleteSelectMultiple)
            # Uses overridden get_autocomplete_fields
            model_admin = OtherSongAdmin(Song, self.site)
            form = model_admin.get_form(request)()
            self.assertIsInstance(form.fields["band"].widget.widget,
                                  AutocompleteSelect)
        finally:
            self.site.unregister(Band)

    def test_get_deleted_objects(self):
        mock_request = MockRequest()
        mock_request.user = User.objects.create_superuser(username="******",
                                                          email="*****@*****.**",
                                                          password="******")
        self.site.register(Band, ModelAdmin)
        ma = self.site._registry[Band]
        (
            deletable_objects,
            model_count,
            perms_needed,
            protected,
        ) = ma.get_deleted_objects([self.band], request)
        self.assertEqual(deletable_objects, ["Band: The Doors"])
        self.assertEqual(model_count, {"bands": 1})
        self.assertEqual(perms_needed, set())
        self.assertEqual(protected, [])

    def test_get_deleted_objects_with_custom_has_delete_permission(self):
        """
        ModelAdmin.get_deleted_objects() uses ModelAdmin.has_delete_permission()
        for permissions checking.
        """
        mock_request = MockRequest()
        mock_request.user = User.objects.create_superuser(username="******",
                                                          email="*****@*****.**",
                                                          password="******")

        class TestModelAdmin(ModelAdmin):
            def has_delete_permission(self, request, obj=None):
                return False

        self.site.register(Band, TestModelAdmin)
        ma = self.site._registry[Band]
        (
            deletable_objects,
            model_count,
            perms_needed,
            protected,
        ) = ma.get_deleted_objects([self.band], request)
        self.assertEqual(deletable_objects, ["Band: The Doors"])
        self.assertEqual(model_count, {"bands": 1})
        self.assertEqual(perms_needed, {"band"})
        self.assertEqual(protected, [])

    def test_modeladmin_repr(self):
        ma = ModelAdmin(Band, self.site)
        self.assertEqual(
            repr(ma),
            "<ModelAdmin: model=Band site=AdminSite(name='admin')>",
        )