Context error while testing RESTful API URL in Django - django

Dear community and forum,
I am in charge of developing RESTful API URLs for a website project. However, when it comes to testing I have got that error:
self = HyperlinkedIdentityField('api:request')
value = <ResourceRequest: JoKLLwwKqxrkrwWmcjOWzIscGzpsbgWJqRAOZabnwxQpiEDRfifeZhvzpRRp...ewyOFcaQVhchYNVIhUoiWBzKMrFYvYQBMNRZsLFfOZSjclHUXwyXZQHxjMtbHvWefMIlyZqvTvXqiu>
def to_representation(self, value):
assert 'request' in self.context, (
"`%s` requires the request in the serializer"
" context. Add `context={'request': request}` when instantiating "
"the serializer." % self.__class__.__name__
)
E AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
/usr/lib/python2.7/site-packages/rest_framework/relations.py:351: AssertionError
The serialiser is (sorry for the ugly code so far):
class ResourceRequestSerializer(serializers.ModelSerializer):
# context = self.kwargs.get('context', None)
# request = kwargs['context']['request']
# print(kwargs)
view_name = 'api:request'
url = serializers.HyperlinkedIdentityField(view_name)
# print(self)
# url = URLField(view_name=view_name, read_only=True, many=True)
# url = serializers.SerializerMethodField()
support_level = serializers.SerializerMethodField()
originator = BriefUCLProfileSerializer(source='originator.ucl_profile')
sponsor = BriefUCLProfileSerializer()
versioned_dependencies = serializers.StringRelatedField(many=True)
types_of_work = serializers.StringRelatedField(many=True)
previous = serializers.SerializerMethodField()
status_history = RequestStatusChangeSerializer(many=True)
For the test:
# Change the status through a POST request
response = self.app.post(
reverse(
'api:request',
args=[request1.pk],
),
params={
'context': request1,
'format': 'json',
'status': ResourceRequest.STATUS_APPROVED,
},
# context=request1,
headers=self.auth_headers,
)
I am still wondering if the context has to be passed from within the serialiser or from the test.
Here is the view too:
class ResourceRequestAPIView(RetrieveAPIView):
"""Retrieve an individual resource request by pk and optionally update status"""
serializer_class = ResourceRequestSerializer
permission_classes = (IsAuthenticated,)
authentication_classes = (TokenAuthentication, SessionAuthentication)
def get_object(self):
try:
return ResourceRequest.objects.get(pk=self.kwargs.get('pk'))
except ResourceRequest.DoesNotExist:
raise Http404
def post(self, request, *args, **kwargs):
resource_request = self.get_object()
status = request.data['status']
if status != ResourceRequest.STATUS_SUBMITTED:
# Change the status
resource_request.set_status(status,
request.user)
# And send emails
request_url = request.build_absolute_uri(
reverse('api:request', args=[resource_request.pk])
)
send_emails(resource_request,
request_url=request_url,
)
serializer = ResourceRequestSerializer(resource_request)
return Response(serializer.data)
Any help greatly appreciated !
Thank you
Roland

Right, so with that viewset in hand, you'll have to initialize the serializer with the context:
serializer = ResourceRequestSerializer(
resource_request,
context=self.get_serializer_context(),
)
get_serializer_context() is provided by default by DRF viewsets.

Related

My view object 'has no attribute' {my attribute} in Django

Background
I'm trying to load a custom url (e.g. www.mysite.com/order-2523432) that will show a user details about their order.
Problem
I am trying to use the method order_id in my models.py in order to get the correct url. The problem is that I am getting the error:
'OrderDetailView' object has no attribute 'order_id'
Does anyone know what I can do to get order_id to work?
My views.py:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(self)
except RequestException as e:
logger.exception(e)
return context
My utils.py:
def get_orders(orders):
url = f"mysite.com/customer/{orders.order_id}"
method = "GET"
content_type = "application/json"
header = Sender(
credentials etc
).request_header
response = requests.request(
headers etc
)
response.raise_for_status()
return response.json()
My models.py:
class Orders(CustomModel):
table_name = models.CharField(max_length=256, unique=True)
#property
def order_id(self):
return f"order-{self.table_name}"
def get_absolute_url(self):
return reverse("order:edit", args=(self.id,))
you should use self.object or context['object'] or get_object() instead of passing self
please try this:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(context['object'])
except RequestException as e:
logger.exception(e)
return context

