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.
Related
Viewsets are convenient because we can do stuff like this and get a fully working serializer:
class StoreObjectViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes = [IsAuthenticated]
queryset = StoreObject.active_objects.all()
serializer_class = serializers.StoreObjectSerializer
Unfortunately, as far as I know– to pass the context into the serializer we need to do things like this:
PostSerializer(data=request.data, context={'request': request})
Which means we need to manually override every convenient method provided by ViewSets (as far as I know). Is there a way to inject the context into every serializer while still keeping Viewsets convenient?
By default, request is being sent to any Generic View and ViewSet. You can check the source code in GitHub as well. So you do not have to inject them in every view. If you want to pass extra context, then override get_serializer_context(...) method:
class StoreObjectViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
...
def get_serializer_context(self):
context = super().get_serializer_context()
context['custom_context'] = 'Your custom context'
return context
i'm using django rest framework to build an api, here is my problem
url(r'^profiles/(?P<pk>[0-9]*)', ProfileRetrieveView.as_view(), name='profiles-detail'),
url(r'^profiles/(?P<pk>[0-9]*)', ProfileUpdateView.as_view(), name='profiles-update'),
class ProfileRetrieveView(RetrieveAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileUpdateView(UpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
When i query the api with the link /profile/2 and the method patch, i receive 405, method not allowed, only the get method is allowed, how can i solve that without haven to transform my two view classes into on class with a GenericView Base class and Retrive + update Mixins.
urls.py
url(r'^profiles/(?P<pk>[0-9]*)', ProfileRetrieveUpdateView.as_view(), name='profiles-detail-update'),
views.py
from rest_framework.generics import RetrieveUpdateAPIView
class ProfileRetrieveUpdateView(RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def get_permissions(self):
if self.request.method == "GET":
return []
else:
return [IsAuthenticated()]
You should condense this into a single endpoint. You can have a single class handle all the listing, update, get, etc for this. Try...something like this:
from rest_framework import mixins, viewsets
class ProfileUpdateView(viewset.ModelViewSet,
mixins.ListModelMixin,
mixins.UpdateModelMixin):
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
get_queryset(self):
return Profile.objects.all()
If you're using pure models, use the built in model stuff, and check out the mixins. It will save you TONS of generic code writing. It has some magic that knows how to route the request to the matching http method.
http://www.django-rest-framework.org/api-guide/generic-views/#mixins
Django rest framework provides generic view you don't require Mixins.
You can directly use RetrieveUpdateAPIView. Provides request methods get to retrieve data, put update data and patch for partial update.
from rest_framework.generics import RetrieveUpdateAPIView
class ProfileUpdateView(RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = (IsAuthenticated, )
Reference: http://www.django-rest-framework.org/api-guide/generic-views/#retrieveupdateapiview
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'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}/.
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)