Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

newforms-admin: Fixed #5374 -- Added validation for ModelAdmin and In…

…lineModelAdmin options including tests. Thanks mrts for initial legwork.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7929 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit a2c7bfc1bee48b2aed9124c1a5a59a72274c73f7 1 parent affc9b9
Brian Rosner authored July 15, 2008
7  django/contrib/admin/sites.py
@@ -7,6 +7,7 @@
7 7
 from django.utils.text import capfirst
8 8
 from django.utils.translation import ugettext_lazy, ugettext as _
9 9
 from django.views.decorators.cache import never_cache
  10
+from django.conf import settings
10 11
 import base64
11 12
 import cPickle as pickle
12 13
 import datetime
@@ -65,6 +66,10 @@ def register(self, model_or_iterable, admin_class=None, **options):
65 66
 
66 67
         If a model is already registered, this will raise AlreadyRegistered.
67 68
         """
  69
+        do_validate = admin_class and settings.DEBUG
  70
+        if do_validate:
  71
+            # don't import the humongous validation code unless required
  72
+            from django.contrib.admin.validation import validate
68 73
         admin_class = admin_class or ModelAdmin
69 74
         # TODO: Handle options
70 75
         if isinstance(model_or_iterable, ModelBase):
@@ -72,6 +77,8 @@ def register(self, model_or_iterable, admin_class=None, **options):
72 77
         for model in model_or_iterable:
73 78
             if model in self._registry:
74 79
                 raise AlreadyRegistered('The model %s is already registered' % model.__name__)
  80
+            if do_validate:
  81
+                validate(admin_class, model)
75 82
             self._registry[model] = admin_class(model, self)
76 83
 
77 84
     def unregister(self, model_or_iterable):
254  django/contrib/admin/validation.py
... ...
@@ -0,0 +1,254 @@
  1
+
  2
+from django.core.exceptions import ImproperlyConfigured
  3
+from django.db import models
  4
+from django.newforms.models import BaseModelForm, BaseInlineFormset
  5
+from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
  6
+from django.contrib.admin.options import HORIZONTAL, VERTICAL
  7
+
  8
+def validate(cls, model):
  9
+    """
  10
+    Does basic ModelAdmin option validation. Calls custom validation
  11
+    classmethod in the end if it is provided in cls. The signature of the
  12
+    custom validation classmethod should be: def validate(cls, model).
  13
