What/Where to modify django-registration to use with mobile broswers - django

I am using django-registration along side django auth for my client account creation and login.
Our site will be used by moble users and desktop users. We just started tackling the idea of mobile users by loading different templates from the view depending on user agent strings. It's cleanly done, but I am not sure if it is the right way to do it as we are now stuck with what to do on views that are not easily accessible (that we didn't write ourselves).
Which brings me to the problem at hand:
I have no idea how to tackle redirecting the mobile user away from the login url that django-registration/auth sends them to (the desktop version).
I could change tactics and tackle the different browsers in the template files themselves. That feels like it is going to get messy fast. I don't like that idea at all!
Or I stay with my current method, which is to render the request with different templates based on user agent strings. Then i need to know how I should be dealing with django-registration (how to load a different set of templates based on the user agent string). I would rather not change the django-registration code, if just to make updating modules easier.

The django registration templates are very simple and are used very rarely. I simply handle these as special cases and just come up with a base.html for that works on both platforms reasonably well.
My registration pages look very simple, many sites do this and it is not unexpected.
Another option is to us a middleware which sets the template directory based upon detecting if it is a mobile device. You can detect the mobile browser like this Detect mobile browser (not just iPhone) in python view and then have a middleware that uses the make_tls_property trick to update the TEMPLATE_DIRS something like this:
TEMPLATE_DIRS = settings.__dict__['_wrapped'].__class__.TEMPLATE_DIRS = make_tls_property(settings.TEMPLATE_DIRS)
class MobileMiddleware(object):
"""Sets settings.SITE_ID based on request's domain"""
def process_request(self, request):
if *mobile*:
TEMPLATE_DIRS.value = *mobiletemplates* + settings.BASE_TEMPLATE_DIRS
else:
TEMPLATE_DIRS.value = *normaltemplates* + settings.BASE_TEMPLATE_DIRS
Just to be clear, make_tls_property, which is part of djangotoolbox, makes the TEMPLATE_DIRS setting a per thread variable instead of a global variable so each request response loop gets it's own "version" of the variable.

One method is to simply write your own login view that calls the django-registration view to do the hard work, but passing it a different template depending on the context:
def login(request, *args, **kwargs):
my_kwargs = kwargs.copy()
if <mobile condition>:
my_kwargs['template_name'] = 'my_app/some_template.html'
else:
my_kwargs['template_name'] = 'my_app/some_other_template.html'
from django.contrib import auth
return auth.login(request, *args, **my_kwargs)

Related

Mobile templates based on user-agent in django ensuring thread safety

I am developing the mobile version of my website, so thought of using user-agent as the criteria for serving different templates for mobile and web version.
I successfully read the user-agent information from nginx and passed it as header to gunicorn server.
Then I created a middleware which reads this header and changes the templates directory in settings file. This seemed to work initially but then I realized that there is race condition happening as this method is not thread safe. (I should have thought of it before-hand).
So I started thinking of other alternatives. One solution was to overwrite the render method of django to include "dirs" parameter based on request header. But then I found out that the "dirs" parameter is deprecated. Following is the reference link https://docs.djangoproject.com/en/1.9/_modules/django/shortcuts/#render
So even this will not work.
Another solution is to have different template names for mobile and web and load them accordingly. However I don't want to do this and want to keep the templates directory structure exactly same for both web and mobile.
There has to be a way to just overwrite the template directory. This will give me an advantage of falling back on web version of templates if its missing in mobile templates directory.
Any advise on how to achieve this will be helpful.
This is how my templates are organized.
App1
templates
App1
index.html
catalog.html
App2
templates
App2
about.html
And in the project directory(not part of the app folder), there is a mobile templates folder which has the following structure
mobile-templates
App1
index.html
App2
about.html
Thanks
Anurag
Here's how I would organize my templates:
Make two directories inside templates dir - mobile and desktop.
Keep mobile templates in mobile dir and desktop templates in desktop.
This way you won't have to rename the templates.
And here's how I would render them:
Read User-Agent in a middleware.
Set an attribute on request called template_prefix whose value will either be mobile or desktop, depending on the User-Agent. Eg:
def process_request(self, request):
# read user agent and determine if
# request is from mobile or desktop
# ...
if mobile_user_agent:
request.template_prefix = 'mobile'
else:
request.template_prefix = 'desktop'
In your views, use request.template_prefix before template names. Eg:
def home(request):
...
template = request.template_prefix + '/home.html'
return render(request, template)
This will render the templates from either mobile or desktop dirs depending on the value template_prefix attribute.
UPDATE (according to question edit):
Looking at how your templates are organized, I'd do this:
Middleware:
Only set template_prefix for mobile requests.
def process_request(self, request):
if mobile_user_agent:
request.template_prefix = 'mobile-templates'
else:
request.template_prefix = '' # set an empty string
Views:
Use os.path.join; catch TemplateDoesNotExist exception.
import os.path
from django.template.loader import get_template
from django.template.base import TemplateDoesNotExist
def index(request):
try:
template = os.path.join(request.template_prefix, 'App1/index.html')
get_template(template)
except TemplateDoesNotExist:
template = 'App1/index.html'
return render(request, template)
I've tested this and it works. But writing a try...except block in every view seems redundant. If I come up with a better solution, I will update.
It seems it isn't possible to this out of the box right now. If you really want to follow this architecture, you will have to write your own custom loader and also figure out a way to pass the request/indicator to let it know its a mobile request.
It's not too tough to write the loader (just see the Django filesystem loader, if the request is from mobile, loop through all the templates_dirs and add the proper suffix to it, so that you include mobile dirs too).
However the biggest challenge as I see it is being able to pass a dynamic parameter to it (indicating that this is a mobile request). You may store this param in the session or modify the template name before passing it to the custom renderer (The renderer will remove this indicator part and get the template) .

Flask OpenID unittest

I'm trying to write a unit test for my flask app for OpenID but upon calling
oid.try_login(<oid provider>, <params>)
I get an error:
RuntimeError: <class 'flask.testing.FlaskClient'> does not support redirect to external targets
So, like every good SO user, I looked around for some solutions:
Disguise oid provider using the NoExtRef flask extension. I'm not sure if this is possible at the app level since I assume flask-openid messes around with the oid url (and it just redirected me to the original page when I tried it). But this seems quite ugly since I'm making a code change strictly for a unittest.
Create my own oid server but this might still be an external redirect (I'll try this later as soon as I get desperate enough).
I guess another alternative is to ignore writing unit tests for login and just set the user in Flask.g using the awesome Flask test framework. But I'd prefer to keep the login unit tests.
There is an alternative - monkey-patch the open-id extension's try_login method:
class LoginTestMonkeyPatch(object):
def __init__(self, oid=None, default_response=None):
self.response = default_response
if oid is not None:
self.init(oid)
def init(self, oid):
oid.try_login = self.try_login
def try_login(self, *args, **kwargs):
# Do whatever you want to do here
If you are patching the login, you may not be testing it.
I had the same problem. For me the best solution was to disable the "log in required" part of the view.
I don't know if you are using Flask Login, but if you are you can bypass the #login_required so that you don't even need to worry about trying to login the user with something like:
def setUp
env = Environments(app)
env.from_object('config.Testing')
lm = LoginManager()
lm.init_app(app)
self.app = app.test_client()
Just a thought, I hope this helps you or someone else :)
P.S. This is my first post on Stack Overflow. Thanks to all the many posters that have helped me so much!

How to prevent Django from writing to django_session table for certain URLs

Apologies if my question is very similar to this one and my approach to trying to solve the issue is 100% based on the answers to that question but I think this is slightly more involved and may target a part of Django that I do not fully understand.
I have a CMS system written in Django 1.5 with a few APIs accessible by two desktop applications which cannot make use of cookies as a browser does.
I noticed that every time an API call is made by one of the applications (once every 3 seconds), a new entry is added to django_session table. Looking closely at this table and the code, I can see that all entries to a specific URL are given the same session_data value but a different session_key. This is probably because Django determines that when one of these calls is made from a cookie-less application, the request.session._session_key is None.
The result of this is that thousands of entries are created every day in django_session table and simply running ./manage clearsessions using a daily cron will not remove them from this table, making whole database quite large for no obvious benefit. Note that I even tried set_expiry(1) for these requests, but ./manage clearsessions still doesn't get rid of them.
To overcome this problem through Django, I've had to override 3 Django middlewares as I'm using SessionMiddleware, AuthenticationMiddleware and MessageMiddleware:
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.contrib.messages.middleware import MessageMiddleware
class MySessionMiddleware(SessionMiddleware):
def process_request(self, request):
if ignore_these_requests(request):
return
super(MySessionMiddleware, self).process_request(request)
def process_response(self, request, response):
if ignore_these_requests(request):
return response
return super(MySessionMiddleware, self).process_response(request, response)
class MyAuthenticationMiddleware(AuthenticationMiddleware):
def process_request(self, request):
if ignore_these_requests(request):
return
super(MyAuthenticationMiddleware, self).process_request(request)
class MyMessageMiddleware(MessageMiddleware):
def process_request(self, request):
if ignore_these_requests(request):
return
super(MyMessageMiddleware, self).process_request(request)
def ignore_these_requests(request):
if request.POST and request.path.startswith('/api/url1/'):
return True
elif request.path.startswith('/api/url2/'):
return True
return False
Although the above works, I can't stop thinking that I may have made this more complex that it really is and that this is not the most efficient approach as 4 extra checks are made for every single request.
Are there any better ways to do the above in Django? Any suggestions would be greatly appreciated.
Dirty hack: removing session object conditionally.
One approach would be including a single middleware discarding the session object conditional to the request. It's a bit of a dirty hack for two reasons:
The Session object is created at first and removed later. (inefficient)
You're relying on the fact that the Session object isn't written to the database yet at that point. This may change in future Django versions (though not very likely).
Create a custom middleware:
class DiscardSessionForAPIMiddleware(object):
def process_request(self, request):
if request.path.startswith("/api/"): # Or any other condition
del request.session
Make sure you install this after the django.contrib.sessions.middleware.SessionMiddleware in the MIDDLEWARE_CLASSES tuple in your settings.py.
Also check that settings.SESSION_SAVE_EVERY_REQUEST is set to False (the default). This makes it delay the write to the database until the data is modified.
Alternatives (untested)
Use process_view instead of process_request in the custom middleware so you can check for the view instead of the request path. Advantage: condition check is better. Disadvantage: other middleware might already have done something with the session object and then this approach fails.
Create a custom decorator (or a shared base class) for your API views deleting the session object in there. Advantage: responsibility for doing this will be with the views, the place where you probably like it best (view providing the API). Disadvantage: same as above, but deleting the session object in an even later stage.
Make sure your settings.SESSION_SAVE_EVERY_REQUEST is set to False. That will go a long way in ensuring sessions aren't saved every time.
Also, if you have any ajax requests going to your server, ensure that the request includes the cookie information so that the server doesn't think each request belongs to a different person.

