2015-11-19 5 views
0

Я хочу, чтобы некоторые из моих моделей Django имели свойство «owner». Возможно, мне придется изменить или увеличить логику позже, и логика будет использоваться повторно во многих классах. Поэтому я хотел бы просто наследовать класс Owned, который позволяет мне хранить пользователя, создавшего класс. Я еще не пытаюсь заполнить поле, мне просто нужно, чтобы он существовал.Подкласс Django ModelBase (метакласс для моделей Django)

Сначала я попытался это:

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 Reverse Accessor Clashes

Похоже, что этот собственности «владелец» должен иметь другое «related_name» в подклассах класса Owned.

Так что я попытался это:

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' 

Что я делаю неправильно?

ответ

2

На самом деле, django решает точно Ваша проблема (наличия внешнего ключа с родственным именем для абстрактного класса)! Пожалуйста, проверьте документы @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 

Смежные вопросы