+    """
  14
+    opts = model._meta
  15
+    _validate_base(cls, model)
  16
+
  17
+    # currying is expensive, use wrappers instead
  18
+    def _check_istuplew(label, obj):
  19
+        _check_istuple(cls, label, obj)
  20
+
  21
+    def _check_isdictw(label, obj):
  22
+        _check_isdict(cls, label, obj)
  23
+
  24
+    def _check_field_existsw(label, field):
  25
+        return _check_field_exists(cls, model, opts, label, field)
  26
+
  27
+    def _check_attr_existsw(label, field):
  28
+        return _check_attr_exists(cls, model, opts, label, field)
  29
+
  30
+    # list_display
  31
+    if hasattr(cls, 'list_display'):
  32
+        _check_istuplew('list_display', cls.list_display)
  33
+        for idx, field in enumerate(cls.list_display):
  34
+            f = _check_attr_existsw("list_display[%d]" % idx, field)
  35
+            if isinstance(f, models.ManyToManyField):
  36
+                raise ImproperlyConfigured("`%s.list_display[%d]`, `%s` is a "
  37
+                        "ManyToManyField which is not supported."
  38
+                        % (cls.__name__, idx, field))
  39
+
  40
+    # list_display_links
  41
+    if hasattr(cls, 'list_display_links'):
  42
+        _check_istuplew('list_display_links', cls.list_display_links)
  43
+        for idx, field in enumerate(cls.list_display_links):
  44
+            _check_attr_existsw('list_display_links[%d]' % idx, field)
  45
+            if field not in cls.list_display:
  46
+                raise ImproperlyConfigured("`%s.list_display_links[%d]`"
  47
+                        "refers to `%s` which is not defined in `list_display`."
  48
+                        % (cls.__name__, idx, field))
  49
+
  50
+    # list_filter
  51
+    if hasattr(cls, 'list_filter'):
  52
+        _check_istuplew('list_filter', cls.list_filter)
  53
+        for idx, field in enumerate(cls.list_filter):
  54
+            _check_field_existsw('list_filter[%d]' % idx, field)
  55
+
  56
+    # list_per_page = 100
  57
+    if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
  58
+        raise ImproperlyConfigured("`%s.list_per_page` should be a integer."
  59
+                % cls.__name__)
  60
+
  61
+    # search_fields = ()
  62
+    if hasattr(cls, 'search_fields'):
  63
+        _check_istuplew('search_fields', cls.search_fields)
  64
+        
  65
+    # date_hierarchy = None
  66
+    if cls.date_hierarchy:
  67
+        f = _check_field_existsw('date_hierarchy', cls.date_hierarchy)
  68
+        if not isinstance(f, (models.DateField, models.DateTimeField)):
  69
+            raise ImproperlyConfigured("`%s.date_hierarchy is "
  70
+                    "neither an instance of DateField nor DateTimeField."
  71
+                    % cls.__name__)
  72
+
  73
+    # ordering = None
  74
+    if cls.ordering:
  75
+        _check_istuplew('ordering', cls.ordering)
  76
+        for idx, field in enumerate(cls.ordering):
  77
+            if field == '?' and len(cls.ordering) != 1:
  78
+                raise ImproperlyConfigured("`%s.ordering` has the random "
  79
+                        "ordering marker `?`, but contains other fields as "
  80
+                        "well. Please either remove `?` or the other fields."
  81
+                        % cls.__name__)
  82
+            if field.startswith('-'):
  83
+                field = field[1:]
  84
+            _check_field_existsw('ordering[%d]' % idx, field)
  85
+
  86
+    # list_select_related = False
  87
+    # save_as = False
  88
+    # save_on_top = False
  89
+    for attr in ('list_select_related', 'save_as', 'save_on_top'):
  90
+        if not isinstance(getattr(cls, attr), bool):
  91
+            raise ImproperlyConfigured("`%s.%s` should be a boolean."
  92
+                    % (cls.__name__, attr))
  93
+
  94
+    # inlines = []
  95
+    if hasattr(cls, 'inlines'):
  96
+        _check_istuplew('inlines', cls.inlines)
  97
+        for idx, inline in enumerate(cls.inlines):
  98
+            if not issubclass(inline, BaseModelAdmin):
  99
+                raise ImproperlyConfigured("`%s.inlines[%d]` does not inherit "
  100
+                        "from BaseModelAdmin." % (cls.__name__, idx))
  101
+            if not inline.model:
  102
+                raise ImproperlyConfigured("`model` is a required attribute "
  103
+                        "of `%s.inlines[%d]`." % (cls.__name__, idx))
  104
+            if not issubclass(inline.model, models.Model):
  105
+                raise ImproperlyConfigured("`%s.inlines[%d].model` does not "
  106
+                        "inherit from models.Model." % (cls.__name__, idx))
  107
+            _validate_base(inline, inline.model)
  108
+            _validate_inline(inline)
  109
+            
  110
+def _validate_inline(cls):
  111
+    # model is already verified to exist and be a Model
  112
+    if cls.fk_name: # default value is None
  113
+        f = _check_field_exists(cls, cls.model, cls.model._meta,
  114
+                'fk_name', cls.fk_name)
  115
+        if not isinstance(f, models.ForeignKey):
  116
+            raise ImproperlyConfigured("`%s.fk_name is not an instance of "
  117
+                    "models.ForeignKey." % cls.__name__)
  118
+    # extra = 3
  119
+    # max_num = 0
  120
+    for attr in ('extra', 'max_num'):
  121
+        if not isinstance(getattr(cls, attr), int):
  122
+            raise ImproperlyConfigured("`%s.%s` should be a integer."
  123
+                    % (cls.__name__, attr))
  124
+
  125
+    # formset
  126
+    if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseInlineFormset):
  127
+        raise ImproperlyConfigured("`%s.formset` does not inherit from "
  128
+                "BaseInlineFormset." % cls.__name__)
  129
+
  130
+def _validate_base(cls, model):
  131
+    opts = model._meta
  132
+    # currying is expensive, use wrappers instead
  133
+    def _check_istuplew(label, obj):
  134
+        _check_istuple(cls, label, obj)
  135
+
  136
+    def _check_isdictw(label, obj):
  137
+        _check_isdict(cls, label, obj)
  138
+
  139
+    def _check_field_existsw(label, field):
  140
+        return _check_field_exists(cls, model, opts, label, field)
  141
+
  142
+    # raw_id_fields
  143
+    if hasattr(cls, 'raw_id_fields'):
  144
+        _check_istuplew('raw_id_fields', cls.raw_id_fields)
  145
+        for idx, field in enumerate(cls.raw_id_fields):
  146
+            f = _check_field_existsw('raw_id_fields', field)
  147
+            if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
  148
+                raise ImproperlyConfigured("`%s.raw_id_fields[%d]`, `%s` must "
  149
+                        "be either a ForeignKey or ManyToManyField."
  150
+                        % (cls.__name__, idx, field))
  151
+
  152
+    # fields
  153
+    if cls.fields: # default value is None
  154
+        _check_istuplew('fields', cls.fields)
  155
+        for field in cls.fields:
  156
+            _check_field_existsw('fields', field)
  157
+
  158
+    # fieldsets
  159
+    if cls.fieldsets: # default value is None
  160
+        _check_istuplew('fieldsets', cls.fieldsets)
  161
+        for idx, fieldset in enumerate(cls.fieldsets):
  162
+            _check_istuplew('fieldsets[%d]' % idx, fieldset)
  163
+            if len(fieldset) != 2:
  164
+                raise ImproperlyConfigured("`%s.fieldsets[%d]` does not "
  165
+                        "have exactly two elements." % (cls.__name__, idx))
  166
+            _check_isdictw('fieldsets[%d][1]' % idx, fieldset[1])
  167
+            if 'fields' not in fieldset[1]:
  168
+                raise ImproperlyConfigured("`fields` key is required in "
  169
+                        "%s.fieldsets[%d][1] field options dict."
  170
+                        % (cls.__name__, idx))
  171
+        for field in flatten_fieldsets(cls.fieldsets):
  172
+            _check_field_existsw("fieldsets[%d][1]['fields']" % idx, field)
  173
+
  174
+    # form
  175
+    if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
  176
+        raise ImproperlyConfigured("%s.form does not inherit from "
  177
+                "BaseModelForm." % cls.__name__)
  178
+
  179
+    # filter_vertical
  180
+    if hasattr(cls, 'filter_vertical'):
  181
+        _check_istuplew('filter_vertical', cls.filter_vertical)
  182
+        for idx, field in enumerate(cls.filter_vertical):
  183
+            f = _check_field_existsw('filter_vertical', field)
  184
+            if not isinstance(f, models.ManyToManyField):
  185
+                raise ImproperlyConfigured("`%s.filter_vertical[%d]` must be "
  186
+                    "a ManyToManyField." % (cls.__name__, idx))
  187
+
  188
+    # filter_horizontal
  189
+    if hasattr(cls, 'filter_horizontal'):
  190
+        _check_istuplew('filter_horizontal', cls.filter_horizontal)
  191
+        for idx, field in enumerate(cls.filter_horizontal):
  192
+            f = _check_field_existsw('filter_horizontal', field)
  193
+            if not isinstance(f, models.ManyToManyField):
  194
+                raise ImproperlyConfigured("`%s.filter_horizontal[%d]` must be "
  195
+                    "a ManyToManyField." % (cls.__name__, idx))
  196
+
  197
+    # radio_fields
  198
+    if hasattr(cls, 'radio_fields'):
  199
+        _check_isdictw('radio_fields', cls.radio_fields)
  200
+        for field, val in cls.radio_fields.items():
  201
+            f = _check_field_existsw('radio_fields', field)
  202
+            if not (isinstance(f, models.ForeignKey) or f.choices):
  203
+                raise ImproperlyConfigured("`%s.radio_fields['%s']` "
  204
+                        "is neither an instance of ForeignKey nor does "
  205
+                        "have choices set." % (cls.__name__, field))
  206
+            if not val in (HORIZONTAL, VERTICAL):
  207
+                raise ImproperlyConfigured("`%s.radio_fields['%s']` "
  208
+                        "is neither admin.HORIZONTAL nor admin.VERTICAL."
  209
+                        % (cls.__name__, field))
  210
+
  211
+    # prepopulated_fields
  212
+    if hasattr(cls, 'prepopulated_fields'):
  213
+        _check_isdictw('prepopulated_fields', cls.prepopulated_fields)
  214
+        for field, val in cls.prepopulated_fields.items():
  215
+            f = _check_field_existsw('prepopulated_fields', field)
  216
+            if isinstance(f, (models.DateTimeField, models.ForeignKey,
  217
+                models.ManyToManyField)):
  218
+                raise ImproperlyConfigured("`%s.prepopulated_fields['%s']` "
  219
+                        "is either a DateTimeField, ForeignKey or "
  220
+                        "ManyToManyField. This isn't allowed."
  221
+                        % (cls.__name__, field))
  222
+            _check_istuplew("prepopulated_fields['%s']" % field, val)
  223
+            for idx, f in enumerate(val):
  224
+                _check_field_existsw("prepopulated_fields['%s'][%d]"
  225
+                        % (f, idx), f)
  226
+
  227
+def _check_istuple(cls, label, obj):
  228
+    if not isinstance(obj, (list, tuple)):
  229
+        raise ImproperlyConfigured("`%s.%s` must be a "
  230
+                "list or tuple." % (cls.__name__, label))
  231
+
  232
+def _check_isdict(cls, label, obj):
  233
+    if not isinstance(obj, dict):
  234
+        raise ImproperlyConfigured("`%s.%s` must be a dictionary."
  235
+                % (cls.__name__, label))
  236
+
  237
+def _check_field_exists(cls, model, opts, label, field):
  238
+    try:
  239
+        return opts.get_field(field)
  240
+    except models.FieldDoesNotExist:
  241
+        raise ImproperlyConfigured("`%s.%s` refers to "
  242
+                "field `%s` that is missing from model `%s`."
  243
+                % (cls.__name__, label, field, model.__name__))
  244
+
  245
+def _check_attr_exists(cls, model, opts, label, field):
  246
+    try:
  247
+        return opts.get_field(field)
  248
+    except models.FieldDoesNotExist:
  249
+        if not hasattr(model, field):
  250
+            raise ImproperlyConfigured("`%s.%s` refers to "
  251
+                    "`%s` that is neither a field, method or property "
  252
+                    "of model `%s`."
  253
+                    % (cls.__name__, label, field, model.__name__))
  254
+        return getattr(model, field)
65  django/core/management/validation.py
@@ -143,59 +143,6 @@ def get_validation_errors(outfile, app=None):
143 143
                     if r.get_accessor_name() == rel_query_name:
144 144
                         e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
145 145
 
146  
-        # Check admin attribute.
147  
-        if opts.admin is not None:
148  
-            # prepopulated_fields
149  
-            if not isinstance(opts.admin.prepopulated_fields, dict):
150  
-                e.add(opts, '"%s": prepopulated_fields should be a dictionary.' % f.name)
151  
-            else:
152  
-                for field_name, field_list in opts.admin.prepopulated_fields.items():
153  
-                    if not isinstance(field_list, (list, tuple)):
154  
-                        e.add(opts, '"%s": prepopulated_fields "%s" value should be a list or tuple.' % (f.name, field_name))
155  
-
156  
-            # list_display
157  
-            if not isinstance(opts.admin.list_display, (list, tuple)):
158  
-                e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.')
159  
-            else:
160  
-                for fn in opts.admin.list_display:
161  
-                    try:
162  
-                        f = opts.get_field(fn)
163  
-                    except models.FieldDoesNotExist:
164  
-                        if not hasattr(cls, fn):
165  
-                            e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn)
166  
-                    else:
167  
-                        if isinstance(f, models.ManyToManyField):
168  
-                            e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn)
169  
-            # list_display_links
170  
-            if opts.admin.list_display_links and not opts.admin.list_display:
171  
-                e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.')
172  
-            if not isinstance(opts.admin.list_display_links, (list, tuple)):
173  
-                e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.')
174  
-            else:
175  
-                for fn in opts.admin.list_display_links:
176  
-                    try:
177  
-                        f = opts.get_field(fn)
178  
-                    except models.FieldDoesNotExist:
179  
-                        if not hasattr(cls, fn):
180  
-                            e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn)
181  
-                    if fn not in opts.admin.list_display:
182  
-                        e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn)
183  
-            # list_filter
184  
-            if not isinstance(opts.admin.list_filter, (list, tuple)):
185  
-                e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.')
186  
-            else:
187  
-                for fn in opts.admin.list_filter:
188  
-                    try:
189  
-                        f = opts.get_field(fn)
190  
-                    except models.FieldDoesNotExist:
191  
-                        e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn)
192  
-            # date_hierarchy
193  
-            if opts.admin.date_hierarchy:
194  
-                try:
195  
-                    f = opts.get_field(opts.admin.date_hierarchy)
196  
-                except models.FieldDoesNotExist:
197  
-                    e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy)
198  
-
199 146
         # Check ordering attribute.
200 147
         if opts.ordering:
201 148
             for field_name in opts.ordering:
@@ -213,18 +160,6 @@ def get_validation_errors(outfile, app=None):
213 160
                 except models.FieldDoesNotExist:
214 161
                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
215 162
 
216  
-        # Check core=True, if needed.
217  
-        for related in opts.get_followed_related_objects():
218  
-            if not related.edit_inline:
219  
-                continue
220  
-            try:
221  
-                for f in related.opts.fields:
222  
-                    if f.core:
223  
-                        raise StopIteration
224  
-                e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
225  
-            except StopIteration:
226  
-                pass
227  
-
228 163
         # Check unique_together.
229 164
         for ut in opts.unique_together:
230 165
             for field_name in ut:
568  tests/regressiontests/modeladmin/models.py
... ...
@@ -1,7 +1,9 @@
1 1
 # coding: utf-8
2  
-from django.db import models
3 2
 from datetime import date
4 3
 
  4
+from django.db import models
  5
+from django.contrib.auth.models import User
  6
+
5 7
 class Band(models.Model):
6 8
     name = models.CharField(max_length=100)
7 9
     bio = models.TextField()
@@ -21,6 +23,16 @@ class Concert(models.Model):
21 23
         (3, 'Bus')
22 24
     ), blank=True)
23 25
 
  26
+class ValidationTestModel(models.Model):
  27
+    name = models.CharField(max_length=100)
  28
+    slug = models.SlugField()
  29
+    users = models.ManyToManyField(User)
  30
+    state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
  31
+    is_active = models.BooleanField()
  32
+    pub_date = models.DateTimeField()
  33
+
  34
+class ValidationTestInlineModel(models.Model):
  35
+    parent = models.ForeignKey(ValidationTestModel)
24 36
 
25 37
 __test__ = {'API_TESTS': """
