How to POST and GET a model with Foreign Key - django

I am working on a NextJS + Django REST Framework project where I have three models; Document, MySource, and QuestionBlock.
Document is created along with several “question_blocks” linked to the created document. They are created together, and I have already implemented this with nested serializers.
After the Document is created, I want to be able to POST a MySource model linked to the document. Then, when I make a GET request of a document, all mysource objects should be displayed as well.
POST request: notice how I just put the document’s id that I want to link with.
{
"url": "urlasdf",
"title": "tuitle",
"publisher": "afdfas ",
"desc": "qwefqwef",
"summary": "asdfasdf",
"document": "J9DY2pE"
}
GET request: I want the document GET request to show something like below.
"id": "LwpQr6Y",
"question_blocks": [
{
"id": 16,
"document": "LwpQr6Y",
"title": "question 4",
"content": "qweqgadssdffasdf asdf"
},
]
"mysource": [
{
"id": 16,
"url": "google.com",
etc. . .
},
],
"title": "renamed title",
"template": "commonapp",
"updated": "2022-05-19T02:16:00+0000",
"created": "2022-04-21T06:59:05+0000"
The weird part is that I do not see any errors with the code below, and the functionality itself is working properly. But when I attempt to GET the document which has at least one mysource object, it takes at a couple minutes to load, which is making me think there’s something wrong with my code that is perhaps making DRF repeat itself.
models.py
class Document(models.Model):
id = HashidAutoField(primary_key=True)
title = models.CharField(max_length=100, default="Untitled")
template = models.CharField(max_length=100, default="")
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class QuestionBlock(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(
Document,
related_name="question_blocks",
on_delete=models.CASCADE,
null=True,
blank=True,
)
title = models.CharField(max_length=500, default="")
content = models.CharField(max_length=100000, default="", blank=True)
class MySource(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(
Document,
related_name="mysource",
on_delete=models.CASCADE,
null=True,
blank=True,
)
url = models.CharField(max_length=500, default="")
title = models.CharField(max_length=500, default="", blank=True)
publisher = models.CharField(max_length=500, default="", blank=True)
desc = models.CharField(max_length=500, default="", blank=True)
summary = models.CharField(max_length=500, default="", blank=True)
serializers.py
class MySourceSerializer(serializers.ModelSerializer):
class Meta:
model = MySource
fields = ("id", "url", "title", "publisher", "desc", "summary")
def to_representation(self, instance):
self.fields["document"] = DocumentSerializer(read_only=True)
return super(MySourceSerializer, self).to_representation(instance)
class DocumentSerializer(serializers.ModelSerializer):
id = HashidSerializerCharField(source_field="documents.Document.id", read_only=True)
question_blocks = QuestionBlockSerializer(many=True)
mysource = MySourceSerializer(many=True)
class Meta:
model = Document
fields = "__all__"
def create(self, validated_data):
question_blocks = validated_data.pop("question_blocks")
document = Document.objects.create(**validated_data)
for qBlock in question_blocks:
QuestionBlock.objects.create(document=document, **qBlock)
document.save()
return document
EDIT: adding the QuestionBlockSerializer for more context
class QuestionBlockSerializer(serializers.ModelSerializer):
document = serializers.PrimaryKeyRelatedField(
pk_field=HashidSerializerCharField(source_field="documents.Document.id"),
read_only=True,
)
class Meta:
model = QuestionBlock
fields = "__all__"
optional_fields = ["content"]

I think that an appropriate way to do this maybe be this one:
###imports
from django.forms.models import model_to_dict
class DocumentListingField(serializers.RelatedField):
def to_representation(self, instance):
return model_to_dict(instance.document)
and then in MySourceSerializer remove the to_representation function and update to something like this:
class MySourceSerializer(serializers.ModelSerializer):
document = DocumentListingField(many=False, read_only=True)
class Meta:
model = MySource
fields = (
"id", "url", "title", "publisher", "desc", "summary", "document")
*Edit: I added the read_only set to True, 'caus the those models are using Hashfields thar are not easily selializable.
**edit:The reason that cause the slow response it's becaus you have a circle call of serializators so the system never know when to stop.
right here:
class MySourceSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
...
self.fields["document"] = DocumentSerializer(read_only=True)
#Mysource its calling Documentserializer
...
#And here:
class DocumentSerializer(serializers.ModelSerializer):
...
mysource = MySourceSerializer(many=True) #this one its calling the MysorceSerialize, so there are a endless loop recursion
source : Django Rest Framework-Custom relational fields

Related

I leave a question regarding DRF multi-image processing

class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = "image"
class PostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = (
"id",
"author",
"content",
"images",
)
I'd like to get the results the following response from the above communication codes.
(위 코드로 GET 통신에서 아래와 같은 응답 결과를 얻고 싶은데요.)
{
"id": 53,
"author": 1,
"post": "awesome",
"images": {
"image": "abc.png",
"image": "def.jpg",
}
}
The images field does not apply and appears like below.
(images 필드가 적용되지 않고 아래와 같이 나오는 상황입니다.)
{
"id": 53,
"author": 1,
"post": "awesome",
}
Which part should I fix? I also attach the code for the model.
(어떤 부분을 고쳐야할까요? 모델 단 코드도 같이 첨부합니다.)
class Post(models.Model):
author = models.ForeignKey("User", on_delete=models.CASCADE)
post = models.CharField(max_length=100)
class PostImage(models.Model):
post = ForeignKey("Post", on_delete=models.CASCADE)
image = models.ImageField(upload_to="image", blank=True)
Edit models.py
(models.py를 수정하시면 됩니다.)
class Post(models.Model):
author = models.ForeignKey("User", on_delete=models.CASCADE)
post = models.CharField(max_length=100)
class PostImage(models.Model):
post = ForeignKey("Post", on_delete=models.CASCADE, related_name="images")
image = models.ImageField(upload_to="image", blank=True)

Add related ForeignKey fields with serializer in Django REST Framework

I'm using Django 2.2 and Django REST Framework
I have three models like
class Plan(models.Model):
name = models.CharField(_('Plan Name'), max_length=100)
default = models.NullBooleanField(default=None, unique=True)
created = models.DateTimeField(_('created'), db_index=True)
quotas = models.ManyToManyField('Quota', through='PlanQuota')
class Quota(models.Model):
codename = models.CharField(max_length=50, unique=True)
name = models.CharFieldmax_length=100)
unit = models.CharField(max_length=100, blank=True)
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE)
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
I have to get all quota and their value from PlanQuota in the plan serializer while getting a list of plans.
I have the following serializer
class PlanQuotaSerialier(serializers.ModelSerializer):
class Meta:
model = PlanQuota
depth = 1
fields = ['quota', 'value']
class PlanListSerializer(serializers.ModelSerializer):
plan_quota = PlanQuotaSerialier(read_only=True, many=True)
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quota']
But there is no plan_quota in the response.
How can I add all Quota and their value for each plan in a single query (SQL JOIN)?
Edit 2:
Adding source to the serializer field worked
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
And the result is like
"results": [
{
"name": "Test Plan 1",
"default": true,
"plan_quotas": [
{
"quota": {
"id": 1,
"order": 0,
"codename": "TEST",
"name": "Test Domain",
"unit": "count",
"description": "",
"is_boolean": false,
"url": ""
},
"value": 10
},
]
}
]
Can I club all fields from quota with value field in the plan_quotas list?
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE, related_name='plan_quotas')
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
class PlanListSerializer(serializers.ModelSerializer):
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quotas']
This is how I got it solved.
For the first query, added source
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
For removing quota key, added to_presentation() in the PlanQuotaSerializer
def to_representation(self, instance):
representation = super().to_representation(instance)
if 'quota' in representation:
representation['quota']['quota_id'] = representation['quota'].pop('id')
representation.update(representation.pop('quota'))
return representation

