2014-10-15 16 views
7

Ho un'app Django che usa Celery per scaricare alcune attività. Principalmente, rimuove il calcolo di alcuni campi in una tabella di database.Risoluzione delle importazioni circolari in sedano e django

Così, ho un 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() 

E 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) 

Ora, ovviamente, questo non lavorare a causa di un'importazione circolare (models importazioni tasks e tasks importazioni models).

Ho risolto questo momento chiamando my_task.delay() da views.py, ma sembra avere senso mantenere la logica del modello all'interno della classe del modello. C'è un modo migliore per farlo?

+0

Creare un ModelManager personalizzato e inserire un file separato. –

risposta

3

Utilizzare i segnali.

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

Nei modelli invece di importare my_task all'inizio del file, è possibile importarlo appena prima di utilizzarlo. Risolverà il problema delle importazioni circolari.

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) 

In alternativa, si può anche fare la stessa cosa nella vostra tasks.py. Puoi importare i tuoi modelli appena prima di usarli invece di iniziare.

Alternativa:

È possibile utilizzare send_task metodo da chiamare il vostro compito

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

Funzionerà, ma è un po 'un odore di codice. –

+1

Non sono d'accordo sul fatto che si tratti di un odore di codice, è una necessità. – cerberos

6

La soluzione postato da Giosuè è molto buona, ma quando l'ho provato, ho scoperto che i miei @receiver decoratori hanno avuto alcun effetto. Questo perché il modulo tasks non è stato importato da nessuna parte, il che era previsto poiché ho utilizzato task auto-discovery.

Vi è, tuttavia, un altro modo per disaccoppiare tasks.py da modules.py. Vale a dire, le attività possono essere inviate per nome e non devono essere valutati (importato) nel processo che li invia:

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() è un metodo su oggetti Sedano app.

In questa soluzione è importante take care of correct, predictable names per le attività.

5

Giusto per lanciare un'altra soluzione non eccezionale in questa lista, quello che ho finito è affidarsi a django's now-built-in app registry.

Quindi, in tasks.py, anziché importare dai modelli, è possibile utilizzare apps.get_model() per accedere al modello.

Lo faccio con un metodo di supporto con un po 'sano di documentazione solo per esprimere il motivo per cui questo è doloroso:

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) 

E poi:

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

si potrebbe certamente utilizzare solo apps.get_model() direttamente però .

+0

Mi piace questa soluzione. Se non sbaglio, la funzione AppConfig di django è stata aggiunta appositamente per questo tipo di casi in cui non è possibile (per qualche motivo o altro) caricare ancora alcuni modelli di Django. – nemesisdesign

+0

Penso che questo sia il modo migliore per farlo! Grazie mille! – Mettek

Problemi correlati