2014-10-15 11 views
7

Ich habe eine Django App, die Sellerie verwendet, um einige Aufgaben zu entladen. Hauptsächlich wird die Berechnung einiger Felder in einer Datenbanktabelle verschoben.Resolving kreisförmigen Einfuhren in Sellerie und Django

Also, ich habe eine tasks.py:

from models import MyModel 
from celery import shared_task 

@shared_task 
def my_task(id): 
    qs = MyModel.objects.filter(some_field=id) 
    for record in qs: 
     my_value = #do some computations 
     record.my_field = my_value 
     record.save() 

Und in models.py

from django.db import models 
from tasks import my_task 

class MyModel(models.Model): 
     field1 = models.IntegerField() 
     #more fields 
     my_field = models.FloatField(null=True) 

     @staticmethod 
     def load_from_file(file): 
      #parse file, set fields from file 
      my_task.delay(id) 

Jetzt offensichtlich, dies wird nicht wegen eines Kreis Import arbeiten (models Importe tasks und tasks Importe models).

Ich habe dies im Moment gelöst, indem ich my_task.delay() von views.py aufrufen, aber es scheint sinnvoll zu sein, die Modelllogik innerhalb der Modellklasse zu behalten. Gibt es einen besseren Weg, dies zu tun?

+0

Erstellen Sie einen benutzerdefinierten ModelManager und fügen Sie eine separate Datei ein. –

Antwort

3

Verwenden Signale zu rufen.

tasks.py

from models import MyModel, my_signal 
from celery import shared_task 
from django.dispatch import receiver 

@shared_task 
def my_task(id): 
    qs = MyModel.objects.filter(some_field=id) 
    for record in qs: 
     my_value = #do some computations 
     record.my_field = my_value 
     record.save() 

@receiver(my_signal) 
def my_receiver(sender, **kwargs): 
    my_task.delay(kwargs['id']) 

models.py

from django.db import models 
from tasks import my_task 
from django.dispatch import Signal 

my_signal = Signal(providing_args=['id']) 

class MyModel(models.Model): 
     field1 = models.IntegerField() 
     #more fields 
     my_field = models.FloatField(null=True) 

     @staticmethod 
     def load_from_file(file): 
      #parse file, set fields from file 
      my_signal.send(sender=?, id=?) 
5

In Ihren Modellen, anstatt die Datei my_task am Anfang der Datei zu importieren, können Sie sie importieren, bevor Sie sie verwenden. Es wird das Problem der zirkulären Importe lösen.

from django.db import models 

class MyModel(models.Model): 
     field1 = models.IntegerField() 
     #more fields 
     my_field = models.FloatField(null=True) 

     @staticmethod 
     def load_from_file(file): 
      #parse file, set fields from file 
      from tasks import my_task # import here instead of top 
      my_task.delay(id) 

Alternativ können Sie auch das gleiche in Ihrem tasks.py tun. Sie können Ihre Modelle direkt vor der Verwendung importieren, anstatt sie zu starten.

Alternative:

Sie send_task Methode verwenden, können Sie Ihre Aufgabe

from celery import current_app 
from django.db import models 

class MyModel(models.Model): 
     field1 = models.IntegerField() 
     #more fields 
     my_field = models.FloatField(null=True) 

     @staticmethod 
     def load_from_file(file): 
      #parse file, set fields from file 
      current_app.send_task('myapp.tasks.my_task', (id,)) 
+1

Das wird funktionieren, aber es ist ein bisschen ein Code-Geruch. –

+1

Ich stimme nicht zu, dass dies ein Code-Geruch ist, es ist eine Notwendigkeit. – cerberos

6

Die Lösung von joshua geschrieben ist sehr gut, aber wenn ich es zuerst versucht, fand ich, dass meine @receiver Dekorateure keine Wirkung hatte. Das war, weil das tasks Modul nicht irgendwo importiert wurde, was erwartet wurde, wie ich task auto-discovery verwendete.

Es gibt jedoch eine andere Möglichkeit, tasks.py von modules.py zu entkoppeln. Es kann nämlich Aufgaben mit Namen geschickt und sie haben nicht in den Prozess ausgewertet (importiert) werden, die sie sendet:

from django.db import models 
#from tasks import my_task 
import celery 

class MyModel(models.Model): 
    field1 = models.IntegerField() 
    #more fields 
    my_field = models.FloatField(null=True) 

    @staticmethod 
    def load_from_file(file): 
     #parse file, set fields from file 
     #my_task.delay(id) 
     celery.current_app.send_task('myapp.tasks.my_task', (id,)) 

send_task() ist ein Verfahren auf Sellerie App-Objekte.

In dieser Lösung ist es wichtig, take care of correct, predictable names für Ihre Aufgaben.

5

Um nur eine weitere nicht-großartige Lösung in diese Liste zu werfen, was ich am Ende getan habe, ist auf django's now-built-in app registry verlassen.

So in tasks.py, anstatt von Modellen importieren, verwenden Sie apps.get_model(), um Zugriff auf das Modell zu erhalten.

Ich tue dies mit einer Helfer-Methode mit einem gesund bisschen Dokumentation nur zum Ausdruck bringen, warum dies schmerzhaft ist:

from django.apps import apps 

def _model(model_name): 
    """Generically retrieve a model object. 

    This is a hack around Django/Celery's inherent circular import 
    issues with tasks.py/models.py. In order to keep clean abstractions, we use 
    this to avoid importing from models, introducing a circular import. 

    No solutions for this are good so far (unnecessary signals, inline imports, 
    serializing the whole object, tasks forced to be in model, this), so we 
    use this because at least the annoyance is constrained to tasks. 
    """ 
    return apps.get_model('my_app', model_name) 

Und dann:

@shared_task 
def some_task(post_id): 
    post = _model('Post').objects.get(pk=post_id) 

Sie könnten sicherlich apps.get_model() nur direkt verwenden, obwohl .

+0

Ich mag diese Lösung. Wenn ich nicht falsch liege, wurde die AppConfig-Funktion von django absichtlich für diese Art von Fällen hinzugefügt, in denen Sie (aus dem einen oder anderen Grund) noch nicht einige Django-Modelle laden können. – nemesisdesign

+0

Ich denke, das ist der beste Weg, dies zu tun! Vielen Dank! – Mettek

Verwandte Themen