How to create records when a model has one-to-many self referential relationship using TastyPie?

I am making POST requests using TastyPie. The Task model has a one-to-many self referential relationship via the parent_task_id field.
Model:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
parent_task_id = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True, blank=True)
In my api.py
class TaskResource(ModelResource):
parent_task_id_id = fields.ToOneField('self', 'id', null=True, full=True)
class Meta:
queryset = Task.objects.all()
authorization = Authorization()
allowed_methods = ['post']
resource_name = "create_task"
I am unable to create a Task when I specify the parent_task_id using Postman.
{
"title": "ABCDERT",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task_id_id": "2"
}
This is the error message I am getting when I do that:
"error_message": "An incorrect URL was provided '2' for the 'CreateTaskResource' resource.",
You should specify parent_task's uri rather than id, like
{
"title": "ABCDERT",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task_id_id": "/create_task/2"
}
In addition, it is incorrect to define the foreignkey field in resource in this way,
class TaskResource(ModelResource):
parent_task_id_id = fields.ToOneField('self', 'id', null=True, full=True)
you can see the docs for detail.
I adjust your example,like:
model:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
parent_task = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
api.py
class TaskResource(ModelResource):
parent_task = fields.ToOneField('self', 'parent_task', null=True,
full=True)
class Meta:
queryset = Task.objects.all()
authorization = Authorization()
allowed_methods = ['post', 'get']
filtering = {'id': ALL, 'parent_task': ALL_WITH_RELATIONS}
resource_name = "task"
Create task
POST body like:
{
"title": "task2",
"description": "world this week",
"due_date": "2018-11-12 1:2:1",
"parent_task":"/api/v1/task/1/"
}
foreignkey query
GET parameter like:
0.0.0.0:8000/api/v1/task/?parent_task__id=1

