Circular module dependencies in Python/Django with split-up models - python

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.

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.

Related

django signals on inherited profile

I have User model and a user can be of type 1 or 2.
Depending on what type of user is created, I want to associate a profile to the model. If type 1, it will be a Person, and type 2 a Company.
I have tried writing the code in models.py and also following the tutorial : https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html
signals/apps/entitiesmodels.py
class CompanyModel(AuditedModel):
name = models.CharField(max_length=64, db_index=True, verbose_name='Name', null=True, blank=True)
class PersonModel(AuditedModel):
name = models.CharField(max_length=64, db_index=True, verbose_name='Name', null=True, blank=True)
class Tester(PersonModel,PersistentModel):
# Link with user
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, related_name='%(class)s_user')
class Company(CompanyModel,PersistentModel):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, related_name='%(class)s_user')
signals/apps/entities/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from . import models as entities_models
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def user_create_profile(sender, instance, created, **kwargs):
if created:
if instance.user_type == 1:
entities_models.Tester.objects.create(user=instance)
elif instance.user_type == 2:
entities_models.Company.objects.create(user=instance)
else:
pass
signals/apps/entities/app.py
from django.apps import AppConfig
class EntitiesConfig(AppConfig):
name ='entities'
def ready(self):
import entities.signals
signals/apps/entities/api_v1/views.py
from signals.apps.entities import models
from . import serializers
from signals.libs.views import APIViewSet
class PersonViewSet(APIViewSet):
queryset = models.Person.objects.all()
serializer_class = serializers.PersonSerializer
signals/apps/entities/api_v1/urls.py
from rest_framework.routers import DefaultRouter
from signals.apps.entities.api_v1 import views
# Create a router and register our viewsets with it.
app_name='entities'
router = DefaultRouter()
router.register(r'persons', views.PersonViewSet, base_name="entities-persons")
urlpatterns = router.urls
settings.py
LOCAL_APPS = (
'signals.apps.authentication',
'signals.apps.entities.apps.EntitiesConfig',
)
When running server, the error is:
File "/home/gonzalo/Playground/signals3/signals/signals/apps/entities/api_v1/urls.py", line 2, in <module>
from signals.apps.entities.api_v1 import views
File "/home/gonzalo/Playground/signals3/signals/signals/apps/entities/api_v1/views.py", line 1, in <module>
from signals.apps.entities import models
File "/home/gonzalo/Playground/signals3/signals/signals/apps/entities/models.py", line 47, in <module>
class Person(PersonModel):
File "/home/gonzalo/.virtualenvs/signals-test/lib/python3.6/site-packages/django/db/models/base.py", line 108, in __new__
"INSTALLED_APPS." % (module, name)
untimeError: Model class signals.apps.entities.models.Person doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS
I have the example code on github if someone wants to check it out : https://github.com/gonzaloamadio/django-signals3
Thank for the answer, when using that way of referring an app on settings, then you should use the import like this:
signals/apps/entities/api_v1/views.py
from signals.apps.entities import models
and in urls.py
from entities.api_v1 import views

Django 2: Can't import models and save it

