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

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) .

Related

How to access app name from within blueprint template?

I've written a common blueprint for a bunch of independent little apps as a python package. Mostly this is just a top-level template and a style sheet to give the apps a unified design. I'd like to display the current app's name in the top-level template, but I have no idea how. It seems that there is no way to get to the app context from within the blueprint that the app registered.
I've tried this:
#blueprint.context_processor
def appinfo():
return { 'appname': flask.current_app.name }
but it only works when the template is rendered by one of the blueprint's view functions but not from the app's.
blueprint.app_context_processor() does the trick.
Using the Blueprint.context_processor decorator implies that the function will be called only for requests handled by the blueprint. Namespaces are great!
If you'd like 'appname' to be available in every template: app and blueprints alike, registering the context processor with Flask.context_processor should satisfy that.
app = Flask(__name__)
#app.context_processor
def appinfo():
return dict(appname=app.name)

Where to place helping functions in Django 1.5

I have a function "GenerateUsername" which returns a random username. I use it in a manager in managers.py, but where would be the best place to have this function? In a separate file called helpers.py?
def GenerateUsername():
username = str(random.randint(0,1000000))
try:
User.objects.get(username=username)
return GenerateUsername()
except User.DoesNotExist:
return username;
I think a more comprehensive answer is deserved here. There are several ways to go about it.
utils.py for the whole django site: This has the flaw that all django apps in that site will dump their generic functions in here and this file would become a blob
utils.py for each django app inside the site: So if you have two apps, app1 and app2, then you have app1/utils.py and app2/utils.py. This is slightly better than (1)
Library outside of Django (maybe on the same level as Django): I often make libraries for generic Django APIs in case they may be used by more than 1 site. This has the additional advantage that if you launch a new Django site tomorrow, you could use this generic function in that site as well just by importing this library after having it on your path. Inside the library, you could have separate modules such as userutils.py, mailutils.py which will address the 'blob' issue and also organize your code better. I recommend this approach.

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'))

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

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)

How to add app to all pages in django?

I have an app called lastapp that I would like it to be viewable on all pages. It renders base.html so it's available on all apps. So far I've tried adding it to the urls.py like this:
urlpatterns = patterns('',
(r'^first/$', firstapp, lastapp),
(r'^last/$', lastapp),
)
But when I go to /first/ it gives me the error: Error, 'function' not iterable. It shows fine when going to /last/. If I remove lastapp from the /first/ site in urls.py, then firstapp shows fine also. Is there a special way to do this? Thanks in advance.
What exactly are you trying to do?
All installed applications are available in your project, but the application by it self does not return any data to the template, that's the job of the views which are part of that application.
My guess is that firstapp and lastapp are rather views then apps.
If that is a case, context processors make it possible to always pass certain data to a template.
So, create a context processor out of lastapp and set it in TEMPLATE_CONTEXT_PROCESSORS in settings.py.
I think what you need is probably Middleware.