Opened 4 years ago

Last modified 7 months ago

#16732 new Bug

Unable to have abstract model with unique_together

Reported by: mitar Owned by: nobody
Component: Database layer (models, ORM) Version: 1.3
Severity: Normal Keywords:
Cc: mmitar@…, segfault, direx Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no 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 (6)

comment:1 Changed 4 years ago by mitar

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

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.

comment:2 Changed 4 years ago by mitar

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 Changed 4 years ago by aaugustin

  • Triage Stage changed from Unreviewed to 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 Changed 15 months ago by anonymous

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 Changed 7 months ago by segfault

  • Cc segfault added

comment:6 Changed 7 months ago by direx

  • Cc direx added
Note: See TracTickets for help on using tickets.
Back to Top