Datepickers for Django Sites that Work on the Desktop and Mobile

As of April 2017, HTML datepickers are still a mess. Here’s how it should be; all browsers should support the <input> attribute “type”. And when the browser sees <input type=”date” …. > the browser should offer up an awesome builtin date picker. Unfortunately we are far from that world.

My work around starts with using a javascript datepicker plugin. There are lots of choices and I have not tried them all. The one I am using is Bootstrap Datetime Picker. When using this picker, make sure to set <input type=”text” …. >, by doing this you prevent the browser from simultaneously providing a native date picker. For example, when you are running in “no icon” mode, with type=text this is what you get in Chrome:

but when type=”date” you get this:

Pretty ugly. It’s even worse in IE. This works pretty well on big screens, but not as well on small screens (e.g. phones).

On the small screen, it’s best to use the native datepicker. To make that happen you need not initialize the datepicker widget and you need to change the type attribute to “date”. Here is one way to do that that uses the Responsive Bootstrap Toolkit:

(function($, viewport){
    $(document).ready(function() {
        var datepickers = $(".bootstrapdatepickerwidget3");

        // Executes in XS and SM breakpoints
        if(viewport.is('>=md')) {
            datepickers.each(function(index, el) {
                $(el).datetimepicker({format: 'YYYY-MM-DD'});
            })
        } else {
            datepickers.each(function(index, el) {
                $(el).attr('type','date');
            })
        }
    });
})(jQuery, ResponsiveBootstrapToolkit);

Add a custom Django form field widget and you can make all these steps automatically:

from django import forms

class BootstrapDatePickerWidget(forms.DateInput):
    # noinspection PyClassHasNoInit
    class Media:
        css = {'all': [
            '3s_hts/js/bootstrap-datetimepicker-master/bootstrap-datetimepicker.min.css'
        ]}

        js = ('3s_hts/js/bootstrap-datetimepicker-master/moment.js',
              '3s_hts/js/bootstrap-datetimepicker-master/bootstrap-datetimepicker.min.js',
              '3s_hts/js/responsive-toolkit/dist/bootstrap-toolkit.min.js',
              '3s_hts/js/setup_bootstrap_datepicker.js')

class BootstrapDatePickerField(forms.DateField):
    widget = BootstrapDatePickerWidget3
Advertisements

Connecting Android Device to Ubuntu 12.04

I was trying to follow the simple “hello world” example in python-for-android, when I got to the statement:

“Plug your android device, and ensure you can install development application”

I wasn’t quite sure how to do that. In the past I have connected my phone over USB, but then it seems to mount as external storage. Anyway, I went ahead with the build command. Not surprisingly I got the error:

“[exec] error: device not found”

Turns out the missing manual is at: http://developer.android.com/tools/device.html

That put the app on my phone (with a cool icon), but it does not work. On my phone, I get the error message:

“Could not extract public data”

I plugged my phone from my computer. Now the app works. Yippee!

 

Testing A Mobile Django Site on a Phone on a LAN

Getting a site to layout well on mobile can be tricky. Although there are some decent mobile emulators, I still frequently run into important differences when I view it on a real phone.

Web pages for the desktop are easy to test using the Django development server. This note describes how to browse the development server from a mobile device that is on the same LAN as the server.

    1. Connect to the LAN with your phone
    2. Get the local IP address of the computer that runs the development server. On Linux the command is ifconfig (that’s not a typo. It’s not ipconfig). My computer’s IP address is 192.186.1.64
    3. Start the development server as follows: python manage.py runserver 0.0.0.0:8800 
    4. Start a browser on your phone
    5. Goto: http://<the server IP address>:8800  In my case that would be http://192.168.1.64:8800

You should see your website on your phone now.

Can’t Connect?

Is wi-fi on on your phone?

 

 

 

Mobile Detection on Django

Most of the Django sites I develop these days need to work on mobile. I am using Bootstrap 3 to make my pages responsive. Bootstrap’s philosophy is “mobile first”. But there are many cases where I do not want users with big screens to be penalized by a site that is optimized for a small screen.

While Bootstrap has some css classes for showing and hiding based on screen size. That approach is somewhat limited. It also slows the page down because the content still needs to be downloaded even if its not shown. A more complete solution would be to have the option of detecting the device on the server side and altering the page before its sent. This is especially easy using Django templates.

The big question is how to detect the users device and hopefully get its screen size in Django. I started with fiftyonedegrees.mobi. I was able to install it on Ubuntu 12.04 without any problems. I was using the “lite-pattern-wrapper” method, so I did not download the trie database. It worked fine, locally with the Django development server. However, when I tried to install it on a CentOS 5, 32-bit machine (Webfaction), it seg faulted. The folks at Webfaction suggested that I migrate to one of their CentOS 6, 64-bit machines. It installed without problems on the machine. But when I tried using the code by fetching a page from my DroidX, it seg faulted. I am not sure how to debug something like that. It works locally. And when it seg faults there are no debugging messages.

Wait. All is not lost. The middleware and context processor works with Firefox on Ubuntu. I added a bunch of print statements to the middleware. The problem occurs in the call to mobile_detector.match in _match. Up until then all the parameters look reasonable. To make matter worse, if I fire up my virtualenv and python interpreter on the server and run that command from the command line, with the params that caused the seg fault, everything works fine. It looks like this is a problem with mod_wsgi.

There is some stuff on stackoverflow related to this. I tried adding WSGIApplicationGroup %{GLOBAL} but no change. This is a mess. Moving on. I am going to try a pure python package.

django-mobile looks OK, but seems to be limited to just differentiating between mobile, tablet and full. Moble ESP looks interesting. The python code is just one file: mdetect.py. It’s very readable and understandable. Not as many Django features as django-mobile. But most of what I want, I can roll my own. Here is quick and dirty middleware:

 
from xxx import mdetect

class DetectMobile:
    def process_request(self, request):
        user_agent = request.META.get("HTTP_USER_AGENT")
        http_accept = request.META.get("HTTP_ACCEPT")
        if user_agent and http_accept:
            agent = mdetect.UAgentInfo(userAgent=user_agent, httpAccept=http_accept)
            request.mobile_esp_agent = agent   # in case we want more information about the device
            if agent.detectMobileQuick():
                request.device_type = 'mobile'
            elif agent.detectTierTablet():
                request.device_type = 'tablet'
            else:
                request.device_type = 'desktop'
        else:
            request.mobile_esp_agent = None
            request.device_type = 'desktop'   # default

The careful reader will no doubt notice that after I called django-mobile limited, I set about creating those exact limits in my middleware. Ha. But an even carefuller reader will notice I include the agent in request, so I can get at the details as needed. So far this approach is working well.