Adding forms to Django Rest Framework ViewSets - python

I've created some views that subclass directly from viewsets.ViewSet rather than from ModelViewSet, many (but not all) of which don't have serializers declared on the class explicitly because I don't need them that way. An example of one such viewset is a "verify email" viewset, that simply takes in a verification code, and then the .list() method on the ViewSet looks up the verification code and verifies the user. No serializer or model necessary, and I use ViewSet only so that I can properly register the view in the router.
However, these views are all lacking the form automatically generated at the bottom of the Browseable API pages that the ModelViewSet pages usually have for POST and PUT requests.
What do I need to do or specify to get these forms to appear with my preferred fields for these ViewSet subclasses?

you need to have serializer for the viewset.
BrowsableAPIRenderer returns in it's get_context method:
'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
'post_form': self.get_rendered_html_form(data, view, 'POST', request),
and get_rendered_html_form is looking for serializer defined for view.
it make sense, because you can't process data sent by POST or PUT without serializer.

Related

Django rest framework urls

I'm structuring a Django API with rest framework, I read the docs and DRF only makes a crud (get, post, patch, delete) from a model. Now the deal is how I can make custom actions with DRF.
Example:
api/v1/model/custom_action
Code:
class DistrictViewSet(viewsets.ModelViewSet):
queryset = District.objects.all()
serializer_class = DistrictSerializer
def custom_action(request, param):
# do many actions and return as Json Object
urls.py
url(r'api/v1/', include(router.urls))
Where router
router.register(r'model',api.ModelViewSet)
I'm correct with this or I need to create another modelview, customize the code and add it to router list?
You can add custom actions as you have done but you may need the #action decorator to configure the url to apply to a single object, or many.
#action(detail=True) adds pk to the url, as it applies to one object.
The url is generated from the action name, so for example
#action(detail=True)
def custom_action(self):
pass
Would yield the url ^<app_name>/{pk}/custom_action/$
You may find this useful:
https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing

Accessing a hyperlinkedRelatedField object from a permission class

I am trying to make an api backend of something like reddit. I want to ensure that whoever is creating a post (model Post) within a particular subreddit is a member of that subreddit (subreddit model is Sub). Here is my latest effort, which works but seems pretty sloppy, and the serializer for some context.
Post permissions.py
class IsMemberOfSubOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
elif request.data:
# prevent creation unless user is member of the sub
post_sub_pk = get_pk_from_link(request.data['sub'])
user = request.user
user_sub_pks = [sub.pk for sub in user.subs.all()]
if not (post_sub_pk in user_sub_pks):
return False
return True
Post serializers.py
from .models import Post
from redditors.models import User
from subs.models import Sub
class PostSerializer(serializers.HyperlinkedModelSerializer):
poster = serializers.HyperlinkedRelatedField(
view_name='user-detail',
#queryset=User.objects.all(),
read_only=True
)
sub = serializers.HyperlinkedRelatedField(
view_name='sub-detail',
queryset=Sub.objects.all()
)
class Meta:
model = Post
fields = ('url', 'id', 'created', 'updated', 'title', 'body',
'upvotes', 'sub', 'poster')
The issue with this approach is that since 'sub' is a hyperlinkedRelatedField on the Post serializer what I get back from request.data['sub'] is just the string hyperlink url. I then have a function, get_pk_from_link that uses regex to read the pk off the end of the url. Then I can use that to grab the actual model I want and check things. It would be nice if there were a more direct way to access the Sub model that is involved in the request.
I have tried searching the fields of the arguments that are available and I can't find a way to get to the Sub object directly. Is there a way to access the Sub model object through the hyperlink url?
I have also solved this problem by just using a serializer field validator (not shown above), but I am interested to know how to do it this way too. Maybe this is just a bad idea and if so please let me know why.
You are right, parsing the url is not the way to go. Since you want to perform the permission check before creating a Post object, I suspect you cannot use object level permissions either, because DRF does not call get_object in a CreateAPIView (since the object does not exist in the database yet).
Considering this is a "business logic" check, a simpler approach would be to not have that permission class at all and perform the check in your perform_create hook in your view (I had asked a similar question about this earlier):
from rest_framework.exceptions import PermissionDenied
# assuming you have a view class like this one for creating Post objects
class PostList(generics.CreateApiView):
# ... other view stuff
def perform_create(self, serializer):
sub = serializer.get('sub') # serializer is already validated so the sub object exists
if not self.request.user.subs.filter(pk=sub.pk).exists():
raise PermissionDenied(detail='Sorry, you are not a member of this sub.')
serializer.save()
This saves you hassle of having to perform that url parsing as the serializer should give you the Sub object directly.