26 38
 
@@ -226,5 +238,559 @@ class and an AdminSite instance, so let's just go ahead and do that manually
226 238
 
227 239
 >>> band.delete()
228 240
 
  241
+# ModelAdmin Option Validation ################################################
  242
+
  243
+>>> from django.contrib.admin.validation import validate
  244
+>>> from django.conf import settings
  245
+
  246
+# Ensure validation only runs when DEBUG = True
  247
+
  248
+>>> settings.DEBUG = True
  249
+
  250
+>>> class ValidationTestModelAdmin(ModelAdmin):
  251
+...     raw_id_fields = 10
  252
+>>> site = AdminSite()
  253
+>>> site.register(ValidationTestModel, ValidationTestModelAdmin)
  254
+Traceback (most recent call last):
  255
+...
  256
+ImproperlyConfigured: `ValidationTestModelAdmin.raw_id_fields` must be a list or tuple.
  257
+
  258
+>>> settings.DEBUG = False
  259
+
  260
+>>> class ValidationTestModelAdmin(ModelAdmin):
  261
+...     raw_id_fields = 10
  262
+>>> site = AdminSite()
  263
+>>> site.register(ValidationTestModel, ValidationTestModelAdmin)
  264
+
  265
+# raw_id_fields
  266
+
  267
