40

I've made a Django site, but I've drank the Koolaid and I want to make an IPhone version. After putting much thought into I've come up with two options:

  1. Make a whole other site, like i.xxxx.com. Tie it into the same database using Django's sites framework.
  2. Find some time of middleware that reads the user-agent, and changes the template directories dynamically.

I'd really prefer option #2, however; I have some reservations, mainly because the Django documentation discourages changing settings on the fly. I found a snippet that would do the what I'd like. My main issue is having it as seamless as possible, I'd like it to be automagic and transparent to the user.

Has anyone else come across the same issue? Would anyone care to share about how they've tackled making IPhone versions of Django sites?

Update

I went with a combination of middleware and tweaking the template call.

For the middleware, I used minidetector. I like it because it detects a plethora of mobile user-agents. All I have to do is check request.mobile in my views.

For the template call tweak:

 def check_mobile(request, template_name):
     if request.mobile:
         return 'mobile-%s'%template_name
     return template_name

I use this for any view that I know I have both versions.

TODO:

  • Figure out how to access request.mobile in an extended version of render_to_response so I don't have to use check_mobile('template_name.html')
  • Using the previous automagically fallback to the regular template if no mobile version exists.
Swaroop C H
  • 16,522
  • 10
  • 41
  • 49
imjoevasquez
  • 12,473
  • 5
  • 29
  • 21
  • 1
    You can also do/need this to have a special IE6 version of the templates. I'm thinking in using 1 template, with some conditionals if the user agent is IE6. – Esteban Küber Jan 12 '10 at 20:25
  • 1
    I like how you did that :) Would you say I have a *plethora* of pinatas? – shacker Jun 02 '11 at 02:57
  • Note that unfortunately, minidetector seems not to be compatible with Python 3. See [this SO question](http://stackoverflow.com/q/42273319/5802289) for alternatives. – J0ANMM Feb 16 '17 at 17:41

9 Answers9

20

Rather than changing the template directories dynamically you could modify the request and add a value that lets your view know if the user is on an iphone or not. Then wrap render_to_response (or whatever you are using for creating HttpResponse objects) to grab the iphone version of the template instead of the standard html version if they are using an iphone.

Aaron
  • 2,345
  • 1
  • 22
  • 24
  • probably the best way to go...I just didn't want to have to add extra code to my views. thanks for the answer! – imjoevasquez Oct 02 '08 at 21:23
  • 5
    What I do is write my own render_to_response wrapper and put it in an project wide utilities library. Then just import that instead of render_to_response (like my_render_response). You have the same amount of code in your views and just have to change up the import and func call in your views. – Aaron Oct 02 '08 at 22:09
  • @imjoevasquez this is way after the fact but you could always do something like `from my_site_wide_stuff.utils import my_render_to_response as render_to_response` - since `render_to_response` is only a shortcut anyways. Although find/replace is probably the way to go ;) – driftcatcher Oct 18 '12 at 14:39
  • would you recommend the same approach if I was trying to render a template in a different location of the DOM depending on mobile or desktop? – Akin Hwan May 22 '19 at 15:16
13

Detect the user agent in middleware, switch the url bindings, profit!

How? Django request objects have a .urlconf attribute, which can be set by middleware.

From django docs:

