I'm facing a issue with setting up a few generic views on this same certain URL. I would like to have a modeled my API as below:
GET /cars/ <- car list
POST /cars/ <- add new car
GET /cars/uuid/ <- get particular car
PUT /cars/uuid/ <- edit particular car
DELETE /cars/uuid/ <- delete specific car
So finally my issue is that the first include in urlconfig is able to perform the HTTP method, on rest of them I'm always getting "Method Not Allowed:". In above example it's occurs for PUT and DELETE. Is it possible to resolve URL as above or should I add prefixes like /cars/get/uuid?
urls.py
urlpatterns = [
path('', CarListApi.as_view(), name='list'),
path('', CarCreateApi.as_view(), name='create'),
path('<uuid:uuid>/', CarDeleteApi.as_view(), name='delete'),
path('<uuid:uuid>/', CarUpdateApi.as_view(), name='update'),
path('<uuid:uuid>/', CarDetailApi.as_view(), name='detail'),
]
partial views.py
class CarUpdateApi(generics.UpdateAPIView,
generics.GenericAPIView):
permission_classes = (IsOwnerOrReadOnly,)
lookup_field = 'uuid'
http_method_names = ['delete', 'get', 'post', 'put']
#extend_schema_serializer(component_name="CarUpdateInputSerializer")
class InputSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ['plate', 'make', 'model', 'uuid']
#extend_schema(request=InputSerializer, responses={201: InputSerializer})
def put(self, request, uuid):
serializer = self.InputSerializer(data=request.data, context={'request': request})
if serializer.is_valid(raise_exception=True):
car = car_update(serializer.validated_data, uuid)
return Response(self.InputSerializer(car).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CarDeleteApi(generics.DestroyAPIView,
generics.GenericAPIView):
http_method_names = ['delete', 'get', 'post', 'put']
#extend_schema_serializer(component_name='CarDeleteInputSerializer')
class InputSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ['uuid']
def get_serializer_class(self):
return self.InputSerializer
def delete(self, request, uuid, *args, **kwargs):
serializer = self.InputSerializer(data=request.data)
if serializer.is_valid(raise_exception=True) and car_delete(uuid):
return Response(status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
Any ideas how to fix this issue?
The url pattern has to have only the urls, and in the views you separated the business logic for the http methods. On urls.py:
urlpatterns = [
path('', CarApi.as_view(), name='car'),
path('<uuid:uuid>/', SpecificApi.as_view(), name='specific'),
]
and then in the views, something like:
class CarApi(APIView):
def get(self, request):
# logic for get and return car list
def post(self, request):
# logic for add card
Related
i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see
I have a model named Article and a few api views for it. They are divided for diffrent purposes (for example ArticleUpdateAPI class for UPDATE http method, ArticleDeleteAPI for DELETE method etc). In urls.py they are separated to diffrent endpoints (aritcle/pk/update, /article/pk/delete etc).
As I know, it's not good practice to build endpoint like this, so I want to bind them to single url and use diffrent classes for handling diffrent http methods. Is it possible and how? Examples are below
ArticleAPI.py
class ArticlePostAPI(generics.CreateAPIView):
serializer_class = ArticleSerializer
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
"comment": CommentSerializer.data
}, status=201)
class ArticleRetrieveAPI(generics.RetrieveAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.AllowAny
]
class ArticleListAPI(generics.ListAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.order_by('number', 'headline')
permission_classes = [
permissions.AllowAny
]
class ArticleUpdateAPI(generics.UpdateAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.IsAuthenticated
]
lookup_field = 'pk'
def update(self, request, *args, **kwargs):
instance = self.get_object()
if request.user != instance.author:
return Response({
"errors": "Logged in user and author must be same"
}, status=403)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
urls.py
urlpatterns = [
...
# Article API
path('article/post/', ArticlePostAPI.as_view(), name='article_creation'),
path('article/<int:pk>/', ArticleRetrieveAPI.as_view(), name='article_retrieve'),
path('article/', ArticleListAPI.as_view(), name='article_list'),
path('article/<int:pk>/update/', ArticleUpdateAPI.as_view(), name='article_update'),
]
Use ModelViewSet for simplifying it.
Using this you can perform all http method actions with a single url and single view.
from rest_framework import viewsets
class ArticleView(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [permissions.AllowAny]
urls.py
router = DefaultRouter()
router.register(r'articles', views.ArticleView, basename='articles')
here
GET - /articles/ - gives list of all articles
GET - /articles/id/ - gives one object
POST - /articles/ - creates an article
PUT - /articles/id/ - updates an article
PATCH - /articles/id/ - partially updates an article
DELETE - /articles/id/ - deletes an article
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
This is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, location=self.request.user.userextended.location)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self,
'location': self.request.user.userextended.location
}
#detail_route(methods=['post'], permission_classes=[IsFromLocationOrReadOnly])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
This is my router / urls.py:
router = routers.DefaultRouter()
router.register(r'posts', views.PostViewSet)
So when I go to /posts I get a list of all posts. What I want is to be able to allow the end-user to go to a specific URL like so: /posts/username and when he does, I want to give him all the posts of that specific username (the filtering will be simple. Something along these lines:
queryset = Post.objects.filter(username=usernameProvidedByTheURL)
How do I go about doing this? Is it possible using DRF?
In your url:
url(r'^/post/(?P<username>\w+)/?$', PostViewSet.as_view({'get': 'list'})),
Then in your PostViewSet, overwrite the get_queryset() method to filter the data by username
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
UPDATE
If you want to keep /post/ endpoint to retrieve all post. Then you need to create an extra view to handle /post/username
class PostsListByUsername(generics.ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
Then in your urls.py
url(r'^/post/(?P<username>\w+)/?$', PostsListByUsername.as_view()),
Note:
In your get_serializer_context method, you don't need to return request, format and view. DRF will append it for you.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'location': self.request.user.userextended.location
}
I'm quite new to Django & Tastypie. I would like to return only one of the objects from the query. I've tried almost everything and cannot seem to find the solution. Here is my code below:
class ProfileResource(ModelResource):
person = fields.ForeignKey(UserResource, 'user', full=True)
class Meta:
queryset = Person.objects.all()
resource_name = 'profile'
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
serializer = Serializer(formats=['json'])
Now the part I'm having trouble with is how can I return a single user object from a single resource using request.user.
If you only want to show one resource I would probably create new resource view (named as my_profile) that would call normal detail view with user in kwargs and removed other urls:
from django.conf.urls import url
from tastypie.utils import trailing_slash
class ProfileResource(ModelResource):
...
def base_urls(self):
return [
url(r"^(?P<resource_name>%s)%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('dispatch_my_profile'), name="api_dispatch_my_profile")
]
def dispatch_my_profile(self, request, **kwargs):
kwargs['user'] = request.user
return super(ProfileResource, self).dispatch_detail(request, **kwargs)