Django REST Framework update foreign key field on PUT

I'm using Django 2.x and Django REST Framework
My models.py file contents
class ModeOfPayment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField()
class AmountGiven(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField()
mode_of_payment = models.ForeignKey(
ModeOfPayment,
on_delete=models.PROTECT,
blank=True,
default=None,
null=True
)
and serializers.py
class ModeOfPaymentSerializer(serializers.ModelSerializer):
class Meta:
model = ModeOfPayment
fields = ('id', 'title')
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = ModeOfPaymentSerializer()
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'mode_of_payment',
)
def update(self, instance, validated_data):
mode_of_payment = validated_data.pop('mode_of_payment')
instance.mode_of_payment_id = mode_of_payment.id
return instance
and views.py
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
filter_fields = ('contact__id',)
def get_queryset(self):
queryset = AmountGiven.objects.filter(
contact__user=self.request.user
)
return queryset
But when I post data using postman with PUT method to update the existing record
It still says
{
"mode_of_payment": [
"This field is required."
]
}
Edit 2: Response after Daniel answer
{
"id": "326218dc-66ab-4c01-95dc-ce85f226012d",
"contact": {
"id": "b1b87766-86c5-4029-aa7f-887f436d6a6e",
"first_name": "Prince",
"last_name": "Raj",
"user": 3
},
"amount": 3000,
"mode_of_payment": "0cd51796-a423-4b75-a0b5-80c03f7b1e65",
}
You've told AmountSerializer to accept a nested dict representing a ModeOfPayment instance, by setting the mode_of_payment field to ModeOfPaymentSerializer. But that's not what you're sending; you're sending the ID of the ModeOfPayment.
You should remove that line in AmountGivenSerializer.
Edit
I was wrong, you need to declare the field explicitly as a PrimaryKeyRelatedField:
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = serializers.PrimaryKeyRelatedField(queryset=ModeOfPayment.objects.all())
class Meta:
...
Now it will accept a UUID in the data.

Using reverse relationships with django-rest-framework's serializer

My model looks like this:
class User(TimestampedModel):
name = models.CharField(max_length=30, null=False, blank=False)
device = models.CharField(max_length=255, null=False, blank=False)
class Comment(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
And my serializer looks like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class CommentListItemSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Comment
fields = ('user', 'contents', 'rating')
And the view:
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all()
It's almost getting the job done ;). The response I'm getting looks like this:
"results": [
{
"user": {
"name": "Ania"
},
"contents": "Very good",
"rating": 6
},
{
"user": {
"name": "Anuk"
},
"contents": "Not very good",
"rating": 1
}
]
There are two problems with that response.
I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username".
Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name. Since that's unacceptable, how to fix that?
Serializer makes a database query (not a join, but a separate query)
for each user, to get his/her name.
You can use select_related() on the queryset attribute of your view. Then accessing user.name will not result in further database queries.
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all().select_related('user') # use select_related
I don't want to have this nested object "user.name". I'd like to
receive that as a simple string field, for example "username"
You can define a read-only username field in your serializer with source argument. This will return a username field in response.
class CommentListItemSerializer(serializers.ModelSerializer):
# define read-only username field
username = serializers.CharField(source='user.name', read_only=True)
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')
You can add custom functions as fields
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
def username(self):
return self.user.name
class CommentListItemSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')