2017-02-16 4 views
1

У меня есть абстрактные модели, как это:Override ForeignKey эталонной модели, определенной в абстрактной модели Django

class Like(models.Model): 
    TARGET_MODEL = 'TargetModel' 
    user = models.ForeignKey(settings.AUTH_USER_MODEL) 
    target = models.ForeignKey(TARGET_MODEL) 

    class Meta: 
     abstract = True 

, и я хочу сделать TARGET_MODEL быть разными в каждом подклассе. Например, модель LikeForPost, который ссылается на Post модель из blog применения:

class LikeForPost(Like): 
    TARGET_MODEL = 'blog.Post' 

Это, кажется, не работает, так как TARGET_MODEL не инстанцирован из подкласса. Каким будет правильный способ достичь этого?

Я знаю, что могу переопределить все поле target в классе LikeForPost, но я надеюсь, что есть более элегантное решение, которое позволяет переопределить только название модели.

ответ

0

Похоже, я нашел решение на основе метода класса абстрактного класса, который создает правильное поле с помощью метода contribute_to_class и инициируется сигналом class_prepared.

Это было навеяно следующей статьей: Django Model Field Injection, и в некоторых случаях статья Django application import and missed class_prepared signals может быть полезна, но в моих конкретных случаях это не имело значения.

Таким образом, решение состоит в определении метода в абстрактном классе, который будет создавать собственное поле:

class Like(models.Model): 
    TARGET_MODEL = None # will be overridden in subclass 
    user = models.ForeignKey(settings.AUTH_USER_MODEL) 

    @classmethod 
    def on_class_prepared(cls): 
     target_field = models.ForeignKey(cls.TARGET_MODEL) 
     target_field.contribute_to_class(cls, 'target') 

    class Meta: 
     abstract = True 

Подкласс можно оставить нетронутым:

class LikeForPost(Like): 
    TARGET_MODEL = 'blog.Post' 

Чтобы заставить его работать, функция on_class_prepared() должна быть вызвана после создания класса LikeForPost, что может быть достигнуто путем подключения его к сигналу class_prepared Django. Лучшее место для размещения, согласно documentation, находится в методе AppConfig.__init__(). Таким образом, мы выбираем приложение, которое должно отвечать за это установки, в моем случае это blog, и добавьте следующий код blog/apps.py:

from django.apps import AppConfig 
from django.db.models import signals 

def call_on_class_prepared(sender, **kwargs): 
    """Calls the function only if it is defined in the class being prepared""" 
    try: 
     sender.on_class_prepared() 
    except AttributeError: 
     pass 


class BlogConfig(AppConfig): 
    name = 'blog' 

    def __init__(self, app_name, app_module): 
     super(BlogConfig, self).__init__(app_name, app_module) 
     # Connect programmatic class adjustment function to the signal 
     signals.class_prepared.connect(call_on_class_prepared) 

И сделать эту конфигурацию активной по умолчанию, настроить его в blog/__init__.py:

default_app_config = 'blog.apps.BlogConfig' 

Этот раствор был испытан в Django 1.10, и ведет себя идентично нормально определенную область закодированной при запуске сервера, испытание модели, а при подготовке миграции с manage.py makemigrations.

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