I'm trying to create a route in Django REST Framework so that I can access a comment from the object it is related to.
My models are Comment, User and Marker, and each marker can have one comment per user.
What I would like is a way to do GET /comments/marker/{marker-pk}/ that would return the comment that the connected user left on that marker, if any.
Right now I have GET /comments/{comment-pk}/ which is the default, and if I use a #detail_route decorator on a custom method I'll only have access to comments but not by marker.
My viewset:
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = MarkerCommentSerializer
So I figured out how to do this while I was writing the question. Not sure if it is very idiomatic or RESTful though...
I added a new route:
router.register(r'comments/marker', maps_views.CommentByMarkerViewSet, base_name="comments/marker")
And a new ViewSet:
class CommentByMarkerViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def retrieve(self, request, pk=None):
comment = get_object_or_404(Comment, user=request.user, marker__pk=pk)
serializer = self.get_serializer(comment)
return Response(serializer.data)
Now I can access the comment at /comments/marker/{marker-pk}/.
Related
In my API, I have a create view tied that references another record OneToOne. However, occasionally it seems that users send through two requests at once and the second fails due to a duplicate record clash:
class CreateProfileLink(generics.CreateAPIView):
def perform_create(self, serializer):
ins = serializer.save(user=self.request.user)
serializer_class = ProfileLinkSerializer
Is there a way I could override the create method to return the record if it already exists rather than creating it?
You could use get_or_create in your serializer class, by overriding its create() method:
class ProfileLinkSerializer(serializers.ModelSerializer):
...
class Meta:
model = Profile
fields = (...)
def create(self, validated_data):
profile, _ = Profile.objects.get_or_create(**validated_data)
return profile
Since you haven't provided your models.py, I am using Profile as a model name here. Make sure to replace it if it is different in your project.
How to perform crud operation in One URL End point in django rest framework?
Currently i am having 2 url end points
url(r'^recipient/$', views.RecipientView.as_view()), # in this APiview im performing get all and post
url(r'^recipient/(?P<pk>[0-9]+)/$', views.RecipientDetail.as_view()), # in this APiview im performing retrieve, update delete.
Now the requirement is i have remove 2nd url and perform all operations in first api view?
I am new to django framework can anyone please help me achieve this?
Below is my code.
View.py
class RecipientView(APIView):
def get(self, request, format=None):
Recipients = Recipient.objects.all()
serializer = RecipientSerializer(Recipients, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = RecipientSerializer(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)
"""
class RecipientDetail(APIView):
def get_object(self, pk):
try:
return Recipient.objects.get(pk=pk)
except Recipient.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
Recipient = self.get_object(pk)
serializer = RecipientSerializer(Recipient)
return Response(serializer.data)
def put(self, request, pk, format=None):
Recipient = self.get_object(pk)
serializer = RecipientSerializer(Recipient, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
Recipient = self.get_object(pk)
Recipient.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
"""
model.py
class Recipient(models.Model):
recipient = models.CharField(max_length=32, blank=False, null=False)
def __str__(self):
"""returns the model as string."""
return self.racipi
ent
serializer.py
class RecipientSerializer(serializers.ModelSerializer):
class Meta:
model = Recipient
fields = '__all__'
I am not able to update and delete in the same view please needed help?
You can avoid passing the ID in the URL with a POST request. Supply the ID and some kind of "action" verb, e.g. action=delete in the body of the request.
That's not considered RESTful though, partly because the HTTP DELETE and PUT verbs perfectly describes the requested operations, but also because POST is considered a non-idempotent method, meaning that the server state will change with each successful request. Being idempotent, duplicate DELETE/PUT (and GET for that matter) requests will leave the server in the same state.
It's not a major hassle to have a second route and view to implement the REST API so it's best to leave it as it is.
Your 2nd URL is receiving a parameters that can be used to fetch data object from the database and then perform any action on that particular instance. If you see the class RecipientDetail, you'll see all the methods are accepting a parameter called pk that relates to the object you want to fetch from database.
But your 1st URL is for generic actions like Create New Object or List All Objects and it is not good to use these endpoints to do instance specific actions.
You can read more about REST API standard enpoints to know details. Here is a reference link:
https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
The easiest way is to use DRF's ViewSet. It already provides the basic CRUD operation for you so you can just create the view in something like this:
# views.py
from rest_framework import viewsets
from .models import Recipient
from .serializers import RecipientSerializer
class RecipientViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing recipient instances.
"""
serializer_class = RecipientSerializer
queryset = Recipient.objects.all()
Since we are using ModelViewSet, it already provides actions like get, list, update, etc. as you can see in the documentation.
You can then use routers in your urls.py like this:
# urls.py
from rest_framework.routers import DefaultRouter
from myapp.views import RecipientViewSet
router = DefaultRouter()
router.register(r'recipients', RecipientViewSet)
urlpatterns = router.urls
The url above will generate a url that is like what you wrote in you question:
# Add recipient
POST /recipients/
# Get list of recipients
GET /recipients/
# Get recipient detail
GET /recipients/:recipient_id/
# Update recipient
PUT/PATCH /recipients/:recipient_id/
# Delete recipient
DELETE /recipients/:recipient_id/
Please take note that this is a simplified version and you can even create your own urls pattern with your specified actions.
UPDATE:
Thanks to mhawke for clarification. As what mhawke said in the comment, this may not be what the OP wanted, if you just want to avoid passing the ID in the url, then you can follow mawke's answer, and yes it is not considered RESTful.
I have a setup similar to this - a Cookbook class, which has multiple Recipes.
I have a
class CookbookListCreateView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Cookbook.objects.all()
serializer_class = CookbookSerializer
and this handles creating / listing the cookbooks.
I need a ListCreateView for the Recipe model but the list must belong to a specific cookbook, in such a way that this url:
/cookbook/2/recipes
would return only recipes found in a cookbook with pk of 2.
How can I modify ListCreateAPIView to follow this behavior?
You can create a new route/url:
/cookbook/<cookbook_pk>/recipes
And an api view as you want:
class RecipeListCreateView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
def get_cookbook(self):
queryset = Cookbook.objects.all()
return get_object_or_404(queryset, pk=self.kwargs['cookbook_pk'])
def get_queryset(self):
cookbook = self.get_cookbook()
return super().get_queryset().filter(cookbook=cookbook)
def perform_create(self, serializer):
cookbook = self.get_cookbook()
serializer.save(cookbook=cookbook)
Use get_cookbook whenever you need the cookbook (eg. in perform_create method as above)
That's what called a "Detail Route" in DRF.
class CookbookListCreateView(ListCreateAPIView):
....
#detail_route(methods=['get'])
def recipes(self, request, **kwargs):
# Do what you would do in a function-based view here
It will suffice for simple cases but in more complex views using nested route functionality of DRF-extensions is a better solution.
I am new to DRF, and I had came across the following problem when I trying to customize the Permission in DRF.
Suppose I had the following code in my permissions.py file:
class GetPermission(BasePermission):
obj_attr = 'POITS'
def has_permission(self, request, view):
user = request.user
employee = Employee.objects.get(user=user)
# Return a dict which indicates whether the request user has the corresponding permissions
permissions = get_permission(employee.id)
return permissions[GetPermission.obj_attr]
And in my view, I want to override the static variable in the GetPermission class:
class AssignmentList(generics.ListCreateAPIView):
GetPermission.obj_attr = 'ASSIGNMENT'
permission_classes = (IsAuthenticated, IsStaff, GetPermission)
queryset = Assignment.objects.all()
serializer_class = AssignmentSerializer
pagination_class = LargeResultsSetPagination
def perform_create(self, serializer):
employee = Employee.objects.get(user=self.request.user)
serializer.save(sender=employee, status=0, operatable=0)
However, as the documentation of DRF points out:
Permission checks are always run at the very start of the view, before any other code is allowed to proceed.
So how am I supposed to do, thanks in advance, any ideas will be welcomed, since I am a new fish to DRF.
You need to create subclasses of your permission for each attribute, and use self.obj_attr inside the has_permission method.
class DefaultPermission(GetPermission):
obj_attr = 'POITS'
class AssignmentPermission(GetPermission):
obj_attr = 'ASSIGNMENT'
I am using django-rest-framework generic views to create objects in a model via POST request. I would like to know how can I return the id of the object created after the POST or more general, any additional information about the created object.
This is the view class that creates (and lists) the object:
class DetectorAPIList(generics.ListCreateAPIView):
serializer_class = DetectorSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
parser_classes = (MultiPartParser, FileUploadParser,)
def pre_save(self, obj):
obj.created_by = self.request.user.get_profile()
def get_queryset(self):
return (Detector.objects
.filter(get_allowed_detectors(self.request.user))
.order_by('-created_at'))
The model serializer:
class DetectorSerializer(serializers.ModelSerializer):
class Meta:
model = Detector
fields = ('id', 'name', 'object_class',
'created_by', 'public', 'average_image', 'hash_value')
exclude = ('created_by',)
Thanks!
Here, DetectorSerializer inherits from ModelSerializer as well as your view inherits from generics ListCreateAPIView so when a POST request is made to the view, it should return the id as well as all the attributes defined in the fields of the Serializer.
Because it took me a few minutes to parse this answer when I had the same problem, I thought I'd summarize for posterity:
The generic view ListCreateApiView does return the created object.
This is also clear from the documentation listcreateapiview: the view extends createmodelmixin, which states:
If an object is created this returns a 201 Created response, with a serialized representation of the object as the body of the response.
So if you have this problem take a closer look at your client side!
post$.pipe(tap(res => console.log(res)))
should print the newly created object (assuming rxjs6 and ES6 syntax)
As mentioned above, To retrieve the id for the new created object, We need to override the post method, find the the update code for more details:
class DetectorAPIList(generics.ListCreateAPIView):
serializer_class = DetectorSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
parser_classes = (MultiPartParser, FileUploadParser,)
def post(self, request, format=None):
serializer = DetectorSerializer(data=request.data)
if serializer.is_valid():
obj = serializer.save()
return Response(obj.id, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)