+>>> class ValidationTestModelAdmin(ModelAdmin):
  268
+...     raw_id_fields = 10
  269
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  270
+Traceback (most recent call last):
  271
+...
  272
+ImproperlyConfigured: `ValidationTestModelAdmin.raw_id_fields` must be a list or tuple.
  273
+
  274
+>>> class ValidationTestModelAdmin(ModelAdmin):
  275
+...     raw_id_fields = ('non_existent_field',)
  276
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  277
+Traceback (most recent call last):
  278
+...
  279
+ImproperlyConfigured: `ValidationTestModelAdmin.raw_id_fields` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  280
+
  281
+>>> class ValidationTestModelAdmin(ModelAdmin):
  282
+...     raw_id_fields = ('name',)
  283
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  284
+Traceback (most recent call last):
  285
+...
  286
+ImproperlyConfigured: `ValidationTestModelAdmin.raw_id_fields[0]`, `name` must be either a ForeignKey or ManyToManyField.
  287
+
  288
+>>> class ValidationTestModelAdmin(ModelAdmin):
  289
+...     raw_id_fields = ('users',)
  290
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  291
+
  292
+# fieldsets
  293
+
  294
+>>> class ValidationTestModelAdmin(ModelAdmin):
  295
+...     fieldsets = 10
  296
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  297
+Traceback (most recent call last):
  298
