DRF end-point posting error for nested serialization - django

Get method working in browse-able api end-point but when i try to post using my end-point through browser, it fires me this error: (My serializers are nested)
This is my serializers.py and it is Nested serilizers
from rest_framework import serializers
from . models import Author, Article, Category, Organization
class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
model = Organization
fields = '__all__'
class AuthorSerializer(serializers.ModelSerializer):
organization = OrganizationSerializer()
class Meta:
model = Author
fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class ArticleSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
category = CategorySerializer()
class Meta:
model = Article
fields = '__all__'
and this is my models.py
from django.db import models
import uuid
class Organization(models.Model):
organization_name = models.CharField(max_length=50)
contact = models.CharField(max_length=12, unique=True)
def __str__(self):
return self.organization_name
class Author(models.Model):
name = models.CharField(max_length=40)
detail = models.TextField()
organization = models.ForeignKey(Organization, on_delete=models.DO_NOTHING)
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Article(models.Model):
alias = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='author')
title = models.CharField(max_length=200)
body = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
and this is my views.py ( I am using APIView, not VIewset)
class ArticleDeleteUpdate(DestroyAPIView, UpdateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = 'alias'
and this is my urls.py
path('api/v1/article', views.ArticleListCreateGet.as_view(), name='article2'),
I worked 10 hours on it to fix the issue but i failed to fix it...
I am not getting whats wrong with this... this error ruined my sleep..
Can anyone help me please to fix this issue?

EDIT with the correct approach:
Sorry I just realized you are using ModelSerializers instead of Serializers.
You need to change from
class ArticleSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
category = CategorySerializer()
class Meta:
model = Article
fields = '__all__'
to
class ArticleSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField()
category = serializers.PrimaryKeyRelatedField()
class Meta:
model = Article
fields = '__all__'
And check the documentation of PrimaryKeyRelatedField as it includes some different options that might be interesting for the design of your API
https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
Most likely you will need the read_only=True option so you don't need to override any method
Original answer with a bit of explanation:
You are getting the error because the POST is trying to create the nested objects but your serializer does not override the .create() method so the serializer does not know how to handle the nested relationships.
Take a look on https://www.django-rest-framework.org/api-guide/serializers/#writing-create-methods-for-nested-representations where you can get a grasp of what you need.

Related

how to save multiple objects to the database in django rest framework views

so what i'm trying to do is add a new product to my data base using django's restapi
but a product may contain multiple categories which are related throught a third many to many
model and extra pictures which are ForeignKeyed to the product
this is my models.py
class Products(models.Model):
product_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Category(models.Model):
category_id = models.AutoField(primary_key=True)
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Category'
class ProductsCategory(models.Model):
productscategory_id = models.AutoField(primary_key=True)
category = models.ForeignKey(to=Category, on_delete=models.CASCADE)
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'ProductsCategory'
class Pictures(models.Model):
picture_id = models.AutoField(primary_key=True)
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Pictures'
and heres what i've tryed:
#api_view(['POST'])
#permission_classes([IsModerator])
def create_product(request):
product_details = ProductsSerializer(request.POST, request.FILES)
pictures = PicturesSerializer(request.POST, request.FILES, many=True)
category_list = request.POST.getlist("category")
if product_details.is_valid() and validate_file_extension(request.FILES.get("main_image")):
try:
product = product_details.save()
if len(category_list) > 0:
for i in category_list:
category = Category.objects.get(category=i)
ProductsCategory.objects.create(category=category, product=product)
if pictures:
for image in request.FILES.getlist("image"):
if validate_file_extension(image):
Pictures.objects.create(image=image, product=product)
else:
error = {"error": "invalid extra pictures extension"}
return Response(error)
return Response((product_details.data, pictures.data, category_list), status=status.HTTP_201_CREATED)
except Exception as e:
return Response(e)
else:
return Response((product_details._errors, pictures._errors), status=status.HTTP_400_BAD_REQUEST)
and the output:
result
how am i supposed to use this content input?
or if you know a better for my main question of saving multiple models in the database and their relationships please leave an answer, thanks in advance
I suggest you change your models.py structure to this:
from django.db import models
class Category(models.Model):
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Categories"
class Picture(models.Model):
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Product(models.Model):
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
more_images = models.ManyToManyField(Pictures, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now=True)
Then in your serializer.py add:
from rest_framework import serializers
from .models import Category, Picture, Product
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
In your views, I suggest you use ViewSets:
views.py
from .models import Category, Picture, Product
from .serializer import CategorySerializer, PictureSerializer, ProductSerializer
from rest_framework import viewsets
# import custom permissions if any
class CategoryViewSet(viewsets.ModelViewSet):
serializer_class = CategorySerializer
queryset = Category.objects.all()
class PictureViewSet(viewsets.ModelViewSet):
serializer_class = PictureSerializer
queryset = Picture.objects.all()
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
permission_classes = [IsModerator]
In your app's urls.py, add the router for your viewsets and it will create the paths for your views automatically:
from django.urls import path
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'category', views.CategoryViewSet, basename='category')
router.register(r'picture', views.PictureViewSet, basename='picture')
router.register(r'product', views.ProductViewSet, basename='product')
urlpatterns = [
path('', include(router.urls)),
]
Changes log:
You do not need to add an ID field to every model, Django does that for you. Unless it's a particular case.
Your database tables are named after your model by default. So no need to specify that too.
I simplified your models' structure to make it cleaner. But it still does what you want it to do.
Django adds an s to create a plural name for every model. So you can name it in singular form unless needed to specify. eg. categories.
The viewsets will reduce your work by providing you with listing and retrieval actions.
To access a specific instance of eg. a product, you will just add a /<product id> after the product listing and creation endpoint.
Note: You have to add the id without the brackets.
I also suggest you go through this DRF tutorial. It will improve your understanding of Django REST framework.

