I understand how to break up models, and I understand why circular module dependencies blow things up, but I've run across a problem where breaking up a model into separate files appears to be causing circular dependencies. Here's an exerpt from the code, and I'll follow it up with the traceback from the failing process:
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)
From what I can see, there's no explicit circular recursion here, save for the required references in __init__.py
which appears to be where things are blowing up in my code. Here's the traceback:
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
All I'm trying to do here is import that Elearning
model, which is a subclass of CompetencyProduct
, and in turn, Product
. However, because Product
comes from a break-up of the larger ecom/models.py
, the ecom/__init__.py
file contains the obligatory import of all of the broken-out models, including Permission
which has to import Course
which requires CompetencyProduct
.
The wacky thing is that the whole site works pefectly. Logins, purchases, everything. This problem only occurs when I'm trying to run celery in the background and a new task is loaded or I try to run a shell script using the Django environment.
Is my only option here to remove Permission
from the ecom
app, or there a better, smarter way to handle this? Additionally, any comments on how I've laid out the project in general are appreciated.
Circular dependencies are imports in different Python modules from each other. You should never cross-import from the different models.py files, because that causes serious stability issues. Instead, if you have interdependencies, you should use the actions described in this recipe.
If the error occurs due to a circular dependency, it can be resolved by moving the imported classes to a third file and importing them from this file. If the error occurs due to a misspelled name, the name of the class in the Python file should be verified and corrected.
Python Circular Imports A circular import occurs when two or more modules depend on each other. In this example, m2.py depends on m1.py and m1.py depends on m2.py . module dependency (Created by. Xiaoxu Gao. ) # m1.py.
Your problem is that Permission
imports Product
, but both are imported in ecom/models/__init__.py
. You should find a way to either have these two models in the same file or separate them into two apps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With