开发者

Error using a base class field in subclass unique_together meta option

Using the following code:

class Organization(models.Model):
    name = models.CharField(max_length="100",)
    alias = models.SlugField()
    ...

class Division(Organization):
    parent_org = models.ForeignKey(Organization)

    class Meta:
        unique_together=['parent_org', 'alias']
        ...

Trying to syncdb give me this error:

Error: One or more models did not validate:
organizations.开发者_StackOverflowdivision: "unique_together" refers to alias. This is not in the 
same model as the unique_together statement.

Any help is appreciated,

Thanks,

Eric


This is by design. Reading the documentation for the unique_together option, it states that:

It's used in the Django admin and is enforced at the database level.

If you look at the table that a subclass creates, you'll see that it doesn't actually have the fields that its parent has. Instead, it gets a soft Foreign Key to the parent table with a field name called [field]_ptr_id, where [field] is the name of the table you're inheriting from excluding the app name. So your division table has a Primary Foreign Key called organization_ptr_id.

Now because unique_together is enforced at the database level using the UNIQUE constraint, there's no way that I know of for the database to actually apply that to a field not in the table.

Your best bet is probably through using Validators at your business-logic level, or re-thinking your database schema to support the constraint.

Edit: As Manoj pointed out, you could also try using Model Validators such as validate_unique.


[Model] Validators would work for you. Perhaps simplest, though, would be to use:

class BaseOrganization(models.Model):
    name = models.CharField(max_length="100",)
    alias = models.SlugField()
    class Meta:
        abstract = True

class Organization(BaseOrganization):
    pass

class Division(BaseOrganization):
    parent_org = models.ForeignKey(Organization)

    class Meta:
        unique_together=['parent_org', 'alias']

Note: as with your current code, you could not have subdivisions of divisions.


This is a solution I recently used in Django 1.6 (thanks to Manoj Govindan for the idea):

class Organization(models.Model):
    name = models.CharField(max_length="100",)
    alias = models.SlugField()
    ...

class Division(Organization):
    parent_org = models.ForeignKey(Organization)

    # override Model.validate_unique
    def validate_unique(self, exclude=None):     
        # these next 5 lines are directly from the Model.validate_unique source code
        unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
        errors = self._perform_unique_checks(unique_checks)
        date_errors = self._perform_date_checks(date_checks)
        for k, v in date_errors.items():
            errors.setdefault(k, []).extend(v)

        # here I get a list of all pairs of parent_org, alias from the database (returned 
        # as a list of tuples) & check for a match, in which case you add a non-field 
        # error to the error list
        pairs = Division.objects.exclude(pk=self.pk).values_list('parent_org', 'alias')
        if (self.parent_org, self.alias) in pairs:
                errors.setdefault(NON_FIELD_ERRORS, []).append('parent_org and alias must be unique')

        # finally you raise the ValidationError that includes all validation errors, 
        # including your new unique constraint
        if errors:
            raise ValidationError(errors)


This does not strictly apply the question but is very closely related; unique_together will work if the base class is abstract. You can mark abstract model classes as such using:

class Meta():
    abstract = True

This will prevent django from creating a table for the class, and its fields will be directly included in any subclasses. In this situation, unique_together is possible because all fields are in the same table.

https://docs.djangoproject.com/en/1.5/topics/db/models/#abstract-base-classes


I had similar challenge when trying to apply unique_together to a permission group created by a given client.

class ClientGroup(Group):
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    is_active = models.BooleanField()

    class Meta():
        # looking at the db i found a field called group_ptr_id.
        # adding group_ptr did solve the problem.
        unique_together = ('group_ptr', 'client', 'is_active')
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