+...
  299
+ImproperlyConfigured: `ValidationTestModelAdmin.fieldsets` must be a list or tuple.
  300
+
  301
+>>> class ValidationTestModelAdmin(ModelAdmin):
  302
+...     fieldsets = ({},)
  303
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  304
+Traceback (most recent call last):
  305
+...
  306
+ImproperlyConfigured: `ValidationTestModelAdmin.fieldsets[0]` must be a list or tuple.
  307
+
  308
+>>> class ValidationTestModelAdmin(ModelAdmin):
  309
+...     fieldsets = ((),)
  310
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  311
+Traceback (most recent call last):
  312
+...
  313
+ImproperlyConfigured: `ValidationTestModelAdmin.fieldsets[0]` does not have exactly two elements.
  314
+
  315
+>>> class ValidationTestModelAdmin(ModelAdmin):
  316
+...     fieldsets = (("General", ()),)
  317
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  318
+Traceback (most recent call last):
  319
+...
  320
+ImproperlyConfigured: `ValidationTestModelAdmin.fieldsets[0][1]` must be a dictionary.
  321
+
  322
+>>> class ValidationTestModelAdmin(ModelAdmin):
  323
+...     fieldsets = (("General", {}),)
  324
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  325
+Traceback (most recent call last):
  326
+...
  327
+ImproperlyConfigured: `fields` key is required in ValidationTestModelAdmin.fieldsets[0][1] field options dict.
  328
+
  329
+>>> class ValidationTestModelAdmin(ModelAdmin):
  330
+...     fieldsets = (("General", {"fields": ("non_existent_field",)}),)
  331
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  332
+Traceback (most recent call last):
  333
+...
  334
+ImproperlyConfigured: `ValidationTestModelAdmin.fieldsets[0][1]['fields']` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  335
+
  336
+>>> class ValidationTestModelAdmin(ModelAdmin):
  337
+...     fieldsets = (("General", {"fields": ("name",)}),)
  338
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  339
+
  340
+# form
  341
+
  342
+>>> class FakeForm(object):
  343
+...     pass
  344
+>>> class ValidationTestModelAdmin(ModelAdmin):
  345
+...     form = FakeForm
  346
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  347
+Traceback (most recent call last):
  348
+...
  349
+ImproperlyConfigured: ValidationTestModelAdmin.form does not inherit from BaseModelForm.
  350
+
  351
+# filter_vertical
  352
+
  353
+>>> class ValidationTestModelAdmin(ModelAdmin):
  354
+...     filter_vertical = 10
  355
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  356
+Traceback (most recent call last):
  357
+...
  358
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_vertical` must be a list or tuple.
  359
+
  360
+>>> class ValidationTestModelAdmin(ModelAdmin):
  361
+...     filter_vertical = ("non_existent_field",)
  362
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  363
+Traceback (most recent call last):
  364
+...
  365
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_vertical` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  366
+
  367
+>>> class ValidationTestModelAdmin(ModelAdmin):
  368
+...     filter_vertical = ("name",)
  369
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  370
+Traceback (most recent call last):
  371
+...
  372
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_vertical[0]` must be a ManyToManyField.
  373
+
  374
+>>> class ValidationTestModelAdmin(ModelAdmin):
  375
+...     filter_vertical = ("users",)
  376
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  377
+
  378
+# filter_horizontal
  379
+
  380
+>>> class ValidationTestModelAdmin(ModelAdmin):
  381
+...     filter_horizontal = 10
  382
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  383
+Traceback (most recent call last):
  384
+...
  385
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_horizontal` must be a list or tuple.
  386
+
  387
+>>> class ValidationTestModelAdmin(ModelAdmin):
  388
+...     filter_horizontal = ("non_existent_field",)
  389
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  390
+Traceback (most recent call last):
  391
