Going Mobile with Django and jQuery

Django    JavaScript    2012-07-30

This is not the only way to build a mobile site - certainly, some methods are probably better depending on your needs.

But this method is simple, and if your mobile needs are also simple, this may be the way to go.

Maybe you just have a blog (such as this one) that you want to format a little differently for mobile devices? Or maybe you're runnng an online shop with inventory to display, where almost every page is a product page (e.g., http://www.amazon.com/gp/product/B006PALI6Y/ versus http://www.amazon.com/gp/aw/d/B006PALI6Y - that second one looks a lot nicer on a mobile device).

This method is ideal for sites without a lot of specialized navigation, where the majority of the content is displayed in the same template. And I'd be surprised if it took more than an hour to get up and running.

Create the mobile page with jQuery Mobile

For this blog site, I started by creating a new base template just for mobile pages. It's a stripped down version of my main base template that includes all the jQuery Mobile CSS and script references:

<!DOCTYPE html>
<html>
	<head> 
	<title>
		{% block site_title %} My Site Title {% endblock %}
	</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">

	{% block page_css %}
	  <!-- jQuery Mobile style sheet -->
	  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0-rc.1/jquery.mobile-1.1.0-rc.1.min.css" />

	  <!-- my own style sheet - this doesn't override any of the jQuery styles, 
                     just includes some of the styles I've already set up for the blog: -->
	  <link rel="stylesheet" type="text/css" href="/path/to/my.css">
	{% endblock %}

	{% block page_js %}
	  <!-- jQuery mobile script includes -->
	  <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
	  <script src="http://code.jquery.com/mobile/1.1.0-rc.1/jquery.mobile-1.1.0-rc.1.min.js"></script>
	{% endblock %}

</head>
<body>

{% block content %}{% endblock %}

</body>
</html>

Then, under the application that serves all my content, I created a new template that serves as my mobile template. This template actually contains the entirety of what displays as the mobile version of my site, but uses internal links to navigate to each "page".

{% extends "basemobile.html" %}

{% block content %}

  <!-- every "page" looks something like this -->

  <div data-role="page" id="page0" data-theme="d">

	<div data-role="header">
	  <h1>Page 0 Title</h1>
	</div><!-- /header -->

	<div data-role="content">
		Page 0 Content
	</div><!-- /content -->

	<div data-role="footer" data-theme="d">
	  <div data-role="navbar">
		<ul>
		  <li><a href="#page0">Page 0</a></li>
		  <li><a href="#page1">Page 1</a></li>
		  <li><a href="#page2">Page 2</a></li>
		</ul>
	  </div><!-- /navbar -->
	</div><!-- /footer -->

  </div><!-- /page -->
	...
	...

{% endblock %}

Use this Quick Start guide to get some basic information about jQuery Mobile tags, styles and themes: http://jquerymobile.com/demos/1.1.1/docs/about/getting-started.html.

In my home "page", I've opted to display links to the 5 most recent articles - that fits inside a content div like so:

<div data-role="content">
  {% if article_list %}
    <ul data-role="listview" data-inset="true" data-filter="false">
      {% for article in article_list %}
        <li><a href="#{{ article.slug }}">{{ article.title }}</a></li>
      {% endfor %}
    </ul>
  {% endif %}
</div>

Dynamically generating "pages" for each of those articles uses that same list of five to create the internal link names:

{% if article_list %}
  {% for article in article_list %}
  <div data-role="page" id="{{ article.slug }}" data-theme="d">

	<div data-role="header">
	  <h1>{{ article.title }}</h1>
	</div><!-- /header -->

	<div data-role="content">
	  {% autoescape off %}{{ entry.body }}{% endautoescape %}
	</div><!-- /content -->

  </div><!-- /page -->
  {% endfor %}
{% endif %}

And so on.

A new view

The list of articles comes from a simple view method, again in the same application that returns my regular article content:

def mobile(request):
	"" return just the article metadata I need for the mobile page"""

	template_name = 'mobile.html'
	context = {}

	# pull only the five most recent articles - couldn't be simpler
	grouped_list = Post.objects.filter(publish=True).order_by('-created_at')[:5]
	context['article_list'] = grouped_list

	return render_to_response(template_name, context, context_instance=RequestContext(request))

In my urls.py:

url(r'^mobile/*$', views.mobile, name='articles_mobile'),

JavaScript redirect

The last piece is a simple JavaScript redirect in the main base template that uses JavaScript's navigator object to detect browser information (and thus detect whether or not a user is in a mobile browser):

<script type="text/JavaScript">
  var mobile = (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()));  
  if (mobile) {  
    document.location = "http://www.mydomain.com/mobile/";  
  }
</script>

With all of that in place, a user on any major mobile device visiting http://www.mechanicalgirl.com/ should automatically be redirected to http://www.mechanicalgirl.com/mobile/. I've set up a subdomain that does the same thing, using an Apache rewrite condition:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^mobile.mydomain.com [NC]
RewriteRule ^.*$ http://www.mydomain.com/mobile/ [R=301,L]

What you won't get with this method

  1. Your URLs won't map exactly. For example, if you hit my post at http://www.mechanicalgirl.com/post/where-have-all-the-women-gone/ on a mobile device, you're redirected to the mobile home page at http://www.mechanicalgirl.com/mobile/.
  2. I've chosen not to do this, but this could be solved with a change to the JavaScript redirect (in the base template). The document.location value could easily have url parameters appended/persisted using a function that pulls down window.location properties.

    Some examples:
    Get URL Parameters Using Javascript [NetLobo]
    get URL Parameters from current URL [Stack Overflow]

  3. Another down side is that placing this JavaScript redirect in your base template means mobile users won't ever be able to get to your main site - they'll always be redirected back to the mobile version.
  4. One possible way to solve this: In the mobile template, include a URL for the main site that includes some kind of appended query string. Then use that query string value to disable the JavaScript redirect conditionally.

    For example, if I add "?m" to the end of a URL in the mobile page:

    <a href="/article/{{ entry.slug }}/?m">Click here to view this page on the regular site</a>
    

    I can use it to limit the redirect in the base template:

    <script type="text/JavaScript">
      if(window.location.search != '?m'){
        var mobile = (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()));
        if (mobile) {  ...
    

    There are certainly more elegant ways to do this, but this one does not rely on changes to the Python code.

Some other ideas:

  1. Depending on what you need out of a mobile site, it might also be more useful to keep your mobile template in a separate project or application, import the relevant views and models across projects/applications so that you don't have to duplicate any code, and use server settings to map the separate projects/applications to different URLs.
  2. There is a lot of Django middleware out there that will give you more control over your device detection and URL redirects, but I can't recommend any one package in particular - I would recommend using Google and doing a little research.

Your choice, of course, is dependent on your server setup, how your projects are organized and how comfortable you are with changing them.