django middleware set user special global variable - django

if every web page has a user new message notice(new message count, like message(1)), how can i pass variable '{ new_message_count: 1}' to every view
i want to using middleware:
class page_variable():
def process_request(self, request):
# put the variable to the request or response so can used in template variable
return None
and template look like:
new <em>({{ new_message_count }})</em>

There's already a built-in messaging framework that handles all of this for you.
However, assuming you really want to roll your own, you can't pass things into the context from middleware. You can attach it to the request object, which you can then use in your view or template, or add a context processor which takes the variable from the request and adds it into the context.

In the development version of django, you can edit template context from a middleware before rendering:
class MessageCountMiddleware:
def process_template_response(self, request, response):
response.context['new_message_count'] = message_count(request.user)
In Django 1.2 you can create a custom context processor:
def add_message_count(request):
return { 'new_message_count': message_count(request.user) }
and register it in settings
TEMPLATE_CONTEXT_PROCESSORS += [ 'my_project.content_processors.add_message_count' ]

Related

Django: How to provide context to all views (not templates)?

I want to provide some context to all my function-based views (FBV) similar to the way TEMPLATE_CONTEXT_PROCESSORS (CP) provides context to all of one's templates. The latter doesn't work for me because I need that context prior to rendering the templates.
In particular, on my site I have a function which takes a request and returns the model for the Category of item in focus. My CP provides this for all templates, but I find myself making the same call from my FBV's and would like to remove this redundancy.
This question is similar but it presupposes the approach of accessing the output of the CP from the views. This seems hacky, and I'm not sure it's the best approach.
What's the Django way to do this?
Use Middleware...
class MyModelMiddleware(object):
def process_request(self, request):
request.extra_model = self.get_model(request.user)
Based on mwjackson 's answer and on docs, for Django 1.11, I think the middleware should be:
# middleware/my_middleware.py
class MyModelMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# TODO - your processing here
request.extra_model = result_from_processing
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
In settings.py, add the path to your Middleware on MIDDLEWARE = () . Following the tips from this site, I had created a folder inside my app called middleware and added a new file, say my_middleware.py, with a class called, say, MyModelMiddleware. So, the path that I had added to MIDDLEWARE was my_app.middleware.my_middleware.MyModelMiddleware.
# settings.py
MIDDLEWARE = (
...
'my_app.middleware.my_middleware.MyModelMiddleware',
)

Django: How to re-route a request to a different view using middleware

I'm trying to work with the following scenario:
When a GET request comes in to my "/" route I normally want to handle it with my HomeView. However, my site is heavy AJAX so if the request's UserAgent is a bot then I serve it up with a fully rendered version of the page (standard PhantomJS stuff). The approach works fine, but the performance of the fully rendered version, and the SLA for that version, is very different than the regular user view. As such, I would like to use a piece of middleware to do the bot detection and based on that middleware I would then like to send the request to a different View.
The middleware part is easy, I have a process_request handler that detects the bot - no big deal. However, I can't figure any option for overriding the View function that will be invoked. Is there a "proper" way to do this in Django? My current thoughts are:
modify request.path_info to change the requested URL so that the router will then send HtmlRendererView rather than HomeView
Call the HtmlRendererView directly from the middleware and return the appropriate HttpResponse. This feels clunky because it then takes away the opportunity for any other middleware to run.
Notes:
I don't want to return a redirect, the crawler is getting a different version of the same resource
I'm on heroku so I can't rewrite the route before it hits Django. If I was using nginx I'd probably just put this logic at that layer and rewrite the URL before it hit Django.
This is not a direct answer to your question ("Reroute a request to a different view"), but maybe this solution could adress your problem.
First, you keep your middleware, but use it only to detect if the visitor is a bot:
def process_request(self, request):
request.is_bot = is_bot(request) # assuming you have a function for detecting bots
return
Then you create a class based view that call a specific method when request.is_bot is True:
class BotViewMixin(object):
def dispatch(self, request, **kwargs):
if request.is_bot:
return self.handle_bot()
return super(BotViewMixin, self).dispatch(request, **kwargs)
You can then inherit this view anywhere you need (e.g. for your Home Page View). You just have to create a handle_bot method on your view, that will return your response for bots.
Advantages of this solution:
You don't need to write different views for bots, just create a dedicated method
You don't block other middlewares
Your logic stay in your views (and not in your middleware)
This is not tested though, so you may need to adapt the code.
EDIT:
Since you use NewRelic and must use a dedicated view for bots in order to get accurate statistics, this approach won't work for you.
You can go with the middleware thing, and still get all middlewares working. You just have to put your own middleware last in MIDDLWARE_CLASSES:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'yourproject.CrawlerDetector',
)
Also, I think you should write two middlewares methods: process_request for detecting bots, and process_view for redirecting bots to dedicated view.
The following code should probably work for your situation:
from django.core.urlresolvers import reverse
class CrawlerDetector(object):
def process_request(self, request):
"""detect if the user agent is a bot"""
user_agent = request.META.get('HTTP_USER_AGENT', "")
request.is_bot = self.is_crawler(user_agent)
return
def process_view(request, view_func, view_args, view_kwargs):
if request.is_bot and request.path == reverse('home_page'):
return HtmlRendererView().get(request)
return
My current working solution, while not as clean as Eliot's suggested solution, looks (basically) like this:
class CrawlerDetector(object):
# Middleware that detects requests that should be rendered by the HtmlRendererView.
def process_request(self, request):
user_agent = request.META.get('HTTP_USER_AGENT', "")
if not self.is_crawler(user_agent):
return None
return HtmlRendererView().get(request)
It has the downside of removing any downstream middleware from the flow, but it does allow me to call my crawler-specific View before the root View is routed to.

