0

I want some of my Django models to have an "owner" property. I may need to change or augment the logic later, and the logic is reused across many classes. So I'd like to just inherit from an Owned class that lets me store the user who created the class. I'm not trying to populate the field yet, I just need it to exist.

First I tried this:

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

class Owned(models.Model):
    owner = models.ForeignKey(User, related_name='owner')

    class Meta:
        abstract = True

But when I inherited from Owned in several subclasses, I got a Django reverse accessor error: Django Reverse Accessor Clashes

It looks like this "owner" property needs to have a different "related_name" in subclasses of the Owned class.

So I tried this:

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


class _OwnedMeta(ModelBase):
    '''
    Should makes "Owned" class below work.
    Gets around problem with reverse accessor clashes:
    '''
    def __init__(cls, name, bases, dct):
        related_name = '{}_owner'.format(name)
        dct['owner'] = models.ForeignKey(User, related_name=related_name)

        super(_OwnedMeta, cls).__init__(name, bases, dct)


class Owned(models.Model):
    '''
    Instances get an "owner" attribute
    that is a foreign key to '<class_name>_owner'
    '''
    __metaclass__ = _OwnedMeta
    owner = models.ForeignKey(User, related_name='owner')

    class Meta:
        abstract = True

The idea is that when I subclass Owned I'll get an owner property with related name *class_name*_owner.

Like this:

Class Subclass(Owned):
    pass

instance = Subclass()

and now, if this worked, instance.subclassed would be a foreign key to the Django User model and the related_name would be "Subclass_owner."

But it doesn't work. This is an excerpt of the error message:

  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/base.py", line 297, in add_to_class
    value.contribute_to_class(cls, name)
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1588, in contribute_to_class
    super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 272, in contribute_to_class
    add_lazy_relation(cls, self, other, resolve_related_class)
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 84, in add_lazy_relation
    operation(field, model, cls)
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 271, in resolve_related_class
    field.do_related_class(model, cls)
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 307, in do_related_class
    self.set_attributes_from_rel()
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 304, in set_attributes_from_rel
    self.rel.set_field_name()
  File "/Users/maxwellheiber/dev/dc/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1259, in set_field_name
    self.field_name = self.field_name or self.to._meta.pk.name
AttributeError: 'NoneType' object has no attribute 'name'

What am I doing wrong?

Community
  • 1
  • 1
Max Heiber
  • 14,346
  • 12
  • 59
  • 97

1 Answers1

2

Actually, django solves exactly your problem (of having a foreign key with a related_name to an abstract class)! Please check the docs @ https://docs.djangoproject.com/en/1.8/topics/db/models/#be-careful-with-related-name

Copying from there for answer completeness:

If you are using the related_name attribute on a ForeignKey or ManyToManyField, you must always specify a unique reverse name for the field. This would normally cause a problem in abstract base classes, since the fields on this class are included into each of the child classes, with exactly the same values for the attributes (including related_name) each time.

To work around this problem, when you are using related_name in an abstract base class (only), part of the name should contain '%(app_label)s' and '%(class)s'.

'%(class)s' is replaced by the lower-cased name of the child class that the field is used in. '%(app_label)s' is replaced by the lower-cased name of the app the child class is contained within. Each installed application name must be unique and the model class names within each app must also be unique, therefore the resulting name will end up being different.

So, for example in your case, just change Owned to:


class Owned(models.Model):
    owner = models.ForeignKey(User, related_name='%(app_label)s_%(class)s_owner')

    class Meta:
        abstract = True

Community
  • 1
  • 1
Serafeim
  • 14,962
  • 14
  • 91
  • 133