Циклические зависимости модулей в Python / Django с разделенными моделями

#python #django

#python #django

Вопрос:

Я понимаю, как разбивать модели, и я понимаю, почему циклические зависимости модулей все портят, но я столкнулся с проблемой, когда разбиение модели на отдельные файлы, по-видимому, вызывает циклические зависимости. Вот выдержка из кода, и я добавлю к ней обратную трассировку из сбоящего процесса:

elearning/tasks.py

 from celery.task import task

@task
def decompress(pk):
    from elearning.models import Elearning
    Elearning.objects.get(pk=pk).decompress()
  

elearning/models.py

 from competency.models import CompetencyProduct
from core.helpers import ugc_elearning
from elearning.fields import ArchiveFileField

class Elearning(CompetencyProduct):

    archive = ArchiveFileField(upload_to=ugc_elearning)

    def decompress(self):

        import zipfile

        src = self.archive.path
        dst = src.replace(".zip","")

        print "Decompressing %s to %s" % (src, dst)

        zipfile.ZipFile(src).extractall(dst)
  

ecom/models/products.py

 from django.db import models
from django.utils.translation import ugettext_lazy as _

from core.models import Slugable, Unique
from django_factory.models import Factory
from core.helpers import ugc_photos

class Product(Slugable, Unique, Factory):

    photo          = models.ImageField(upload_to=ugc_photos, width_field="photo_width", height_field="photo_height", blank=True)
    photo_width    = models.PositiveIntegerField(blank=True, null=True, default=0)
    photo_height   = models.PositiveIntegerField(blank=True, null=True, default=0)
    description    = models.TextField()
    price          = models.DecimalField(max_digits=16, decimal_places=2)
    created        = models.DateTimeField(auto_now_add=True)
    modified       = models.DateTimeField(auto_now=True)
  

ecom/models/__init__.py

 from django.contrib.auth.models import User
from django.db import models

from ecom.models.products import Product, Credit, Subscription
from ecom.models.permissions import Permission
from ecom.models.transactions import Transaction, DebitTransaction, CreditTransaction, AwardTransaction, FinancialTransaction, PermissionTransaction, BundleTransaction
  

competency/models.py

 from django.db import models
from django.utils.translation import ugettext_lazy as _

from core.models import Slugable, Unique
from ecom.models import Product
from rating.models import Rated
from trainer.models import Trainer

class Competency(Slugable, Unique):

    class Meta:
        verbose_name = _("Competency")
        verbose_name_plural = _("Competencies")

    description = models.TextField()



class CompetencyProduct(Product, Rated):

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    release  = models.DateField(auto_now_add=True)

    trainers     = models.ManyToManyField(Trainer)
    competencies = models.ManyToManyField(Competency, related_name="provided_by")
    requirements = models.ManyToManyField(Competency, related_name="required_for", blank=True, null=True)
    forsale      = models.BooleanField("For Sale", default=True)
  

ecom/models/permissions.py

 from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import ugettext_lazy as _

from treebeard.mp_tree import MP_Node

from collective.models import Collective
from course.models import Course
from ecom.models.products import Product

class Permission(MP_Node):

    class Meta:
        app_label = "ecom"

    product      = models.ForeignKey(Product, related_name="permissions")
    user         = models.ForeignKey(User, related_name="permissions")
    collective   = models.ForeignKey(Collective, null=True)
    course       = models.ForeignKey(Course, null=True)
    redistribute = models.BooleanField(default=False)
    created      = models.DateTimeField(auto_now_add=True)
    modified     = models.DateTimeField(auto_now=True)
    accessed     = models.DateTimeField(auto_now=True)
  

course/models.py

 from django.db import models
from django.utils.translation import ugettext_lazy as _

from competency.models import CompetencyProduct
from ecom.models import Product
from rating.models import Rated

class Chapter(models.Model):
    seq  = models.PositiveIntegerField(name="Sequence", help_text="Chapter number")
    name = models.CharField(max_length=128)
    note = models.CharField(max_length=128)



class Course(Product, Rated):

    level    = models.PositiveIntegerField(choices=CompetencyProduct.LEVELS)
    chapters = models.ManyToManyField(Chapter)



