Django REST Framework - Set request in serializer test? - python

I built a web app where the back-end is implemented using the Django REST Framework. Now I'm writing unit tests and I have come across a problem in testing my serializer methods. Here is one example of a serializer method I'm struggling with:
def get_can_edit(self, obj):
request = self.context.get('request')
user = User.objects.get(username=request.user)
return user == obj.admin
When trying to call this from the test, first I declare an instance of the serializer:
self.serializer = ConferenceSerializer()
But now I need self.serializer to have the correct request when get_can_edit does self.context.get('request'). I've created a fake request with the correct information using RequestFactory:
self.request1 = RequestFactory().get('./fake_path')
self.request1.user = self.user1
Now I am stuck because I am unsure how to add request1 to serializer such that self.context.get('request') will return request1.
Thanks.

You need to pass context argument to add request1 to serializer's context while instantiating the serializer in your test.
From DRF docs on including extra context:
You can provide arbitrary additional context by passing a context
argument when instantiating the serializer.
You need to do something like:
# pass context argument
self.serializer = ConferenceSerializer(context={'request': request1})
This will provide the desired request1 object to your serializer in its context.

Related

DjangoRestAPI - Using ModelViewSet to perform database management while running your own function (like a event handler)

I am used to using flask for apis and Django for databases and I am trying to learn the DjangoRestAPI. I followed the official Quickstart and another tutorial from medium and did a lot of research but I still struggle to understand how to implement my own function (eg: Doing some calculations or making a request to the Twilio api to send a message after receiving an api request) in a ModelViewSet. I have read that APIView can also be another option but it will making accessing the database quite difficult? When I run a print function in a ModelViewSet class in views.py, or a #action function, it prints but only once at the start of the programme when the server is starting. This is my version of the code in Flask and what I have tried in Django.
Which class should I inherit (APIView or Viewset or ModelViewSet and where can I look for more information about this?
Thank you very much for your kind help in advance: - please note that the flask implementation is a get request
#app.route('/api/upload/<uuid>/<major>/<minor>/<rssi>',methods=['GET'])
def beacon_info_upload(uuid,major,minor,rssi):
if 'uuid' in request.args:
uuid = str(request.args['uuid'])
if 'major' in request.args:
major = str(request.args['major'])
if 'minor' in request.args:
minor = str(request.args['minor'])
if 'rssi' in request.args:
rssi = str(request.args['rssi'])
qualify = send_twilio_message(uuid,major,minor,rssi)
return jsonify(uuid,major,minor,qualify)
Attempt in Django (views.py)
class beacon_occuranceViewSet(viewsets.ModelViewSet):
queryset = beacon_occurance.objects.all().order_by('from_location')
serializer_class = beacon_occuranceSerializer
print("event occ beacon occ views.py L32 success experiment") #only prints once at the beginning
#action(detail=True, methods=['post'])
def register(self, request, pk=None):
print("hello!!!!! L38") #only prints once at the beginning
beacon = self.get_object()
print(beacon)
class beacon_occuranceSerializer(serializers.HyperlinkedModelSerializer):
print("hello") #also only prints once when booting the server
class Meta:
model = beacon_occurance
fields = ('from_location','uuid','major','minor','rssi','time')
Solved - use
def create(self, request):
#do what you want - do access data- do request.data (its a dictionary)
return super().create(request)
refer to Custom function which performs create and update on DRF modelViewSet

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.

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 - can't override list in ListAPIView

I am using Django REST Framework to create an endpoint that will produce a PDF document. The PDF document will have information that corresponds to a particular Department. I have two desired functionalities -- to be able to download a PDF document, and to be able to preview the document within the browser.
Since the PDF document changes over time based on data that is added to the app, the document needs to be generated in real time when it is requested. As a first step, I'm trying to have the document be generated in a remote file storage location when the following endpoint is hit by a GET request:
departments/<department_pk>/result/preview
Since my endpoint should only take GET requests, I am using a ListAPIView. I'm trying to override the list method so that my custom document generation logic is executed, but it looks like the method is never called. How can I have some custom document generation logic be inserted into my endpoint, so that it is executed when the endpoint is hit by a GET request?
api/urls.py
url(r'^departments/(?P<department_pk>[0-9]+)/result/preview',
include(result_document_urls.result_document_preview_router.urls,
document_app/urls.py
result_document_preview_router = routers.DefaultRouter()
result_document_preview_router.register(r'^', ResultDocumentDetailView.as_view(),
base_name='Department')
document_app/views.py
class ResultDocumentDetailView(generics.ListAPIView):
queryset = Department.objects.all()
lookup_field = 'department_pk'
lookup_url_kwarg = 'department_pk'
def list(self, request, department_pk):
queryset = self.get_queryset()
import ipdb; ipdb.set_trace() # this break point is never hit
department = get_object_or_404(queryset, department_pk=department_pk)
...generate document logic...
return Response(status=status.HTTP_200_OK)
replace list method with below code, I think it will work
class ResultDocumentDetailView(generics.ListAPIView):
queryset = Department.objects.all()
lookup_field = 'department_pk'
lookup_url_kwarg = 'department_pk'
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
import ipdb; ipdb.set_trace() # this break point is never hit
department = get_object_or_404(
queryset, department_pk=kwargs.get('department_pk')
)
...generate document logic...
return Response(status=status.HTTP_200_OK)
for more reference see the overrinding method "list"
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L35
In your document_app/urls.py, you are incorrectly passing ResultDocumentDetailView as an argument instead of a viewset.
Router while registering accepts a ViewSet instead of an APIView.
There are two mandatory arguments to the register() method:
prefix - The URL prefix to use for this set of routes.
viewset - The viewset class.
Also, since you are only interested in the retrieve method, you can just create a ResultDocumentRetrieveView and add its corresponding url to your urls.py without the need of creating a ResultDocument router. (Routers are generally used when you want to handle both list and detail requests.)
class ResultDocumentRetrieveView(generics.RetrieveAPIView):
queryset = Department.objects.all()
lookup_field = 'department_pk'
lookup_url_kwarg = 'department_pk'
def retrieve(self, request, department_pk):
department = self.get_object()
...generate document logic...
return Response(status=status.HTTP_200_OK)
urls.py
url(r'^departments/(?P<department_pk>[0-9]+)/result/preview', ResultDocumentRetrieveView.as_view())

Python Django RestFramework route trigger

I'am building an API using python 2.7 and django 1.7 and I'm facing a problem. I'm not an expert in Rest Framework but I understand the basic mechanisms.
I can resume my problem by giving you an example. I have a route lets say
/api/project/
Django Rest Framework provides me all basic operations for this route and I don't need to write them e.g:
POST, GET, PUT, DELETE => /api/project/
The fact is, I want to do some operation when I create a new project. I want to add the id of the user who has created the new project.
I want to add some kind of trigger/callback to the create function:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def create(self, request, *args, **kwargs):
I want to keep the internal behavior of Rest Framework (I don't want to rewrite the route functions), but I want the route to do extra stuff and I need the request object in my trigger/callback. Something like
def callback(request, instance):
instance.created_by = request.user
instance.save()
Do you have any ideas?
Thank you.
You need to add creator_id field to your serializer as well as to model represented by the resource. Then you can do something like this in your view:-
import copy
class ProjectViewSet(viewsets.ModelViewSet):
...
def create(self, request, *args, **kwargs):
data = copy.deepcopy(request.data)
data['creator_id'] = request.user.id
request._data = data
return super(ProjectViewSet, self).create(request, *args, **kwargs)

Categories