Opened 14 years ago
Last modified 6 years ago
#16732 assigned Bug
Unable to have abstract model with unique_together
| Reported by: | Mitar | Owned by: | Can Sarıgöl |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | mmitar@…, Giacomo Graziosi, direx | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | yes | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
I have such model definition:
class SlugVersion(models.Model):
class Meta:
abstract = True
unique_together = (('slug', 'version'),)
slug = models.CharField(db_index=True, max_length=10, editable=False)
version = models.IntegerField(db_index=True, editable=False)
class Base(SlugVersion):
name = models.CharField(max_length=10)
class Test(Base):
test = models.IntegerField()
And I am getting:
Error: One or more models did not validate: test.test: "unique_together" refers to slug. This is not in the same model as the unique_together statement. test.test: "unique_together" refers to version. This is not in the same model as the unique_together statement.
I see this is as a bug. Why there could not be a table for class Base with unique constraints on its slug and version fields, and then 1-1 relationship with additional table for model Test with field test.
Change History (10)
comment:1 by , 14 years ago
comment:2 by , 14 years ago
Using such metaclass for Base class above solves the problem:
class ModelBaseFix(models.base.ModelBase):
def __new__(cls, name, bases, attrs):
# We simply remove all unique_together values which we find in a parent
new_class = super(ModelBaseFix, cls).__new__(cls, name, bases, attrs)
unique_together = list(new_class._meta.unique_together)
for parent_class in new_class._meta.parents.keys():
for parent_unique_together in parent_class._meta.unique_together:
try:
unique_together.remove(parent_unique_together)
except ValueError:
pass
new_class._meta.unique_together = tuple(unique_together)
return new_class
comment:3 by , 14 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
If I add this code below the models definition in the original report:
print SlugVersion._meta.abstract, SlugVersion._meta.unique_together print Base._meta.abstract, Base._meta.unique_together print Test._meta.abstract, Test._meta.unique_together
I get:
% ./manage.py validate
True (('slug', 'version'),)
False (('slug', 'version'),)
False (('slug', 'version'),)
Error: One or more models did not validate:
selftest.test: "unique_together" refers to slug. This is not in the same model as the unique_together statement.
selftest.test: "unique_together" refers to version. This is not in the same model as the unique_together statement.
This shows that Test inherits the Meta of Base, which is wrong, because Base isn't abstract.
comment:4 by , 12 years ago
Would this ever be fixed?
I have been avoiding the use of abstract models because of this and it prevents the model declarations from being DRY...
comment:5 by , 11 years ago
| Cc: | added |
|---|
comment:6 by , 11 years ago
| Cc: | added |
|---|
comment:7 by , 6 years ago
| Has patch: | set |
|---|---|
| Owner: | changed from to |
| Status: | new → assigned |
| Version: | 1.3 → master |
PR
When I read the document about meta-inheritance, I didn't see the topic that in abstract = False case What happens to child meta? as far as I could see from code, ordering and get_latest_by always moving in child meta in every situation. Should we change the code like my PR + document about {{{ abstract = False }} and fix the blown up test?
comment:8 by , 6 years ago
| Patch needs improvement: | set |
|---|
Topic for abstract = False is not a part of "Abstract base classes" documentation (see meta-and-multi-table-inheritance). Falling tests show that it is not a proper solution. ('suffix1', 'suffix2') should be inherited from an abstract class BookXtra. I'm afraid that proposed change is strongly backward incompatible.
comment:9 by , 6 years ago
| Patch needs improvement: | unset |
|---|
I got your backward-incompatible concern. I added a case for classes that have more than one inherited classes and abstract at least one. this can solve BookXtra test.
comment:10 by , 6 years ago
| Needs tests: | set |
|---|
But this does work:
class SlugVersion(models.Model): class Meta: abstract = True unique_together = (('slug', 'version'),) slug = models.CharField(db_index=True, max_length=10, editable=False) version = models.IntegerField(db_index=True, editable=False) class Base(SlugVersion): name = models.CharField(max_length=10) class Test(Base): class Meta: unique_together = () test = models.IntegerField()And also creates tables as wanted.