I'm trying to update an object of my database (but only one of the field), the problem is that when I try to make the update i get an error that says that the PUT method is not allowed.
Here's my View:
class DeviceViewSet(viewsets.ModelViewSet):
"""
Show, create and filter devices.
"""
queryset = Device.objects.all()
serializer_class = DeviceSerializer
def list(self, request, *args, **kwargs):
devices = Device.objects.filter(user=request.user.pk, role='E')
serializer = DeviceSerializer(devices, many=True)
return Response(serializer.data)
def create(self, request, *args, **kwargs):
data = {
'registration_id': request.data['regId'], 'user': request.user.pk, 'device_id': request.data['imei'],
'type': 'android', 'label': request.data['label'], 'role': request.data['role']
}
serializer = DeviceSerializer(data=data)
if serializer.is_valid():
serializer.save()
device = Device.objects.filter(device_id=request.data['imei'])
device.send_message("Enhorabuena!", "El dispositivo se ha registrado correctamente.")
return Response(serializer.data)
return Response(serializer.errors)
def update(self, request, *args, **kwargs):
device = Device.objects.filter(device_id=request.data['imei'])
device.registration_id = request.data['regId']
device.save()
serializer = DeviceSerializer(device)
return Response({'ok': 'oks'})
My serializer:
class DeviceSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
class Meta:
model = Device
fields = '__all__'
My url:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from decaught import views
urlpatterns = [
url(r'^devices/$', views.DeviceViewSet),
]
urlpatterns = format_suffix_patterns(urlpatterns)
I'm using Postman to send a PUT Request:
Any idea what is wrong?
When PUTting the resource identifier should be in the URL (pk). PUT request is idempotent.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT
In the DRF documentation, pk is passed as an argument to the update method
def update(self, request, pk=None):
pass
http://www.django-rest-framework.org/api-guide/viewsets/#viewset-actions
Instead of passing it as a key:value pair and accessing it through request.data PUT call should be like
localhost:8000/devices/<PK-HERE>/
(sorry for not so good english)
The error is in our URL. You need to select some device to PUT information. Try with localhost:8000/devices/1/. I'm assuming that your API take objects by pk
Related
I know that there is something wrong with my urls. But I'm unable to figure it out.
models.py
class Restaraunt(models.Model):
name=models.CharField(max_length=50,blank=True,null=True)
class Schedule(models.Model):
restaraunt=models.ForeignKey(Restaraunt, on_delete=models.CASCADE,related_name='restaraunt_name')
#days=models.CharField(choices=DAYS,max_length=255)
opening_time=models.TimeField(auto_now=False,auto_now_add=False)
closing_time=models.TimeField(auto_now=False,auto_now_add=False)
def __str__(self):
return str(self.restaraunt)
class Restarasunt(viewsets.ViewSet):
def create(self,request):
try:
name=request.data.get('name')
if not name:
return Response({"message": "name is rerquired!","success":False},
status=status.HTTP_200_OK )
res_obj=Restaraunt()
res_obj.name=name
print(res_obj.name)
res_obj.save()
return Response("Restaurant addedd successfully")
except Exception as error:
traceback.print_exc()
return Response({"message":str(error),"success":False},status = status.HTTP_200_OK)
class ScheduleViewSet(viewsets.ViewSet):
def create(self,request,pk):
try:
res_obj=Restaraunt.objects.filter(pk=pk)
print('hie',res_obj)
data=request.data
opening_time=data.get('opening_time')
closing_time=data.get('closing_time')
sce_obj=Schedule()
sce_obj.opening_time=opening_time
sce_obj.closing_time=closing_time
sce_obj.restaraunt=res_obj
sce_obj.save()
return Response("")
except Exception as error:
traceback.print_exc()
return Response({"message":str(error),"success":False},status = status.HTTP_200_OK)
URLS.PY
from rest_framework.routers import DefaultRouter
from auth1 import views
router=DefaultRouter()
router.register(r'retaraunt', views.Restarasunt, basename='Restarasunt')
router.register(r'Timings', views.ScheduleViewSet, basename='ScheduleViewSet')
urlpatterns = router.urls
As showed in the documentation you need to add retrieve method for your class
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
I don't think that the create view is meant to be used with "pk". Can you try getting the "pk" value from request.data and use it to get the Restaraunt object
I have a view with some extra actions:
class MonthsViewSet(ModelViewSet):
authentication_classes = (TokenAuthentication,)
def get_queryset(self):
query_set = Month.objects.filter(user=self.request.user)
return query_set
serializer_class = MonthSerializer
#swagger_auto_schema(
manual_parameters=[AUTH_HEADER_PARAM, MonthParameters.DATE, MonthParameters.DAYS, MonthParameters.FARM])
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
#action(detail=True)
def get_next_year(self, *args, **kwargs):
"""
Return the next 12 months.
"""
first_month, last_month = get_12_months(last=False)
query_set = self.get_queryset().filter(date__range=(first_month, last_month))
serializer = MonthSerializer(query_set, many=True)
return Response(serializer.data, status.HTTP_200_OK)
#action(detail=True)
def get_last_year(self, *args, **kwargs):
"""
Return the last 12 months available.
"""
first_month, last_month = get_12_months(last=True)
print(first_month, last_month)
query_set = self.get_queryset().filter(date__range=(first_month, last_month))
serializer = MonthSerializer(query_set, many=True)
return Response(serializer.data, status.HTTP_200_OK)
And I'm using the default router in my url:
months_router = DefaultRouter()
months_router.register('months', MonthsViewSet, 'months')
urlpatterns = [
path('', include(months_router.urls)),
]
So currently this is my URL:
/months/{date}/get_last_year/
the date is the primary key in my model.
Is there any way to change the action decorator settings to NOT use the primary key?
so the URL would become:
/months/get_last_year/
From the DRF doc,
Like regular actions, extra actions may be intended for either a single object, or an entire collection. To indicate this, set the detail argument to True or False. The router will configure its URL patterns accordingly.
set detail=False in your decorator.
#action(detail=False)
def get_last_year(self, *args, **kwargs):
# res of your code
I'm looking for how to make a GET in django swagger framework by refClient (a unique CharField in my client's model).
I found on the internet that I have to customize the routers, I have that as routers :
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
schema_view = get_schema_view(
title='Swagger documentation',
renderer_classes = [OpenAPIRenderer, SwaggerUIRenderer],
)
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'clients/{refClient}', ClientViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^admin/', admin.site.urls),
# To show the swagger documentation
url(r'^swagger/', schema_view, name="docs"),
url(r'^api/v1/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
But I got this error
ValueError: invalid literal for int() with base 10: 'refClient'
[21/Jul/2017 15:25:22] "GET /swagger/ HTTP/1.1" 500 133494
Should I add something to my serializers's configuration
class ClientSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Client
fields = ('refClient', 'nom', 'prenom')
Or to my views ?
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
def get_queryset(self):
""" GET : get all clients """
return Client.objects.all()
def create(self, request):
""" POST : Create a Client object """
return super(ClientViewSet, self).create(request)
def retrieve(self, request, pk=None):
""" GET : Returns a single client item """
return super(ClientViewSet, self).retrieve(request, pk)
def update(self, request, *args, **kwargs):
""" PUT : Updates a single client item """
return super(ClientViewSet, self).update(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
""" PATCH : Partiel update a client """
return super(ClientViewSet, self).partial_update(request, *args, **kwargs)
def destroy(self, request, pk=None):
""" DELETE : Delete a client """
return super(ClientViewSet, self).destroy(request, pk)
Basically how I can customize my swagger ?
By default django rest frameworks uses this
lookup_field = 'pk'
lookup_url_kwarg = None
You can override this in your ClientViewSet class, change to
lookup_field = 'refClient'
However if you want to support both lookups, either by 'pk' or by 'refClient' I suggest to have a different endpoint for the later one (eg. /api/client_url/ref/), or add filtering option in your list view (eg. /api/client_url?refClient='something')
In the latest version on Django REST Swagger (2.1.0) YAML docstrings have been deprecated. I cannot get swagger to show the POST request parameters.
Here is my view
class UserAuthenticationView(APIView):
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
Here is my Serializer
class UserAuthenticationSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
msg = 'User account is disabled.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Unable to log in with provided credentials.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Must include "username" and "password".'
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
This is what I get in my generated
I do not get a form with the fields for the POST data. How do I get that?
django-rest-swagger uses rest_framework.schemas.SchemaGenerator to generate the schema and SchemaGenerator uses get_serializer_fields to get the serializer information of a view. get_serializer_fields checks if a view has a get_serializer method to generate the form. GenericAPIView provides the get_serializer so inheriting from it is enough.
Inherit view from GenericAPIView rather than simple APIView. And add serializer_class attribute with appropriate serializer
from rest_framework.generics import GenericAPIView
class UserAuthenticationView(GenericAPIView):
serializer_class = UserAuthenticationSerializer
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
This is the rest framework get schema code (the part of it):
def get_serializer_fields(self, path, method, view):
"""
Return a list of `coreapi.Field` instances corresponding to any
request body input, as determined by the serializer class.
"""
if method not in ('PUT', 'PATCH', 'POST'):
return []
if not hasattr(view, 'get_serializer'):
return []
serializer = view.get_serializer()
if isinstance(serializer, serializers.ListSerializer):
return [
coreapi.Field(
name='data',
location='body',
required=True,
type='array'
)
]
...
As you can see - it should work if you define the get_serializer method on your view - which returns the UserAuthenticationSerializer.
-- EDIT --
Forget: Happy Coding.
A working example for a custom ViewSet, with django-rest-swagger==2.2.0:
from rest_framework import viewsets
from rest_framework.schemas import AutoSchema
from rest_framework.compat import coreapi, coreschema
from rest_framework.decorators import action
class DeviceViewSchema(AutoSchema):
"""
Schema customizations for DeviceViewSet
"""
def get_manual_fields(self, path, method):
extra_fields = []
if path.endswith('/send_command/'):
extra_fields = [
coreapi.Field(
"command",
required=True,
location="form",
schema=coreschema.String()
),
coreapi.Field(
"params",
required=False,
location="form",
schema=coreschema.String()
),
]
manual_fields = super().get_manual_fields(path, method)
return manual_fields + extra_fields
class DeviceViewSet(viewsets.ViewSet):
lookup_field = 'channel'
lookup_value_regex = '[\w-]+'
schema = DeviceViewSchema()
#action(methods=['post'], detail=True, url_name='send_command')
def send_command(self, request, channel):
"""
Send command to device
Parameters:
- command: string
- params: string (JSON encoded list or dict)
"""
...
The final result is:
I would like to save and update multiple instances using the Django Rest Framework with one API call. For example, let's say I have a "Classroom" model that can have multiple "Teachers". If I wanted to create multiple teachers and later update all of their classroom numbers how would I do that? Do I have to make an API call for each teacher?
I know currently we can't save nested models, but I would like to know if we can save it at the teacher level.
Thanks!
I know this was asked a while ago now but I found it whilst trying to figure this out myself.
It turns out if you pass many=True when instantiating the serializer class for a model, it can then accept multiple objects.
This is mentioned here in the django rest framework docs
For my case, my view looked like this:
class ThingViewSet(viewsets.ModelViewSet):
"""This view provides list, detail, create, retrieve, update
and destroy actions for Things."""
model = Thing
serializer_class = ThingSerializer
I didn't really want to go writing a load of boilerplate just to have direct control over the instantiation of the serializer and pass many=True, so in my serializer class I override the __init__ instead:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
Posting data to the list URL for this view in the format:
[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
Created two resources with those details. Which was nice.
I came to a similar conclusion as Daniel Albarral, but here's a more succinct solution:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
Here's another solution, you don't need to override your serializers __init__ method. Just override your view's (ModelViewSet) 'create' method. Notice many=isinstance(request.data,list). Here many=True when you send an array of objects to create, and False when you send just the one. This way, you can save both an item and a list!
from rest_framework import status, viewsets
from rest_framework.response import Response
class ThingViewSet(viewsets.ModelViewSet):
"""This view snippet provides both list and item create functionality."""
#I took the liberty to change the model to queryset
queryset = Thing.objects.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
I couldn't quite figure out getting the request.DATA to convert from a dictionary to an array - which was a limit on my ability to Tom Manterfield's solution to work. Here is my solution:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
queryset = myModels\
.Thing\
.objects\
.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
self.user = request.user
listOfThings = request.DATA['things']
serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
if serializer.is_valid():
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And then I run the equivalent of this on the client:
var things = {
"things":[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)
I think the best approach to respect the proposed architecture of the framework will be to create a mixin like this:
class CreateListModelMixin(object):
def create(self, request, *args, **kwargs):
"""
Create a list of model instances if a list is provided or a
single model instance otherwise.
"""
data = request.data
if isinstance(data, list):
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Then you can override the CreateModelMixin of ModelViewSet like this:
class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
...
...
Now in the client you can work like this:
var things = [
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)
or
var thing = {
'loads':'foo','of':'bar','fields':'buzz'
}
thingClientResource.post(thing)
EDIT:
As Roger Collins suggests in his response is more clever to overwrite the get_serializer method than the 'create'.
You can simply overwrite the get_serializer method in your APIView and pass many=True into get_serializer of the base view like so:
class SomeAPIView(CreateAPIView):
queryset = SomeModel.objects.all()
serializer_class = SomeSerializer
def get_serializer(self, instance=None, data=None, many=False, partial=False):
return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
I came up with simple example in post
Serializers.py
from rest_framework import serializers
from movie.models import Movie
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
Views.py
from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
class MovieList(generics.ListCreateAPIView):
queryset = Movie.objects.all().order_by('-id')[:10]
serializer_class = MovieSerializer
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = self.get_queryset()
serializer = MovieSerializer(queryset, many=True)
return Response(serializer.data)
def post(self, request, format=None):
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(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)
These line are the actual logic of Multiple Instance -
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
If you are confused with many=True, see this
When we send data it will be inside list somewhat like this -
[
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
},
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
}
]
The Generic Views page in Django REST Framework's documentation states that the ListCreateAPIView generic view is "used for read-write endpoints to represent a collection of model instances".
That's where I would start looking (and I'm going to actually, since we'll need this functionality in our project soon as well).
Note also that the examples on the Generic Views page happen to use ListCreateAPIView.
Most straightforward method I've come across:
def post(self, request, *args, **kwargs):
serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)