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

时间:2015-11-19 03:57:14

标签: django python-2.7 django-models metaclass

我希望我的一些Django模型拥有“所有者”属性。我可能需要稍后更改或扩充逻辑,并且逻辑在许多类中重用。所以我想继承一个允许我存储创建类的用户的Owned类。我还没有尝试填充这个领域,我只需要它存在。

首先我尝试了这个:

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

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

看起来这个“owner”属性需要在Owned类的子类中有一个不同的“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

我的想法是,当我继承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'

我做错了什么?

1 个答案:

答案 0 :(得分:2)

实际上,django解决完全你的问题(将一个带有related_name的外键带到一个抽象类)!请查看文档@ https://docs.djangoproject.com/en/1.8/topics/db/models/#be-careful-with-related-name

从那里复制以获得答案完整性:

  

如果在ForeignKey或ManyToManyField上使用related_name属性,则必须始终为该字段指定唯一的反向名称。这通常会导致抽象基类出现问题,因为此类中的字段包含在每个子类中,每次都具有完全相同的属性值(包括related_name)。

     

要解决此问题,当您在抽象基类(仅)中使用related_name时,名称的一部分应包含'%(app_label)s''%(class)s'

     

'%(class)s'将替换为使用该字段的子类的低级名称。   '%(app_label)s'将替换为子类包含在其中的应用程序的低级名称。每个安装的应用程序名称必须是唯一的,并且每个应用程序中的模型类名称也必须是唯一的,因此结果名称最终会有所不同。

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


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

    class Meta:
        abstract = True