Build a Mobile and Desktop-Friendly Application in Django in 15 Minutes
This article quickly walks you through the process of setting up an absolutely minimal mobile- and desktop-friendly web application using the Django framework. This particular framework works very well with mobile thanks to the way its templates are structured and makes for a good choice for new web-based applications. This article assumes that you have python 2.3 or later and SVN already installed and working.
First we need to download the latest Django:
~$ wget http://www.djangoproject.com/download/1.0.2/tarball/ ~$ tar xzvf Django-1.0.2-final.tar.gz ~$ cd Django-1.0.2-final ~$ sudo python setup.py install
Install sqlite (often not included in Linux distributions):
~$ sudo apt-get install python-sqlite
Create a new project:
~$ django-admin.py startproject mobapp
(django-admin.py should now be in path). This should give you a directory structure like the following:
mobapp/ __init__.py manage.py settings.py urls.py
Edit settings.py to use sqlite, and pick a db name
DATABASE_ENGINE = 'sqlite3' DATABASE_NAME = 'mobapp'
Sqlite is probably the easiest way to get started but if you want to use MySQL instead it requires just the addition of the DB name and authentication credentials to work. Now you need to write the relevant initial data into the DB (this is the data the Django uses for the Administration interface):
~$ cd mobapp ~/mobapp$ ./manage.py syncdb
You will be asked if you want to create a superuser. This is recommended (to get all the cool Django DB admin stuff). Right now the application is ready to run, but does absolutely nothing. Remedy this by typing the following:
~/mobapp$ ./manage.py startapp pub
This command creates a new "app" within the project. This should create a new directory called pub with some files in it, something like this:
pub/ __init__.py models.py views.py
Now open up the settings.py and edit it to make it aware of the new app:
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'mobapp.pub' )
Now to develop the actual app. For the sake of simplicity we'll leave the models.py alone for now -- this application will not use any database functionality.
Edit pub/views.py -- this is where the guts of the code will reside. Paste in the following code:
views.py
# Some standard Django stuff from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.template import Context, loader # list of mobile User Agents mobile_uas = [ 'w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac', 'blaz','brew','cell','cldc','cmd-','dang','doco','eric','hipt','inno', 'ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-', 'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-', 'newt','noki','oper','palm','pana','pant','phil','play','port','prox', 'qwap','sage','sams','sany','sch-','sec-','send','seri','sgh-','shar', 'sie-','siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-', 'tosh','tsm-','upg1','upsi','vk-v','voda','wap-','wapa','wapi','wapp', 'wapr','webc','winw','winw','xda','xda-' ] mobile_ua_hints = [ 'SymbianOS', 'Opera Mini', 'iPhone' ] def mobileBrowser(request): ''' Super simple device detection, returns True for mobile devices ''' mobile_browser = False ua = request.META['HTTP_USER_AGENT'].lower()[0:4] if (ua in mobile_uas): mobile_browser = True else: for hint in mobile_ua_hints: if request.META['HTTP_USER_AGENT'].find(hint) > 0: mobile_browser = True return mobile_browser def index(request): '''Render the index page''' if mobileBrowser(request): t = loader.get_template('m_index.html') else: t = loader.get_template('index.html') c = Context( { }) # normally your page data would go here return HttpResponse(t.render(c))
This code requires 2 templates, one for mobile users, the other for desktop users. We need to create these templates and put them in a directory that Django knows about.
~/mobapp$ mkdir pub/templates
Create two new files in this directory, called m_index.html and index.html. You can put the following content in these files:
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>mobapp (sedentary)<title> </head> <body> <h1>Hello, sedentary user</h1> </body> </html>
m_index.html
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE html PUBLIC '-//WAPFORUM//DTD XHTML Mobile 1.0//EN' 'http://www.wapforum.org/DTD/xhtml-mobile10.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>mobapp (mobile)</title> </head> <body> <h1>Hello, mobile user</h1> </body> </html>
Now we need to let Django know where the templates for the project are. Open settings.py and add a line pointing to the relevant directory e.g.
TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. '/home/ronan/mobapp/pub/templates' )
Now that our index method is ready in views.py, we need to point a URL at it. Add the following line to urls.py
(r'^$', 'mobapp.pub.views.index'),
This pattern tells Django to point any blank URL at the index method in views.py e.g.
urls.py
urlpatterns = patterns('', # Example:<br /> # (r'^mobapp/', include('mobapp.foo.urls')), # Uncomment the admin/doc line below and add 'django.contrib.admindocs' # to INSTALLED_APPS to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # (r'^admin/(.*)', admin.site.root), (r'^$', 'mobapp.pub.views.index'), )
And that's it. Now if you start the server you have a minimal mobile- and desktop-friendly app:
~/mobapp$ ./manage.py runserver Validating models... 0 errors found Django version 1.0.2 final, using settings 'mobapp.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
If you now go to http://localhost:8000/ with your browser you should see the following page:

Visit the same URL with a mobile device and you will see something like this:

Discussion
This is an absolutely minimal Django application with a very modest feature set. Its main purpose is to demonstrate an approach to building a Django application that can simultaneously serve mobile and desktop users rather than show off any of Django's great features. Please note that the device detection method used in this article is very basic, and really serves only as a way to differentiate mobile from desktop users rather than as a way to fine-tune the experience to the end device. To build a really consumer-friendly application you should use a more sophisticated device detection technology such as DeviceAtlas, which works fine with Django. Note that the device detection code used in this example is modelled on Andy Moore's PHP code.
This code for this application can be downloaded from the link below (mobapp.zip).
| Attachment | Size |
|---|---|
| mobapp.zip | 11.19 KB |





Posted by john.boxall 4 years ago
Be careful with this line:
Browsers can be set to not send the User-Agent Header and this line will throw an exception! Safer like this:
ua = request.META.get('HTTP_USER_AGENT', '').lower()[0:4]John
Posted by versae 4 years ago
You can use djangobile (http://code.google.com/p/djangobile/). Djangobile support client detection according to User Agent (using WURFL) and allows only one logic and N templates and other interesting features ;-P
I would like DeviceAtlas formed part of procces of device detection, although the ideal would be to implement the interface proposed by the W3C (http://www.w3.org/TR/2008/PR-DDR-Simple-API-20080917/).
Regards.
Posted by James Pearce 4 years ago
I rather like the idea of using the context processor, rather than hardcoding the switched templates to the view.
(Especially if you are using default views and can't so easily change the template as you do above.)
So here's an approach that seems to work quite well.
First create the context processor. Somewhere (say in views.py) :
def mobile_context(request): host=request.META['HTTP_HOST'] if host.endswith('.mobi'): return { "mobile":True, "page_template":"page.mobile.html" } return { "mobile":False, "page_template":"page.desktop.html" }Then in settings.py make sure this processor is called:
Then, create two base templates, page.desktop.html and page.mobile.html. These are the outer page structures for the two types of page (and should include the appropriate XML declaration, html, head, CSS etc for the two types of browser)
Then have a single base page template (called, say page.html) that merely provides a dynamically-decided extension of one of these two templates:
{% extends page_template %}(It's very helpful that 'extends' can take a variable argument. Note this variable has been set by our context processor.)
And now all your app's pages can extend page.html, and the switching kicks in magically, without changes required to any of your app's existing view code.
If you do need to toggle on or off parts of the UI down within your templates (say extraneous panels that clutter up a small mobile UI), you can use the 'mobile' variable (also set by the context processor) to turn them on or off.
Endless extension of this idea possible of course... more complex inheritance, more variables available (screen width, anyone?) and more theme variations for broad varieties of device.
You could hook DeviceAtlas in to the context processor too - to fill out some of this capability richness and make it available to a theme author.
Posted by James Pearce 4 years ago
In fact, I've been taking this further, and attempting to blend the switcher technique into Django.
The catch with using context processors alone is that if you decide that you want to invoke a redirect of any sort (say, the user has come to one domain when they've previously expressed an preference for the other), you're not really in the right place to create an HttpResponse.
The most Djangonic (?) way to implement a solid switcher seems to be to have a middleware class intercepting the request in process_request() and determining the outcome there, as per the algorithm. Any redirects or interstitials can be presented before any 'normal' view processing has begun.
Then, on the way back out of the middleware, in process_response(), you can set any preference cookies.
I added items to the request.META dictionary in order to communicate between the two sides of the middleware. This data can also be read by the context processor, though, so if you want, you can get the middleware to decide the template (whilst it's at it) and the information needed for the intra-site links - and then have the context processor pick those up just prior to the views, in order to populate some helpful variables for your template.
Does that make any sense? I suppose I should figure out how to package this to share ;-)
Posted by versae 4 years ago
Like as James Pearce pointed above, I agree about using a middleware and a context_processor. The djangobile project works in that way.
Setting a new context processor in your project settings.py
TEMPLATE_CONTEXT_PROCESSORS = ( # This context processor is required to djangobile works properly. 'djangobile.context_processors.mobile', )In a summary, the context_processor is
def mobile(request): user_agent = request.META.get('HTTP_USER_AGENT', 'default_user_agent') if hasattr(request, 'device'): device = request.device if (getattr(settings, 'DEBUG', False) and getattr(device, 'user_agent', False) != user_agent): device = get_device(user_agent) request.session['device_id'] = device.id return {'device': device} if request.session.test_cookie_worked(): request.session.delete_test_cookie() device_id = request.session.get('device_id', False) if not device_id: device = get_device(user_agent) request.session['device_id'] = device.id else: device = get_device(device_id=device_id) else: device = get_device(user_agent) request.session['device_id'] = device.id request.session.set_test_cookie() return {'device': device}Besides, the middelware is only a call to the context_processor function.
class DjangoMobileMiddleware(object): def process_request(self, request): if not hasattr(request, 'device'): device = mobile(request)['device'] setattr(request, 'device', device) return NoneThe key function get_device gets a Device object from to WURFL and the render_to_response function (and associated functions) in djangobile selects the right template according to device capabilities or custom families definitions.
The templatetags extends and include can be overriden in order to delegate device detection to them, avoiding changes in the logic layer.
The templates directory could be
Posted by ronan 4 years ago
Versae (& James),
I completely agree that doing this in a middleware class is a better and more "Djangonic" way to do it. Versae, per James' comments, watch this space for updates. We will try to do a follow up article on this at some point.
Ronan Cremin, dotMobiPosted by drashok 3 years ago
Hi Ronan Cremin,
Can you point source for code or make your walk through simpler for novice?
My website is hosted with Word Press using plaintxtblog theme of Scott Wallick.
Regards,
Ashok Koparday