2017-02-16 1 views
1

Ich habe ein abstraktes Modell wie folgt aus:Modell ForeignKey Referenz definiert in abstrakten Django Modell

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

    class Meta: 
     abstract = True 

und ich möchte die TARGET_MODEL in jeder Unterklasse unterschiedlich sein machen. Zum Beispiel kann ein Modell LikeForPost, die das Post Modell aus der blog Anwendung verweist:

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

Es scheint nicht zu funktionieren, da die TARGET_MODEL nicht aus der Unterklasse instanziiert wird. Was wäre der richtige Weg, dies zu erreichen?

Ich weiß, dass ich das gesamte target Feld in der LikeForPost Klasse neu definieren kann, aber ich hoffe, es gibt eine elegantere Lösung, die nur den Modellnamen überschreiben kann.

Antwort

0

Sieht aus wie ich eine Lösung gefunden habe, die auf einer Klassenmethode der abstrakten Klasse basiert, die das richtige Feld über die Methode contribute_to_class erstellt und durch das Signal class_prepared initiiert wird.

Es wurde durch den folgenden Artikel inspiriert: Django Model Field Injection, und in einigen Fällen kann der Artikel Django application import and missed class_prepared signals nützlich sein, aber in meinen besonderen Fällen war es nicht relevant.

So ist die Lösung, die eine Methode in der abstrakten Klasse zu definieren, die das richtige Feld schaffen wird:

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 

Die Unterklasse unberührt gelassen werden kann:

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

Um es funktioniert Die on_class_prepared()-Funktion muss aufgerufen werden, nachdem die LikeForPost-Klasse erstellt wurde, die erreicht werden kann, indem sie an das class_prepared-Signal von Django angeschlossen wird. Der beste Platz, um es zu setzen, nach der documentation, ist in der AppConfig.__init__() Methode. Also haben wir eine Anwendung auswählen, die für diese Einrichtung zuständig sein sollte, in meinem Fall ist es blog, und fügen Sie den folgenden Code zu 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) 

Und diese Konfiguration standardmäßig zu aktivieren, konfigurieren Sie es in blog/__init__.py:

default_app_config = 'blog.apps.BlogConfig' 

Diese Lösung in Django getestet 1,10, und verhält sich in ein normalerweise definierten fest codierten Feld identisch, wenn ein Server, Tests des Modells ausgeführt wird, und wenn die Vorbereitung Migrationen mit manage.py makemigrations.