def process_exception(self, request, exception): # Don't catch exceptions when in debug mode if settings.DEBUG: return # Ignore Http404s (defer to Django's built-in 404 handling) if isinstance(exception, Http404): return # Handle exceptions that occur from REST API requests if is_api_request(request): return rest_api_server_error(request) # Determine the type of exception. If it's a common issue, return a custom error page with instructions. custom_template = None if isinstance(exception, ProgrammingError): custom_template = 'exceptions/programming_error.html' elif isinstance(exception, ImportError): custom_template = 'exceptions/import_error.html' elif isinstance(exception, PermissionError): custom_template = 'exceptions/permission_error.html' # Return a custom error message, or fall back to Django's default 500 error handling if custom_template: return server_error(request, template_name=custom_template)
def __call__(self, request): response = self.get_response(request) if is_api_request(request): response['API-Version'] = settings.REST_FRAMEWORK_VERSION return response
def __call__(self, request): # Initialize an empty list to cache objects being saved. _thread_locals.changed_objects = [] # Assign a random unique ID to the request. This will be used to associate multiple object changes made during # the same request. request.id = uuid.uuid4() # Connect our receivers to the post_save and post_delete signals. post_save.connect(handle_changed_object, dispatch_uid='handle_changed_object') pre_delete.connect(handle_deleted_object, dispatch_uid='handle_deleted_object') # Provide a hook for purging the change cache purge_changelog.connect(purge_objectchange_cache) # Process the request response = self.get_response(request) # If the change cache is empty, there's nothing more we need to do. if not _thread_locals.changed_objects: return response # Disconnect our receivers from the post_save and post_delete signals. post_save.disconnect(handle_changed_object, dispatch_uid='handle_changed_object') pre_delete.disconnect(handle_deleted_object, dispatch_uid='handle_deleted_object') # Create records for any cached objects that were changed. redis_failed = False for instance, action in _thread_locals.changed_objects: # Refresh cached custom field values if action in [ ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE ]: if hasattr(instance, 'cache_custom_fields'): instance.cache_custom_fields() # Record an ObjectChange if applicable if hasattr(instance, 'to_objectchange'): objectchange = instance.to_objectchange(action) objectchange.user = request.user objectchange.request_id = request.id objectchange.save() # Enqueue webhooks try: enqueue_webhooks(instance, request.user, request.id, action) except RedisError as e: if not redis_failed and not is_api_request(request): messages.error( request, "There was an error processing webhooks for this request. Check that the Redis service is " "running and reachable. The full error details were: {}" .format(e)) redis_failed = True # Increment metric counters if action == ObjectChangeActionChoices.ACTION_CREATE: model_inserts.labels(instance._meta.model_name).inc() elif action == ObjectChangeActionChoices.ACTION_UPDATE: model_updates.labels(instance._meta.model_name).inc() elif action == ObjectChangeActionChoices.ACTION_DELETE: model_deletes.labels(instance._meta.model_name).inc() # Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in # one or more changes being logged. if settings.CHANGELOG_RETENTION and random.randint(1, 100) == 1: cutoff = timezone.now() - timedelta( days=settings.CHANGELOG_RETENTION) purged_count, _ = ObjectChange.objects.filter( time__lt=cutoff).delete() return response