Passing request to custom Django template loader

I want to write custom template loader for my Django app which looks for a specific folder based on a key that is part of the request.
Let me get into more details to be clear. Assume that I will be getting a key on every request(which I populate using a middleware).
Example: request.key could be 'india' or 'usa' or 'uk'.
I want my template loader to look for the template "templates/<key>/<template.html>". So when I say {% include "home.html" %}, I want the template loader to load "templates/india/home.html" or "templates/usa/home.html" or "templates/uk/home.html" based on the request.
Is there a way to pass the request object to a custom template loader?
I've been searching for the same solution and, after a couple days of searching, decided to use threading.local(). Simply make the request object global for the duration of the HTTP request processing! Commence rotten tomato throwing from the gallery.
Let me explain:
As of Django 1.8 (according to the development version docs) the "dirs" argument for all template finding functions will be deprecated. (ref)
This means that there are no arguments passed into a custom template loader other than the template name being requested and the list of template directories. If you want to access paramters in the request URL (or even the session information) you'll have to "reach out" into some other storage mechanism.
import threading
_local = threading.local()
class CustomMiddleware:
def process_request(self, request):
_local.request = request
def load_template_source(template_name, template_dirs=None):
if _local.request:
# Get the request URL and work your magic here!
pass
In my case it wasn't the request object (directly) I was after but rather what site (I'm developing a SaaS solution) the template should be rendered for.
To find the template to render Django uses the get_template method which only gets the template_name and optional dirs argument. So you cannot really pass the request there.
However, if you customize your render_to_response function to pass along a dirs argument you should be able to do it.
For example (assuming you are using a RequestContext as most people would):
from django import shortcuts
from django.conf import settings
def render_to_response(template_name, dictionary=None, context_instance=None, content_type=None, dirs):
assert context_instance, 'This method requires a `RequestContext` instance to function'
if not dirs:
dirs = []
dirs.append(os.path.join(settings.BASE_TEMPLATE_DIR, context_instance['request'].key)
return shortcuts.render_to_response(template_name, dictionary, context_instance, content_type, dirs)

How to set cookie for many views?

I have site with many views and I want to check the cookie in each of them, and when it does not - save them. But site have a lot of views.
How to do it only once for all views?
You can write custom middleware to achieve your goal as you have many views and of course you can not update every view. The custom middleware would be something like this:
class MyCookieProcessingMiddleware(object):
# your desired cookie will be available in every django view
def process_request(self, request):
# will only add cookie if request does not have it already
if not request.COOKIES.get('your_desired_cookie'):
request.COOKIES['set_your_desired_cookie'] = 'value_for_desired_cookie'
# your desired cookie will be available in every HttpResponse parser like browser but not in django view
def process_response(self, request, response):
if not request.COOKIES.get('your_desired_cookie'):
response.set_cookie('set_your_desired_cookie', 'value_for_desired_cookie')
return response
In your settings.py file, just add the path to your custom middleware like this:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'MyProject.myapp.mymodule.MyCookieProcessingMiddleware', # path to custom class
)
The order of middleware is important and yours belongs after SessionMiddleware.
What I understood is that, you want to set the cookie once and then want to check it's value in any view. If this is your problem then you can save cookie once in views like this:
from project.settings import IS_COOKIE_SET # Set Global value for cookie
response = render_to_response("your-template.html")
if !IS_COOKIE_SET:
response.set_cookie('key', 'value')
return response
else:
return response
You can check the value of cookie in any other view like this:
request.COOKIES.get('key', None) # Return None If cookie not exists

Setting a variable in middleware to be accessed in the template

I seem to be having difficulty setting a variable in one of my middleware classes that I can then access in the template layer.
The basic layout is this:
class TheMiddleware(object):
def __init__(self, etc):
stuff...
def process_response(self, request, response):
request.my_var = "whatever"
return response
Then on the template for a different view I have:
{% custom_tag arg_a %}
Which is is a template tag that should return the variable from the request:
#register.simple_tag
def custom_tag(arg_a):
return threading.currentThread().request.my_var
This errors out with "Caught AttributeError while rendering: 'WSGIRequest' object has no attribute 'my_var'"
I thought it might be the way I was accessing the request in the template tag. So I added django.core.context_processors.request to my TEMPLATE_CONTEXT_PROCESSORS as in This question and tried passing the request object to the tag, then accessing request directly from the template but with no luck.
I think I lack an understanding on how request objects work. Is it possible to assign a variable to a request object and pick that variable up several views on? I thought the request object was passed through the views, but it seems that instead a new instance is generated.
If that is the case, how would you go about storing a global variable within middleware that you could then access from any point in your app, be it in a view or a template?
Update:
To clear up the confusion (whether mine or others I'm not sure!) I'm not trying to set the request variable in the process_response middleware of a view and then pick it up in the template of that same view. I understand that that wouldn't work because the template has been processed before the variable is saved. (This is a deliberate act on my part).
I have two views, view1 and view2 view one has a decorator that causes the middleware to set the variable in the request. It is the view2 template, which comes after the variable has been set, that I wish to access the variable.
You trying to set variable during processing of response in your middleware.
I think you should be implementing process_request() instead to set the variable.
def process_request(self, request):
request.my_var = "whatever"
return
If you're setting it on the request, I can't see any reason at all to try and use threadlocals here. You should use the context processor as you describe.
The reason for your problem, though, is that process_response is run in the response phase of the request/response cycle: ie, after your view has been called. You should define process_request instead. See here for a description of the order that middleware methods are called.