Django polymorphic models with unique_together
Asked Answered
D

2

13

Lets say I have these this base model:

class Trackable(PolymorphicModel):
    uuid = UUIDField(unique=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

And a child model extends it:

class Like(Trackable):
    content = models.ForeignKey(Content, related_name='likes')

    class Meta:
        unique_together = ['content', 'created_by']

When I run migration, it complains about:

django.db.models.fields.FieldDoesNotExist: Like has no field named u'created_by'
Detention answered 1/3, 2015 at 22:27 Comment(4)
Do you want Trackable to be its own table, related to Like by a foreign key? If not, use abstract=True and your unique_together will work as expected. If so, you won't be able to enforce that constraint with unique_together.Altdorfer
I am not sure if adding that will work properly using github.com/chrisglass/django_polymorphic since I don't see it mentions about using abstract=True in base models.Detention
I glanced at that project and it looks like it's designed for abstract=False inheritance. In which case you're talking about two different tables, making it impossible to use unique_together that way. Note that abstract=True will provide better performance and allow the unique constraint, so think about whether you really need to use multiple tables.Altdorfer
Thanks @KevinChristopherHenry I need to use the ploymorphic model because it casts types for me automatically, also multi-table allows me to get query the base modelDetention
A
6

Here is how I handled this problem. Bear in mind that I use PostGres as my database and I do not know if the same problems occur with other databases (though I guess that they do).

Unique together constraints can only be applied to a single table or view in PostGres. This means that out of the box Django/Django-polymorphic cannot express database-enforced unique constraints on a combination of fields that are in both a parent table and a child table of Django models in an inheritance hierarchy.

If you really want database enforced unique constraints on these fields, you can do one of these two things:

  1. copy any parent-model fields that are involved in the unique constraint into the child's table, and express the unique constraint on the child's fields and the fields copied from the parent, or
  2. create a view on the child that includes both the fields from the parent and those from the child, and express the unique constraint on this view.

You will have to either do this manually, or develop your own framework for inserting/altering/deleting these constraints automatically.

Anuria answered 2/3, 2015 at 10:12 Comment(3)
I believe you can't copy parent-model fields in inheriting models so option 1 doesn't work.Vedis
You can't copy the parent fields such that they have the same fieldname in the child model in Django - but you can copy them to a new fieldname in the child model. I think you can then setup database triggers to take care of keeping the copied fields in sync. The constraint is then expressed on the copied field in the child model as explained above. (I'm not saying this pretty! But it is possible)Anuria
Oh I see the logic there that would indeed work. Ultimately if django_polymorphic was set up to work with abstract base models it would work without hacking it together but there doesn't seem to be any plans to support that.Vedis
T
0
class Trackable(PolymorphicModel):
    uuid = UUIDField(unique=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class Like(Trackable):
    content = models.ForeignKey(Content, related_name='likes')
    copy_created_by = models.ForeignKey(settings.AUTH_USER_MODEL)

    class Meta:
        unique_together = ['content', 'copy_created_by']
    def save(self, *args, **kwargs):
        self.copy_created_by= self.created_by
        super(Like, self).save(*args, **kwargs)
Talmudist answered 5/6 at 14:34 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Cohesion

© 2022 - 2024 — McMap. All rights reserved.