Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitfdcd9ef

Browse files
committed
Support UniqueConstraint
1 parente5fb9af commitfdcd9ef

File tree

3 files changed

+175
-47
lines changed

3 files changed

+175
-47
lines changed

‎rest_framework/serializers.py‎

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,23 @@ def get_extra_kwargs(self):
13961396

13971397
returnextra_kwargs
13981398

1399+
defget_unique_together_constraints(self,model):
1400+
"""
1401+
Returns iterator of (fields, queryset), each entry describe an unique together
1402+
constraint on `fields` in `queryset`.
1403+
"""
1404+
forparent_classin [model]+list(model._meta.parents):
1405+
forunique_togetherinparent_class._meta.unique_together:
1406+
yieldunique_together,model._default_manager
1407+
forconstraintinparent_class._meta.constraints:
1408+
ifisinstance(constraint,models.UniqueConstraint)andlen(constraint.fields)>1:
1409+
yield (
1410+
constraint.fields,
1411+
model._default_manager
1412+
ifconstraint.conditionisNone
1413+
elsemodel._default_manager.filter(constraint.condition)
1414+
)
1415+
13991416
defget_uniqueness_extra_kwargs(self,field_names,declared_fields,extra_kwargs):
14001417
"""
14011418
Return any additional field options that need to be included as a
@@ -1424,12 +1441,11 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs
14241441

14251442
unique_constraint_names-= {None}
14261443

1427-
# Include each of the `unique_together` field names,
1444+
# Include each of the `unique_together`and `UniqueConstraint`field names,
14281445
# so long as all the field names are included on the serializer.
1429-
forparent_classin [model]+list(model._meta.parents):
1430-
forunique_together_listinparent_class._meta.unique_together:
1431-
ifset(field_names).issuperset(unique_together_list):
1432-
unique_constraint_names|=set(unique_together_list)
1446+
forunique_together_list,querysetinself.get_unique_together_constraints(model):
1447+
ifset(field_names).issuperset(unique_together_list):
1448+
unique_constraint_names|=set(unique_together_list)
14331449

14341450
# Now we have all the field names that have uniqueness constraints
14351451
# applied, we can add the extra 'required=...' or 'default=...'
@@ -1526,11 +1542,6 @@ def get_unique_together_validators(self):
15261542
"""
15271543
Determine a default set of validators for any unique_together constraints.
15281544
"""
1529-
model_class_inheritance_tree= (
1530-
[self.Meta.model]+
1531-
list(self.Meta.model._meta.parents)
1532-
)
1533-
15341545
# The field names we're passing though here only include fields
15351546
# which may map onto a model field. Any dotted field name lookups
15361547
# cannot map to a field, and must be a traversal, so we're not
@@ -1556,34 +1567,33 @@ def get_unique_together_validators(self):
15561567
# Note that we make sure to check `unique_together` both on the
15571568
# base model class, but also on any parent classes.
15581569
validators= []
1559-
forparent_classinmodel_class_inheritance_tree:
1560-
forunique_togetherinparent_class._meta.unique_together:
1561-
# Skip if serializer does not map to all unique together sources
1562-
ifnotset(source_map).issuperset(unique_together):
1563-
continue
1564-
1565-
forsourceinunique_together:
1566-
assertlen(source_map[source])==1, (
1567-
"Unable to create `UniqueTogetherValidator` for "
1568-
"`{model}.{field}` as `{serializer}` has multiple "
1569-
"fields ({fields}) that map to this model field. "
1570-
"Either remove the extra fields, or override "
1571-
"`Meta.validators` with a `UniqueTogetherValidator` "
1572-
"using the desired field names."
1573-
.format(
1574-
model=self.Meta.model.__name__,
1575-
serializer=self.__class__.__name__,
1576-
field=source,
1577-
fields=', '.join(source_map[source]),
1578-
)
1579-
)
1570+
forunique_together,querysetinself.get_unique_together_constraints(self.Meta.model):
1571+
# Skip if serializer does not map to all unique together sources
1572+
ifnotset(source_map).issuperset(unique_together):
1573+
continue
15801574

1581-
field_names=tuple(source_map[f][0]forfinunique_together)
1582-
validator=UniqueTogetherValidator(
1583-
queryset=parent_class._default_manager,
1584-
fields=field_names
1575+
forsourceinunique_together:
1576+
assertlen(source_map[source])==1, (
1577+
"Unable to create `UniqueTogetherValidator` for "
1578+
"`{model}.{field}` as `{serializer}` has multiple "
1579+
"fields ({fields}) that map to this model field. "
1580+
"Either remove the extra fields, or override "
1581+
"`Meta.validators` with a `UniqueTogetherValidator` "
1582+
"using the desired field names."
1583+
.format(
1584+
model=self.Meta.model.__name__,
1585+
serializer=self.__class__.__name__,
1586+
field=source,
1587+
fields=', '.join(source_map[source]),
1588+
)
15851589
)
1586-
validators.append(validator)
1590+
1591+
field_names=tuple(source_map[f][0]forfinunique_together)
1592+
validator=UniqueTogetherValidator(
1593+
queryset=queryset,
1594+
fields=field_names
1595+
)
1596+
validators.append(validator)
15871597
returnvalidators
15881598

15891599
defget_unique_for_date_validators(self):

‎rest_framework/utils/field_mapping.py‎

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,34 @@ def get_detail_view_name(model):
6262
}
6363

6464

65+
defget_unique_validators(field_name,model_field):
66+
"""
67+
Returns a list of UniqueValidators that should be applied to the field.
68+
"""
69+
field_set=set([field_name])
70+
conditions= {
71+
c.condition
72+
forcinmodel_field.model._meta.constraints
73+
ifisinstance(c,models.UniqueConstraint)andset(c.fields)==field_set
74+
}
75+
ifgetattr(model_field,'unique',False):
76+
conditions.add(None)
77+
ifnotconditions:
78+
return
79+
unique_error_message=model_field.error_messages.get('unique',None)
80+
ifunique_error_message:
81+
unique_error_message=unique_error_message% {
82+
'model_name':model_field.model._meta.verbose_name,
83+
'field_label':model_field.verbose_name
84+
}
85+
queryset=model_field.model._default_manager
86+
forconditioninconditions:
87+
yieldUniqueValidator(
88+
queryset=querysetifconditionisNoneelsequeryset.filter(condition),
89+
message=unique_error_message
90+
)
91+
92+
6593
defget_field_kwargs(field_name,model_field):
6694
"""
6795
Creates a default instance of a basic non-relational field.
@@ -216,17 +244,7 @@ def get_field_kwargs(field_name, model_field):
216244
ifnotisinstance(validator,validators.MinLengthValidator)
217245
]
218246

