Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when inheriting an abstract class with foreign keys

Tags:

python

django

I have the below code written in django

from django.db import models
from django.contrib.auth.models import User

class AuditColumns(models.Model):
    created_at=models.DateField("Created at")
    created_by=models.ForeignKey(User, db_column="created_by", related_name="poll_user_created_by")
    updated_at=models.DateTimeField("Updated at")
    updated_by=models.ForeignKey(User, db_column="updated_by", null=True, related_name="poll_user_updated_by")
    class Meta:
        abstract = True


class Poll(AuditColumns):
    question=models.CharField(max_length=300)
    start_poll_at=models.DateTimeField(null=True)
    end_poll_at=models.DateTimeField(null=True)
    is_active=models.BooleanField(default=True)


class Choice(AuditColumns):
    choice=models.CharField(max_length=200)

when i execute this code am getting the following error

mo@debian:~/PycharmProjects/KlamTam$ ./manage.py sql polls
Error: One or more models did not validate:
polls.poll: Accessor for field 'created_by' clashes with related field 'User.poll_user_created_by'. Add a related_name argument to the definition for 'created_by'.
polls.poll: Reverse query name for field 'created_by' clashes with related field 'User.poll_user_created_by'. Add a related_name argument to the definition for 'created_by'.
polls.poll: Accessor for field 'updated_by' clashes with related field 'User.poll_user_updated_by'. Add a related_name argument to the definition for 'updated_by'.
polls.poll: Reverse query name for field 'updated_by' clashes with related field 'User.poll_user_updated_by'. Add a related_name argument to the definition for 'updated_by'.
polls.choice: Accessor for field 'created_by' clashes with related field 'User.poll_user_created_by'. Add a related_name argument to the definition for 'created_by'.
polls.choice: Reverse query name for field 'created_by' clashes with related field 'User.poll_user_created_by'. Add a related_name argument to the definition for 'created_by'.
polls.choice: Accessor for field 'updated_by' clashes with related field 'User.poll_user_updated_by'. Add a related_name argument to the definition for 'updated_by'.
polls.choice: Reverse query name for field 'updated_by' clashes with related field 'User.poll_user_updated_by'. Add a related_name argument to the definition for 'updated_by'.

this has to do with setting up a unique related_name, is there a way to dynamically generate related_names to make sure they always unique? off course it would be better to know what is related_name for and if possible to avoid setting them?

Regards,

like image 934
Mo J. Mughrabi Avatar asked Feb 25 '23 13:02

Mo J. Mughrabi


1 Answers

Related names are provided by the Django ORM to enable you to easily find all models that key into the current model. So if you have a key from a Foo to a Bar, the instance of Bar will have, by default, an attribute called foo_set that would give you a list of all Foos that are linked to that specific Bar.

You can specify your own related_name to make your code easier to read back, with more natural/appropriate related_names.

However, as you're seeing, Django's ORM requires unique related_name values, and because you've got FKs in your base class, it's automatically creating those FKs for all child models and using the same name for them, which - clearly - is not viable.

Good news is, this note in the documentation shows a workaround for Django 1.2+

If, for some reason, you're having to use Django 1.1 or earlier, here's an alternative workaround: Instead of using FKs on the abstract model, just use a field to store the pk for the keyed model, and add accessors accordingly. eg:

class AuditBase(models.Model):
    created_at = models.DateTimeField("Created at", auto_now_add=True)
    created_by = models.IntegerField(required=True)
    updated_at = models.DateTimeField("Updated at", auto_now=True)
    updated_by = models.IntegerField(required=True)

    class Meta:
        abstract = True

@property
def creator(self):
    return User.objects.get(id=self.created_by) 

@property
def last_editor(self):
    return User.objects.get(id=self.updated_by)

def save(self, *args, **kwargs):
    #track the creator/last editor via an optional kwarg
    active_user = self.kwargs.get('user')
    if active_user:
       self.updated_by = active_user
    if active_user and not self.created_by:
       self.created_by = active_user

    return super(AuditBase, self).save(*args, **kwargs)
like image 153
Steve Jalim Avatar answered Feb 28 '23 04:02

Steve Jalim