class Bundle(models.Model):

    class Meta:
        unique_together = (("product", "chapter"),)

    product = models.ForeignKey(Product, related_name="bundles")
    chapter = models.ForeignKey(Chapter, related_name="bundles")
    amount  = models.PositiveIntegerField()
    seq     = models.PositiveIntegerField(name="Sequence", default=1)
  

Из того, что я вижу, здесь нет явной циклической рекурсии, за исключением требуемых ссылок в __init__.py , которые, похоже, являются тем местом, где в моем коде происходят сбои. Вот обратная трассировка:

   File "/path/to/project/virtualenv/lib/python2.6/site-packages/celery/execute/trace.py", line 47, in trace
    return cls(states.SUCCESS, retval=fun(*args, **kwargs))
  File "/path/to/project/virtualenv/lib/python2.6/site-packages/celery/app/task/__init__.py", line 247, in __call__
    return self.run(*args, **kwargs)
  File "/path/to/project/virtualenv/lib/python2.6/site-packages/celery/app/__init__.py", line 175, in run
    return fun(*args, **kwargs)
  File "/path/to/project/django/myproj/elearning/tasks.py", line 5, in decompress
    from elearning.models import Elearning
  File "/path/to/project/django/myproj/elearning/models.py", line 2, in <module>
    from competency.models import CompetencyProduct
  File "/path/to/project/django/myproj/competency/models.py", line 5, in <module>
    from ecom.models import Product
  File "/path/to/project/django/myproj/ecom/models/__init__.py", line 5, in <module>
    from ecom.models.permissions import Permission
  File "/path/to/project/django/myproj/ecom/models/permissions.py", line 8, in <module>
    from course.models import Course
  File "/path/to/project/django/myproj/course/models.py", line 4, in <module>
    from competency.models import CompetencyProduct
ImportError: cannot import name CompetencyProduct
  

Все, что я пытаюсь здесь сделать, это импортировать эту Elearning модель, которая является подклассом CompetencyProduct , и, в свою очередь, Product . Однако, поскольку Product происходит из-за разделения более крупных моделей, ecom/models.py файл ecom/__init__.py содержит обязательный импорт всех разделенных моделей, включая Permission которые должны импортировать, Course что требует CompetencyProduct .

Самое странное, что весь сайт работает идеально. Входы в систему, покупки, все. Эта проблема возникает только тогда, когда я пытаюсь запустить celery в фоновом режиме и загружается новая задача, или я пытаюсь запустить сценарий оболочки, используя среду Django.

Мой единственный вариант здесь — удалить Permission из ecom приложения, или есть лучший, более разумный способ справиться с этим? Кроме того, приветствуются любые комментарии о том, как я изложил проект в целом.

Комментарии:

1. Зачем утруждать себя приведением упрощенного примера, который не иллюстрирует проблему? Вы правы; там нет циклической зависимости, но она есть в вашем реальном коде. Публикуя свой фактический код, вы можете отредактировать все, что не применимо, но было бы полезно увидеть, как ваши фактические файлы и модели размещены в вашем проекте.

2. Вы правы. Сейчас я это изменил.

Ответ №1:

Ваша проблема в том, что Permission импортируется Product , но оба импортируются в ecom/models/__init__.py . Вы должны найти способ либо поместить эти две модели в один файл, либо разделить их на два приложения.

Комментарии:

1. Дело в том, что мне на самом деле не нужны эти модели в __init__.py , кроме как для syncdb, чтобы найти модели и установить их. Думаю, я надеялся найти способ избежать помещения этих импортированных данных в этот файл в первую очередь.

2. Невозможно. Это единственный способ разделить ваши модели на отдельные файлы с помощью Django. Эти импорта должны быть в __init__.py , поэтому, по сути, вы не можете выполнять перекрестный импорт в одном приложении. Если две модели в одном приложении нуждаются друг в друге, они должны находиться в одном файле.

3. Безумно расстраивает, если на самом деле нет обходного пути. Почти все мои классы перекрестно зависят друг от друга..