Serialization of nested models in Django Rest Framework - django

I have nested models and many to many relations. When I try to serialize them, results are not visible.
Tried everything in documentation and related names etc.
My base model is like this:
class Question(models.Model):
ques_code = models.CharField(max_length=12, null=True, default='Ques Code')
def __str__(self):
return self.ques_code
Child Model is:
class MCQuestion(Question):
answer_order = models.CharField(
max_length=30, null=True, blank=True,
choices=ANSWER_ORDER_OPTIONS,
help_text=_("The order in which multichoice "
"answer options are displayed "
"to the user"),
verbose_name=_("Answer Order"))
Then linked answer class with key as:
class Answer(models.Model):
mcquestion = models.ForeignKey(MCQuestion,related_name='answers', on_delete=models.CASCADE)
content = models.CharField(max_length=1000,
blank=False,
help_text=_("Enter the answer text that "
"you want displayed"),
verbose_name=_("Content"))
correct = models.BooleanField(blank=False,
default=False,
help_text=_("Is this a correct answer?"),
verbose_name=_("Correct"))
def __str__(self):
return self.content
Serializers are as:
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
model = Answer
fields = ('content','correct')
class MCQuestionSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True, read_only=True)
#answers = serializers.SerializerMethodField()
quiz = QuizSerializer(many=True)
class Meta:
model = MCQuestion
fields = ('ques_code','answers')
Views are:
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = MCQuestionSerializer
When I access API for questions, nested answers are not visible. I checked all documentation and checked and changed my code.
If I try using answers = serializers.SerializerMethodField() and define get_answers function for it, error comes saying "Question has no attribute of answers"
I think it is due to child and mother model system. It is searching attribute in Question, not in MCQuestion model. What can I do?

You were using the "wrong queryset-serializer combination" for the viewset class.
So, change the queryset reference in your view class as ,
class QuestionViewSet(viewsets.ModelViewSet):
queryset = MCQuestion.objects.all()
serializer_class = MCQuestionSerializer
Apart from that, I'm not sure you are aware of Django Model Inheritance. Anyway read it from here if necessary, Django Model Inheriitance

Related

Can't render nested relationship in Django Rest Framework

The problem is I have a 'details' field which should render into a nested relationship with it's parent serializer. I have tried a bunch of stuff and nothing seems to be working.
Here's my models:
class BusinessOrderModel(OrderToModel):
reference = models.IntegerField()
business_num = models.ForeignKey('BusinessModel', on_delete=models.CASCADE)
def __str__(self):
return str(self.reference)
class BusinessModel(models.Model):
Business_num = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.Business_num)
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', on_delete=models.CASCADE)
and here's my serializers which aren't working:
class DetailSerializer(serializers.ModelSerializer):
class Meta:
model = DetailModel
fields = ('id', 'detail')
class BusinessOrderSerializer(serializers.ModelSerializer):
details = DetailSerializer(many=True)
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
I've tried many different things but I get this error:
Got AttributeError when attempting to get a value for field details
on serializer BusinessOrderSerializer. The serializer field might be
named incorrectly and not match any attribute or key on the
BusinessOrderModel instance. Original exception text was:
'BusinessOrderModel' object has no attribute 'details'.
Any help is much appreciated.
Thank you very much.
Using details to lookup reverse relationships only works if you set it as the related_name. The default for BusinessOrderModel to DetailModel will be detailmodel_set.
To make it accessible by calling details you should make this change:
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', related_name="details", on_delete=models.CASCADE)
Now you can use DetailModel.objects.get(id=1).details.all()
You can also customize the query in your serializer:
class BusinessOrderSerializer(serializers.ModelSerializer):
details = SerializerMethodField()
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
def get_details(self, obj):
return DetailSerializer(obj.details.filter(), many=True).data

fields in class Meta got invalid

