Django REST framework - reverse ForeignKey relations - django

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

Related

Rendering Foreign Key Elements from Django Rest Framework

I have the standard django polls models
class Poll(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.question_text
class Choice(models.Model):
poll = models.ForeignKey(Poll, null=True, blank=True)
choice_text = models.CharField(max_length=200)
vote_count = models.IntegerField(default=0)
In the serialisers I have added a reference to foreign key as follows :
class PollSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, read_only=True, required=False)
class Meta:
model = Poll
fields = ('id', 'question_text', 'pub_date', 'choices')
I have created the choices specific to the questions by the django admin. In the poll list from django rest framework, I expect to see the list of choices associated with the poll. It only shows the Poll objects as follows.
Can someone guide me, as to what has gone missing, and how can I see the choices associated with the Poll?
Since reverse manager for choice objects is choice_set You should set source='choice_set' for choices field:
class PollSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, read_only=True, required=False, source='choice_set')
Apart from #neverwalkaloner's answer, you could do it this way also,
class PollSerializer(serializers.ModelSerializer):
choices_set = ChoiceSerializer(many=True, read_only=True, required=False)
class Meta:
model = Poll
fields = ('id', 'question_text', 'pub_date', 'choices_set')

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

Django REST framework, dealing with related fields on creating records

Preliminary note: this is a rather newbie question, though I have not found a sufficient answer on StackOverflow; many similar questions, but not this one. So I am asking a new question.
The problem: I'm having difficulty creating records where one field is a foreign key to an existing record, and I do not know what I'm doing wrong in my code.
In my app there are two models in question, a one-to-many relationship between Company and BalanceSheet:
models:
class Company(models.Model):
cik = models.IntegerField(default=0, unique=True)
symbol = models.CharField(max_length=4, unique=True)
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.StringRelatedField()
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
Views:
class BalanceSheetCreate(generics.CreateAPIView):
model = BalanceSheet
queryset = BalanceSheet.objects.all()
serializer_class = BalanceSheetSerializer
urls include:
url(r'^(?P<symbol>[A-Z]{1,4})/create-balance-sheet/$', views.BalanceSheetCreate.as_view(),
name='create_balance_sheet'),
To this point, I have zero problem reading data. However, when trying to create records, I get errors I don't understand:
curl http://localhost:8000/financials/AAPL/create-balance-sheet/ -X POST -d "company=AAPL&date=1968-04-17&profit=1&loss=1"
IntegrityError at /financials/AAPL/create-balance-sheet/
null value in column "company_id" violates not-null constraint
Dropping the company data from that curl command results in the same error.
How do I get around this error? I thought I was telling the api what company I'm interested in, both explicitly in the url and in the post data.
Using python3.6, django 1.11, and djangorestframework 3.7.7
You get the IntegrityError because your code will try to create a new BalanceSheet without a company. That's because StringRelatedField is read-only (see docs) and therefore it's not parsed when BalanceSheetSerializer is used in write mode.
SlugRelatedField is what you need here:
class BalanceSheetSerializer(serializers.ModelSerializer):
company = serializers.SlugRelatedField(slug_field='symbol')
class Meta:
model = BalanceSheet
fields = ('company','date','profit','loss')
To answer my own question, here's what I wound up with. Thanks again go to dukebody.
models:
class Company(models.Model):
cik = models.IntegerField(default=0)
symbol = models.CharField(max_length=4)
name = models.CharField(max_length=255)
def __str__(self):
return self.symbol
class BalanceSheet(models.Model):
company = models.ForeignKey(Company,
null=True,
on_delete=models.CASCADE,
related_name='balance_sheets',)
date = models.DateField()
profit = models.BigIntegerField()
loss = models.BigIntegerField()
class Meta:
unique_together = (('company', 'date'),)
def __str__(self):
return '%s - %s' % (self.company, self.date)
serializers:
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('cik', 'symbol', 'name')
class BalanceSheetSerializer(serializers.ModelSerializer):
company = CompanySerializer(many=False)
class Meta:
model = BalanceSheet
fields = ('company', 'date', 'profit', 'loss')
def create(self, validated_data):
company_data = validated_data['company']
company, created = Company.objects.get_or_create(**company_data)
validated_data['company'] = company
sheet = BalanceSheet.objects.create(**validated_data)
return sheet
I also had to include the full company data within my curl statement as a nested dict.

How to set SlugRelated field to a field within an object field

I have the following models:
class Category(models.Model):
name = models.CharField()
... # fields omitted
class Prediction(models.Model):
conversation = models.ForeignKey(Conversation)
category = models.ForeignKey(Category)
... # fields omitted
class Conversation(models.Model):
sid = models.CharField()
... # fields omitted
Now I'm trying to create a model serializer for Category that would return me the following serialized object:
{
"name":"blah",
"conversations":[
"2af22188c5c97256", # This is the value of the sid field
"073aef6aad0883f8",
"5d3dc73fc8cf34be",
]
}
Here is what I have in my serializer:
class CategorySerializer(serializers.ModelSerializer):
conversations = serializers.SlugRelatedField(many=True,
read_only=True,
source="prediction_set",
slug_field='conversation.sid')
class Meta:
model = models.Class
fields = ('class_name', 'conversations')
However, this doesn't work because somehow django doesn't allow me to set slug_field to be a field that's within an object field. Any suggestions on how to accomplish this?
You are modelling a Many-to-Many relationship between Categorys and Conversations with a explicit table called Prediction. The django way of doing this would be to explicitly state the Many-to-Many on either side of the relationship and specify Prediction as the "through-model":
Shamelessly stolen example from this question:
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=255, blank=True,default=None)
desc = models.TextField(blank=True, null=True )
...
class Post(models.Model):
title = models.CharField(max_length=255)
pub_date = models.DateTimeField(editable=False,blank=True)
author = models.ForeignKey(User, null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True, through='CatToPost')
...
class CatToPost(models.Model):
post = models.ForeignKey(Post)
category = models.ForeignKey(Category)
...
This shows a good way to set up the relationship.
Equally shamelessly stolen from the answer by #Amir Masnouri:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name','slug')
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id','{anything you want}','categories')
depth = 2
This shows a nice way of achieving the nested serialization behavior that you would like.

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