I somehow can not import my models from another app. I already looked it up and tried but does not work.
events/models.py
class Registration(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
team = models.CharField(max_length=50, blank=True)
date_created = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
payment = Payment.objects.create(registration=self)
super().save(*args, **kwargs)
When I run python manage.py makemigrations I get this.
Error
File "C:\Users\Rubber\ems\events\models.py", line 5, in <module>
from payments.models import Payment
File "C:\Users\Rubber\ems\payments\models.py", line 6, in <module>
from events.models import Registration
ImportError: cannot import name 'Registration'
payments/models.py
import uuid
from django.db import models
from django.utils import timezone
from events.models import Registration # THIS WONT WORK
def _create_uuid():
return uuid.uuid1()
def _get_uuid():
return _create_uuid()
class Payment(models.Model):
uuid = models.CharField(max_length=1, default=_get_uuid)
paid = models.BooleanField(default=False)
registration = models.ForeignKey(Registration, on_delete=models.CASCADE)
What am I doing wrong?
You have a circular import. In this case, you can avoid it by removing the Registration import and using the string instead:
class Payment(models.Model):
registration = models.ForeignKey('events.Registration', on_delete=models.CASCADE)
Another option would be to move the Payment.objects.create() code into a signal. Note that your current save() method creates a Payment every time the registration is saved, which might not be what you want.

avoid Circular import in Django

I have two models Company and Actions:
from companies.models import Company
class Action(models.Model):
company = models.ForeignKey(Company, blank=True, null=True, related_name='activity', on_delete=models.CASCADE)
I have then an utility in utils.py
from .models import Action
def create_action(user, verb, target_name=None, target=None):
action = Action(user=user, verb=verb, target=target)
This utility I called in Company model on def save, so on Company Model I have:
from not.utils import create_action
so Action Model import Company Model as FK, utils import Action Model, and Company Model import utils
Now, because of circular import Django gives an error:
ImportError: cannot import name 'Company'
I saw some q/a here to use import directly (without from) I tried but didn't worked
import not.utils as nt
nt.create_action(...)
Remove the Company import from actions/models.py and use a string instead:
class Action(models.Model):
company = models.ForeignKey('companies.Company', blank=True, null=True, related_name='activity', on_delete=models.CASCADE)

Can't import serializer from other serializer in django rest-framework?

Problem
I have 2 models, leads and notes. I want a lead to be able to have 1 or more notes. I have used a generic foreign key because I want to plan for the future and a note could be assigned to say a person or a meeting for example.
Following the instructions for django rest framework and Rest Framework Generic Relations I am trying to import one serializer from the other to make a reverse relation possible.
Error
I can't import the serializers in both files(call one serializer from the other) because I get:
File "/Users/james/Documents/UtilityCRM-Server/crm/leads/urls.py", line 2, in <module>
from leads import views
File "/Users/james/Documents/UtilityCRM-Server/crm/leads/views.py", line 11, in <module>
from leads.serializers import LeadSerializer
File "/Users/james/Documents/UtilityCRM-Server/crm/leads/serializers.py", line 4, in <module>
from notes.serializers import NoteSerializer
File "/Users/james/Documents/UtilityCRM-Server/crm/notes/serializers.py", line 6, in <module>
from leads.serializers import LeadSerializer
ImportError: cannot import name LeadSerializer
Its weird because if I open the django shell and run the following it lets me import them all:
from leads.serializers import LeadSerializer
from notes.serializers import NotesSerializer
from callbacks.serializers import CallbackSerializer
Any help would be greatly appreciated!
Code
This is my installed app section of my settings file:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd Party Apps
'rest_framework',
'generic_relations',
# My Apps
'leads.apps.LeadsConfig',
'callbacks.apps.CallbacksConfig',
'notes.apps.NotesConfig',
]
notes/models.py
from __future__ import unicode_literals
from django.db import models
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Note(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=100)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
# Relations
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
note_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.title
leads/models.py
from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
from django.utils import timezone
from notes.models import Note
from callbacks.models import Callback
GAS = 'G'
ELECTRICITY = 'E'
LEAD_TYPE_CHOICES = (
(GAS, 'Gas'),
(ELECTRICITY, 'Electricity'),
)
# Create your models here.
class Lead(models.Model):
author = models.ForeignKey('auth.User')
type = models.CharField(
max_length=1,
choices=LEAD_TYPE_CHOICES,
default=GAS,
)
business_registration_number = models.IntegerField(max_length=20)
business_name = models.CharField(max_length=50)
mpan = models.IntegerField(max_length=21)
supplier = models.CharField(max_length=45)
contract_length = models.IntegerField(max_length=2)
contract_start_date = models.DateField()
contract_end_date = models.DateField()
address_line_1 = models.CharField(max_length=45)
address_line_2 = models.CharField(max_length=45)
address_line_3 = models.CharField(max_length=45)
address_city = models.CharField(max_length=45)
address_county = models.CharField(max_length=45)
address_postcode = models.CharField(max_length=10)
contact_title = models.CharField(max_length=45)
contact_first_name = models.CharField(max_length=45)
contact_middle_name = models.CharField(max_length=45)
contact_last_name = models.CharField(max_length=45)
contact_telephone = models.IntegerField(max_length=11)
contact_email = models.EmailField(max_length=60)
created_date = models.DateTimeField(default=timezone.now)
# Relations
assigned_to = models.ForeignKey('auth.User', related_name='+')
#from_batch = models.ForeignKey('data_batch.DataBatch', related_name='+')
#callbacks = GenericRelation(Callback)
notes = GenericRelation(Note)
class Meta:
ordering = ('contract_end_date', 'business_name',)
def __str__(self):
return self.business_name
I have 2 serializers:
leads/serializers.py
from rest_framework import serializers
from leads.models import Lead, LEAD_TYPE_CHOICES
from notes.serializers import NoteSerializer
class LeadSerializer(serializers.ModelSerializer):
notes = NoteSerializer(many=True, read_only=True)
class Meta:
model = Lead
fields = (
'id',
'business_name',
'business_registration_number',
'supplier',
'contract_length',
'contract_start_date',
'notes'
)
notes/serializers.py
from generic_relations.relations import GenericRelatedField
from rest_framework import serializers
from notes.models import Note
from leads.models import Lead
from leads.serializers import LeadSerializer
from callbacks.models import Callback
from callbacks.serializers import CallbackSerializer
class NoteSerializer(serializers.ModelSerializer):
"""
A `Note` serializer with a `GenericRelatedField` mapping all possible
models to their respective serializers.
"""
note_object = GenericRelatedField({
Lead: LeadSerializer(),
Callback: CallbackSerializer()
})
class Meta:
model = Note
fields = (
'id',
'author',
'title',
'text',
'created_date',
'note_object',
)
As I have mentioned previously in a comment, I believe this happens due to circular (cyclic) imports in Python.
This happens particularly when you are declaring related fields in models, and some models have not been instanced yet.
In this case, when you execute your program, it tries to import LeadSerializer, that requires importing NoteSerializer, that requires importing LeadSerializer, that requires importing NoteSerializer... see where this is going?
Your stacktrace says it all:
from leads.serializers import LeadSerializer
from notes.serializers import NoteSerializer
from leads.serializers import LeadSerializer
Generating ImportError: cannot import name LeadSerializer
What I have done to solve this was declaring all the serializers in a single file. Therefore, you have two options:
Move LeadSerializer to notes/serializers.py
Move NoteSerializer to leads/serializers.py
It is not the most elegant way to solve this, but it has done its trick.
The section below provides no further explanation on how to solve this problem, but an observation regarding this issue.
Perhaps Django & DRF could futurely provide methods to avoid this, such as declaring the serializers as
note_object = GenericRelatedField({
Lead: 'leads.serializers'.LeadSerializer,
Callback: CallbackSerializer()
})
or
notes = 'notes.serializers'.NoteSerializer(many=True, read_only=True)
Faced with this and made this thing:
notes = serializers.SerializerMethodField()
def get_notes(self, obj):
from leads.serializers import LeadSerializer
return LeadSerializer(<your_queryset>, many=True/False, read_only=True/False).data
There is an idea to tackle this before I have also got this same error here I will explain to you how I resolve this.
Put your apps inside the project directory
project
-project
-appname1
-models.py
-serilizer.py
-appname2
-models.py
-serilizer.py
-settings.py
in settings.py
INSTALLED_APPS = ['project.appname1', 'project.appname2']
then try to import appname1 serializers into appname2
like this
from project.appname1.serializers import( ArtistSerializer, ArtisTokenSerilizer, ProfessionSerilizer, FollowersSerializer,
FollowingSerializer, ChatMessageSerializer, SendMessageSerializer, ConversationMessageSerializer,
ProjectTypeSerializer)