models.py
class Product(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
price = models.DecimalField(decimal_places=5,max_digits= 1500)
summary = models.TextField()
featured = models.BooleanField()
def __str__(self):
return self.title
# return f'product title:{self.title}-product price:{self.price}'workok
class Meta:
ordering = ('-price',)
class Opinion(models.Model):
name = models.CharField(max_length=20)
email = models.EmailField(max_length=20)
body = models.TextField()
opinion_date = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='opinion_set')
def __str__(self):
return f'({self.name}) add opinion about ({self.product})'
forms.py:
from django.forms import ModelForm
from .models import Product #space after from keyword
class OpinionModelForm(ModelForm):
class Meta:
model = Product
fields = ['name','email','body','product']
invalid in code line :
fields = ['name','email','body','product'] #---- NOT WORK !!!
, but if i change above code to :
fields = "__all__" # ----it is WORKing ok without any problem !!
question : what is the error? I am not need all the fields in the Product model (like active boolean field), I need only 'name','email','body','product' fields .
According to the error and the code you provided the main problem is that you made a mistake in chosing model in serializer:
class OpinionModelForm(ModelForm):
class Meta:
model = Product
fields = ['name','email','body','product']
Serializer name is OpinionModelForm and listed fields belong to Opinion so I guess you actually wanted to serialize Opinion and no Product as you defined at this line:
model = Product
Simply change it to:
model = Opinion

Django REST framework - reverse ForeignKey relations

I have the following three models structured around the premise of the Survey.
class Survey(models.Model):
...
id = models.UUIDField(_('Id'), primary_key=True, default=uuid.uuid4, editable=False,)
name = models.CharField(_('Name'), max_length=120, blank=True, unique=True)
slug = models.SlugField(_('Slug'), max_length=120, blank=True, unique=True)
description = models.TextField(_('Description'), blank=True)
...
Each Survey can have multiple questions SurveyQuestion:
class SurveyQuestion(models.Model):
...
survey = models.ForeignKey('surveys.Survey', on_delete=models.CASCADE, null=True, blank=True,)
And each SurveyQuestion can have multiple answers SurveyQuestionAnswer:
class SurveyQuestionAnswer(models.Model):
...
survey_question = models.ForeignKey('surveys.SurveyQuestion', on_delete=models.CASCADE, null=True, blank=True,)
For the sake of brevity, imagine my Survey serializers as being as simple as possible:
class SurveySerialializer(serializers.ModelSerializer):
class Meta:
model = Survey
fields = ('__all__')
Effectively, what I have is the following:
class Survey(APIView):
"""
Survey GET request endpoint: fetches Survey
"""
permission_classes = User
def get(self, request, survey_slug):
survey = Survey.objects.get(slug=survey_slug)
serializer = SurveySerializer(survey)
response = get_hug_response(message='Organisation Active Survey Fetched Successfully', data=serializer.data)
return Response(data=response, status=status.HTTP_200_OK)
But, as you could all probably tell, the corresponding surveys.get('slug') fetch only returns the fields in the Survey model. Ideally, I would like to have some sort of fetch for each SurveyQuestion, and within that nested the SurveyQuestionAnswers
Any pro-tips and pointers would be most appreciated.
I have tried a few things, that only throw errors. I'm struggling to know what this type of API relationship is called in DRF so I can't find appropriate example guides to base the same principles from...
Relevant versions:
Django==2.2.1
djangorestframework==3.9.3
Create two serializers, SurveyQuestionAnswerSerializer and SurveyQuestionSerializer
class SurveyQuestionAnswerSerializer(serializers.ModelSerializer):
class Meta:
model = SurveyQuestionAnswer
fields = '__all__'
class SurveyQuestionSerializer(serializers.ModelSerializer):
survey_questionanswers = SurveyQuestionAnswerSerializer(many=True, read_only=True, source="surveyquestionanswer_set")
class Meta:
model = SurveyQuestion
fields = '__all__'
class SurveySerializer(serializers.ModelSerializer):
survey_questions = SurveyQuestionSerializer(many=True, read_only=True, source="surveyquestion_set")
class Meta:
model = Survey
fields = '__all__'
For more info,
1. What is related_name used for in Django?
2. DRF Serializer's source argument

ManyToManyField Serializer throws "This field must be unique" error

