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.
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
When I'm trying to save a model object (its name here is 'RSS) in the view's post() it doesn't get saved, how can I save the model instance 'rss' from the view's post()?
In the Serializers class:
class RSSSerializer(serializers.ModelSerializer):
class Meta:
model = RSS
fields = ('feed_url', 'website_url', 'description', 'title')
def create(self, validated_data):
rss = RSS(**validated_data)
rss.created_at = datetime.now()
rss.last_scan_time = '2001-01-01 00:00:00'
rss.id = None
return rss
In the View class:
class RSSList(APIView):
def post(self, request):
serializer = RSSSerializer(data=request.data)
if serializer.is_valid():
print("saving rss post")
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Your current create method instantiates a new RSS object but doesn't save it. Try this:
def create(self, validated_data):
rss = RSS.objects.create(**validated_data)
rss.created_at = datetime.now()
...
return rss.save()
More info: Django rest framework: override create() in ModelSerializer passing an extra parameter
http://www.django-rest-framework.org/api-guide/serializers/#saving-instances
If your object instances correspond to Django models you'll also want to ensure that these methods save the object to the database.
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.
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)