Showing posts with label django. Show all posts
Showing posts with label django. Show all posts

Mobile website with Django

Although I'm not a web guy, but more specialized on backend logic, I had a very interesting experience today creating a website version for mobiles. I had a simple website with a few pages, minimum server side logic and a slice of documentation there. This website perfectly works for desktop and tablet browsers, but is not that good for mobiles. I use to call this website as "normal" version throughout this post. It's important to have a working mobile version of website, as website is about Android application.

A little bit about technologies and task. Website is running on Django as well as mobile version should be too. Mobile version of website should be much simpler than normal version, not so beautiful, but must do own job well: allow users to know about the product, download and access to the documentation.

I decided to use the same django application for both normal and mobile website versions. I found a few different ways to add mobile website, for example: redirect to another django application, replace TEMPLATE_DIRS setting with a version that points to mobile templates if user is using mobile browser or just check and use different logic/templates in every view method. As I wanted to have a different structure for mobile website, I decided to go by my own way.

Mobile website path should be prefixed with /m/ rather than be the same or use a different domain name. So, if normal website download page is http://example.com/download/, then mobile download page should be http://example.com/m/download/. Django should check every user request whether it's from mobile browser or not using a User-Agent metadata, and if so, redirect to the mobile version of the page. In other words, if user opens a /download/ page from mobile browser, then she will be redirected to the /m/download/ page. But if she opens a /m/download page, then no redirects happen - just a mobile version of page is returned to user.

I've created different templates and views for mobile version of website. Normal version of website has references only to the normal pages and mobile version of website has references only to the mobile pages. This was done to avoid not necessary redirects that could slow down a work with website. Thus, I had a different version of URL patterns for website in project's urls.py:


urlpatterns = patterns('website.views',
    url(r'^$', 'home', name='home'),
    url(r'^download/$', 'download', name='download'),
    url(r'^screenshots/$', 'screenshots', name='screenshots'),
    url(r'^contact/$', 'contact', name='contact'),
    ... 
)

urlpatterns += patterns('website.mobile',
    url(r'^m/$', 'home', name='m_home'),
    url(r'^m/download/$', 'download', name='m_download'),
    url(r'^m/screenshots/$', 'screenshots', name='m_screenshots'),
    url(r'^m/contact/$', 'contact', name='m_contact'),
    ...
)
As you see, website.views are responsible for handling normal website pages, while website.mobile for handling mobile website pages.

Now need to redirect mobile browser users to the mobile version of website. For this purpose, I wrote a custom middleware called MobileWebsiteMiddleware. The task of this middleware is to check if user is from a popular mobile platform and needs to be redirected to the mobile version of website. The other responsibility is to redirect users from mobile to the normal version of website in case if request isn't from mobile browser. This is an extract option, that should be disabled while testing.


class MobileWebsiteMiddleware(object):

    MOBILE_PREFIX = '/m/'
    MOBI_REG = re.compile(
        '(iphone|windows ce|mobile|phone|symbian|mini|pda|ipod|mobi|blackberry|playbook|vodafone|kindle)',
        re.IGNORECASE)

    def process_request(self, request):

        if 'HTTP_USER_AGENT' in request.META:
            userAgent = request.META.get('HTTP_USER_AGENT')
            matches = self.MOBI_REG.search(userAgent)
            path = request.path_info

            if matches:
                # from mobile browser, check if need to redirect to mobile
                if not path.startswith(self.MOBILE_PREFIX):
                    # need to redirect to mobile version
                    return HttpResponseRedirect('/m' + path)
            elif path.startswith(self.MOBILE_PREFIX):
                    # need to redirect to normal version
                    return HttpResponseRedirect(path[2:])

        return None

If you'll check the list list of user agent patterns in MOBI_REG you wouldn't find neither iPad nor Android. I want to show a "normal" website for tablets. "Android" keyword is used in User Agent for both mobiles and tablets. While Mobile Android browsers pass a User Agent with a "mobile" keyword, but Tablet Android browsers don't do that. Thus, I will have a normal version for desktop and tablets browsers, but mobile version for mobiles and Kindle.

Now I have a simple website, that has two different version: one for desktop and tablet browsers and another for mobile browsers. User will be automatically redirected to the right version of website which depends on used device.

In my next post I'm going to tell more about using jQuery Mobile to build a mobile version of website.

Django: use global variables on rendering templates

Problem

Some settings or global value is used in every template or, for example, in base layout template. This value should be passed as context variable to each template. I call such value a global context variable in this post.

For instance, it can be a Google Analytics Code, which should be used in production, but not in staging or development environments. In this case, it’s fine to move GA code to the settings file (of course, if different settings used for different environments). Though need to find a way to get the value from the settings and pass it to the template context before rendering.

Solutions

There are a few simple solutions that solve the described problem:
  1. Add global variable to the template context in every view. But according to DRY and good sense this is a bad solution.
  2. Write a tag to fetch setting value by name and output it. Nice solution and flexible enough in case we need to show different settings in different pages. The only disadvantage is fetching setting value each time it’s accessed. But in most cases this is a minor.
  3. Write a context processor. In the documentation said that context processor is a method that takes a request object as argument and returns a dictionary of items to be merged into the context. In other words, context processor returns a dictionary of items that will be available in template on render phase. In our case, we can get setting value and return from our own context processor method.

    from django.conf import settings
    
    def global_vars(request):
        return {'GA_CODE': settings.GA_CODE}
    
    Also, need to declare our context processor in settings.TEMPLATE_CONTEXT_PROCESSORS:

    TEMPLATE_CONTEXT_PROCESSORS = (
       . . . 
       'synctab.website.context_processors.global_vars',
    )
    
    From now, GA_CODE context variable can be used in any template, and will be processed correctly.

In my opinion, the best decision for fixed list of globally accessed template context variables is a custom context provider. It's also a right way, if most or all templates use same global variables. Otherwise, it's good to write a few tags to cover access to the global state, like settings.