+...
  392
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_horizontal` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  393
+
  394
+>>> class ValidationTestModelAdmin(ModelAdmin):
  395
+...     filter_horizontal = ("name",)
  396
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  397
+Traceback (most recent call last):
  398
+...
  399
+ImproperlyConfigured: `ValidationTestModelAdmin.filter_horizontal[0]` must be a ManyToManyField.
  400
+
  401
+>>> class ValidationTestModelAdmin(ModelAdmin):
  402
+...     filter_horizontal = ("users",)
  403
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  404
+
  405
+# radio_fields
  406
+
  407
+>>> class ValidationTestModelAdmin(ModelAdmin):
  408
+...     radio_fields = ()
  409
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  410
+Traceback (most recent call last):
  411
+...
  412
+ImproperlyConfigured: `ValidationTestModelAdmin.radio_fields` must be a dictionary.
  413
+
  414
+>>> class ValidationTestModelAdmin(ModelAdmin):
  415
+...     radio_fields = {"non_existent_field": None}
  416
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  417
+Traceback (most recent call last):
  418
+...
  419
+ImproperlyConfigured: `ValidationTestModelAdmin.radio_fields` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  420
+
  421
+>>> class ValidationTestModelAdmin(ModelAdmin):
  422
+...     radio_fields = {"name": None}
  423
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  424
+Traceback (most recent call last):
  425
+...
  426
+ImproperlyConfigured: `ValidationTestModelAdmin.radio_fields['name']` is neither an instance of ForeignKey nor does have choices set.
  427
+
  428
+>>> class ValidationTestModelAdmin(ModelAdmin):
  429
+...     radio_fields = {"state": None}
  430
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  431
+Traceback (most recent call last):
  432
+...
  433
+ImproperlyConfigured: `ValidationTestModelAdmin.radio_fields['state']` is neither admin.HORIZONTAL nor admin.VERTICAL.
  434
+
  435
+>>> class ValidationTestModelAdmin(ModelAdmin):
  436
+...     radio_fields = {"state": VERTICAL}
  437
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  438
+
  439
+# prepopulated_fields
  440
+
  441
+>>> class ValidationTestModelAdmin(ModelAdmin):
  442
+...     prepopulated_fields = ()
  443
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  444
+Traceback (most recent call last):
  445
+...
  446
+ImproperlyConfigured: `ValidationTestModelAdmin.prepopulated_fields` must be a dictionary.
  447
+
  448
+>>> class ValidationTestModelAdmin(ModelAdmin):
  449
+...     prepopulated_fields = {"non_existent_field": None}
  450
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  451
+Traceback (most recent call last):
  452
+...
  453
+ImproperlyConfigured: `ValidationTestModelAdmin.prepopulated_fields` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  454
+
  455
+>>> class ValidationTestModelAdmin(ModelAdmin):
  456
+...     prepopulated_fields = {"slug": ("non_existent_field",)}
  457
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  458
+Traceback (most recent call last):
  459
+...
  460
+ImproperlyConfigured: `ValidationTestModelAdmin.prepopulated_fields['non_existent_field'][0]` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  461
+
  462
+>>> class ValidationTestModelAdmin(ModelAdmin):
  463
+...     prepopulated_fields = {"users": ("name",)}
  464
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  465
+Traceback (most recent call last):
  466
+...
  467
+ImproperlyConfigured: `ValidationTestModelAdmin.prepopulated_fields['users']` is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.
  468
+
  469
+>>> class ValidationTestModelAdmin(ModelAdmin):
  470
+...     prepopulated_fields = {"slug": ("name",)}
  471
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  472
+
  473
+# list_display
  474
+
  475
+>>> class ValidationTestModelAdmin(ModelAdmin):
  476
+...     list_display = 10
  477
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  478
+Traceback (most recent call last):
  479
+...
  480
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display` must be a list or tuple.
  481
+
  482
+>>> class ValidationTestModelAdmin(ModelAdmin):
  483
+...     list_display = ('non_existent_field',)
  484
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  485
+Traceback (most recent call last):
  486
+...
  487
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display[0]` refers to `non_existent_field` that is neither a field, method or property of model `ValidationTestModel`.
  488
+
  489
+>>> class ValidationTestModelAdmin(ModelAdmin):
  490
+...     list_display = ('users',)
  491
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  492
+Traceback (most recent call last):
  493
+...
  494
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display[0]`, `users` is a ManyToManyField which is not supported.
  495
+
  496
+>>> class ValidationTestModelAdmin(ModelAdmin):
  497
+...     list_display = ('name',)
  498
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  499
+
  500
+# list_display_links
  501
+
  502
+>>> class ValidationTestModelAdmin(ModelAdmin):
  503
+...     list_display_links = 10
  504
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  505
+Traceback (most recent call last):
  506
+...
  507
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display_links` must be a list or tuple.
  508
+
  509
+>>> class ValidationTestModelAdmin(ModelAdmin):
  510
+...     list_display_links = ('non_existent_field',)
  511
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  512
+Traceback (most recent call last):
  513
+...
  514
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display_links[0]` refers to `non_existent_field` that is neither a field, method or property of model `ValidationTestModel`.
  515
+
  516
+>>> class ValidationTestModelAdmin(ModelAdmin):
  517
+...     list_display_links = ('name',)
  518
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  519
+Traceback (most recent call last):
  520
+...
  521
+ImproperlyConfigured: `ValidationTestModelAdmin.list_display_links[0]`refers to `name` which is not defined in `list_display`.
  522
+
  523
+>>> class ValidationTestModelAdmin(ModelAdmin):
  524
+...     list_display = ('name',)
  525
+...     list_display_links = ('name',)
  526
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  527
+
  528
+# list_filter
  529
+
  530
+>>> class ValidationTestModelAdmin(ModelAdmin):
  531
+...     list_filter = 10
  532
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  533
+Traceback (most recent call last):
  534
+...
  535
+ImproperlyConfigured: `ValidationTestModelAdmin.list_filter` must be a list or tuple.
  536
+
  537
