I have some Internet Explorer problems with my Angular 5 app, because in the
IE 11 my get-requests to a django-REST backend are cached by the browser.
I found a question suggesting to add special cache-control-headers to the response, but I didn't find a working answer how to do it with Django REST. All other browsers I have tested seem to work without problems.
Perhaps you could add the Cache-Control header to all responses using a middleware class.
class CacheControlMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Cache-Control'] = 'no-cache'
return response
and then wire that into your settings.py
MIDDLEWARE = [
...
'your.app.CacheControlMiddleware',
...
]
Bear in mind that will apply to all views (all your ModelViewSets), which you may or may not want.
If you want to apply it only to specific view sets, you may be better to override the retrieve() and list() methods in your view sets where you can set the Cache-Control header on the Response. For more info on that see http://www.django-rest-framework.org/api-guide/generic-views/#mixins
I had the same problem.
Adding the 'Cache-Control' header to the response worked for me.
response = Response()
response['Cache-Control'] = 'no-cache'
An example views.py:
from rest_framework import status
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
def snippet_list(request):
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
response = Response(serializer.data)
response['Cache-Control'] = 'no-cache'
return response
The other answers have the right idea, but here is a slightly more refined solution.
def disable_cache_middleware(get_response):
def middleware(request):
response = get_response(request)
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
return response
return middleware
The main difference here is that no-store and must-revalidate are included in the header as well. The Mozilla documentation on the header suggests this combination to prevent browser caching. You can read more about the extra header values there.
Secondly, I just find that the function-based middleware results in a bit less boilerplate than the class-based solution in this case.
Related
we are using Django1.11 and we are having some problems because our header Content-Type does not contain the charset part set to UTF-8. Something like this:
Content-Type: application/json; charset=UTF-8
I want to fix that for all endpoints, so I have thought to include a middleware to run after all midlewares have been run. The problem is that I do not know if that's possible. Any ideas? Or alternative solutions?
You can write a custom middleware like this:
from django.utils.deprecation import MiddlewareMixin
class AllIsJsonMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response['Content-Type'] = 'application/json; charset=UTF-8'
return response
But I don't recommend this. This converts all responses to JSON. Best use a framework like https://www.django-rest-framework.org/.
However, can use a standard view respone...
return HttpResponse(data, content_type='application/json; charset=UTF-8')
... or a custom decorator:
from functools import wraps
def json_response(function):
#wraps(function)
def wrap(request, *args, **kwargs):
response = function(request, *args, **kwargs)
response['Content-Type'] = 'application/json; charset=UTF-8'
return response
return wrap
#json_response
def my_view(request):
# ....
I'm a bit confused by the middleware description in the official django docs and can't seem to wrap my head around it.
When implementing class-based middleware it looks something like this (ex. from docs):
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
The middleware gets called for requests as well as for response. This is my first question:
How do I implement different behavior depending on if I'm processing a request or a response?
Also, and this is related, I'm a bit confused because there also seems to be an old deprecated way of writing middleware where it was possible to overwrite the methods process.request() and process_response(). I'm not sure if this way of writing middleware is inadvisable now. Usually, the Django docs are spot on but this part is a little confusing (to me).
Can someone clear this up for me?
Basically the codes before responses = self.get_response(request) should process the request. self.get_response() will get the response from view, then codes after that should process the response. Finally you need to return the response from the function. Here is a visualization:
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Codes here will process the request
# -----------------------------------
response = self.get_response(request)
# -----------------------------------
# Codes here will process the response
return response
I am trying to write a middleware class that ensures that the user is logged in.
But the problem is this middleware class will only be applicable to a small set of views and these views return a DRF's Response object rather then the HTTPResponse object and these views are also decorated using api_view.
So when I try to return a Response object from the middle ware class it raises this error.
assert renderer, ".accepted_renderer not set on Response"
AssertionError: .accepted_renderer not set on Response
I've searched a bit on SO and I guess that the error is somehow related to api_view decorator. But I am confused on how to solve this problem.
Any help is appreciated. :)
I've just recently hit this problem. This solution doesn't use the Django Rest Framework Response, but if your server just returns JSON this solution might work for you.
New in django 1.7 or greater is the JSONResponse response type.
https://docs.djangoproject.com/en/3.0/ref/request-response/#jsonresponse-objects
In the middleware you can return these responses without having all the "No accepted renderers" and "Response has no attribute encode" errors.
It's very similar format to the DRF Response
The import is as follows:
from django.http import JsonResponse
And how you use it:
return JsonResponse({'error': 'Some error'}, status=401)
Hopefully this helps you out!
I've solved this myself by mimicking how rest frameworks views patch the response object with accepted_renderer, accepted_media_type, and renderer_context. In my case I just wanted to return a 401 response using rest frameworks Response class, partly because my tests expect a rest framework response when I call self.client.get(...) and assert response.data.
Other use cases may require you to provide additional info in the renderer_context or use a different accepted_renderer.
from rest_framework import status
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
class MiddlewareClass(object):
def __init__(self, get_response):
self.get_response = get_response
def unauthorized_response(self, request):
response = Response(
{"detail": "This action is not authorized"},
content_type="application/json",
status=status.HTTP_401_UNAUTHORIZED,
)
response.accepted_renderer = JSONRenderer()
response.accepted_media_type = "application/json"
response.renderer_context = {}
return response
def __call__(self, request: HttpRequest):
if not self.authorized(request):
return self.unauthorized_response(request)
return self.get_response(request)
Down voted the answer above as it doesn't work for me.
The neighbor answer isn't helpful, too.
Both still return ContentNotRenderedError without manual call of the .render() method.
Tested with Python 3.8, Django 2.2, DRF 3.12.1.
The working mimicking middleware method for me is:
from rest_framework.views import APIView
# ... in the middleware class
def __call__(self, request):
try:
# Middleware logic before calling a view.
except APIException as err:
# No DRF auto-wrap out of view, so let's do it manually.
response = some_exception_handler(err, {})
return self.django_response(request, response)
return self.get_response(request)
def django_response(self, request: HttpRequest, resp: Response) -> HttpResponse:
view = APIView()
# copy-pasted from APIView.dispatch
view.headers = view.default_response_headers
return view.finalize_response(request, resp).render()
Docs investigation
To support my doubts about other solutions, here's the problem with an exception in middleware before the view has been called.
I bet the .render() method cannot be called automatically, cause the docs say:
There are three circumstances under which a TemplateResponse will be rendered:
When the TemplateResponse instance is explicitly rendered, using the SimpleTemplateResponse.render() method.
When the content of the response is explicitly set by assigning response.content.
After passing through template response middleware, but before passing through response middleware.
In our case only the 3-rd option matches. So, what is that "template response middleware"? There's only one similar thing in the docs:
process_template_response() is called just after the view has finished executing, if the response instance has a render() method, indicating that it is a TemplateResponse or equivalent.
But we haven't reached the view being executed! That's why the .render() method won't be called in our case.
I want to add a X-Frame-Options header to Django CreateView. I need this because I'll be serving a form that will be loaded in iframe tags.
The problem is, there are several methods in django class-based views that return HttpResponse objects.
Is there a way to add the header to the responses without overwriting all those methods?
class MyView(CreateView):
def get(self, request, *args, **kwargs):
resp = super(MyView, self).get(request, *args, **kwargs)
resp['X-Frame-Options'] = ''
return resp
# Same would go for form_invalid, post, put, etc...
Okay, I fixed it. If you've encountered similar problem, here's how to do it.
You have to overwrite render_to_response method in same way I did with get in the example code above.
I tried the overwrite render to response method, but I wanted a solution that I could use for a whole chunk of urls mapped to several views, and not have to deal with overwriting the same method on several views.
I made a middleware class based off of django-cors-headers so I could allow iframe-ing of part of my django app. I keep a middleware.py in my main project directory and save a couple random middleware classes I have made there, like this one here and a ForceResponse Exception for example.
import re
from django import http
from django.conf import settings
class XFrameAllowMiddleware(object):
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
if (self.is_enabled(request) and
request.method == 'OPTIONS' and
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META):
response = http.HttpResponse()
return response
return None
def process_response(self, request, response):
if self.is_enabled(request):
response['X-Frame-Options'] = 'ALLOWALL'
return response
def is_enabled(self, request):
return re.match(settings.XFRAME_URLS_REGEX, request.path)
Add it to your MIDDLEWARE_CLASSES and configure the regex in your settings:
MIDDLEWARE_CLASSES = (
...
'your_django_app.middleware.XFrameAllowMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
XFRAME_URLS_REGEX = r'^/iframe_this_url/.*$'
from the django-cors-headers read.me:
CORS_URLS_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under /api/.
Example:
CORS_URLS_REGEX = r'^/api/.*$'
Default:
CORS_URLS_REGEX = '^.*$'
I'm trying to do it this way, but it doesn't work.
class MyView(View):
def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse()
if self.kwargs.has_key('xml'):
response['Content-Type'] = 'text/xml; charset=utf-8'
return response
You don't need to write additional code. Use TemplateResponseMixin and set content_type attribute to whatever you need:
class MyView(TemplateResponseMixin):
content_type='application/xml'
...
I think the key point is render_to_response in django.views.generic.base , whose code is this:
def render_to_response(self, context, **response_kwargs):
"""
Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type) # key
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
**response_kwargs
)
As for your case, May be you need this code:
class MyView(ListView):
def get(self, request, *args, **kwargs):
context = self.get_context_data()
if self.kwargs.has_key('xml'):
return self.render_to_response(context, content_type="text/xml; charset=utf-8")
return self.render_to_response(context)
I made a middleware class based off of django-cors-headers so I could allow iframe-ing of part of my django app. I keep a middleware.py in my main project directory and save a couple random middleware classes I have made there, like this one here and a ForceResponse Exception for example.
import re
from django import http
from django.conf import settings
class XFrameAllowMiddleware(object):
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
if (self.is_enabled(request) and
request.method == 'OPTIONS' and
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META):
response = http.HttpResponse()
return response
return None
def process_response(self, request, response):
if self.is_enabled(request):
response['X-Frame-Options'] = 'ALLOWALL'
return response
def is_enabled(self, request):
return re.match(settings.XFRAME_URLS_REGEX, request.path)
Add it to your MIDDLEWARE_CLASSES and configure the regex in your settings:
MIDDLEWARE_CLASSES = (
...
'your_django_app.middleware.XFrameAllowMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
XFRAME_URLS_REGEX = r'^/iframe_this_url/.*$'
from the django-cors-headers read.me:
CORS_URLS_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under /api/.
Example:
CORS_URLS_REGEX = r'^/api/.*$'
Default:
CORS_URLS_REGEX = '^.*$'