子类 Django ModelBase(Django 模型的元类)

Subclass Django ModelBase (metaclass for Django models)

我希望我的某些 Django 模型具有 "owner" 属性。稍后我可能需要更改或扩充逻辑,并且该逻辑会在许多 class 中重复使用。所以我只想继承 Owned class,它让我存储创建 class 的用户。我还没有尝试填充该字段,我只是需要它存在。

首先我尝试了这个:

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

但是当我在几个子classes中从Owned继承时,我得到了一个Django反向访问器错误:Django Reverse Accessor Clashes

看起来这个 "owner" 属性 需要在 Owned class 的子 class 中有一个不同的 "related_name"。

所以我尝试了这个:

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

我的想法是当我 subclass Owned 我会得到一个 owner 属性 与相关名称 *class_name*_owner.

像这样:

Class Subclass(Owned):
    pass

instance = Subclass()

现在,如果这可行,instance.subclassed 将是 Django User 模型的外键,而 related_name 将是 "Subclass_owner."

但是没用。这是错误消息的摘录:

  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'

我做错了什么?

实际上,django 完全 解决了您的问题(具有 related_name 到摘要 class 的外键)!请查看文档@ https://docs.djangoproject.com/en/1.8/topics/db/models/#be-careful-with-related-name

从那里复制答案的完整性:

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.

因此,例如在您的情况下,只需将 Owned 更改为:


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

    class Meta:
        abstract = True