DRF use field-value instead of default (pk) in POST request - python-3.x

Trying send valid POST request.
It's works when i send like this:
{
"name":"but",
"sklad":1
}
Response is something like this, all is valid:
{
"name": "but",
"getsklad": "fence"
}
But when I'm trying send this, with 'fence':
{
"name":"but",
"sklad":"fence"
}
Got error:
Invalid type. The primary key value was expected, str was received.
Have this piece of code in models.py:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett'
)
class Zabor(models.Model):
sklad = models.ForeignKey(Sklad, on_delete=models.CASCADE)
name = models.CharField(max_length=10)
...
This in serializers.py:
class ZaborPostSerializer(serializers.ModelSerializer):
class Meta:
model = Zabor
fields = (
...
'name',
'sklad',
)
And Views.py part:
class ZaborView(APIView):
def post(self, request):
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
I know what when i write '1' in request, this means 'pk'. But how I can send name rather than the id/pk?
I am beginner. So don't kick me hard for stupid questions)

Since you are providing the Sklad FOREIGN_KEY, which is unique you can filter by that value only with one type: integer, string, etc.
You can set the storage field of Sklad to the primary key:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett',
primary_key=True
)
Now you can directly filter with your string value.
Another option is to filter in the view:
from django.shortcuts import get_object_or_404
from django.http import Http404
class ZaborView(APIView):
def post(self, request):
dataDict = dict(request.data)
try:
sklad = get_object_or_404(Sklad.objects, storage=dataDict['sklad'][0])
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
except Http404:
return Response({'status': 'Failed'})
Note that it will only work if you have only one Sklad with the given storage field.
If you have multiple ones you can pick the first one, since you do not supply a primary key and you have not other fields to filter:
from django.shortcuts import get_list_or_404
.....
sklad = list(get_list_or_404(Sklad.objects, storage=dataDict['sklad'][0]))[0]
.....
Hope it helps !

Related

Django Rest Framework Field Validation Issue

My subscription view is located inside of UserViewSet. I'm wondering why I'm getting
IntegrityError at /api/users/1/subscribe/
new row for relation "users_subscription" violates check constraint "prevent_self_subscription"
DETAIL: Failing row contains (11, 1, 1).
instead of proper json answer. Somehow SubscriptionSerializer field validation doesnt wish to work. Any thoughts?
models.py
class Subscription(models.Model):
user = models.ForeignKey(
User, related_name='subscriber',
on_delete=models.CASCADE)
author = models.ForeignKey(
User, related_name='subscribing',
on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=('user', 'author'),
name='unique_subscription'
),
models.CheckConstraint(
check=~models.Q(user=models.F('author')),
name='prevent_self_subscription'
)
]
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = models.Subscription
fields = ('author', 'user', )
validators = [
serializers.UniqueTogetherValidator(
queryset=models.Subscription.objects.all(),
fields=['author', 'user', ]
)
]
def create(self, validated_data):
return models.Subscription.objects.create(
user=self.context.get('request').user, **validated_data)
def validate_subscribing(self, value):
if self.context.get('request').user == value:
raise serializers.ValidationError(
'You cant subscribe to yourself!')
return value
views.py
#action(['post'], detail=True)
#permission_classes(permissions.IsAuthenticated)
def subscribe(self, request, *args, **kwargs):
author = get_object_or_404(models.User, id=kwargs['id'])
data = request.data.copy()
data.update({'author': author.id})
serializer = serializers.SubscriptionSerializer(
data=data, context={'request': request})
if request.method == 'POST':
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
status=status.HTTP_201_CREATED,
data=self.get_serializer(author).data)
It was the wrong method name, field name is 'author' and method was validate_subscribing() instead of validate_author().

How to filter Django serializer data?

I'm trying to filter data based on userName in JWT. This is how I've been trying to do it:
views.py:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user)
except ValidationError as v:
print("validation error", v)
return Response(person[0])
This works as I can get the Person data with print("Person: ", person[0]). The return Response(person[0]) however returns an error: TypeError: Object of type Person is not JSON serializable. I guess I could use a serializer class to return a valid JSON, am I right? I have this in my serializers.py:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
I just don't know how to use this with my view. If I use serializer instead of person = Person.objects.filter(userName=request.user), how is the filtering supposed to be done?
Please correct me if I'm not on right track at all.
You can add serializer to view, provide query result to serializer and get serialized data:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = TestSerializer # add serializer
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user).last()
data = self.serializer_class(instance=person).data # serialize query result
except ValidationError as v:
print("validation error", v)
raise v
return Response(data) # return serialized response
Here you can find some examples of using serializer with class based views.
Also note that your Person model might have security-sensitive fields like password, etc, so it's better to specify exact fields you need in serializer rather then use fields = '__all__'.
Are you using Django-Rest-Framework (based on your use of ModelSerializer)?
In pure Django, from the documentation on serializers, you can do something like:
from django.core import serializers
json_response = serializers.serialize("json", person[0])
return Response(json_response)
If you are using Django-Rest-Framework:
return Response(TestSerializer(person[0]).data)

