Some motivation

Django has a field called ChoiceField which lets you very easily create a <select> dropdown in your forms. A great feature is the ability to transform nested choices into appropriate <optgroup> groups. For instance, the following code:

The problem

As it turns out, ChoiceField only supports one level of nesting. Five years ago someone submitted a patch to correct the problem, but it was rejected for good reason: HTML itself only officially supports one level of <optgroup> nesting.

To see if it would work anyway, I manually applied the patch to my Django installation. Although the patch itself worked (Django produced the nested option groups), Chrome wasn’t having any of it. My dropdown displayed incorrectly, and developer tools showed that the HTML got mangled during parsing.

Emulating nested optgroups

My solution to the problem is to fake it by using disabled <option> elements as group headers. Then, we apply indentation to the actual options to make them appear to belong to the groups. This is accomplished by a custom widget called NestedSelect (below), which is a cross between the original Select widget and the patch I linked to above.

Improving the aesthetics

The solution above works, but it can be improved in two ways:

The manual addition of padding results in an ugly side-effect – that padding is visible in the selected option. Look at the difference when Choice 1 and Choice 5 are selected (below). Choice 5 is aligned correctly, but Choice 1 appears to be floating out in no-man’s land.

We no longer have the nice bolding effect on the option group headers. It’s not possible for us to individually style the elements that are supposed to be headers.

To overcome these shortcomings of HTML, we must turn a JavaScript based replacement: Select2. With that plugin installed, the fix is just a little bit of JavaScript:

Finished product

I came across a nice example of a Twisted “man-in-the-middle” style proxy on Stack Overflow. This style of proxy is great for logging traffic between two endpoints, as well as modifying the requests and responses that travel between them.

The original was posted here, and I reproduce the vast majority of the code below with some modifications. My real motivation for posting this is to “get the code out there”, because I had a hard time finding it originally. A big thanks to the original author for posting his code on Stack Overflow.

All you need to do is change the three constants at the top, and add whatever validation/modification logic you want in the dataReceived and write methods. Those four methods are labeled so you know which “hop” the data is taking. A request is going to take the following path: client => proxy => server => proxy => client. For example, the first dataReceived method handles data travelling from the client to your proxy.

The final result will be a simple command line interface (for now) that looks like this:

Requirements

Before even choosing the language I wanted to use, I decided to list a few requirements for the client-side software.

The software will automatically detect the drumset; the user will not have to manually choose a serial port.

The software will support playing multiple drumpad sounds at once (like a real drumset). This will require audio mixing.

The software must be cross-platform and easy to install.

My development process

I ended up developing this application three times: twice in C# and once in Python. There was also almost a C++ version. In this section, I’ll explain the initial prototype versions and why I decided upon Python in the end. To do this, I’ll go down the list of the requirements.

Requirement 1 is easy: if you remember, back in part 3 we added a feature to the embedded software that supports responding to “pings”:

// The client might want to know what we are, so let
// them ping us
if (Serial.available()){
while (Serial.available()){
Serial.read();
}
Serial.println("{ "msgType": "ping" }");
}

I didn’t want users to have to figure out which serial port to use. It should Just Work™. This is a very simple feature to implement, and because of this, requirement 1 didn’t actually steer me towards any one language.