Django determines the root URLconf module to use. Ordinarily, this is the value of the ROOT_URLCONF setting, but if the incoming HttpRequest object has an attribute called urlconf (set by middleware request processing), its value will be used in place of the ROOT_URLCONF setting.

  1. In yourproj/middlware.py, write a class that checks the http_user_agent string:

    import re
    MOBILE_AGENT_RE=re.compile(r".*(iphone|mobile|androidtouch)",re.IGNORECASE)
    class MobileMiddleware(object):
        def process_request(self,request):
            if MOBILE_AGENT_RE.match(request.META['HTTP_USER_AGENT']):
                request.urlconf="yourproj.mobile_urls"
    
  2. Don't forget to add this to MIDDLEWARE_CLASSES in settings.py:

    MIDDLEWARE_CLASSES= [...
        'yourproj.middleware.MobileMiddleware',
    ...]
    
  3. Create a mobile urlconf, yourproj/mobile_urls.py:

    urlpatterns=patterns('',('r'/?$', 'mobile.index'), ...)
    
Stephen Fuhry
  • 11,090
  • 6
  • 49
  • 52
Aneil Mallavarapu
  • 3,291
  • 4
  • 35
  • 37
3

I'm developing djangobile, a django mobile extension: http://code.google.com/p/djangobile/

2

You should take a look at the django-mobileadmin source code, which solved exactly this problem.

ak.
  • 2,172
  • 4
  • 18
  • 17
2

Other way would be creating your own template loader that loads templates specific to user agent. This is pretty generic technique and can be use to dynamically determine what template has to be loaded depending on other factors too, like requested language (good companion to existing Django i18n machinery).

Django Book has a section on this subject.

zgoda
  • 12,351
  • 4
  • 35
  • 43
2

There is a nice article which explains how to render the same data by different templates http://www.postneo.com/2006/07/26/acknowledging-the-mobile-web-with-django

You still need to automatically redirect the user to mobile site however and this can be done using several methods (your check_mobile trick will work too)

Amit
  • 723
  • 1
  • 5
  • 10
1

How about redirecting user to i.xxx.com after parsing his UA in some middleware? I highly doubt that mobile users care how url look like, still they can access your site using main url.

Dmitry Shevchenko
  • 28,728
  • 10
  • 52
  • 62
  • How does this help? You still have the template names and layouts for the site. Either you create a whole new set of logic, or the problem persists. – boatcoder Aug 05 '10 at 21:11
1

best possible scenario: use minidetector to add the extra info to the request, then use django's built in request context to pass it to your templates like so

from django.shortcuts import render_to_response
from django.template import RequestContext

def my_view_on_mobile_and_desktop(request)
    .....
    render_to_response('regular_template.html', 
                       {'my vars to template':vars}, 
                       context_instance=RequestContext(request))

then in your template you are able to introduce stuff like:

<html>
  <head>
  {% block head %}
    <title>blah</title>
  {% if request.mobile %}
    <link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-mobile.css">
  {% else %}
    <link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-desktop.css">
  {% endif %}
  </head>
  <body>
    <div id="navigation">
      {% include "_navigation.html" %}
    </div>
    {% if not request.mobile %}
    <div id="sidebar">
      <p> sidebar content not fit for mobile </p>
    </div>
    {% endif %>
    <div id="content">
      <article>
        {% if not request.mobile %}
        <aside>
          <p> aside content </p>
        </aside>
        {% endif %}
        <p> article content </p>
      </aricle>
    </div>
  </body>
</html>
Thomas
  • 11,032
  • 4
  • 37
  • 53
0

A simple solution is to create a wrapper around django.shortcuts.render. I put mine in a utils library in the root of my application. The wrapper works by automatically rendering templates in either a "mobile" or "desktop" folder.

In utils.shortcuts:

from django.shortcuts import render
from user_agents import parse

def my_render(request, *args, **kwargs):
  """
  An extension of django.shortcuts.render.

  Appends 'mobile/' or 'desktop/' to a given template location
  to render the appropriate template for mobile or desktop

  depends on user_agents python library
  https://github.com/selwin/python-user-agents

  """
  template_location = args[0]
  args_list = list(args)

  ua_string = request.META['HTTP_USER_AGENT']
  user_agent = parse(ua_string)

  if user_agent.is_mobile:
      args_list[0] = 'mobile/' + template_location
      args = tuple(args_list)
      return render(request, *args, **kwargs)
  else:
      args_list[0] = 'desktop/' + template_location
      args = tuple(args_list)
      return render(request, *args, **kwargs)

In view:

from utils.shortcuts import my_render

def home(request):    return my_render(request, 'home.html')
Samora Dake
  • 120
  • 1
  • 8