219-
ifgetattr(model_field,'unique',False):
220-
unique_error_message=model_field.error_messages.get('unique',None)
221-
ifunique_error_message:
222-
unique_error_message=unique_error_message% {
223-
'model_name':model_field.model._meta.verbose_name,
224-
'field_label':model_field.verbose_name
225-
}
226-
validator=UniqueValidator(
227-
queryset=model_field.model._default_manager,
228-
message=unique_error_message)
229-
validator_kwarg.append(validator)
247+
validator_kwarg+=get_unique_validators(field_name,model_field)
230248

231249
ifvalidator_kwarg:
232250
kwargs['validators']=validator_kwarg

‎tests/test_validators.py‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,106 @@ def filter(self, **kwargs):
451451
assertqueryset.called_with== {'race_name':'bar','position':1}
452452

453453

454+
classUniqueConstraintModel(models.Model):
455+
race_name=models.CharField(max_length=100)
456+
position=models.IntegerField()
457+
global_id=models.IntegerField()
458+
fancy_conditions=models.IntegerField(null=True)
459+
460+
classMeta:
461+
constraints= [
462+
models.UniqueConstraint(
463+
name="unique_constraint_model_global_id_uniq",
464+
fields=('global_id',),
465+
),
466+
models.UniqueConstraint(
467+
name="unique_constraint_model_fancy_1_uniq",
468+
fields=('fancy_conditions',),
469+
condition=models.Q(global_id__lte=1)
470+
),
471+
models.UniqueConstraint(
472+
name="unique_constraint_model_fancy_3_uniq",
473+
fields=('fancy_conditions',),
474+
condition=models.Q(global_id__gte=3)
475+
),
476+
models.UniqueConstraint(
477+
name="unique_constraint_model_together_uniq",
478+
fields=('race_name','position'),
479+
condition=models.Q(race_name='example'),
480+
)
481+
]
482+
483+
484+
classUniqueConstraintSerializer(serializers.ModelSerializer):
485+
classMeta:
486+
model=UniqueConstraintModel
487+
fields='__all__'
488+
489+
490+
classTestUniqueConstraintValidation(TestCase):
491+
defsetUp(self):
492+
self.instance=UniqueConstraintModel.objects.create(
493+
race_name='example',
494+
position=1,
495+
global_id=1
496+
)
497+
UniqueConstraintModel.objects.create(
498+
race_name='example',
499+
position=2,
500+
global_id=2
501+
)
502+
UniqueConstraintModel.objects.create(
503+
race_name='other',
504+
position=1,
505+
global_id=3
506+
)
507+
508+
deftest_repr(self):
509+
serializer=UniqueConstraintSerializer()
510+
# the order of validators isn't deterministic so delete
511+
# fancy_conditions field that has two of them
512+
delserializer.fields['fancy_conditions']
513+
expected=dedent("""
514+
UniqueConstraintSerializer():
515+
id = IntegerField(label='ID', read_only=True)
516+
race_name = CharField(max_length=100, required=True)
517+
position = IntegerField(required=True)
518+
global_id = IntegerField(validators=[<UniqueValidator(queryset=UniqueConstraintModel.objects.all())>])
519+
class Meta:
520+
validators = [<UniqueTogetherValidator(queryset=<QuerySet [<UniqueConstraintModel: UniqueConstraintModel object (1)>, <UniqueConstraintModel: UniqueConstraintModel object (2)>]>, fields=('race_name', 'position'))>]
521+
""")
522+
assertrepr(serializer)==expected
523+
524+
deftest_unique_together_field(self):
525+
"""
526+
UniqueConstraint fields and condition attributes must be passed
527+
to UniqueTogetherValidator as fields and queryset
528+
"""
529+
serializer=UniqueConstraintSerializer()
530+
assertlen(serializer.validators)==1
531+
validator=serializer.validators[0]
532+
assertvalidator.fields== ('race_name','position')
533+
assertset(validator.queryset.values_list(flat=True))==set(
534+
UniqueConstraintModel.objects.filter(race_name='example').values_list(flat=True)
535+
)
536+
537+
deftest_single_field_uniq_validators(self):
538+
"""
539+
UniqueConstraint with single field must be transformed into
540+
field's UniqueValidator
541+
"""
542+
serializer=UniqueConstraintSerializer()
543+
assertlen(serializer.validators)==1
544+
validators=serializer.fields['global_id'].validators
545+
assertlen(validators)==1
546+
assertvalidators[0].queryset==UniqueConstraintModel.objects
547+
548+
validators=serializer.fields['fancy_conditions'].validators
549+
assertlen(validators)==2
550+
ids_in_qs= {frozenset(v.queryset.values_list(flat=True))forvinvalidators}
551+
assertids_in_qs== {frozenset([1]),frozenset([3])}
552+
553+
454554
# Tests for `UniqueForDateValidator`
455555
# ----------------------------------
456556

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp