Django: WSGIRequest' object has no attribute 'user' on some pages? - python

I want to set a cookie if user is logged in or not.
My middleware:
class UserStatus(object):
def process_response(self,request,response):
user_status = 1 if request.user.is_authenticated() else 0
max_age = (20)*52*7*24*60*60 # 20 years (After expiry, cookie gets deleted)
response.set_cookie(user_status_cookie,user_status,max_age)
return response
Added to MIDDLEWARE_CLASSES in settings.py at the end.
Problem:
Error: 'WSGIRequest' object has no attribute 'user'
Why, when I have the Authentication and the Session middlewares active already ?
Also, some pages are working smooth where as some are giving this error.
What am I doing wrong ?

Ran into the same issue recently, and found that it happened when a url is being accessed without the trailing slash, and the APPEND_SLASH setting is set to true:
Django processes initial request
CommonMiddleware.process_request
Redirects to newurl, which has the trailing slash
process_response is still run in custom middleware
request.user is not present
HTTP 301
Django then processes the request of url with trailing slash
process_response is run in custom middleware
request.user is now present
Anyone knows why some of the main attributes (user and session) are not accessible in process_response after a permanent redirect?

So it has to do with APPEND_SLASH being applied with via a redirect by Django Common Middleware, preventing the process_request() in AuthenticationMiddleware (which adds the user attribute) from being run but your process_response still being run.
Here's how Django Process Middleware ACTUALLY Works (from django/core/handlers/base.py in Django 1.6)
You request a URL that does not have a trailing slash. So yourdomain.com/view. This starts the middleware flow.
Once the request reaches CommonMiddleware, the middleware sees that there is not a slash and returns a http.HttpResponsePermanentRedirect(newurl). This immediately stops any additional process_requests from being run, including one in AuthenticationMiddleware that add the user attribute to request
Because CommonMiddleware did not return an exception (including Http404), django will now take the response from the middleware and run it through EVERY process_response() in EVERY middleware listed in MIDDLEWARE_CLASSES, no matter if that middleware's process_request() had a chance to run.
The only real way to fix this is to either move your code into a process_request() method located after AuthenticationMiddleware in MIDDLEWARE_CLASSES or detect via hasattr() if the request object has a user attribute.

According to the FineManual:
During the response phases (process_response() and process_exception() middleware), the classes are applied in reverse order, from the bottom up
So I'd say you'd better add your middleware before the auth and session middlewares (assuming it only processes the response).
This being said, I'm a bit puzzled by the fact that you only have the error on some pages ???

do you have active this middleware?:
'django.contrib.auth.middleware.AuthenticationMiddleware'
And this middleware run before your middleware?

I had a similar issue, some of my pages dont have the user in the request so in my middleware I do a quick check
if not hasattr(request, 'user'):
return response

There could be an exception raised within some middleware or any other code that runs before the django's AuthenticationMiddleware (which is responsible for assigning the .user to request object).
Then there will be an AttributeError upon accessing the .user variable.
For example, any exception triggered before the AuthenticationMiddleware had a chance to run might cause the error view to execute. You'll get the error as mentioned in the title of the question, if the error view depends on request.user.

Related

How to detect redirection in Django view

I have a view that returns a redirection.
return redirect(reverse('my-view-name'))
Is it a way to detect redirection inside a view method?
I've searched in request object attributes but couldn't find any one that indicates redirection.
A redirection simply means you return a HttpResponseRedirect object [Django-doc] with status code 302 and a header Location that contains the location to which you redirect. There is nothing special about that response, and the target view is not triggered by the redirect response itself.
The browser that receives such response, can then thus act by visiting that link, and then it will thus trigger the view to which you redirected. But a browser does not per se does that (you can usually alter some settings to let your browser (not) follow such redirects).
You can thus for example implement your own middleware that does something with such redirect:
def redirect_processing_middleware(get_response):
def middleware(request):
response = get_response(request)
if 300 <= response.status_code < 400:
# the response is a redirect
# ...
pass
return middleware
An alternative is to add a watchdog on the redirect(..) call, but that can be circumvented, by simply creating a HttpResponse for example, and manually alter that response to transform it into a redirect response.
You can do some static code analyses (automatically or manually). But due to the dynamic nature of Python, it is impossible to know for sure what views will trigger redirections: one can obfuscate the redirect call, by using a proxy function, etc., or dynamically alter/monkey patch code such that redirections are taking place without (explicitly) calling redirect(..).

Django REST: OPTIONS request is registered as PUT

Whenever the browser sends an OPTIONS request, Django REST registers that as a PUT request.
I was writing my permission code when I noticed it. I use the default request parameter that is passed into the def has_object_permission( self, request, view, obj ):.
Using request.method will return the correct request method for every request except for OPTIONS.
However, when I use request.method in a my get_permission( self, request, view ): function in a different part of the project, correct response is returned. Could there be something wrong with the has_object_permission() function?
Currently I just check if the request is PUT, as I believe PUT requests are not used by Django anyway. But it would be nice if I could use the actual name instead.
My Django REST version is 3.9.0
OPTIONS requests are often used for what are called "pre-flight" requests in Cross-origin resource sharing (CORS). This is not anything wrong with has_object_permission(), it's just that these pre-flight requests are probably not intended to handled by your view. There is a more detailed answer in this SO post: https://stackoverflow.com/a/29954326/784648

Looking for Django equivalent of Flask's request.get_data() (for Slack request verification with raw request body) [duplicate]

