-
-
Notifications
You must be signed in to change notification settings - Fork 33k
Fixed #29641 -- Added UniqueConstraint support. #10271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…rd-only arguments. Using keyword only arguments follows the `Index.__init__` pattern signature to easen the addition of constraint options in the future. Also renamed the `constraint` argument to `check` to better represent which part of the constraint the provided `Q` object represents. This change will be particularily useful when we add partial constraint support because an additional `Q` object passing be required.
This type of constraint is currently similar to the unique_together entries of a model but allow a name to be specified. Eventually it will be possible to pass conditions to it in order to define partial unique constraints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main request for feedback at this point is whether or not this would be better implemented as Index(unique=True)
instead.
In all cases I think the schema level refactor and the BaseConstraint
extraction are worth merging because it allows the definition of custom constraints without too much boiler plate.
For example, PostgreSQL exclusion constraints could be defined in django.contrib.postgres
without too much work.
|
||
|
||
class CheckConstraint(BaseConstraint): | ||
def __init__(self, *, check, name): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation behind this signature change it detailed in the commit message but I wanted to gather feedback about it here.
- Using keyword only arguments aligns this API with the
Index
one and makes sure arguments are explicitly named. This is something that was mentioned during the review of Fixed #11964 -- Added support for check constraints in model Meta. #7615 related to the ordering ofconstraint
andname
. - Changed the
constraint
name tocheck
because it wasn't the actual constraint that was being provided. Maybe unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1: I'm definitely happy with this. I'm not sure why I didn't go for it myself.
2: Fine by me, I don't feel strongly.
My instinct on the |
class UniqueConstraint(BaseConstraint): | ||
def __init__(self, *, fields, name): | ||
if not fields: | ||
raise ValueError('At least one field is required to define an index.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting an error about an index when creating a constraint is confusing unless you understand the implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely right, this was copy-pasted from Index.__init__()
.
.. module:: django.db.models.constraints | ||
|
||
.. currentmodule:: django.db.models | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this file should be renamed to constraints.txt
and the title changed to Constraints reference
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
constraint to enforce. | ||
|
||
For example ``UniqueConstraint(fields=['room', 'date'], name='unique_location')`` | ||
ensures only a room location can exist for each ``date``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"only one" reads slightly better I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this docs is quite rough so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ian-Foote
My instinct on the Index vs Constraint question is that a Constraint is probably more intuitive and discoverable.
That's how I felt as well. My main concern what that there's so such thing as partial unique constraints.
From PostgreSQL docs
A uniqueness restriction covering only some rows cannot be written as a unique constraint, but it is possible to enforce such a restriction by creating a unique partial index.
So it'd probably be less effort to add Index(unique=True)
support once #10140 lands instead because such constraints would have to be created using the CREATE INDEX
syntax anyway.
constraint to enforce. | ||
|
||
For example ``UniqueConstraint(fields=['room', 'date'], name='unique_location')`` | ||
ensures only a room location can exist for each ``date``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this docs is quite rough so far.
.. module:: django.db.models.constraints | ||
|
||
.. currentmodule:: django.db.models | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
class UniqueConstraint(BaseConstraint): | ||
def __init__(self, *, fields, name): | ||
if not fields: | ||
raise ValueError('At least one field is required to define an index.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely right, this was copy-pasted from Index.__init__()
.
I don't think we should let the implementation details of partial unique constraints on postgres prevent us choosing the more intuitive api in Django for full unique constraints. When we have support for partial unique indexes, we can add a note to the constraints documentation explaining the workaround. |
model._meta.get_field(field_name).column for field_name in self.fields | ||
) | ||
return schema_editor.sql_unique_constraint % { | ||
'columns': ', '.join(map(schema_editor.quote_name, columns)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you could turn columns
into a list comprehension that uses schema_editor.quote_name
and get rid of the map()
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@orf I wouln't mind switching to a generator expression if deemed necessary, I just found the former shorter then
', '.join(schema_editor.quote_name(c) for c in columns)
Closing in favor of #10337. |
https://code.djangoproject.com/ticket/29641
/cc @atombrella @Ian-Foote