Django Rest Framework DELETE request responds like a GET request

I'm trying to delete an entry in my data base that is returned by a modelviewset get_queryset. When sending a DELETE request through the DRF web interface and via postman, I receive this response "DELETE /api/remove_self/3 HTTP/1.1" 200 along with the data I am trying to delete. The code that gives this result looks like this:
Models.py
class EventAtendee(models.Model):
"""Lists users atending an event"""
#below connects user profile to event
id = models.AutoField(primary_key=True)
event_id = models.IntegerField(null = True)
user_profile = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
def __str__(self):
return self.event_id
views.py
class RemoveSelfFromEvent(viewsets.ModelViewSet):
"""Remove Yourself From an Event you were attending"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.EventAtendeeSerializer
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
#user_profile = self.kwargs['user_profile']
event_id = self.kwargs['event_id']
return models.EventAtendee.objects.filter(event_id=event_id, user_profile=self.request.user)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
urls.py
router = DefaultRouter(trailing_slash=False)
router.register('events', views.EventAtendeeViewSet, basename='EventAtendee')
urlpatterns = [
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
]
Any help is much appreciated!
You are mapping the method DELETE to list in your urls.
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
Correct way to do:
path('remove_self/<pk>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'destroy'})),
mapping of various methods:
POST : create
GET : retrieve
PUT : update
PATCH : partial_update
DELETE : destroy

get() returned more than one Sub_Topic -- it returned 3

I have started a project using Django. Where I used add multiple sub-topics under one main topic by taking staticid. When I am giving same staticid to multiple sub-topics, I am getting the error below (get() returned more than one Sub_Topic -- it returned 3!).
Model:
class Sub_Topic(models.Model):
IMPORTANCE_SCORE = (
('LOW','Low'),
('NORMAL', 'Normal'),
('HIGH','High'),
)
staticid = models.ForeignKey(SID,on_delete=models.CASCADE, blank=True, default=None, null=True)
sub_topic = models.CharField(max_length=250)
Num_Of_Sub_subTopics = models.PositiveIntegerField(default=0)
Num_Of_Questions = models.PositiveIntegerField(default=0)
importance = models.CharField(max_length=6, choices= IMPORTANCE_SCORE, default='LOW')
complexity = models.PositiveIntegerField(default=0)
prerequisite = models.CharField(max_length=250)
def __str__(self):
return self.sub_topic
View:
class Sub_TopicDetailView(generics.RetrieveUpdateDestroyAPIView):
"""
GET sub_topic/:id/
PUT sub_topic/:id/
DELETE sub_topic/:id/
"""
queryset = Sub_Topic.objects.all()
serializer_class = Sub_TopicSerializer
def get(self, request, *args, **kwargs):
try:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
return Response(Sub_TopicSerializer(a_sub_topic).data)
except Sub_Topic.DoesNotExist:
return Response(
data={
"message": "Sub_Topic with id: {} does not exist".format(kwargs["staticid"])
},
status=status.HTTP_404_NOT_FOUND
)
#validate_request_data
def put(self, request, *args, **kwargs):
try:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
serializer = Sub_TopicSerializer()
updated_sub_topic = serializer.update(a_sub_topic, request.data)
return Response(Sub_TopicSerializer(updated_sub_topic).data)
except Sub_Topic.DoesNotExist:
return Response(
data={
"message": "Sub_Topic with id: {} does not exist".format(kwargs["staticid"])
},
status=status.HTTP_404_NOT_FOUND
)
Error:
get() returned more than one Sub_Topic -- it returned 3!
How do I overcome this?
If you have a main topic (say, "donuts"), and many subtopics within that ("plain donuts", "chocolate donuts", "vanilla donuts", ...), you cannot reference a subtopic by just saying "donuts", you have to be more specific.
Your sub-topic views should accept a sub-topic ID, not the main topic ID. Try changing this:
a_sub_topic = self.queryset.get(staticid=kwargs["staticid"])
# 'staticid' is the foreign key of the main topic: it is
# the same for many sub-topics!
to this:
a_sub_topic = self.queryset.get(id=kwargs["id"])
# 'id' is the primary key field generated automatically by Django:
# it's unique for every sub-topic
If instead you want to display all sub-topics for a given topic, then you should use filter() instead of get():
sub_topics = self.queryset.filter(staticid=kwargs["staticid"])

How does one properly create a customized Swagger Schema in the Django Rest Framework?

I am having trouble creating a customizable swagger schema in the Django Rest Framework. I have read pages of documentation, but have not found a clear cut example on how to generate swagger annotations in python.
I am aware that swagger/schema documentation is readily generated when using ViewSets in Django. However, I am solely using APIViews and want to write a customized schema. I have tried creating a CoreAPI schema but am unaware of how to implement it. I am enclosing some of my sample code and some screenshots as well. The screen shots go from what I have to what I want.
Sample code:
urls.py
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from Views import SampleView as sv
from rest_framework_swagger.views import get_swagger_view
from rest_framework.documentation import include_docs_urls
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.schemas import get_schema_view
schema_view enter code here= get_swagger_view(
title='Sample API')
urlpatterns = [
url(r'^sample/$', pv.SampleList.as_view()),
url(r'^sample/(?P<id>[a-f\d]{24})/$', sv.SampleDetail.as_view()),
url('^schema/$', schema_view),
]
urlpatterns = format_suffix_patterns(urlpatterns)
views.py
from rest_framework.views import APIView
from Manager.SampleManager import SampleManager as sm
_sampleManager = sm()
class SampleList(APIView):
"""
get:
Return a list of all the existing samples.
post:
Create a new sample.
"""
def get(self, request, format=None):
return _sampleManager.getAll()
def post(self, request, format=None):
return _sampleManager.create( request)
class SampleDetail(APIView):
"""
get:
Get a sample.
put:
Update a sample.
delete:
Delete a sample.
"""
def get(self, request, id, format =None):
return _sampleManager.getById( id)
def put(self, request, id, format =None):
return _sampleManager.update( request, id)
def delete(self, request, id, format =None):
return _sampleManager.deleteById( id)
Serializers.py
from rest_framework_mongoengine.serializers import DocumentSerializer
from .modles import Sample, SampleInner
from Serializers.SampleInnerSerializer import SampleInnerSerializer
class SampleSerializer(DocumentSerializer):
other = SampleInnerSerializer(many=True)
class Meta:
model = Sample
fields = '__all__'
def create(self, validated_data):
samples = validated_data.pop('other')
created_instance = super(SampleSerializer, self).create(validated_data)
for sample_data in samples:
created_instance.other.append(SampleInner(**sample_data))
created_instance.save()
return created_instance
def update(self, instance, validated_data):
samples = validated_data.pop('other')
updated_instance = super(SampleSerializer, self).update(instance, validated_data)
for sample_data in samples:
updated_instance.other.append(SampleInner(**sample_data))
updated_instance.save()
return updated_instance
Schema.py
import coreapi
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import renderers, response
schema = coreapi.Document(
title='Sample API',
content={
'sample': coreapi.Link(
url='/sample/',
action='post',
fields=[
coreapi.Field(
name='from',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='to',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='date',
required=True,
location='query',
description='Flight date in "YYYY-MM-DD" format.'
)
],
description='Create partner'
)
}
)
#api_view()
#renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
return response.Response(schema)
There is two solution for you in this senario, One "go with the GenricApiView" Two "Create Custom row Schema"
let's go with
-- >solution one.
urls.py
schema_view = get_swagger_view(title='Test All API')
urlpatterns = [
path('swagger2/', schema_view),
]
view.py
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Result
--> Solution two:
urls.py
configuration is same as before
views.py
class BlacklistTokenAdding(APIView):
permission_classes = [permissions.AllowAny]
schema = ManualSchema(fields=[
coreapi.Field(
"first_field",
required=True,
location="body",
schema=coreschema.String()
),
coreapi.Field(
"second_field",
required=True,
location="body",
schema=coreschema.String()
),
])
def post(self, request, format='json'):
try:
refresh_token = request.data["refresh_token"]
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
//Note the first_field & second_field is to demonstration you can add here as much field as you want.
Result

Resources