I am trying to create a Many-To-Many relationship between two models- Author and Book. My use-case is that I should be able to add a new book to the database with an author that already exists in the database.
models.py
class Author(models.Model):
author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
class Book(models.Model):
title = models.CharField(max_length=50, primary_key=True)
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('author_id', 'name')
class BookSerializer(serializers.ModelSerializer):
authors = AuthorSerializer(many=True)
class Meta:
model = Book
fields = ('title', 'authors')
def create(self, validated_data):
book = Book.objects.create(name=validated_data['title'])
for item in validated_data['authors']:
author = Author.objects.get(author_id=item['author_id'])
book.authors.add(author)
return book
Let's say my Author table already has an Author:
1, George RR Martin
Now if I want to add a new book with an existing author, this is the request I send using httpie:
http -j POST http://localhost/books title="The Winds of Winter" authors:='[{"author_id":"1"}]'
and when I do, I get this error:
Output Error
{
"authors": [
{
"author_id": [
"This field must be unique."
]
}
]
}
It seems like the AuthorSerializer is being called which checks the provided author_id against the ones in the database already and throws this error.
Any help on this would be appreciated.
Is there a specific reason you have to use a custom PK field?
Django automatically creates primary key fields for you. If you simply delete that field from your model and your serializer (and create/run a migration on your database), you won't have to specify the pk in your POST call from your frontend, and Django will create an AutoField that auto-increments your model's id:
class Author(models.Model):
# Remove this line and run makemigrations.
# author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
If not, consider using an models.AutoField rather than models.CharField for your primary key field, and again, don't include this in your POST call.
Note, that if you already have a big database created, you might have to do some intricate work in your migration, a la this answer:

Filter on nested serializer in django rest framework

I'm building a serializer in django using the django rest framework. I need to filter the query set for a nested model.
I found How do you filter a nested serializer in Django Rest Framework?, which seemed to have the answer, but when I implemented it there was no change in my data. The only difference I can see is that the serializer referencing the filtered list serializer has other fields as well.
The models (abbreviated for clarity):
class GCUser(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField()
is_member = models.BooleanField(default=False)
age = models.SmallIntegerField(blank=True, null=True)
session_key = models.CharField(max_length=100, db_index=True, blank=True, null=True)
class Connection(models.Model):
creation_date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(GCUser, related_name='user_connection')
event = models.ForeignKey(Event, related_name='event_connection')
role = models.CharField(max_length=8, choices=constants.Roles.ROLE_CHOICES,)
class Event(Game):
creation_date = models.DateTimeField(auto_now_add=True)
public = models.BooleanField(default=False)
start_time = models.DateTimeField(null=True, blank=True)
end_time = models.DateTimeField(null=True, blank=True)
gm_is_player = models.BooleanField(default=False,
help_text='Check if GM will be playing the game',
verbose_name='GM is a player')
gm_is_designer = models.BooleanField(default=False, help_text='Check if GM designed the game')
user_notes = models.TextField(blank=True, default='', verbose_name='Note to Scheduler')
scheduler_notes = models.TextField(blank=True, default='')
experience = models.CharField(max_length=3, choices=constants.ExpLevels.EXPERIENCE_CHOICES,
default=constants.ExpLevels.NOVICE,)
status = models.CharField(max_length=4, db_index=True,
choices=constants.Status.STATUS_CHOICES,)
Here's my code:
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(status=constants.Status.ASSIGNED).order_by('start_time')
return super(FilteredListSerializer, self).to_representation(data)
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = UserEventSerializer()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
class GCUserSerializer(serializers.ModelSerializer):
user_connection = UserConnectionSerializer(many=True)
class Meta:
model = models.GCUser
fields = ('pk', 'first_name', 'last_name', 'email',
'is_member', 'age', 'user_connection')
PyCharm tells me that "class FilteredListSerializer must implement all abstract methods" but it doesn't actually throw an error. I put a breakpoint at the first line of the list serializer, but it doesn't get tripped.
I'm using Python 3.4 with django 1.7.
Thanks in advance for your help.
Edited to add: Looking into the serializer code, I realized what may be the key difference: my call has many=True, whereas the one from the previous post didn't. I tried taking out the model=, but as expected that threw an error, so apparently the "working" code in the earlier post didn't actually run as written.
So I am not sure how to use the method you are using, but, if I understand your question correctly, I believe you could do something like this:
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = serializers.SerializerMethodField()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
def get_event(self, obj):
if obj.event.status == constants.Status.ASSIGNED:
serializer = UserEventSerializer(obj.event)
return serializer.data
else:
serializer = UserEventSerializer(None)
return serializer.data
N.B. This assumes that you are trying to exclude Events from being serialized if their status is not assigned.
I hope this helps. If I didn't understand the problem, let me know.
it's not a bug or error. ModelSerializer has got already implemented all needed methods (most inhereted from Serializer class), but ListSerializer inherits from BaseSerializer and got implemented e.g.: .create() or .to_representation(), but not .update(). I've got some similar problems in PyCharm, when subclassing a Serializer. After implementing create, update and to_representation methods this issue was gone