Skip to content

Commit 0ea97f8

Browse files
EkluvEkluvSingh
authored andcommitted
fix unique=True validation for ChoiceField
1 parent 63a4021 commit 0ea97f8

File tree

2 files changed

+89
-67
lines changed

2 files changed

+89
-67
lines changed

rest_framework/utils/field_mapping.py

Lines changed: 63 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,70 @@ def get_field_kwargs(field_name, model_field):
123123
kwargs['allow_folders'] = model_field.allow_folders
124124

125125
if model_field.choices:
126-
# If this model field contains choices, then return early.
127-
# Further keyword arguments are not valid.
128126
kwargs['choices'] = model_field.choices
129-
return kwargs
130-
131-
# Our decimal validation is handled in the field code, not validator code.
132-
# (In Django 1.9+ this differs from previous style)
133-
if isinstance(model_field, models.DecimalField) and DecimalValidator:
134-
validator_kwarg = [
135-
validator for validator in validator_kwarg
136-
if not isinstance(validator, DecimalValidator)
137-
]
127+
else:
128+
# Ensure that max_value is passed explicitly as a keyword arg,
129+
# rather than as a validator.
130+
max_value = next((
131+
validator.limit_value for validator in validator_kwarg
132+
if isinstance(validator, validators.MaxValueValidator)
133+
), None)
134+
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
135+
kwargs['max_value'] = max_value
136+
validator_kwarg = [
137+
validator for validator in validator_kwarg
138+
if not isinstance(validator, validators.MaxValueValidator)
139+
]
140+
141+
# Ensure that max_value is passed explicitly as a keyword arg,
142+
# rather than as a validator.
143+
min_value = next((
144+
validator.limit_value for validator in validator_kwarg
145+
if isinstance(validator, validators.MinValueValidator)
146+
), None)
147+
if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
148+
kwargs['min_value'] = min_value
149+
validator_kwarg = [
150+
validator for validator in validator_kwarg
151+
if not isinstance(validator, validators.MinValueValidator)
152+
]
153+
154+
# URLField does not need to include the URLValidator argument,
155+
# as it is explicitly added in.
156+
if isinstance(model_field, models.URLField):
157+
validator_kwarg = [
158+
validator for validator in validator_kwarg
159+
if not isinstance(validator, validators.URLValidator)
160+
]
161+
162+
# EmailField does not need to include the validate_email argument,
163+
# as it is explicitly added in.
164+
if isinstance(model_field, models.EmailField):
165+
validator_kwarg = [
166+
validator for validator in validator_kwarg
167+
if validator is not validators.validate_email
168+
]
169+
170+
# SlugField do not need to include the 'validate_slug' argument,
171+
if isinstance(model_field, models.SlugField):
172+
validator_kwarg = [
173+
validator for validator in validator_kwarg
174+
if validator is not validators.validate_slug
175+
]
176+
177+
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
178+
if isinstance(model_field, models.GenericIPAddressField):
179+
validator_kwarg = [
180+
validator for validator in validator_kwarg
181+
if validator is not validators.validate_ipv46_address
182+
]
183+
# Our decimal validation is handled in the field code, not validator code.
184+
# (In Django 1.9+ this differs from previous style)
185+
if isinstance(model_field, models.DecimalField) and DecimalValidator:
186+
validator_kwarg = [
187+
validator for validator in validator_kwarg
188+
if not isinstance(validator, DecimalValidator)
189+
]
138190

139191
# Ensure that max_length is passed explicitly as a keyword arg,
140192
# rather than as a validator.
@@ -160,62 +212,6 @@ def get_field_kwargs(field_name, model_field):
160212
if not isinstance(validator, validators.MinLengthValidator)
161213
]
162214

163-
# Ensure that max_value is passed explicitly as a keyword arg,
164-
# rather than as a validator.
165-
max_value = next((
166-
validator.limit_value for validator in validator_kwarg
167-
if isinstance(validator, validators.MaxValueValidator)
168-
), None)
169-
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
170-
kwargs['max_value'] = max_value
171-
validator_kwarg = [
172-
validator for validator in validator_kwarg
173-
if not isinstance(validator, validators.MaxValueValidator)
174-
]
175-
176-
# Ensure that max_value is passed explicitly as a keyword arg,
177-
# rather than as a validator.
178-
min_value = next((
179-
validator.limit_value for validator in validator_kwarg
180-
if isinstance(validator, validators.MinValueValidator)
181-
), None)
182-
if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
183-
kwargs['min_value'] = min_value
184-
validator_kwarg = [
185-
validator for validator in validator_kwarg
186-
if not isinstance(validator, validators.MinValueValidator)
187-
]
188-
189-
# URLField does not need to include the URLValidator argument,
190-
# as it is explicitly added in.
191-
if isinstance(model_field, models.URLField):
192-
validator_kwarg = [
193-
validator for validator in validator_kwarg
194-
if not isinstance(validator, validators.URLValidator)
195-
]
196-
197-
# EmailField does not need to include the validate_email argument,
198-
# as it is explicitly added in.
199-
if isinstance(model_field, models.EmailField):
200-
validator_kwarg = [
201-
validator for validator in validator_kwarg
202-
if validator is not validators.validate_email
203-
]
204-
205-
# SlugField do not need to include the 'validate_slug' argument,
206-
if isinstance(model_field, models.SlugField):
207-
validator_kwarg = [
208-
validator for validator in validator_kwarg
209-
if validator is not validators.validate_slug
210-
]
211-
212-
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
213-
if isinstance(model_field, models.GenericIPAddressField):
214-
validator_kwarg = [
215-
validator for validator in validator_kwarg
216-
if validator is not validators.validate_ipv46_address
217-
]
218-
219215
if getattr(model_field, 'unique', False):
220216
unique_error_message = model_field.error_messages.get('unique', None)
221217
if unique_error_message:

tests/test_serializer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,29 @@ class Grandchild(Child):
519519
assert len(Parent().get_fields()) == 2
520520
assert len(Child().get_fields()) == 2
521521
assert len(Grandchild().get_fields()) == 2
522+
523+
524+
class Poll(models.Model):
525+
CHOICES = (
526+
('choice1', 'choice 1'),
527+
('choice2', 'choice 1'),
528+
)
529+
530+
name = models.CharField(
531+
'name', max_length=254, unique=True, choices=CHOICES
532+
)
533+
534+
535+
@pytest.mark.django_db
536+
class Test5004UniqueChoiceField:
537+
def test_unique_choice_field(self):
538+
Poll.objects.create(name='choice1')
539+
540+
class PollSerializer(serializers.ModelSerializer):
541+
class Meta:
542+
model = Poll
543+
fields = '__all__'
544+
545+
serializer = PollSerializer(data={'name': 'choice1'})
546+
assert not serializer.is_valid()
547+
assert serializer.errors == {'name': ['poll with this name already exists.']}

0 commit comments

Comments
 (0)