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)
Related
I want to write django custom error handler as in flask custom error handler .
Suppose i have 100 api's which gets the same error everytime let's say
json.decoder.JSONDecodeError
Sample code
def post(self, request):
if not request: return Response({"message": "Please enter credentials"})
input_param = json.load(request)
print(input_param)
return "something"
The above code will return json.decoder.JSONDecodeError if no params are passed in post request .
In flask this can be handled by writing custom error handler like
#app.errorhandler(json.decoder.JSONDecodeError)
def handle_marshmallow_validaton_errors(err):
return jsonify({"error": "Bad request"}), 400
In django is there any way we can write custom error handlers
Thanks in advance
I think you can use DRF custom exception handler https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling.
you can use it to check the spawn exception and return a proper response.
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
The get_object_or_404 shortcut function in Django when encountering an exception gives a very nice error message in the following format:
'No %s matches the given query.' % queryset.model._meta.object_name)
However, while using this inside a DRF 3.X Class based view, the final 404 response data has a very stripped down version, which is as follows:
{"detail": "Not found."}
As is evident, the DRF message is very generic with no information about the Model name.I am assuming that the DRF NotFound Exception class defined here strips down the message to its current bare minimum.
How can I get the original nice error message that Django returns in-spite of using it within a DRF Class Based View ?
The default exception handler for an APIView class is decided by the get_exception_handler method. Ie,
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return self.settings.EXCEPTION_HANDLER
In other words the handler function that an APIView class use by default is rest_framework.views.exception_handler(Which is specified on the default settings).
As per the DRF Doc,
By default DRF handle the REST framework APIException, and
alsoDjango's built-in Http404 and PermissionDenied exceptions.
Since in this case you want to customize the error message only for the Http404, you can create your own handler like this.
ie,
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 overide the error message.
if response is not None and isinstance(exc, Http404):
# set the custom message data on response object
response.data['detail'] = exc.args[0] # exc.args[0] will have the message text,
return response
Then place this custom handler in your project settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Note: This solution is not tested.
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!
I'm developing a REST API which takes POST requests from some really brain-dead software which can't PATCH or anything else. The POSTs are to update Model objects which already exist in the database.
Specifically, I'm POSTing data for objects with a related field (a SlugRelatedField, as the POSTer knows the 'name' attribute but NOT the 'pk'). However, I need to return a 404 if the POSTer sends data where the 'name' returns nothing on the SlugRelatedField (e.g. the related object does not exist). I've been through this with a debugger but it seems that DRF uses some Django signals magic to do it The Way DRF Does Itâ„¢, which is to return a 400 BAD REQUEST. I don't know how to modify this - only when it's the above condition and not a true 400-worthy POST - into a 404.
By the way, pre_save() in my view is NOT executing during execution of the failing test.
Here's the serializer:
class CharacterizationSerializer(serializers.ModelSerializer):
"""
Work-in-progress for django-rest-framework use. This handles (de)serialization
of data into a Characterization object and vice versa.
See: http://www.django-rest-framework.org/tutorial/1-serialization
"""
creator = serializers.Field(source='owner.user.username')
sample = serializers.SlugRelatedField(slug_field='name',
required=True,
many=False,
read_only=False)
class Meta:
model = Characterization
# leaving 'request' out because it's been decided to deprecate it. (...maybe?)
fields = ('sample', 'date', 'creator', 'comments', 'star_volume', 'solvent_volume',
'solution_center', 'solution_var', 'solution_minimum', 'solution_min_stddev',
'solution_test_len',)
And here's the view where pre_save isn't being run in the given test (but does get run in some others):
class CharacterizationList(generics.ListCreateAPIView):
queryset = Characterization.objects.all()
serializer_class = CharacterizationSerializer
permission_classes = (AnonPostAllowed,) # #todo XXX hack for braindead POSTer
def pre_save(self, obj):
# user isn't sent as part of the serialized representation,
# but is instead a property of the incoming request.
if not self.request.user.is_authenticated():
obj.owner = get_dummy_proxyuser() # this is done for CharacterizationList so unauthed users can POST. #todo XXX hack
else:
obj.owner = ProxyUser.objects.get(pk=self.request.user.pk)
# here, we're fed a string sample name, but we need to look up
# the actual sample model.
# #TODO: Are we failing properly if it doesn't exist? Should
# throw 404, not 400 or 5xx.
# except, this code doesn't seem to be run directly when debugging.
# a 400 is thrown; DRF must be bombing out before pre_save?
obj.sample = Sample.objects.get(name=self.request.DATA['sample'])
And for good measure, here's the failing test:
def test_bad_post_single_missing_sample(self):
url = reverse(self._POST_ONE_VIEW_NAME)
my_sample_postdict = self.dummy_plqy_postdict.copy()
my_sample_postdict["sample"] = "I_DONT_EXIST_LUL"
response = self.rest_client.post(url, my_sample_postdict)
self.assertTrue(response.status_code == 404,
"Expected 404 status code, got %d (%s). Content: %s" % (response.status_code, response.reason_phrase, response.content))
If I put a breakpoint in at the self.rest_client.post() call, the response already has a 400 in it at that point.
You can use a Django Shortcut for that, getting the obj.sample:
from django.shortcuts import get_object_or_404
obj.sample = get_object_or_404(Sample, name=self.request.DATA['sample'])
Instead of using pre_save why not override post in your API view:
def post(self, request, *args, **kwargs):
...other stuff
try:
obj.sample = Sample.objects.get(name=self.request.DATA['sample'])
...or whatever other tests you want to do
except:
return Response(status=status.HTTP_404_NOT_FOUND)
response = super(CharacterizationList, self).post(request, *args, **kwargs)
return response
Make sure you import DRF's status:
from rest_framework import status
Also, note you will likely want to be more specific with the Exceptions you catch. Django's get method will return either DoesNotExist if nothing matches or MultipleObjectsReturned if more than one object matches. The relevant documentation:
Note that there is a difference between using get(), and using
filter() with a slice of [0]. If there are no results that match the
query, get() will raise a DoesNotExist exception. This exception is an
attribute of the model class that the query is being performed on - so
in the code above, if there is no Entry object with a primary key of
1, Django will raise Entry.DoesNotExist.
Similarly, Django will complain if more than one item matches the
get() query. In this case, it will raise MultipleObjectsReturned,
which again is an attribute of the model class itself.