Since Django 1.5 raw post data is accessible via request.body.
In my application I sometimes get data send via a form and sometimes raw data (json for example).
Is there any way to write a function like this that does not fail?
def get_post_var(request, name):
result = request.POST.get(name)
if result:
return result
post_body = dict(urlparse.parse_qsl(request.body))
result = post_body.get(name)
if result:
return result
return None
Use request.data instead of request.body.
request.data does not read the data stream again.
The error You cannot access body after reading from request's data stream will be triggered on a request if (1) that request method is POST, (2) that request's POST dictionary is accessed in middleware, in either process_request or process_view and (3) within the view function, request.body is accessed. It is on (3) that the error will be raised, even though the real cause of the bug is (2).
In order to resolve the error, you need to examine your middleware for where it accesses request.POST and modify it such that it doesn't access request.POST anymore.
The Django docs say that middleware should not access request.POST, and this is one consequence of ignoring that recommendation.
Also check out this Django ticket on the issue, which includes the note:
[M]iddleware that hits request.POST should (usually) be considered a
bug. It means that the view will be unable to set any custom upload
handlers, perform custom parsing of the request body, or enforce
permission checks prior to file uploads being accepted.
Adding to Adam Easterling's answer it is worth noting that Django itself 'violates' the hint of not using request.POST in middleware:
The CsrfViewMiddleware class can be considered an exception, as it
provides the csrf_exempt() and csrf_protect() decorators which allow
views to explicitly control at what point the CSRF validation should
occur.
Which does not sanitilize the violation IMO
For those interested to know, I faced this issue:
You cannot access body after reading from request's data stream
when I added 'oauth2_provider.contrib.rest_framework.OAuth2Authentication'
in the "REST_FRAMEWORK" like so in the settings.py:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
...
),
Of course disabling this will work but not a workaround I would be proud of.
I was able to read my request.POST after putting #csrf_exempt before my view function. Because CSRF middleware accesses POST data.
I found a trick, using for my middleware,
request._read_started = False
after doing that, ready body again and it works.
For those with the same error who are not readying the body or POST, I had this same error when I used this line of code in a process_view middleware::
event = request.event if 'event' in request else None
Solved by settings request.event = None at the top of the function so I could then use:
event = request.event

Setting cookies within Django Middleware

I would like to use Custom Django Middleware (Django 1.9) to check whether or not an anonymous user has accepted a site's terms & conditions - I'll show the user a dialog if they've not yet clicked 'Agree'.
I have no need to store this in the database and would prefer to simply use a Cookie. I have the following in settings.py:
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
'myapp.middleware.app_custom_middleware.TermsMiddleware',]
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
I'm then trying something very rudimentary in TermsMiddleware - here I'm deliberately trying to modify the request and the response just to try to get it to work:
class TermsMiddleware(object):
def process_request(self, request):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return
def process_response(self, request, response):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return response
If I inspect the response in the browser, I see that myCookieKey isn't being set, so I think I've misunderstood how this should be written.
Can I manipulate the session within middleware like this, having it take effect in the cookie sent in the response Django was going to send anyway? The documentation makes it sound like I should be able to:
It should return either None or an HttpResponse object. If it returns
None, Django will continue processing this request, executing any
other process_request() middleware, then, process_view() middleware,
and finally, the appropriate view. If it returns an HttpResponse
object, Django won’t bother calling any other request, view or
exception middleware, or the appropriate view; it’ll apply response
middleware to that HttpResponse, and return the result.
I've also seen the note in the documentation to use SessionStore() when outside of a view function. In theory I don't think I should need to do that here because I've got access to request (and response). I tried it anyway, and I still couldn't get it to work.
Help much appreciated!
The thing you've misunderstood is the relationship between the session and the cookies.
You can't set arbitrary cookies by modifying the session. The session is a collection of data, usually stored in the db or a file, and the only cookie is the one that contains the session key for the current user.
You certainly can modify the session in middleware, but wherever you do so, you shouldn't expect to see a cookie being set.
(Separately, you should never call double underscore methods like __setitem__ directly. The session is accessed via a dict-like interface: request.session['foo'] = 'bar'.)

How to check USER IP in django project when website loads?

I am working on django website. I want make this website country restricted. So I want to check user ip when website is accessed. It doesn't matter what is url or which app is loaded. So if user is not from specific country, I want to show them error message that they cannot access this website.
I am using GeoLitCity database to get user's country. I know how to get user's ip. But I don't know how to do it in initialization step, before anything loads. Can anyone help me with this?
You can create a custom middleware class ValidateUserCountryMiddleware which will check if the country from where the request was made is from a specific country or not. If the request is made from a valid country, then Django will process the request. Otherwise, it will return a HttpResponseForbidden response.
In our custom middleware class, we will define a process_request() method. It should return either None or an HttpResponse object.
From the docs on process_request():
If it returns None, Django will continue processing this request,
executing any other process_request() middleware, then, process_view()
middleware, and finally, the appropriate view. If it returns an
HttpResponse object, Django won’t bother calling any other request,
view or exception middleware, or the appropriate view; it’ll apply
response middleware to that HttpResponse, and return the result.
Here, we will return a HttpResponseForbidden response in case the request did not come from a specified country.
from django.http import HttpResponseForbidden
class ValidateUserCountryMiddleware(object):
"""
This custom middleware class will check the country from where the request
was made using the IP address. If the country is valid, then Django will process
the request. Otherwise, it will return a 403 forbidden response
"""
def process_request(self, request):
# here write the logic to obtain the country from the IP address.
# Then check if its a valid country
if not valid_country: # check if its a valid country
return HttpResponseForbidden('You cannot access the website from this location.') # dont't process the request and return 403 forbidden response
return # return None in case of a valid request
Then add your middleware to the MIDDLEWARE_CLASSES in your settings.py file.
MIDDLEWARE_CLASSES = (
...
# add your custom middleware here
'my_project.middlewares.ValidateUserCountryMiddleware',
)

Categories