2
0

tests.py 9.0 KB

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