Is this Django Middleware Thread-safe?

I am writing forum app on Django using custom session/auth/users/acl system. One of goals is allowing users to browse and use my app even if they have cookies off. Coming from PHP world, best solution for problem is appending sid= to every link on page. Here is how I plan to do it:
Session middleware checks if user has session cookie or remember me cookie. If he does, this most likely means cookies work for him. If he doesnt, we generate new session ID, open new session (make new entry in sessions table in DB), then send cookie and redirect user to where he is, but with SID appended to url. After redirect middleware will see if session id can be obtained from either cookie or GET. If its cookie, we stop adding sid to urls. If its GET, we keep them.
I plan to insert SID= part into url's by decorating django.core.urlresolvers.reverse and reverse_lazy with my own function that appends ?sid= to them. However this raises some problems because both middlewares urlresolvers and are not thread safe. To overcome this I created something like this:
class SessionMiddleware(object):
using_decorator = False
original_reverse = None
def process_request(self, request):
self.using_decorator = True
self.original_reverse = urlresolvers.reverse
urlresolvers.reverse = session_url_decorator(urlresolvers.reverse, 's87add8ash7d6asdgas7dasdfsadas')
def process_response(self, request, response):
# Turn off decorator if we are using it
if self.using_decorator:
urlresolvers.reverse = self.original_reverse
self.using_decorator = False
return response
If SID has to be passed via links, process_request sets using_decorator to true and stores undecorated urlresolvers.revers in separate method. After page is rendered process_response checks using_decorator to see if it has to perform "garbage collection". If it does, it returns reverse function to original undecorated state.
My question is, is this approach thread-safe? Or will increase in traffic on my forum may result in middleware decorating those functions again and again and again, failing to run "garbage collection"? I also tought about using regex to simply skim generated HTML response for links and providing template filters and variables for manually adding SID to places that are omitted by regex.
Which approach is better? Also is current one thread safe?
First of all: Using SIDs in the URL is quite dangerous, eg if you copy&paste a link for a friend he is signed in as you. Since most users don't know what a SID is they will run into this issue. As such you should never ever use SIDs in the url and since Facebook and friends all require cookies you should be fine too...
Considering that, monkeypatching urlresolvers.reverse luckily doesn't work! Might be doable with a custom URLResolvers subclass, but I recommend against it.
And yes, your middleware is not threadsafe. Middlewares are initialized only once and shared between threads, meaning that storing anything on self is not threadsafe.

Django testing named URLs with additional GET parameters

I am trying to write some tests for a Django application I'm working on but I haven't yet decided on the exact urls I want to use for each view. Therefore, I'm using named urls in the tests.
For example, I have a url named dashboard:
c = Client()
resp = c.get(reverse('dashboard'))
This view should only be available to logged in users. If the current user is anonymous, it should redirect them to the login page, which is also a named url. However, when it does this, it uses an additional GET parameter to keep track of the url it just came from, which results in the following:
/login?next=dashboard
When I then try to test this redirect, it fails because of these additional parameters:
# It's expecting '/login' but gets '/login?next=dashboard'
self.assertRedirects(resp, reverse('login'))
Obviously, it works if I hard code them into the test:
self.assertRedirects(resp, '/login?next=dashboard')
But then, if I ever decide to change the URL for my dashboard view, I'd have to update every test that uses it.
Is there something I can do to make it easier to handle these extra parameters?
Any advice appreciated.
Thanks.
As you can see, reverse(...) returns a string. You can use it as:
self.assertRedirects(resp, '%s?next=dashboard' % reverse('login'))