Răsfoiți Sursa

Merge branch 'allow-any-iterable-for-choices'

Donald Stufft 12 ani în urmă
părinte
comite
398841d6d3

+ 2 - 2
django/core/management/validation.py

@@ -118,8 +118,8 @@ def get_validation_errors(outfile, app=None):
                     e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
                 else:
                     for c in f.choices:
-                        if not isinstance(c, (list, tuple)) or len(c) != 2:
-                            e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
+                        if isinstance(c, six.string_types) or not is_iterable(c) or len(c) != 2:
+                            e.add(opts, '"%s": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).' % f.name)
             if f.db_index not in (None, True, False):
                 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
 

+ 4 - 3
docs/ref/models/fields.txt

@@ -80,9 +80,10 @@ If a field has ``blank=False``, the field will be required.
 
 .. attribute:: Field.choices
 
-An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
-field. If this is given, the default form widget will be a select box with
-these choices instead of the standard text field.
+An iterable (e.g., a list or tuple) consisting itself of iterables of exactly
+two items (e.g. ``[(A, B), (A, B) ...]``) to use as choices for this field. If
+this is given, the default form widget will be a select box with these choices
+instead of the standard text field.
 
 The first element in each tuple is the actual value to be stored, and the
 second element is the human-readable name. For example::

+ 3 - 0
docs/releases/1.6.txt

@@ -238,6 +238,9 @@ Minor features
   Meta option: ``localized_fields``. Fields included in this list will be localized
   (by setting ``localize`` on the form field).
 
+* The ``choices`` argument to model fields now accepts an iterable of iterables
+  instead of requiring an iterable of lists or tuples.
+
 Backwards incompatible changes in 1.6
 =====================================
 

+ 2 - 2
tests/invalid_models/invalid_models/models.py

@@ -375,8 +375,8 @@ invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits
 invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute.
 invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
 invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list).
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
 invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
 invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.
 invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead.

+ 0 - 0
tests/model_validation/__init__.py


+ 27 - 0
tests/model_validation/models.py

@@ -0,0 +1,27 @@
+from django.db import models
+
+
+class ThingItem(object):
+
+    def __init__(self, value, display):
+        self.value = value
+        self.display = display
+
+    def __iter__(self):
+        return (x for x in [self.value, self.display])
+
+    def __len__(self):
+        return 2
+
+
+class Things(object):
+
+    def __iter__(self):
+        return (x for x in [ThingItem(1, 2), ThingItem(3, 4)])
+
+
+class ThingWithIterableChoices(models.Model):
+
+    # Testing choices= Iterable of Iterables
+    #   See: https://code.djangoproject.com/ticket/20430
+    thing = models.CharField(max_length=100, blank=True, choices=Things())

+ 14 - 0
tests/model_validation/tests.py

@@ -0,0 +1,14 @@
+import io
+
+from django.core import management
+from django.test import TestCase
+
+
+class ModelValidationTest(TestCase):
+
+    def test_models_validate(self):
+        # All our models should validate properly
+        # Validation Tests:
+        #   * choices= Iterable of Iterables
+        #       See: https://code.djangoproject.com/ticket/20430
+        management.call_command("validate", stdout=io.BytesIO())