Django Query from Post - django

I am trying to pass into my DRF API a post that needs to be queried against the database. For now I have included only 2 fields that can be queried. I am having trouble getting the view to work. Here is what I have so far.
POST
{
"city": "Denver",
"state": "CO"
}
Serializer only lets city and state be accepted
class EventQuerySerializer(serializers.ModelSerializer):
class Meta:
model = Events
fields = ('city', 'state')
View note that EventsSerializer is used in the return and I can confirm it works for a basic get request
class QueryEvents(APIView):
#staticmethod
def post(request):
serializer = EventQuerySerializer(data=request.data)
if serializer.is_valid():
events = Events.objects.get(serializer)
return Response(EventsSerializer(events).data)
Error
AttributeError: 'CharField' object has no attribute 'split'

You need to run save on you serializer to retrieve instance:
# ...
if serializer.is_valid():
event = serializer.save()
events = Events.objects.get(pk=event.pk)
# ...

Related

Django rest framework - Serializer always returns empty object ({})

I am creating first rest api in django using django rest framework
I am unable to get object in json format. Serializer always returns empty object {}
models.py
class Shop(models.Model):
shop_id = models.PositiveIntegerField(unique=True)
name = models.CharField(max_length=1000)
address = models.CharField(max_length=4000)
serializers.py
class ShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
views.py
#api_view(['GET'])
def auth(request):
username = request.data['username']
password = request.data['password']
statusCode = status.HTTP_200_OK
try:
user = authenticate(username=username, password=password)
if user:
if user.is_active:
context_data = request.data
shop = model_to_dict(Shop.objects.get(retailer_id = username))
shop_serializer = ShopSerializer(data=shop)
if shop:
try:
if shop_serializer.is_valid():
print('is valid')
print(shop_serializer.data)
context_data = shop_serializer.data
else:
print('is invalid')
print(shop_serializer.errors)
except Exception as e:
print(e)
else:
print('false')
else:
pass
else:
context_data = {
"Error": {
"status": 401,
"message": "Invalid credentials",
}
}
statusCode = status.HTTP_401_UNAUTHORIZED
except Exception as e:
pass
return Response(context_data, status=statusCode)
When i try to print print(shop_data) it always returns empty object
Any help, why object is empty rather than returning Shop object in json format?
Edited:
I have updated the code with below suggestions mentioned. But now, when shop_serializer.is_valid() is executed i get below error
{'shop_id': [ErrorDetail(string='shop with this shop shop_id already exists.', code='unique')]}
With the error it seems it is trying to update the record but it should only get the record and serialize it into json.
You're using a standard Serializer class in this code fragment:
class ShopSerializer(serializers.Serializer):
class Meta:
model = Shop
fields = '__all__'
This class won't read the contend of the Meta subclass and won't populate itself with fields matching the model class. You probably meant to use ModelSerializer instead.
If you really want to use the Serializer class here, you need to populate it with correct fields on your own.
.data - Only available after calling is_valid(), Try to check if serializer is valid than take it's data

Serializer in patch method does not check fields