Django render is very slow

This is my training django project. I am using GeoIP, django-modeltranslation, i18n. Showing video gallery page is very slow. The database contains about 20 entries.
Model
from __future__ import unicode_literals
from django.db import models
from ckeditor_uploader.fields import RichTextUploadingField
from datetime import datetime
import urllib, json, re
from django.utils.translation import ugettext_lazy as _
class Video(models.Model):
class Meta:
abstract = True
title = models.CharField(max_length=80, unique=True)
text = RichTextUploadingField()
link = models.CharField(max_length=80)
slug = models.CharField(db_index=True,max_length=40,blank=True,null=True)
created_date = models.DateTimeField(auto_now_add=True)
pub_date = models.DateTimeField(default=datetime.now())
is_active = models.BooleanField(default=True)
position = models.IntegerField(default=0)
meta = models.TextField(blank=True,null=True)
def __unicode__(self):
return self.title
class VideoMessage(Video):
class Meta:
verbose_name = _('VideoMessage')
verbose_name_plural = _('VideoMessages')
Translation
from modeltranslation.translator import translator, TranslationOptions
from models import VideoMessage
class VideoMessageTranslationOptions(TranslationOptions):
fields = ('text', 'link', 'meta',)
translator.register(VideoMessage, VideoMessageTranslationOptions)
Views
from django.shortcuts import render
from django.views.generic import View
from models import VideoMessage
class Index(View):
def get(self, request):
params={}
messages=VideoMessage.objects.exclude(is_active=False).exclude(link='').order_by('-position')
params['videos']={}
params['videos']['message']=messages
return render(request, 'index.html', params)
gprof2dot tree. 100% = ~2000ms
I parsed the image at each rendering. It was bad idea.
Try to find out what takes the longest time.
You can use something like Opbeat where you can get a free account. You can see a breakdown on what takes time on different requests so you can focus on whatever is slow.
Also, is this happening only during development or also in production? Having DEBUG=True in a production setup is not only a bad idea but also it can have a big impact on performance.

Categories