Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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',
            )
like image 737
J. Barlow Avatar asked Feb 04 '17 22:02

J. Barlow


3 Answers

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:

  1. Move LeadSerializer to notes/serializers.py
  2. 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)
like image 146
malvadao Avatar answered Nov 09 '22 02:11

malvadao


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
like image 4
Stas Mercer Avatar answered Nov 09 '22 00:11

Stas Mercer


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)
like image 3
Cadmus Avatar answered Nov 09 '22 00:11

Cadmus