Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Multiple Input Fields with multiple inheritance

Jerry Stratton, December 7, 2009

Here’s a very simplified version of our Django task management system:

[toggle code]

  • class Task(models.Model):
    • title = models.CharField(max_length=100)
    • parent = models.ForeignKey('self', blank=True, null=True)
    • details = models.TextField(blank=True)
    • step = models.PositiveIntegerField(default=5, choices=zip(range(100), range(100)))

The details of the task are stored in a TextField; many tasks have subtasks. When editing a task, we also often want to edit the titles, step, and details of the immediate subtasks. so we display them as a series of lines using inlineformset_factory. By default, Django’s forms will display a TextField as a Textarea. If there’s more than one subtask, textareas are too big; they defeat the purpose of having a simple means of viewing and editing subtasks all on one screen, because they won’t be all on one screen.

So we use a special ModelForm which overrides Django and tells it to use a CharField for that field:

[toggle code]

  • #for editing tasks inline in a project
  • class taskForm(forms.ModelForm):
    • details = forms.CharField(required=False)

Up until a few weeks ago, this worked fine. While we weren’t allowed to enter multiple lines in an “input type=text” field, they accepted data that already had multiple lines without mangling it. Recently, however, Safari has started double-checking the data, and it’s been collapsing multiple lines of data into one, which, when we save the task, ends up collapsing the lines in the database as well. It’s hard to complain about what Safari is doing here; it almost certainly should be doing that.

My initial suggestion (other than give in and use a textarea for every line) was to block editing the details if the details already contained multiple lines. But as I was taking a look at it, I wondered if this was a job for multiple inheritance. Could I make a class inherit from both TextInput and Textarea, and route the “render” method to create a textarea when necessary, and a single-line input otherwise?

Python does support multiple inheritance. But there’s no obvious way to tell super() to specifically use one or another of the ancestors. A quick Google search brought up Using Mix-ins with Python. The way to alter the order of ancestor searches in Python is to modify an attribute called “__bases__”. It’s a tuple of all of the ancestor classes, and Python searches the classes in that tuple in order to determine which method to use. By altering the order of the tuple, we can alter the method resolution order.

[toggle code]

  • #detail fields need to be a textarea if they contain multiple lines
  • #but we'd prefer them to be a standard text-input when possible
  • class CharOrTextInput(forms.TextInput, forms.Textarea):
    • def render(self, name, value, *args, **kwargs):
      • if value:
        • value = value.strip()
      • if not value or value.find("\n") == -1:
        • CharOrTextInput.__bases__ = (forms.TextInput, forms.Textarea)
      • else:
        • CharOrTextInput.__bases__ = (forms.Textarea, forms.TextInput)
      • return super(CharOrTextInput, self).render(name, value, *args, **kwargs)
  • #for editing tasks inline in a project
  • class taskForm(forms.ModelForm):
    • details = forms.CharField(required=False, widget=CharOrTextInput)

This overrides the render method to render as Textarea if there are multiple lines, or as TextInput when there are not.

Note that you don’t modify __bases__ on self but on the class definition itself. Also, this is the first thing I’ve done with multiple inheritance, so it could easily be all wrong.

  1. <- Django tutorial
  2. Date/time formsets ->