mirror of
				https://github.com/django/django.git
				synced 2025-10-29 08:36:09 +00:00 
			
		
		
		
	Adjusted admin javascript to add newly created related objects to already loaded select widgets. In this version, applies only where limit_choices_to is not set.
		
			
				
	
	
		
			941 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			941 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from datetime import date
 | |
| 
 | |
| from django import forms
 | |
| from django.contrib.admin.models import ADDITION, CHANGE, DELETION, LogEntry
 | |
| from django.contrib.admin.options import (
 | |
|     HORIZONTAL,
 | |
|     VERTICAL,
 | |
|     ModelAdmin,
 | |
|     TabularInline,
 | |
|     get_content_type_for_model,
 | |
| )
 | |
| from django.contrib.admin.sites import AdminSite
 | |
| from django.contrib.admin.widgets import (
 | |
|     AdminDateWidget,
 | |
|     AdminRadioSelect,
 | |
|     AutocompleteSelect,
 | |
|     AutocompleteSelectMultiple,
 | |
| )
 | |
| from django.contrib.auth.models import User
 | |
| from django.db import models
 | |
| from django.forms.widgets import Select
 | |
| from django.test import SimpleTestCase, TestCase
 | |
| from django.test.utils import isolate_apps
 | |
| 
 | |
| from .models import Band, Concert, Song
 | |
| 
 | |
| 
 | |
| class MockRequest:
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class MockSuperUser:
 | |
|     def has_perm(self, perm, obj=None):
 | |
|         return True
 | |
| 
 | |
| 
 | |
| request = MockRequest()
 | |
| request.user = MockSuperUser()
 | |
| 
 | |
| 
 | |
| 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="bill")
 | |
|         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="bob", email="bob@test.com", password="test"
 | |
|         )
 | |
|         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="bob", email="bob@test.com", password="test"
 | |
|         )
 | |
| 
 | |
|         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')>",
 | |
|         )
 | |
| 
 | |
| 
 | |
| class ModelAdminPermissionTests(SimpleTestCase):
 | |
|     class MockUser:
 | |
|         def has_module_perms(self, app_label):
 | |
|             return app_label == "modeladmin"
 | |
| 
 | |
|     class MockViewUser(MockUser):
 | |
|         def has_perm(self, perm, obj=None):
 | |
|             return perm == "modeladmin.view_band"
 | |
| 
 | |
|     class MockAddUser(MockUser):
 | |
|         def has_perm(self, perm, obj=None):
 | |
|             return perm == "modeladmin.add_band"
 | |
| 
 | |
|     class MockChangeUser(MockUser):
 | |
|         def has_perm(self, perm, obj=None):
 | |
|             return perm == "modeladmin.change_band"
 | |
| 
 | |
|     class MockDeleteUser(MockUser):
 | |
|         def has_perm(self, perm, obj=None):
 | |
|             return perm == "modeladmin.delete_band"
 | |
| 
 | |
|     def test_has_view_permission(self):
 | |
|         """
 | |
|         has_view_permission() returns True for users who can view objects and
 | |
|         False for users who can't.
 | |
|         """
 | |
|         ma = ModelAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockViewUser()
 | |
|         self.assertIs(ma.has_view_permission(request), True)
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertIs(ma.has_view_permission(request), False)
 | |
|         request.user = self.MockChangeUser()
 | |
|         self.assertIs(ma.has_view_permission(request), True)
 | |
|         request.user = self.MockDeleteUser()
 | |
|         self.assertIs(ma.has_view_permission(request), False)
 | |
| 
 | |
|     def test_has_add_permission(self):
 | |
|         """
 | |
|         has_add_permission returns True for users who can add objects and
 | |
|         False for users who can't.
 | |
|         """
 | |
|         ma = ModelAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockViewUser()
 | |
|         self.assertFalse(ma.has_add_permission(request))
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertTrue(ma.has_add_permission(request))
 | |
|         request.user = self.MockChangeUser()
 | |
|         self.assertFalse(ma.has_add_permission(request))
 | |
|         request.user = self.MockDeleteUser()
 | |
|         self.assertFalse(ma.has_add_permission(request))
 | |
| 
 | |
|     def test_inline_has_add_permission_uses_obj(self):
 | |
|         class ConcertInline(TabularInline):
 | |
|             model = Concert
 | |
| 
 | |
|             def has_add_permission(self, request, obj):
 | |
|                 return bool(obj)
 | |
| 
 | |
|         class BandAdmin(ModelAdmin):
 | |
|             inlines = [ConcertInline]
 | |
| 
 | |
|         ma = BandAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertEqual(ma.get_inline_instances(request), [])
 | |
|         band = Band(name="The Doors", bio="", sign_date=date(1965, 1, 1))
 | |
|         inline_instances = ma.get_inline_instances(request, band)
 | |
|         self.assertEqual(len(inline_instances), 1)
 | |
|         self.assertIsInstance(inline_instances[0], ConcertInline)
 | |
| 
 | |
|     def test_has_change_permission(self):
 | |
|         """
 | |
|         has_change_permission returns True for users who can edit objects and
 | |
|         False for users who can't.
 | |
|         """
 | |
|         ma = ModelAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockViewUser()
 | |
|         self.assertIs(ma.has_change_permission(request), False)
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertFalse(ma.has_change_permission(request))
 | |
|         request.user = self.MockChangeUser()
 | |
|         self.assertTrue(ma.has_change_permission(request))
 | |
|         request.user = self.MockDeleteUser()
 | |
|         self.assertFalse(ma.has_change_permission(request))
 | |
| 
 | |
|     def test_has_delete_permission(self):
 | |
|         """
 | |
|         has_delete_permission returns True for users who can delete objects and
 | |
|         False for users who can't.
 | |
|         """
 | |
|         ma = ModelAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockViewUser()
 | |
|         self.assertIs(ma.has_delete_permission(request), False)
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertFalse(ma.has_delete_permission(request))
 | |
|         request.user = self.MockChangeUser()
 | |
|         self.assertFalse(ma.has_delete_permission(request))
 | |
|         request.user = self.MockDeleteUser()
 | |
|         self.assertTrue(ma.has_delete_permission(request))
 | |
| 
 | |
|     def test_has_module_permission(self):
 | |
|         """
 | |
|         as_module_permission returns True for users who have any permission
 | |
|         for the module and False for users who don't.
 | |
|         """
 | |
|         ma = ModelAdmin(Band, AdminSite())
 | |
|         request = MockRequest()
 | |
|         request.user = self.MockViewUser()
 | |
|         self.assertIs(ma.has_module_permission(request), True)
 | |
|         request.user = self.MockAddUser()
 | |
|         self.assertTrue(ma.has_module_permission(request))
 | |
|         request.user = self.MockChangeUser()
 | |
|         self.assertTrue(ma.has_module_permission(request))
 | |
|         request.user = self.MockDeleteUser()
 | |
|         self.assertTrue(ma.has_module_permission(request))
 | |
| 
 | |
|         original_app_label = ma.opts.app_label
 | |
|         ma.opts.app_label = "anotherapp"
 | |
|         try:
 | |
|             request.user = self.MockViewUser()
 | |
|             self.assertIs(ma.has_module_permission(request), False)
 | |
|             request.user = self.MockAddUser()
 | |
|             self.assertFalse(ma.has_module_permission(request))
 | |
|             request.user = self.MockChangeUser()
 | |
|             self.assertFalse(ma.has_module_permission(request))
 | |
|             request.user = self.MockDeleteUser()
 | |
|             self.assertFalse(ma.has_module_permission(request))
 | |
|         finally:
 | |
|             ma.opts.app_label = original_app_label
 |