2014-10-15 50 views
7

Mam aplikację Django, która używa Selera do odciążenia niektórych zadań. Głównie, odrzuca obliczenia niektórych pól w tabeli bazy danych.Rozwiązywanie okrągłych importów w selerach i django

Tak, mam 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() 

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

Teraz oczywiście, to nie będzie działać z powodu okrągłego importu (import tasks i modelstasks import models).

Rozwiązałem to na chwilę, dzwoniąc pod numer my_task.delay() z views.py, ale wydaje się, że logika modelu ma sens w klasie modelu. Czy jest lepszy sposób na zrobienie tego?

+0

Utwórz niestandardowy menedżer modeli ModelManager i umieść w osobnym pliku. –

Odpowiedz

3

korzystać z sygnałów.

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

W swoich modelach zamiast importować my_task na początku pliku, możesz go zaimportować tuż przed użyciem. Rozwiąże problem importu okrężnego.

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) 

Alternatywnie możesz zrobić to samo w swoim tasks.py. Możesz importować swoje modele tuż przed użyciem, zamiast zacząć.

Alternatywa:

Można użyć send_task sposób nazwać swoje zadanie

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

To zadziała, ale jest to trochę zapach kodu. –

+1

Nie zgadzam się, że to jest zapach kodu, imo to konieczność. – cerberos

6

Rozwiązanie wysłane przez Jozuego jest bardzo dobra, ale kiedy pierwszy raz spróbowałem, stwierdziliśmy, że moje @receiver dekoratorzy nie miał wpływu. Wynikało to z tego, że moduł tasks nie został zaimportowany nigdzie, co było oczekiwane, ponieważ użyłem task auto-discovery.

Istnieje jednak inny sposób oddzielenia tasks.py od modules.py. Mianowicie, zadania mogą być wysyłane po imieniu i nie muszą być oceniane (importowane) w procesie, który je wysyła:

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() to metoda na obiektach Seler aplikacji.

W tym rozwiązaniu ważne jest, aby dla swoich zadań było take care of correct, predictable names.

5

Wystarczy rzucić jeszcze jedno nie świetne rozwiązanie na tę listę, a ja na tym polegałem polegając na django's now-built-in app registry.

Tak więc w przypadku tasks.py zamiast importować z modeli, można użyć apps.get_model(), aby uzyskać dostęp do modelu.

zrobić to z metody pomocnika z zdrowe trochę dokumentacji tylko wyrazić, dlaczego to jest bolesne:

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) 

, a następnie:

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

Można było na pewno wystarczy użyć apps.get_model() bezpośrednio chociaż .

+0

Podoba mi się to rozwiązanie. Jeśli się nie mylę, funkcja AppConfig django została celowo dodana do tego typu przypadków, w których nie można (z jakiegoś powodu) załadować niektórych modeli django. – nemesisdesign

+0

Myślę, że jest to najlepszy sposób na zrobienie tego! Dziękuje bardzo! – Mettek