making an api to show the books of an auther in django rest framework

I have two models in my project: Author and Book. Each book has a foreignkey that points to the author of the book.
I want to write an api which retrieves and instance of an Author and shows the details of that specific person.
The problem is that I don't know how to include that said person's books in my API.
This is my models.py:
class Book(models.Model):
title = models.CharField(max_length=150)
rating = models.IntegerField(default=0, validators=[MaxValueValidator(10), MinValueValidator(0),])
summary = models.TextField()
author = models.ForeignKey(Author, null=True, on_delete=models.SET_NULL)
class Author(models.Model):
authorID = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
dateOfBirth = models.DateField(null=True)
nationality = models.CharField(null=True, max_length=255)
AND this is the method that didn't work for me:
# Serializers.py
class AuthorRetrieveSerializer(serializers.ModelSerializer):
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
bookDetails = BookSerializer(read_only=True, many=True)
class Meta:
model = Author
fields = ('name', 'dateOfBirth', 'nationality', 'bookDetails')
# Views.py
class AuthorRetrieveViewSet(RetrieveUpdateDestroyAPIView):
permission_classes = (AllowAny,)
serializer_class = serializers.AuthorRetrieveSerializer
queryset = Author.objects.all()
lookup_field = 'authorID'
def get_queryset(self):
return self.queryset
This code retrieves the Author details successfully but doesn't give me their Books.
Have you tried specifying the source on the serializer?
# Serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
class AuthorRetrieveSerializer(serializers.ModelSerializer):
bookDetails = BookSerializer(read_only=True, many=True, source="book_set")#correction here
class Meta:
model = Author
fields = ('name', 'dateOfBirth', 'nationality', 'bookDetails')

Django Rest Framework: how to create and update many to many field?

I'm finding a way to serialize model fields of a Many To Many relation object.
Models
class Tag(models.Model):
name = models.CharField(max_length=150)
description = models.TextField(blank=True)
class Book(models.Model):
name = models.CharField(max_length=150)
slug = models.SlugField(max_length=255, unique=True)
tag= models.ManyToManyField(Tag,
related_name='books',
blank=True)
Serializers -
class BookSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
slug = serializers.SlugField(read_only=True)
class Meta:
model = Book
fields = '__all__'
Views -
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
lookup_field = 'slug'
serializer_class = BookSerializer
def perform_create(self, serializer):
return serializer.save(owner=self.request.user)
I want to add the tag name to the response of the detailed view as well as the list view of books. If I try to create a serializer for the Tag model and use it in the book serializer, I'm not able to create or update the Book.
What should I do?
Also, is there a way to update only the tag field?

StringRelatedField not working as expected

I have models,
class Reporter(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=100)
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
def __str__(self):
return self.title
and serializer,
class ReporterSerializer(serializers.ModelSerializer):
article = serializers.StringRelatedField(source='article_set')
class Meta:
model = Reporter
fields = '__all__'
and views
class ReporterAPI(viewsets.ModelViewSet):
queryset = Reporter.objects.all()
serializer_class = ReporterSerializer
Everything seems fine, but, my response showing something weird
response
Here is the RESPONSE IMAGE
The response article is showing wrong output
Since article_set is list of objects you should add many=True argument:
class ReporterSerializer(serializers.ModelSerializer):
article = serializers.StringRelatedField(source='article_set', many=True)
class Meta:
model = Reporter
fields = '__all__'

Django Rest Framework - How to POST foreign keys in ListCreateAPIView

Post model
class Post(models.Model):
owner = models.ForeignKey(Profile, on_delete=models.CASCADE) # Profile is another model
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
votes = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE) # Subreddit is another model
PostSerializer
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
depth = 1
ListPostsOfReddit
class ListPostsOfReddit(ListCreateAPIView):
serializer_class = PostSerializer
def get_queryset(self):
return Post.objects.filter(subreddit__name=self.kwargs['r_name'])
In the ListCreateAPIView of rest-framework, I am able to GET all the foreign key data. In the form that rest-framework provides, only the
Title
Content
Votes
are asked, I want the foreign key fields to be also asked as input. How do I achieve that?
Use Two serializers, and manage those in get_serializer() method.
class PostListSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
depth = 1
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class ListPostsOfReddit(ListCreateAPIView):
def get_serializer_class(self):
if self.request.method == 'GET':
return PostListSerializer
return PostSerializer
def get_queryset(self):
return Post.objects.filter(subreddit__name=self.kwargs['r_name'])
Note: I didn't tested/verified the solution. Please let me know if any error occured