Code


Version 2 (modified by lllama, 8 years ago) (diff)

--

Custom Form Fields

This page will document my efforts to create a custom form field for my application. Hopefully someone will find it useful, contribute, correct my mistakes or post some pointers.

The Goal

As I'd like my database driven app to be driven by a database and not a flat file I made sure that my model was properly normalised. One model deals with recording information about applications and services and so needs to deal with ports numbers and IP protocols. Since many ports could have many applications I've got a model like this:

class Port(meta.model):
    number = meta.IntegerField()
    protocol = meta.CharField(maxlength=4)

class Application(meta.model):
    name = meta.CharField(maxlength=50)
    version = meta.IntegerField()
    ports = meta.ManyToManyField(Port)

The 'ports' table is prepopulated with all possible combinations of number+protocol and as such weighs in at around 130000 rows. Each request for an add or change manipulator therefore takes quite a while (as all the rows are read in) and is not very usable.

What I'd like is something similar to the raw admin id but without having to enter ids. I'd like the user to be able to enter values like so: 80:tcp, 443:tcp, icmp.

Now what I could do is just add a text box to the form, process it in the view and then populate the M2M field myself but that feels like cheating. Plus there's a few other models where something like this but with more functionality would prove useful.

Tagging

A quick Google made me think that someone had beaten me to it. I found this post: Django and M2M tables which points to an example of adding tagging to a model. This seems to provide the answer but it doesn't quite fit my needs, mainly because the whole choices list is still being pulled in.

Findings

After much messing about I've found that things are a lot simpler than first appeared. In order to validate the input I've subclassed TextField and added a new validation method to its validator list. I created a new render method which simply turns the list into something more presentable. I then created a new convert_post_data method which takes '80:tcp, 443:tcp' or whatever and turns it into a list of ids from the joined table.

This now appears to work correctly for adding but not editing. This is due to the render method getting passed the user input or a list of IDs depending on what manipulator gets called. I can modify the user's data to change it to a list of IDs but this would need doing in the view and doesn't feel 'neat' enough.