Atomic transactions in DRF action? - python

I am trying to get into Django atomic transactions at first time. I wonder if there is a possibility to use it something like this:
class TaskViewSet(MultiSerializerViewSet):
#transaction.atomic
#action(methods=['PATCH'], detail=True)
def move_task(self, request, pk):
# making queries, trying to update them, roll back if last one fails.
return Response("message: SUCCESS", status=_status.HTTP_200_OK)
I have searched a bit - there is some information about how to use transactions but I did not find any info if it is possible to use them with DRF.

class PayViewSet(ModelViewSet):
#action(methods=['PATCH'], detail=True)
#transaction.atomic
def approval(self, request, *args, **kwargs):
sid = transaction.savepoint()
success = something
if success:
transaction.savepoint_commit(sid)
return success_response('yes')
else:
transaction.savepoint_rollback(sid)
return error_response('no')
savepoint is optionally depending on your situation

If you need to wrap all API calls and DRF actions into transactions - you can use (ATOMIC_REQUEST) Django database settings [from Django 1.8]
DATABASES = {
"default": {
....
"ATOMIC_REQUESTS": True,
If you use a custom exception handler (DRF Issue) you may need to adjust it like below
REST_FRAMEWORK = {
....
"EXCEPTION_HANDLER": "api.exceptions.your_exception_handler",
}
import logging
from django.db import IntegrityError
from rest_framework.response import Response
from rest_framework.views import exception_handler, set_rollback
def your_exception_handler(exc, context):
"""
DRF custom exception handler. This will allow us to add useful information
to any exceptions to help out our frontend devs
"""
# Call REST framework's default exception handler first, to get the standard error response.
response = exception_handler(exc, context)
if not response and isinstance(exc, IntegrityError):
# https://github.com/encode/django-rest-framework/issues/5760
if 'duplicate key value violates unique constraint' in str(exc):
set_rollback()
msg = "Unique constraint violated: {exc}".format(exc=exc)
response = Response({"error": True, "content": msg}, status=400)
if response is None:
set_rollback()
logger.exception(exc)
response = Response({"error": True, "content": str(exc)}, status=500)
return response

Related

Django REST Framework with ForeignKey on_delete=models.PROTECT

when I try to delete Django model object that is ForeignKey in another model with option on_delete=models.PROTECT, the error returned is the normal Django 500 Exception HTML web page, how to make Django rest frame work return json response with the error, is there a way for DRF to do that by default or it should be customized?
Raising 500 Exception is the expected behavior. You have to customize to have a custom error. You can refer to this similar question
You can catch the error by using ProtectedError expceptions.
Like this-
from django.db.models import ProtectedError
try:
# Write here your code
except ProtectedError:
# Return your customer validation error
To complete MSI Shafik answer, specifically for django-rest-framework, you can override the destroy method to catch the exception and return protected objects in a response to handle an error message in your application.
For example:
from django.db.models.deletion import ProtectedError
def destroy(self, request, *args, **kwargs):
try:
return super().destroy(request, *args, **kwargs)
except ProtectedError as protected_error:
protected_elements = [
{"id": protected_object.pk, "label": str(protected_object)}
for protected_object in protected_error.protected_objects
]
response_data = {"protected_elements": protected_elements}
return Response(data=response_data, status=status.HTTP_400_BAD_REQUEST)

Caching must be implemented at view level, in models or in serializers in Django

I have a web application purely based on REST API using Django Rest Framework. I have seen that at most of the places the response for my APIs is not changing or not changing frequently, so I'm thinking to cache such API's and for that, I'm using https://pypi.org/project/redis/ package. So my question here is what could be the better way to implement caching, it should be at the view level, at the model level, or in the serializer. How it could be done?
You can use Cache for APIview and ViewSets with decorators like cache_page.
NOTE: The cache_page decorator only caches the GET and HEAD responses with status 200.
Ex:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class UserViewSet(viewsets.ViewSet):
# Cache requested url for each user for 2 hours
#method_decorator(cache_page(60*60*2))
def list(self, request, format=None):
content = {
'user_feed': request.user.get_user_feed()
}
return Response(content)
Or
class PostView(APIView):
# Cache page for the requested url
#method_decorator(cache_page(60*60*2))
def get(self, request, format=None):
content = {
'title': 'Post title',
'body': 'Post content'
}
return Response(content)
DRF cache docs.
settings.py:
# Enable memcache
# https://devcenter.heroku.com/articles/memcache#using_memcache_from_python
CACHES = {
'default': {
'BACKEND': 'django_pylibmc.memcached.PyLibMCCache'
}
}
If you want store just result of a method or a query and store for furture requests, you can just define a key for it and set that result to that key with cache.set(cache_key, result, expire_time) and then get it(cache.get(cache_key)) whenever you want.
Remember you should define a cache backend for your results.Proper and better solution is to use message brokers like redis or memcached to store cache.based on your needs.
//Updated//
check if data is already cashed or not.
class WeatherView(APIView):
def get(self):
if(cache.get('weatherdata') == None):
url = 'https://api.openweathermap.org/data/2.5/forecast?q=' + '...';
serialized_data = urlopen(url).read()
data = json.loads(serialized_data)
print(data)
cache.set('weatherdata', data, 3600)
else:
data = cache.get('weatherdata')
serializer_class = WeatherSerializer(data)
responseJSON = JSONRenderer().render(serializer_class.data)
return Response(responseJSON)

django rest_framework custom exception error

I'm new to django rest_framework, And tried to create customized error reponse in django!.
Django Rest Framework Exceptions
After going through that, all seems to be pretty straight forward, but the import error raise when i try to add the custom exception.
Settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'project.restapi.utils.custom_exception_handler'
}
ImportError
Exception Value:
Could not import 'project.restapi.utils.custom_exception_handler' for API setting 'EXCEPTION_HANDLER'. AttributeError: module 'project.restapi.utils' has no attribute 'custom_exception_handler'
custom_exception_handler.py
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
model.py
class Users(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
# call the original 'list' to get the original response
response = super(Users, self).retrieve(request, *args, **kwargs)
response.data = {"collection": {"data": response.data},"statusCode":response.status_code,"version":"1.0"}
# customize the response data
if response is not None:
return response
else:
# return response with this custom representation
response.data = {"collection": {"data": response.data},"statusCode":response.status_code,"version":"1.0","error":response.exception}
return response
So on the above model works fine, except when i try to hit the user which is not in the database should raise error - not found, so i tried to customise the not found to be meaningful to my own. that's it
I tried to sort out, but hard to do so!!,
Django Version: 2.1.5
Python - 3.7.0
Since your custom_exception_handler resides in a file named custom_exception_handler.py. You can try changing the EXCEPTION_HANDLER setting to this:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'project.restapi.utils.custom_exception_handler.custom_exception_handler'
}

Django rest_framework: How to pass additional attribute to exception / error response?

I'm using Python 3.6 with Django 1.11.9 and rest_framework 3.6.2.
I have a view (APIView) which can only be accessed by some users who successfully pass a given HasUnlimitedAccesPermission check. In case of failure to pass the latter, I raise a PermissionDenied with a detailed message of my choice about the error to pass on to front-end. So far so good, all of that is easily achievable by applying the HasUnlimitedAccessPermission to my view thanks to the "permission_classes" decorator (yeah, I'm using a function_based view, here).
Now, what I would like to achieve is passing an additional attribute to my error response JSON (when the user fails to pass the permission test). This attribute would be an "error_id" attribute that would give the front-end developer the ability to adapt the error display depending on the "error_id" value. An example of the response JSON would be :
{
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
Any idea on how to achieve this?
Your problem can be solved using a middleware issue.
I would recommend you to build a custom middleware. So the custom middleware can help you to create the response as per your need. Just plug the middleware in your django application.
You can read more about it in this blog or this blog
Define a Custom Exception class as,
from rest_framework.serializers import ValidationError
from rest_framework import status
class CustomAPIException(ValidationError):
"""
raises API exceptions with custom messages and custom status codes
"""
status_code = status.HTTP_400_BAD_REQUEST
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and use in views as,
from rest_framework import status
def my_view(request):
if some_condition:
error_msg = {
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
raise CustomAPIException(error_msg)
OK, thanks to Bear Brown's comment and Jerin Peter George's answer (completed by rest_framework source code), I've done as follows:
1) Created a custom PermissionDenied exception:
class CustomPermissionDenied(PermissionDenied):
def __init__(self, detail=None, code=None, error_id=None):
super().__init__(detail=detail, code=code)
self.error_id = error_id
which is raised in HasUnlimitedAccessPermission, for instance, this way:
raise CustomPermissionDenied(detail=_("To use this feature, subscribe to one of our plans."),
error_id="no_unlimited_access")
2) In a custom_exception_handler (that I already had for other purpose) I added the lines between the ellipses
def custom_exception_handler(exc, context):
...
error_id = getattr(exc, "error_id", None)
if error_id is not None:
new_response_data["error_id"] = error_id
...
response.data = new_response_data
return response
And here it is, I have the error response format I was looking for. Thanks to all of you for your help!

Include request data in Django Rest Framework custom exception handler response data

Tech used:
http://www.django-rest-framework.org
Exceptions: http://www.django-rest-framework.org/api-guide/exceptions/
Included rest_framework default example in custom exceptions.py file:
from rest_framework.views import exception_handler
import sys
def custom_exception_handler(exc, context=None):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc)
# Now add the HTTP status code to the response and rename detail to error
if response is not None:
response.data['status_code'] = response.status_code
response.data['request'] = request
response.data['error'] = response.data.get('detail')
del response.data['detail']
return response
This sends basic error info like "Http404" etc, but no request data, like ip address, etc.
Best way to add my request into the response? Thanks in advance.
UPDATE (and solved):
So, I was initially trying to solve this using DjangoRestFramework 2.4.x, but that version doesn't have the request or context data options for the custom exception handler. Upgrading to 3.1.3 made it easy to add the data into the response. New code now looks like this (using version 3.1.3):
def custom_exception_handler(exc, request):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, request)
# Send error to rollbar
rollbar.report_exc_info(sys.exc_info(), request)
# Now add the HTTP status code to the response and rename detail to error
if response is not None:
response.data['status_code'] = response.status_code
response.data['error'] = response.data.get('detail')
del response.data['detail']
return response
This should work for your case.
from rest_framework.views import exception_handler
import sys
def custom_exception_handler(exc, context=None):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc)
# Now add the HTTP status code to the response and rename detail to error
if response is not None:
response.data['status_code'] = response.status_code
response.data['request'] = context['request']
response.data['error'] = response.data.get('detail')
del response.data['detail']
return response
You can access the request from the context passed to the custom_exception_handler. This was added in DRF 3.1.0. Also refer this issue where it was resolved.
If you are using DRF<3.1, there would be no request in the context of exception handler. You can upgrade to DRF 3.1.3(latest version in PyPI) and then easily access the request in context.
Taken from DRF 3.1.1 source code:
def get_exception_handler_context(self):
"""
Returns a dict that is passed through to EXCEPTION_HANDLER,
as the `context` argument.
"""
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
Also, you need to configure the exception handler in your settings.py file.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
If it is not specified, the 'EXCEPTION_HANDLER' setting defaults to the standard exception handler provided by REST framework:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
Note:
Exception handler will only be called for responses generated by
raised exceptions. It will not be used for any responses returned
directly by the view, such as the HTTP_400_BAD_REQUEST responses that
are returned by the generic views when serializer validation fails.

Categories