Thursday, 11 December 2008

Adding dojo tabbed forms to django admin

The django system provides, "out of the box" an admin system that gives webpages with CRUD functionality with next to no work. This is great for me as I have to implement an in house form management system.

Of course I'd like to take that admin system as a basis and make it do what I want- without having to rewrite it

My latest idea of how to do this is to add dojo into django and then have dojo tart up the forms. There is already a django distribution that does dojo integration called dojoango. I wasn't keen on this as it makes me dependant on a third party source who are in turn dependant on django and dojo. So while dojo and django are stable enough to use, the extra risk of a intermediate package doesn't seem worth it for what I want to do. Anyway...

I've used ajax style javascript / dhtml / css manipulation before but not in dojo. I was keen on using dojo as it abstracts a lot of the problems of javascript such as browser imcompatiblity. This is probably true of most javascript libraries. I thought that the docs for dojo were reasonable too.

However, after a week of trying to learn dojo purely from online sources I found two problems. First, all the docs seemed to assume that you were already a top notch javascript developer and/or familiar with dojo. Secondly, I couldn't Cargo Cultishly copy example programs as the versions of the library functions seem to alter quite briskly. Examples for version 1.0.0 from a year or so ago don't work with the current at the time of writing 1.2.2. This was essentially a problem of not being able to understand enough of this comprehensive framework to get started. But fortunately I talked to the right person on the freenode irc channel #dojo and they recommended the book "Mastering Dojo: JavaScript and Ajax Tools for Great Web Experiences". After a weekend reading this I had some idea of how to do what I wanted

The forms I have to implement with django are quite extensive and in the default admin interface come out as one large page. But with dojo it is relatively easy to split the page up into "tabs"

The tricky bit is to add the javascript that makes dojo work to the right template files.
To find the template files I looked at the html source of the existing admin interface. Then I grepped for unusual strings in the django contrib/admin/templates/ directory. Once I had more or less the right files I copied them to my own private template directory ( configured in django settings.py )

First I added a bunch of stuff to the "base.html" file so that the dojo libraries and styles were loaded before I did anything else:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<style type="text/css">
@import "http://localhost:8000/dijit/themes/nihilo/nihilo.css";
</style>

<script type="text/javascript" src="http://localhost:8000/dojo/dojo.js" djConfig="parseOnLoad:true, isDebug: true"></script>
<script type="text/javascript">
dojo.require("dojo.parser")
dojo.require("dijit.layout.TabContainer")
dojo.require("dijit.layout.ContentPane")
</script>
<style>
.formContainer{
width:800px;
height:700px;
}
label {
width:150px;
float:left;
}
</style>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}" />
{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% admin_media_prefix %}css/rtl.css{% endblock %}" />{% endif %}
{% block extrastyle %}{% endblock %}
{% block extrahead %}{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
</head>
{% load i18n %}

<body class="nihilo">

<!-- Container -->
<div id="container">

The rest of the pre modified admin/templates/base.html file continues after the div id container tab

Next I altered the change_form.html container. Here is the fragment, starting from just before the repeated call to the fieldset template:

<div class="formContainer" dojoType="dijit.layout.TabContainer">
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}

{% block after_field_sets %}{% endblock %}

{% for inline_admin_formset in inline_admin_formsets %}
{% include inline_admin_formset.opts.template %}
{% endfor %}

{% block after_related_objects %}{% endblock %}
</div> <!-- end of dijit tabcontainer -->


The above fragment sets up a context or container for enclosed dojo elements to be treated as tabs in a tabbed form.

Then I drilled into the admin/includes/fieldset.html and edit_inline/tabular.html templates and added the dojo magic to define the individual tabs. Basically for the fieldset.html I added this single line at the start of the file:

<div dojoType="dijit.layout.ContentPane" title="{{fieldset.name}}" >

and a closing div tag at the end of the file. I did a similar thing in the edit_inline/tabular.html file.

The only gotcha I found with trying to do this was the way that the dijit component did not like other elements as direct parents/children. If the container is immediately followed by a contentpane and then that contentpane is immediately followed by a another then there is no problem. If there are other bits and pieces in between then the container gets confused. Obviously when modifying a set of templates this can be an issue.

Next I had to alter the way that the admin form was declared to use fieldsets. This is covered in the django docs Each fieldset or inline group appears in its own tab.

Here's a screenshot of the changed form.


Monday, 8 December 2008

How to write a Linux Kernel ioctl

The company I'm working with at the moment have an embedded Linux product

The hardware is supplied by a third party and although they have supplied a pretty good, reliable and comprehensive set of drivers we wanted to add a method for detecting the cable being plugged in the ethernet

The ethernet driver lacked this of course but we have a memory map and thought that it might be available somewhere in there

First I had to find how to add the ioctl code to the device driver. This proved pretty easy! The difficult bit was working out what kernel call got me the data and how to copy the data back to user space.

First, find the init function for the module. Next add a line like this

dev->do_ioctl=&queryisrreg_ioctl;

Where queryisrreg_ioctl is the name of the function to carry out your ioctl needs

here is my function


static int queryisrreg_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) {
static short int c=0xbb;
switch(cmd) {
case SIOCDEVPRIVATE: /* allowed to use this for DIY ioctl */
c=__EVA_REG(MAC_MEM_OFFSET+0xc4);
rq->ifr_flags=c;
return 0;
default:
return -EOPNOTSUPP;
}
return 0; /* this should never be reached..*/
}


So it uses the magic device driver call "_EVA_REG(MAC_MEM_OFFSET+0xc4);" (which i hope is the ethernet cable detect!) and passes it back to user space in "ifr_flags" of the ioctl request. Initially I'd used the "copy_to_user() macros but they didn't seem to work for me, I don't know why.

I also found I had to declare the prototype of this function higher up in the source file.

To load and test the module as I modified it I used a script that modprobed the module in and out and brought the interface up and down.

To help test/debug and stuff it is most useful to print to the kernel log, there is a macro defined "DO_PRINT" in my driver to do this.

To test the driver I found a great example program here (thanks mr pierce) which I then modified to just call my ioctl:


#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>

#include <string.h>

/* Get a socket handle. */
sck = socket(AF_INET, SOCK_DGRAM, 0);
if(sck < 0)
{
perror("socket");
return 1;
}
/* run custom ioctl */
strcpy(myifreq.ifr_name,"eth0");

if(ioctl(sck, SIOCDEVPRIVATE, &myifreq) < 0)
{
printf("oh dear!");
perror("ioctl(SIOCDEVPRIVATE)");
return 1;
}
printf(", magic number %x\n", myifreq.ifr_flags);

return 0;

}


And that's it. What a short blog entry for a weeks work :)
Oh--I used the 2.4 kernel but I'm pretty sure it's similar for any post 2.1 kernel