Everything works good in views and templates. When Ive made serializer and then APIVIew something wrong happend. GET method is ok, but POST is not. When I try to add food I get IntegrityError like this: (but in admin panel and GUI you can add food normally so its not problem with model the problem must be with serializer)
IntegrityError at /food_list_serializer/
Column 'kcal' cannot be null
MODEL look like this:
class Food(models.Model):
name = models.CharField(max_length=124)
kcal = models.FloatField()
proteins = models.FloatField()
carbs = models.FloatField()
fats = models.FloatField()
grams = models.FloatField(default=100, validators=[MinValueValidator(1)])
SERIALIZER:
class FoodSerializer(serializers.ModelSerializer):
class Meta:
model = Food
fields = '__all__'
VIEWS:
class FoodListSerializer(APIView):
def get(self, request, format=None):
food = Food.objects.all()
serializer = FoodSerializer(food, many=True, context={'request': request})
return Response(serializer.data)
def post(self, request):
food_create = Food.objects.create()
serializer = FoodSerializer(food_create, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
POST request in DJANGO rest framework looks like this
The problem lies in this line:
food_create = Food.objects.create()
You are telling Django to create and save a Food object in database with zero arguments, before actually parse the POST data. Inside the Food model you have a lot of fields that are required (one of which is kcal field). Remove this line, since FoodSerializer will be "filled" with request.data (POST) values.
Related
I'm using djangorestframework together with drf-spectacular modules for a Django project, and I'm trying to build some basic API methods for my Project model. Its structure looks like this:
from django.db import models
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length = 128)
description = models.TextField()
image = models.URLField()
date = models.DateTimeField(auto_now_add=True)
I also have a serializer for the model, looking like this:
from rest_framework import serializers
from api.models.Project import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['title', 'description', 'image', 'date']
Then, in views.py, I created two functions: project_list_view, which either lets you to GET all the Project objects from the database, or lets you POST a new object. And finally, project_detail_view, which lets you GET a Project object by typing in its pk (integer id). These are my two functions:
#api_view(['GET', 'POST'])
def project_list_view(request):
if request.method == 'GET':
projects = Project.objects.all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = ProjectSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#api_view(['GET'])
def project_detail_view(request, pk):
if request.method == "GET":
try:
project = Project.objects.get(pk = pk)
serializer = ProjectSerializer(project, many = False)
return Response(serializer.data, status = status.HTTP_200_OK)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
The GET from project_list_view and project_detail_view work, but my problem lays in the POST method.
My Swagger is set to display its API Schema when accessing http://127.0.0.1:8000/docs/, and as I said, GET methods work properly, but when I'm trying to click on "Try it out" at the POST method, the fields are not displayed. I can only press "Execute" without actually being able to complete anything. After I click on "Execute", Swagger returns a 404 Bad Request response.
This is how POST looks like in Swagger:
My question is: Why won't Swagger display fields for each parameter of the model? Thank you.
Swagger Grabs the fields from a serializer_class variable.
I really recommend you change the format to Class-Based Views.
Something using mixins or generic class.
Your view could be like
class ProjectView(mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = ProjectSerializer
queryset = Project.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
More on Mixins and Generic Views
Models.py
from django.contrib.auth.models import User
class Conversation(models.Model):
participants = models.ManyToManyField(User, related_name="conversation")
Urls.py
path('api/conversations/', views.ConversationListView.as_view(),
name='conversation-list'),
Serializers.py
class ConversationSerializer(serializers.ModelSerializer):
participants = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(),
many=True)
class Meta:
model = Conversation
fields = ['id', 'participants']
Views.py
class ConversationListView(APIView):
def get(self, request, format=None):
conversations = Conversation.objects.all()
serializer = ConversationSerializer(
conversations, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ConversationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So I would like to make a POST request on "api/conversations/" with this object:
{"participants": [2,3]} or {"participants": [3,2]}
BUT I would like to check first whether a conversation with participants are both User(id=2) and User(id=3) exist or not. If exists, I need to raise an error. If not exist, create a new conversation. So there is only one conversation between User(id=2) and User(id=3).
What I know so far is I have to make validate_participants(self, value) in the serializer. But I still can't figure out what is the logic to check it. I've tried using Conversation.objects.filter(participants__in=[2,3]) but I think it doesn't work because it does not return the conversation object that has both User(id=2) and User(id=3) as participants.
This code solves my problem. Add this to ConversationSerializer
def validate_participants(self, value):
query1 = Conversation.objects.filter(participants=value[0])
query2 = Conversation.objects.filter(participants=value[1])
if query1.intersection(query2).exists():
raise serializers.ValidationError(
"Conversation with these participants has exist")
return value
In my codes, I have a model Tweet, and in tweet_list_view, I want to show the list of tweets as API view.
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
return Response(serializer.data)
This is what I got as a result.
AssertionError at /tweets/
When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation.
You should either call `.is_valid()` first, or access `.initial_data` instead.
So I called the .is_valid method like following:
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
if serializer.is_valid():
return Response(serializer.data, status=201)
return Response({}, status=400)
Then I get:
TemplateDoesNotExist at /tweets/
rest_framework/api.html
At serializers.py
class TweetSerializer(serializers.ModelSerializer):
class Meta:
model = Tweet
fields = ['content', 'date_posted', 'likes']
models.py
class Tweet(models.Model):
content = models.TextField(blank=True, null=True)
image = models.FileField(upload_to='images/', blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
likes = models.IntegerField(default=0)
def __str__(self):
return self.content
class Meta:
ordering = ['-date_posted']
It's looking for a template, but it's supposed to use the default Django template. Is there any way to fix this problem?
Update:
forgot the # in front of the api_view. Added it. Also added the renderer_class (jsonrenderer) to make sure avoiding the error.
You need to use the data attribute of the serializer only when you have a post, put or patch view. In your case just try it without the data attribute and it should be fine
from rest_framework.renderers import JSONRenderer
#api_view(['GET'])
#renderer_classes([JSONRenderer])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(qs, many=True)
return Response(serializer.data)
Here you can see it in the tutorial example of the official django rest framework docs
Our API has a model defined:
class Job(models.Model):
file = models.FileField('File')
xml = models.FileField('XML')
There is a basic serializer:
class XmlSerializer(serializers.ModelSerializer)
file = serializers.FileField(read_only=True)
xml = serializers.FileField(required=True)
class Meta:
model = Job
fields = '__all__'
We don't want to change the file but we do want to change the xml field. The xml is uploaded by a system that doesn't know the primary key. Of course we need this to update the model.
I have the following code at the moment:
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
pk = 200
serializer = XmlSerializer(request.data)
return Response({})
pk = 200 serves as an example instead of the code we use to parse the xml. I know this doesn't work but it's to show my intention (more or less).
I have tried to use
id = serializers.SerializerMethodField()
def get_id(self, obj):
return 200
without success.
How do I get this primary key into the the serializer?
I was making it much too difficult. The solution was pretty easy.
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
id = magic_xml_parser_function()
job = get_object_or_404(Job, pk=id)
serializer = XmlSerializer(job, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
magic_xml_parser_function() contains the id we found in the xml. This solved it for us.
I'd like to create a SpiderService with a foreign key request.user.
models:
from django.contrib.auth.models import User
class SpiderService(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
serializers:
class SpiderServiceSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.id')
class Meta:
model = SpiderService
fields = ('id', 'user', 'name')
class SpiderServiceListSerializer(viewsets.ModelViewSet):
queryset = SpiderService.objects.all()
serializer_class = SpiderService
views:
class SpiderServiceList(APIView):
def get(self, request, format=None):
services = SpiderService.objects.all()
serializer = SpiderServiceSerializer(services, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SpiderServiceSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def perform_create(elf, serializer):
serializer.save(user=self.request.user)
when I post
{
"name": "2"
}
to the api url, got 500 error:
[01/Feb/2016 15:39:55] "POST /services/ HTTP/1.1" 500 17105
IntegrityError: NOT NULL constraint failed: spmanage_spiderservice.user_id
I have read Serializer relations and found I should not write code as above. The official example is equal to remove user field in SpiderServiceSerializer, and add a spiderservice field to a UserSerializer.Seems aim to achieve the syntax like user.spiderservice_set in django models, which do not fit my purpose.
I just want to create a spiderservice object with request.user.
How to solve it?
You need to add the current user as an extra parameter to the Serializer's save method:
serializer.save(user=request.user)
do you have any Account serializer for the user model ??
if so, in your SpiderServiceSerializer try this :
user = AccountSerializer(read_only=True, required=False)