how to authenticate users in Django rest framework?

I have added some URLs in my Django API for posting deleting and putting data and I don't know how to authenticate users first and give some of them to use these methods and ban some of them
As far as I know, you can use inbuilt decorator
#user_passes_test
then you can specify who can access your views just like below,
from django.contrib.auth.decorators import user_passes_test
def admin_user(user):
return user.is_superuser # write your logic here
#user_passes_test(admin_user)
def your_view(request):
--------
Have a look at the documentation for more clarification: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.decorators.user_passes_test
Since you are using the tag django-rest-framework, I assume that your view is being created with Django REST Framework.
First, you should force users to be authenticated to use the API. Second, you need to define what types of permissions are needed to perform the actions.
You stated that Django Super Users should be able to perform these actions. Thus, you could create a custom permission to make sure that only a user that is a Django Super User will have permission:
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
is_superuser = request.user and request.user.is_superuser
if not is_superuser and request.user:
# Your ban logic goes here
pass
return is_superuser
Then on your view, you can do:
from rest_framework.views import APIView
from your_app.permissions import IsSuperUser
class YourApiView(APIView):
permission_classes = [IsSuperUser]
If this is not enough information for you, I would suggest that you follow Django REST Framework's tutorial.

Django rest framework pk default validation

In Django Rest Framework, if I have a retrieve view (or a detail_route or any other route that is meant to operate on a single item rather than a collection) then I have to have an argument called pk for the primary key or ID to be passed in.
For example:
def retrieve(self, request, pk=None):
...
return Response(data={'id': pk})
My question is what kind of validation (if any) is applied to pulling the PK from the URL? Also, is there an idiomatic way to validate it, rather than writing it inline in the view itself?
I have tried looking in the docs but so far haven't seen this covered.
In itself, using the pk should be pretty safe as it simply a str object. Apart from that you could use the DRF's serializer fields for validation. Lets say pk is a UUID. Calling .to_internal_value() on a field instance will validate given data and return a UUID object.
Nice thing here is that this will automatically throw a DRF ValidationError (in case of a invalid UUID) showing the same behaviour as if pk were part of a failed serializer validation.
from rest_framework import serializers
def retrieve(self, request, pk=None):
# throws ValidationError if pk is not a valid UUID
uuid = serializers.UUIDField().to_internal_value(data=pk)
return Response(data={'id': uuid})
Sidenote: you could also use Django's FormFields with the .clean() method to the same effect, but you would need to handle the thrown exception there yourself.

Django Rest Framework how to post data on the browsable API

Im kind of new to Django Rest Framework. I know it is possible to post data using the Browsable API, I just don't know how. I have this simple view:
class ProcessBill(APIView):
def post(self, request):
bill_data = request.data
print(bill_data)
return Response("just a test", status=status.HTTP_200_OK)
When I go to the url that points to this view, I get the rest_framework browsable api view with the response from the server method not allowed which is understandable cause I am not setting a def get() method. But ... how can I POST the data? I was expecting a form of some kind somewhere.
EDIT
This is a screenshot of how the browsable API looks for me, it is in spanish. The view is the same I wrote above but in spanish. As you can see ... no form for POST data :/ .
Since you are new I will recommend you to use Generic views, it will save you lot of time and make your life easier:
class ProcessBillListCreateApiView(generics.ListCreateAPIView):
model = ProcessBill
queryset = ProcessBill.objects.all()
serializer_class = ProcessBillSerializer
def create(self, request, *args, **kwargs):
bill_data = request.data
print(bill_data)
return bill_data
I will recommend to go also through DRF Tutorial to the different way to implement your endpoint and some advanced feature like Generic views, Permessions, etc.
Most likely the user has read-only permission. If this is the case make sure the user is properly authenticated or remove the configuration from the projects settings.py if it is not necessary as shown below.
```#'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
#],```
Read more on permissions here.

Categories