I am trying to partially update a model and overwriting the patch method. It works fine when I send correct data, but my problem is, that when I send incorrect data the serializer stays True and I still get a 200 status code and nothing gets updated. With incorrect data I mean wrong field names. I could basically send any field name and get a 200 status code. So I think the fields are not checked by the serializer...
class BuildingUpdateAPI(UpdateAPIView):
serializer_class = BuildingSerializer
def patch(self, request, *args, **kwargs):
"""Patches building."""
building = buildings.get(name=self.kwargs['name_building'])
serializer = BuildingSerializer(building, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(status=200, data=serializer.data)
return JsonResponse(status=400, data="Wrong data provided")
serializer:
class BuildingSerializer(serializers.ModelSerializer):
"""Serializer for Buildings."""
class Meta:
model = Building
fields = (
"id",
"name",
"...",
.....
Now I am wondering if this is like this by design or if I am doing something wrong in my view. I thought I could overwrite the validate-method in the serializer but my model has quite a lot of fields... So I would have to check every field single field which is not optimal I think. Or is there a way to do this somehow elegantly?
I am patching like this:
result = requests.request("patch", url=endpoint, json=payload, headers=get_headers())

How to Create a Django Model Instance That Contains a Required OneToOneField with DRF Serializers

The Question
Let's say I have a model that contains a OneToOneField like so:
models.py
class Event(models.Model)
# some basic fields...
class EventDetail(models.Model):
event = models.OneToOneField(Event, on_delete=models.CASCADE,
related_name='event_detail')
# other basic fields, all with default values...
What is a proper way to implement a POST request that intends to create a new Event in the database that automatically creates a default EventDetail linked to it if it is None in the request header, using Django Rest Framework's Serializers?
My Attempt
test.py
class EventTestCase(APITestCase):
def test_post(self):
# Need to provide an id, or else an error occurs about the OneToOneField
event = Event(id=1)
serializer = serializers.EventSerializer(event)
res = self.api_client.post('/event/', serializer.data)
views.py
def post(self, request, format=None):
serializer = EventSerializer(
data=request.data)
# ***This always returns false!***
if serializer.is_valid():
try:
instance = serializer.save()
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializers.py
class EventSerializer(serializers.ModelSerializer):
serialization_title = "Event"
event_detail = EventDetailSerializer()
class Meta:
model = Event
exclude = ('id',)
error_status_codes = {
HTTP_400_BAD_REQUEST: 'Bad Request'
}
class EventDetailSerializer(serializers.ModelSerializer):
serialization_title = "Event Detail"
class Meta:
model = models.EventDetail
exclude = ('id',)
error_status_codes = {
HTTP_400_BAD_REQUEST: 'Bad Request'
}
As noted in a comment above, serializer.is_valid() always returns false with the error:
{'event_detail': [u'This field may not be null.']}
I understand that this is complaining because both EventDetail and Event need to be created in order for a OneToOne relationship to be added, but how do you deal with this using Serializers when a OneToOneField is required and yet not provided by the client?
Thank you for your help.
Disclaimer: I am using Django 1.11
You can declare the EventDetailSerializer with read_only=True or required=False and then handle the creation of the EventDetail in different ways, for example: you could have a post_save signal that listens to the Event class - once a new Event object has been created, you can then create the initial EventDetail object, or you perform this creation after your serializer.save() on the post definition, or even on your create method of your EventSerializer.
edit: an example on how you could perform the creation using the EventDetailSerializer and overriding the create method of your EventSerializer.
def create(self, validated_data):
detail = self.initial_data.get('event_detail')
instance = super().create(validated_data)
if detail:
serializer = EventDetailSerializer(data=detail)
serializer.is_valid(raise_exception=True)
serializer.save(event=instance)
return instance

How to deserialize POST request using a different Serializer

I'm trying to handle POST requests in my API endpoint using a different serializer than the one derived from Model and used for GET or PUT requests. The format of the POSTed messages is different from the Model and from GET/PUT and must be pre-processed before storing to the database.
As a demo of my problem I made a very simple model and the corresponding API view and serializer:
class Message(models.Model):
message = models.CharField(max_length = 500)
class MessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = ('message',)
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all().order_by('-pk')
serializer_class = MessageSerializer
That works well. Then I tried to override the MessageViewSet.create() to handle POST requests differently.
class MessageSerializer_FromTo(serializers.Serializer):
sender = serializers.EmailField()
recipient = serializers.EmailField()
def create(self, validated_data):
message = "Message from <{sender}> to <{recipient}>".format(**validated_data)
return Message(message)
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all().order_by('-pk')
serializer_class = MessageSerializer
# Handle POST requests differently
def create(self, request, format=None):
message = MessageSerializer_FromTo(data = request.data)
if message.is_valid():
message.save()
return Response(message.data, status=status.HTTP_201_CREATED)
return Response(message.errors, status=status.HTTP_400_BAD_REQUEST)
Essentially I want to pass this JSON to POST /api/messages/
{"sender": "some#one.com", "recipient": "someone#else.org"}
GET /api/messages/1/ should return
{"message": "Message from <some#one.com> to <someone#else.org>"}
However the POST fails with this message:
Internal Server Error: /api/messages/
Traceback (most recent call last):
File ".../rest_framework/fields.py", line 441, in get_attribute
return get_attribute(instance, self.source_attrs)
File ".../rest_framework/fields.py", line 100, in get_attribute
instance = getattr(instance, attr)
AttributeError: 'Message' object has no attribute 'sender'
During handling of the above exception, another exception occurred:
[...]
AttributeError: Got AttributeError when attempting to get a value for field `sender` on serializer `MessageSerializer_FromTo`.
The serializer field might be named incorrectly and not match any attribute or key on the `Message` instance.
Original exception text was: 'Message' object has no attribute 'sender'.
[26/Feb/2018 05:09:08] "POST /api/messages/ HTTP/1.1" 500 19059
This is just to demonstrate the problem obviously, I'm doing more complex things in my POST handler, but the error is like this.
Any idea how to achieve what I need? I.e. accept POST fields that are completely different from the Model fields?
Thanks!
UPDATE: The complete code is here: https://github.com/mludvig/drf-demo
problem is with in your serializer. You are just passing model class Message(message) as the output of create function instead of Message object
class MessageSerializer_FromTo(serializers.Serializer):
sender = serializers.EmailField(write_only=True)
recipient = serializers.EmailField(write_only=True)
message = serializers.CharField(read_only=True, max_length = 500)
def create(self, validated_data):
message = "Message from <{sender}> to <{recipient}>".format(**validated_data)
return Message.objects.create(message=message)
To use different serializers depending on if the serializer will deserialize the data from the request or serialize it for the response, you can use drf-rw-serializers.
It has generic classes to help you build views like the Django REST Framework ones, but, instead of using only one serializer_class, they use a read_serializer_class and a write_serializer_class.
You can read more instruction about installation and usage in the documentation.
You can get a different serializer depending on the method type by overriding the get_serializer_class method of you viewset
class MessageViewSet(viewsets.ModelViewSet):
model = Message
def get_serializer_class(self):
serializers_class_map = {
'default': DefaultSerializer,
'create': CreateMessageSerializer,
}
return serializers_class_map.get(self.action, serializers_class_map['default'])
You can also overrides the to_representation method of your CreateMessageSerializer to get a different output
class CreateMessageSerializer(serializer.ModelSerializer):
class Meta:
model = Message
fields = (
'sender', 'recipient'
)
def to_representation(self, instance):
return {
'message': "Message from <{0.sender}> to <{0.recipient}>".format(instance)
}

Posting a foreign key relationship in Django Rest Framework

In my models, I have the following classes:
class Topic(models.Model):
name = models.CharField(max_length=25, unique=True)
class Content(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
topic = models.ForeignKey(Topic, blank=True, null=True)
My serializers is like this:
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = Topic
fields = ('name')
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=True)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
Alright, so in my urls file, I have the following pattern:
urlpatterns = [
...
url(r'^api/topic_detail/(?P<name>[a-zA-Z0-9-]+)/content_list/$', views.topic_content_list, name='topic_content_list'),
...
]
Therefore, when the user goes to say /api/topic_detail/sports/content_list/, we get a list of all the contents that has the topic of sports. Now what I want is if we POST the following data to the above URL, then a Content object is created with the topic field related automatically to sports.
I am trying to do this the following way in my views:
#api_view(['GET', 'POST'])
def topic_content_list(request, name):
try:
topic = Topic.objects.get(name=name)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
contents = Content.objects.filter(topic=topic)
serializer = ContentSerializer(contents, many=True)
return Response(serializer.data)
elif request.method == 'POST':
request.data["topic"] = topic
serializer = ContentSerializer(data=request.data)
print 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)
Now lets say I go the URL /api/topic_detail/sports/content_list/ and POST this:
{
"title": "My blog post",
"body" : ".....",
}
The content object gets created and the title and body field is set properly. However, the topic field is set to null. How can I fix this? Any help is appreciated.
Also, please don't suggest using generic viewsets, as I am uncomfortable with so many things happening automatically.
EDIT
Alright, so I fixed my dumb mistake :
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=False)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
That is, I set the read_only argument to False. However, now the post creates a new error:
{
"topic": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Topic."
]
}
}
This I am pretty sure refers to the fact that the data.website I'm sending in is not JSON, but instead just a Django model instance. How can I JSONify the single instance?
This is from your serializer.
topic = TopicSerializer(read_only=True)
It means your topic is read only so when you are trying to save your serializer, topic is not getting saved. Remove that and problem would be fixed.
EDIT:
Now as per the second error, it is because it is expecting a dict and you are passing the model instance, so you have two options. Either create the dict by hand.
topic_dict = { "name": topic.name }
and pass that as 'topic' in request.data and then save or give the topic_id, as there is a foreign key relationship, it should work.
So it would be something like:
request.data["topic_id"] = topic.id
Now what you choose to do is totally upto you.
Resurrecting this old thread since it seems to be a common issue people are running into. I just got this working on Django 3.1.6.
Since the Topic and Content models are already linked, the serializer is simply
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
No need for topic = TopicSerializer(read_only=False), which will require you to create a new topic with the POST. Now, the body of the POST can be
{
"title": "My blog post",
"body" : ".....",
"topic": 3
}
If you want your GET output to look like
{
"title": "My blog post",
"body" : ".....",
"topic": {
"id": 3
"name": "announcements"
}
}
override to_representation
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
def to_representation(self, instance):
response = super().to_representation(instance)
response['topic'] = TopicSerializer(instance.topic).data
return response
Credit for proper usage of to_representation goes to this answer by #PdotNJ