+>>> class ValidationTestModelAdmin(ModelAdmin):
  538
+...     list_filter = ('non_existent_field',)
  539
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  540
+Traceback (most recent call last):
  541
+...
  542
+ImproperlyConfigured: `ValidationTestModelAdmin.list_filter[0]` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  543
+
  544
+>>> class ValidationTestModelAdmin(ModelAdmin):
  545
+...     list_filter = ('is_active',)
  546
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  547
+
  548
+# list_per_page
  549
+
  550
+>>> class ValidationTestModelAdmin(ModelAdmin):
  551
+...     list_per_page = 'hello'
  552
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  553
+Traceback (most recent call last):
  554
+...
  555
+ImproperlyConfigured: `ValidationTestModelAdmin.list_per_page` should be a integer.
  556
+
  557
+>>> class ValidationTestModelAdmin(ModelAdmin):
  558
+...     list_per_page = 100
  559
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  560
+
  561
+# search_fields
  562
+
  563
+>>> class ValidationTestModelAdmin(ModelAdmin):
  564
+...     search_fields = 10
  565
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  566
+Traceback (most recent call last):
  567
+...
  568
+ImproperlyConfigured: `ValidationTestModelAdmin.search_fields` must be a list or tuple.
  569
+
  570
+# date_hierarchy
  571
+
  572
+>>> class ValidationTestModelAdmin(ModelAdmin):
  573
+...     date_hierarchy = 'non_existent_field'
  574
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  575
+Traceback (most recent call last):
  576
+...
  577
+ImproperlyConfigured: `ValidationTestModelAdmin.date_hierarchy` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  578
+
  579
+>>> class ValidationTestModelAdmin(ModelAdmin):
  580
+...     date_hierarchy = 'name'
  581
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  582
+Traceback (most recent call last):
  583
+...
  584