How to automatically generate django rest framework documentation with parameters?

I use redoc + drf_yasg to generate the documentation of my api made with django-rest-framwork.
It works well as soon as I add an entry point it appears well in the documentation.
However, I don't understand how to add in the documentation the search parameters that can be given during the query.
For example, in the list function, I can pass 2 optional parameters, an id and name:
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)
I don't see any mention of the parameters in the documentation. How can I add them?
I don't know if this helps but here is how I implemented redoc:
schema_view = get_schema_view(
openapi.Info(
title="test API",
default_version='v1',
description=f"""
# Public API
**Reserved to authenticated users**
Token Lifetime :
* ACCESS_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME'))}
* REFRESH_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('REFRESH_TOKEN_LIFETIME'))}
### Links
[Get access token](************)
### Format
Use `?format=` option in url:
* `?format=api` (default)
* `?format=json`
""",
terms_of_service="",
contact=openapi.Contact(email="***********"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
You have to add your parameters to your view if you want to give info about them. I use drf_yasg with swagger but maybe this might help.
Here's an example.
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
id_machine = openapi.Parameter('id_machine', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False)
name = openapi.Parameter('account', openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)

How to write a test for a custom action on a viewset in Django Rest Framework

I am new to Django, and Django Rest Framework.
I would like to know how to go about testing custom actions. For example, assume we have the following code from the DRF tutorials
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail=True, methods=['post', 'put'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
How would I go about calling this view in a test in DRF.
def test_password_set(self):
user = User.objects.create(name="Joe")
factory = APIRequestFactory()
request_url = f'users/{user.id}/set_password/'
request = factory.post(request_url)
view = UserViewSet.as_view({'put': 'update'})
response = view(request)
self.assertEqual(response.status_code, 200)
That code gives me the error below
AssertionError: 405 != 200
which means that the given method is not allowed.
Could anyone help me figure out what the error could be?
You've made a mistake here
request = factory.post(request_url)
It should be put:
request = factory.put(request_url)
And also, you should add the data into the request body. In this line, you're trying to get data from the request object.
serializer = PasswordSerializer(data=request.data)
This data depends on the PasswordSerializer; But for example, it should be something like this (it should contain all parameters of the serializer):
request = factory.put(request_url, {'password': 'sTr0ngPass'})

Pagination DRF not working well

I have a little problem with pagination when I put the default pagination in my project, somethings pages work in others pages not working for example:
this is my file settings.py for all my project
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apicolonybit.notification_clbt.NotificationPagination.PaginationList'}
this is my Configuration app inside my project: myproject / Configuration
class ConfigurationsList(generics.ListAPIView):
"""
list configuration with current user authenticated.
"""
queryset = Configuration.objects.all()
serializer_class = ConfigurationSerializer
when I run this part in my postman, working well, but when I try to run in another module like this:
class TransactionListHistory(generics.ListAPIView):
# queryset = TransactionHistory.objects.all()
serializer_class = TransactionHistorySerializer
pagination_class = PaginationList
page_size = 2
page = 1
def get_object(self, current_user):
# User.objects.get(id=pk)
return TransactionHistory.objects.filter(agent_id=current_user.id).order_by('-id')
#classmethod
def get_object_client(cls, current_user, trans):
# User.objects.get(id=pk)
return TransactionHistory.objects.filter(user_id=current_user.id).order_by('-id')
def get(self, request, format=None):
current_user = request.user
status_trans = 6
agent_user = 2
client_user = 1
trans = {
'status': status_trans
}
typeusers = Profile.objects.get(user_id=current_user.id)
# actions agent user = show all transaction from all client users
if typeusers.type_user == agent_user:
list_trans_init = self.get_object(current_user)
serializer = TransactionHistorySerializer(list_trans_init, many=True)
get_data = serializer.data
# actions normal user (client user) = just see transactions from self user
if typeusers.type_user == client_user:
list_trans_init = self.get_object_client(current_user, trans)
serializer = TransactionHistorySerializer(list_trans_init, many=True)
get_data = serializer.data
# if not PaginationList.get_next_link(self):
# return JsonResponse({'data': get_data}, safe=False, status=status.HTTP_200_OK)
return self.get_paginated_response(get_data)
my custom file pagination like this
class PaginationList(PageNumberPagination):
page_size = 2 # when show me an error I added
offset = 1 # when show me an error I added
limit = 10 # when show me an error I added
count = 10 # when show me an error I added
page = 1 # when show me an error I added
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
the variables page_size and etc, then show me an error like PaginationList is not object page, I added this page_size and pass other error like PaginationList is not object offset and again added var.
well the last error show me is like this 'int' object has no attribute 'has_next'
please help me, how to add my custom pagination in my class TransactionListHistory
thanks for your attention.
You are using self.get_paginated_response() in wrong way.
from rest_framework.response import Response
class TransactionListHistory(generics.ListAPIView):
# Your code
def get(self, request, *args, **kwargs):
queryset = do_something_and_return_QuerySet() # do some logical things here and
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
the do_something_and_return_QuerySet() is fucntion or logic of you, which return a QuerySet.
Example
class TransactionListHistory(generics.ListAPIView):
# Your code
def get(self, request, *args, **kwargs):
queryset = TransactionHistory.objects.filter(user_id=request.user.id)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

Post request handling in Django Rest framework

I am using Django Rest Framework, currently to pull some data from the backend we are using Get request, but due to URL limit going high we are planning to implement a Post request. To do this firstly the backend Django Rest API has to be made available to serve a post request.
I am new to Django and I don't find a post or get method in the code, all i can say is we are using viewsets, I tried using "#detail_route(methods=['post'])" but this didn't work, what am I doing wrong here?
class XViewSet(viewsets.ViewSet):
renderer_classes = ''
def retrieve(self, request, pk=None):
try:
pk = int(pk)
except ValueError:
raise InvalidParameterError(parameter_name='id', invalid_value=pk)
queryset = models.X.objects.all()
x = get_object_or_404(queryset, pk=pk)
pipelines = request.query_params.getlist('pipeline[]')
callsets =
callset_ids =
serializer = serializers.XSerializer(x, context={'request': request})
requested_samples = [z[1:] for z in request.query_params.getlist('s')]
filtered_calls = []
serialized_data = serializer.data
unfiltered_calls = serialized_data.get('calls')
if unfiltered_calls:
for serialized_calls in unfiltered_calls:
if serialized_calls['callset'] in callset_ids:
unfiltered_calls = serialized_calls['data']
for call in unfiltered_calls:
if call['y'] in requested_y:
filtered_calls.append(call)
break
serialized_data['calls'] = filtered_calls
return Response(serialized_data, status=status.HTTP_200_OK)
def list(self, request):
qp = self.request.query_params
validated_qp =
# generate the query
query_object =
query =
# execute the query
cursor = connections['default'].cursor()
cursor.execute(query)
qs = utils.dictfetchall(cursor)
# sanitize query results
if 't' in validated_qp:
return_data =
else:
for x in qs:
if 'calls' in x:
x['calls'] =
else:
x['calls'] = {}
return_data =
resp = Response(return_data, status=status.HTTP_200_OK)
if validated_qp.get(''):
resp['content-disposition'] =
return resp
You can use class-based views to handle the requirements,
from rest_framework.views import APIView
class MyAPI(APIView):
def get(selfself, request):
# do stuff with get
return Response(data="return msg or data")
def post(self, request):
post_data = request.data
# do something with `post_data`
return Response(data="return msg or data")
UPDATE : Using ViewSet
ViewSet class provide us create() methode to create new model instances. So we can override that to handle post data coming to the view. Just add a create() under your view class as below
class XViewSet(viewsets.ViewSet):
renderer_classes = ''
def create(self, request): # Here is the new update comes <<<<
post_data = request.data
# do something with post data
return Response(data="return data")
def retrieve(self, request, pk=None):
# your code
return Response(serialized_data, status=status.HTTP_200_OK)
def list(self, request):
# your code
return resp