/tests/regressiontests/admin_validation/tests.py
Python | 253 lines | 233 code | 11 blank | 9 comment | 0 complexity | 655746680b41ef2809835af44e4f6cef MD5 | raw file
Possible License(s): BSD-3-Clause
1from django.contrib import admin 2from django import forms 3from django.contrib.admin.validation import validate, validate_inline, \ 4 ImproperlyConfigured 5from django.test import TestCase 6 7from models import Song, Book, Album, TwoAlbumFKAndAnE, State, City 8 9class SongForm(forms.ModelForm): 10 pass 11 12class ValidFields(admin.ModelAdmin): 13 form = SongForm 14 fields = ['title'] 15 16class InvalidFields(admin.ModelAdmin): 17 form = SongForm 18 fields = ['spam'] 19 20class ValidationTestCase(TestCase): 21 def assertRaisesMessage(self, exc, msg, func, *args, **kwargs): 22 try: 23 func(*args, **kwargs) 24 except Exception, e: 25 self.assertEqual(msg, str(e)) 26 self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e))) 27 28 def test_readonly_and_editable(self): 29 class SongAdmin(admin.ModelAdmin): 30 readonly_fields = ["original_release"] 31 fieldsets = [ 32 (None, { 33 "fields": ["title", "original_release"], 34 }), 35 ] 36 validate(SongAdmin, Song) 37 38 def test_custom_modelforms_with_fields_fieldsets(self): 39 """ 40 # Regression test for #8027: custom ModelForms with fields/fieldsets 41 """ 42 validate(ValidFields, Song) 43 self.assertRaisesMessage(ImproperlyConfigured, 44 "'InvalidFields.fields' refers to field 'spam' that is missing from the form.", 45 validate, 46 InvalidFields, Song) 47 48 def test_exclude_values(self): 49 """ 50 Tests for basic validation of 'exclude' option values (#12689) 51 """ 52 class ExcludedFields1(admin.ModelAdmin): 53 exclude = ('foo') 54 self.assertRaisesMessage(ImproperlyConfigured, 55 "'ExcludedFields1.exclude' must be a list or tuple.", 56 validate, 57 ExcludedFields1, Book) 58 59 def test_exclude_duplicate_values(self): 60 class ExcludedFields2(admin.ModelAdmin): 61 exclude = ('name', 'name') 62 self.assertRaisesMessage(ImproperlyConfigured, 63 "There are duplicate field(s) in ExcludedFields2.exclude", 64 validate, 65 ExcludedFields2, Book) 66 67 def test_exclude_in_inline(self): 68 class ExcludedFieldsInline(admin.TabularInline): 69 model = Song 70 exclude = ('foo') 71 72 class ExcludedFieldsAlbumAdmin(admin.ModelAdmin): 73 model = Album 74 inlines = [ExcludedFieldsInline] 75 76 self.assertRaisesMessage(ImproperlyConfigured, 77 "'ExcludedFieldsInline.exclude' must be a list or tuple.", 78 validate, 79 ExcludedFieldsAlbumAdmin, Album) 80 81 def test_exclude_inline_model_admin(self): 82 """ 83 # Regression test for #9932 - exclude in InlineModelAdmin 84 # should not contain the ForeignKey field used in ModelAdmin.model 85 """ 86 class SongInline(admin.StackedInline): 87 model = Song 88 exclude = ['album'] 89 90 class AlbumAdmin(admin.ModelAdmin): 91 model = Album 92 inlines = [SongInline] 93 94 self.assertRaisesMessage(ImproperlyConfigured, 95 "SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album.", 96 validate, 97 AlbumAdmin, Album) 98 99 def test_fk_exclusion(self): 100 """ 101 Regression test for #11709 - when testing for fk excluding (when exclude is 102 given) make sure fk_name is honored or things blow up when there is more 103 than one fk to the parent model. 104 """ 105 class TwoAlbumFKAndAnEInline(admin.TabularInline): 106 model = TwoAlbumFKAndAnE 107 exclude = ("e",) 108 fk_name = "album1" 109 validate_inline(TwoAlbumFKAndAnEInline, None, Album) 110 111 def test_inline_self_validation(self): 112 class TwoAlbumFKAndAnEInline(admin.TabularInline): 113 model = TwoAlbumFKAndAnE 114 115 self.assertRaisesMessage(Exception, 116 "<class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'>", 117 validate_inline, 118 TwoAlbumFKAndAnEInline, None, Album) 119 120 def test_inline_with_specified(self): 121 class TwoAlbumFKAndAnEInline(admin.TabularInline): 122 model = TwoAlbumFKAndAnE 123 fk_name = "album1" 124 validate_inline(TwoAlbumFKAndAnEInline, None, Album) 125 126 def test_readonly(self): 127 class SongAdmin(admin.ModelAdmin): 128 readonly_fields = ("title",) 129 130 validate(SongAdmin, Song) 131 132 def test_readonly_on_method(self): 133 def my_function(obj): 134 pass 135 136 class SongAdmin(admin.ModelAdmin): 137 readonly_fields = (my_function,) 138 139 validate(SongAdmin, Song) 140 141 def test_readonly_on_modeladmin(self): 142 class SongAdmin(admin.ModelAdmin): 143 readonly_fields = ("readonly_method_on_modeladmin",) 144 145 def readonly_method_on_modeladmin(self, obj): 146 pass 147 148 validate(SongAdmin, Song) 149 150 def test_readonly_method_on_model(self): 151 class SongAdmin(admin.ModelAdmin): 152 readonly_fields = ("readonly_method_on_model",) 153 154 validate(SongAdmin, Song) 155 156 def test_nonexistant_field(self): 157 class SongAdmin(admin.ModelAdmin): 158 readonly_fields = ("title", "nonexistant") 159 160 self.assertRaisesMessage(ImproperlyConfigured, 161 "SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.", 162 validate, 163 SongAdmin, Song) 164 165 def test_nonexistant_field_on_inline(self): 166 class CityInline(admin.TabularInline): 167 model = City 168 readonly_fields=['i_dont_exist'] # Missing attribute 169 170 self.assertRaisesMessage(ImproperlyConfigured, 171 "CityInline.readonly_fields[0], 'i_dont_exist' is not a callable or an attribute of 'CityInline' or found in the model 'City'.", 172 validate_inline, 173 CityInline, None, State) 174 175 def test_extra(self): 176 class SongAdmin(admin.ModelAdmin): 177 def awesome_song(self, instance): 178 if instance.title == "Born to Run": 179 return "Best Ever!" 180 return "Status unknown." 181 validate(SongAdmin, Song) 182 183 def test_readonly_lambda(self): 184 class SongAdmin(admin.ModelAdmin): 185 readonly_fields = (lambda obj: "test",) 186 187 validate(SongAdmin, Song) 188 189 def test_graceful_m2m_fail(self): 190 """ 191 Regression test for #12203/#12237 - Fail more gracefully when a M2M field that 192 specifies the 'through' option is included in the 'fields' or the 'fieldsets' 193 ModelAdmin options. 194 """ 195 196 class BookAdmin(admin.ModelAdmin): 197 fields = ['authors'] 198 199 self.assertRaisesMessage(ImproperlyConfigured, 200 "'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", 201 validate, 202 BookAdmin, Book) 203 204 def test_cannon_include_through(self): 205 class FieldsetBookAdmin(admin.ModelAdmin): 206 fieldsets = ( 207 ('Header 1', {'fields': ('name',)}), 208 ('Header 2', {'fields': ('authors',)}), 209 ) 210 self.assertRaisesMessage(ImproperlyConfigured, 211 "'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", 212 validate, 213 FieldsetBookAdmin, Book) 214 215 def test_nested_fieldsets(self): 216 class NestedFieldsetAdmin(admin.ModelAdmin): 217 fieldsets = ( 218 ('Main', {'fields': ('price', ('name', 'subtitle'))}), 219 ) 220 validate(NestedFieldsetAdmin, Book) 221 222 def test_explicit_through_override(self): 223 """ 224 Regression test for #12209 -- If the explicitly provided through model 225 is specified as a string, the admin should still be able use 226 Model.m2m_field.through 227 """ 228 229 class AuthorsInline(admin.TabularInline): 230 model = Book.authors.through 231 232 class BookAdmin(admin.ModelAdmin): 233 inlines = [AuthorsInline] 234 235 # If the through model is still a string (and hasn't been resolved to a model) 236 # the validation will fail. 237 validate(BookAdmin, Book) 238 239 def test_non_model_fields(self): 240 """ 241 Regression for ensuring ModelAdmin.fields can contain non-model fields 242 that broke with r11737 243 """ 244 class SongForm(forms.ModelForm): 245 extra_data = forms.CharField() 246 class Meta: 247 model = Song 248 249 class FieldsOnFormOnlyAdmin(admin.ModelAdmin): 250 form = SongForm 251 fields = ['title', 'extra_data'] 252 253 validate(FieldsOnFormOnlyAdmin, Song)