+ImproperlyConfigured: `ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.
  585
+
  586
+>>> class ValidationTestModelAdmin(ModelAdmin):
  587
+...     date_hierarchy = 'pub_date'
  588
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  589
+
  590
+# ordering
  591
+
  592
+>>> class ValidationTestModelAdmin(ModelAdmin):
  593
+...     ordering = 10
  594
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  595
+Traceback (most recent call last):
  596
+...
  597
+ImproperlyConfigured: `ValidationTestModelAdmin.ordering` must be a list or tuple.
  598
+
  599
+>>> class ValidationTestModelAdmin(ModelAdmin):
  600
+...     ordering = ('non_existent_field',)
  601
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  602
+Traceback (most recent call last):
  603
+...
  604
+ImproperlyConfigured: `ValidationTestModelAdmin.ordering[0]` refers to field `non_existent_field` that is missing from model `ValidationTestModel`.
  605
+
  606
+>>> class ValidationTestModelAdmin(ModelAdmin):
  607
+...     ordering = ('?', 'name')
  608
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  609
+Traceback (most recent call last):
  610
+...
  611
+ImproperlyConfigured: `ValidationTestModelAdmin.ordering` has the random ordering marker `?`, but contains other fields as well. Please either remove `?` or the other fields.
  612
+
  613
+>>> class ValidationTestModelAdmin(ModelAdmin):
  614
+...     ordering = ('name',)
  615
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  616
+
  617
+# list_select_related
  618
+
  619
+>>> class ValidationTestModelAdmin(ModelAdmin):
  620
+...     list_select_related = 1
  621
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  622
+Traceback (most recent call last):
  623
+...
  624
+ImproperlyConfigured: `ValidationTestModelAdmin.list_select_related` should be a boolean.
  625
+
  626
+>>> class ValidationTestModelAdmin(ModelAdmin):
  627
+...     list_select_related = False
  628
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  629
+
  630
+# save_as
  631
+
  632
+>>> class ValidationTestModelAdmin(ModelAdmin):
  633
+...     save_as = 1
  634
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  635
+Traceback (most recent call last):
  636
+...
  637
+ImproperlyConfigured: `ValidationTestModelAdmin.save_as` should be a boolean.
  638
+
  639
+>>> class ValidationTestModelAdmin(ModelAdmin):
  640
+...     save_as = True
  641
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  642
+
  643
+# save_on_top
  644
+
  645
+>>> class ValidationTestModelAdmin(ModelAdmin):
  646
+...     save_on_top = 1
  647
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  648
+Traceback (most recent call last):
  649
+...
  650
+ImproperlyConfigured: `ValidationTestModelAdmin.save_on_top` should be a boolean.
  651
+
  652
+>>> class ValidationTestModelAdmin(ModelAdmin):
  653
+...     save_on_top = True
  654
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  655
+
  656
+# inlines
  657
+
  658
+>>> from django.contrib.admin.options import TabularInline
  659
+
  660
+>>> class ValidationTestModelAdmin(ModelAdmin):
  661
+...     inlines = 10
  662
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  663
+Traceback (most recent call last):
  664
+...
  665
+ImproperlyConfigured: `ValidationTestModelAdmin.inlines` must be a list or tuple.
  666
+
  667
+>>> class ValidationTestInline(object):
  668
+...     pass
  669
+>>> class ValidationTestModelAdmin(ModelAdmin):
  670
+...     inlines = [ValidationTestInline]
  671
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  672
+Traceback (most recent call last):
  673
+...
  674
+ImproperlyConfigured: `ValidationTestModelAdmin.inlines[0]` does not inherit from BaseModelAdmin.
  675
+
  676
+>>> class ValidationTestInline(TabularInline):
  677
+...     pass
  678
+>>> class ValidationTestModelAdmin(ModelAdmin):
  679
+...     inlines = [ValidationTestInline]
  680
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  681
+Traceback (most recent call last):
  682
+...
  683
+ImproperlyConfigured: `model` is a required attribute of `ValidationTestModelAdmin.inlines[0]`.
  684
+
  685
+>>> class SomethingBad(object):
  686
+...     pass
  687
+>>> class ValidationTestInline(TabularInline):
  688
+...     model = SomethingBad
  689
+>>> class ValidationTestModelAdmin(ModelAdmin):
  690
+...     inlines = [ValidationTestInline]
  691
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  692
+Traceback (most recent call last):
  693
+...
  694
+ImproperlyConfigured: `ValidationTestModelAdmin.inlines[0].model` does not inherit from models.Model.
  695
+
  696
+>>> class ValidationTestInline(TabularInline):
  697
+...     model = ValidationTestInlineModel
  698
+>>> class ValidationTestModelAdmin(ModelAdmin):
  699
+...     inlines = [ValidationTestInline]
  700
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  701
+
  702
+# fields
  703
+
  704
+>>> class ValidationTestInline(TabularInline):
  705
+...     model = ValidationTestInlineModel
  706
+...     fields = 10
  707
+>>> class ValidationTestModelAdmin(ModelAdmin):
  708
+...     inlines = [ValidationTestInline]
  709
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  710
+Traceback (most recent call last):
  711
+...
  712
+ImproperlyConfigured: `ValidationTestInline.fields` must be a list or tuple.
  713
+
  714
+>>> class ValidationTestInline(TabularInline):
  715
+...     model = ValidationTestInlineModel
  716
+...     fields = ("non_existent_field",)
  717
+>>> class ValidationTestModelAdmin(ModelAdmin):
  718
+...     inlines = [ValidationTestInline]
  719
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  720
+Traceback (most recent call last):
  721
+...
  722
+ImproperlyConfigured: `ValidationTestInline.fields` refers to field `non_existent_field` that is missing from model `ValidationTestInlineModel`.
  723
+
  724
+# fk_name
  725
+
  726
+>>> class ValidationTestInline(TabularInline):
  727
+...     model = ValidationTestInlineModel
  728
+...     fk_name = "non_existent_field"
  729
+>>> class ValidationTestModelAdmin(ModelAdmin):
  730
+...     inlines = [ValidationTestInline]
  731
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  732
+Traceback (most recent call last):
  733
+...
  734
+ImproperlyConfigured: `ValidationTestInline.fk_name` refers to field `non_existent_field` that is missing from model `ValidationTestInlineModel`.
  735
+
  736
+>>> class ValidationTestInline(TabularInline):
  737
+...     model = ValidationTestInlineModel
  738
+...     fk_name = "parent"
  739
+>>> class ValidationTestModelAdmin(ModelAdmin):
  740
+...     inlines = [ValidationTestInline]
  741
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  742
+
  743
+# extra
  744
+
  745
+>>> class ValidationTestInline(TabularInline):
  746
+...     model = ValidationTestInlineModel
  747
+...     extra = "hello"
  748
+>>> class ValidationTestModelAdmin(ModelAdmin):
  749
+...     inlines = [ValidationTestInline]
  750
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  751
+Traceback (most recent call last):
  752
+...
  753
+ImproperlyConfigured: `ValidationTestInline.extra` should be a integer.
  754
+
  755
+>>> class ValidationTestInline(TabularInline):
  756
+...     model = ValidationTestInlineModel
  757
+...     extra = 2
  758
+>>> class ValidationTestModelAdmin(ModelAdmin):
  759
+...     inlines = [ValidationTestInline]
  760
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  761
+
  762
+# max_num
  763
+
  764
+>>> class ValidationTestInline(TabularInline):
  765
+...     model = ValidationTestInlineModel
  766
+...     max_num = "hello"
  767
+>>> class ValidationTestModelAdmin(ModelAdmin):
  768
+...     inlines = [ValidationTestInline]
  769
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  770
+Traceback (most recent call last):
  771
+...
  772
+ImproperlyConfigured: `ValidationTestInline.max_num` should be a integer.
  773
+
  774
+>>> class ValidationTestInline(TabularInline):
  775
+...     model = ValidationTestInlineModel
  776
+...     max_num = 2
  777
+>>> class ValidationTestModelAdmin(ModelAdmin):
  778
+...     inlines = [ValidationTestInline]
  779
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  780
+
  781
+# formset
  782
+
  783
+>>> class FakeFormSet(object):
  784
+...     pass
  785
+>>> class ValidationTestInline(TabularInline):
  786
+...     model = ValidationTestInlineModel
  787
+...     formset = FakeFormSet
  788
+>>> class ValidationTestModelAdmin(ModelAdmin):
  789
+...     inlines = [ValidationTestInline]
  790
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
  791
+Traceback (most recent call last):
  792
+...
  793
+ImproperlyConfigured: `ValidationTestInline.formset` does not inherit from BaseInlineFormset.
  794
+
229 795
 """
230 796
 }

0 notes on commit a2c7bfc

Please sign in to comment.
Something went wrong with that request. Please try again.