Part 4: Custom Fields

South 0.7 introduced a reasonably radical change from previous versions. Before, if you had a custom field, South would attempt to use magic [1] to determine how to freeze that field, so it could be recreated in a migration.

[1]And not very nice magic, either; a combination of regexes and the python parser module.

While this worked surprisingly well for most people, in a small percentage of cases it would get it completely wrong - even worse, you wouldn’t know it was wrong until things changed a few weeks later. In the interests of both sanity and having less magic, you must now tell South how to freeze your custom fields.

Don’t worry, it’s pretty easy, and you only have to do it once per field.

Our Field

In this example, we’ll be using a custom field which stores a list of tags in the database. We’ll just store them in a TEXT column, with some delimiter separating the values (by default, we’ll use |, but they can pass in something else as a keyword argument).

Here’s the field class; in my code, I put this in appname/fields.py (for more on writing custom fields, see the Django docs):

from django.db import models

class TagField(models.TextField):

    description = "Stores tags in a single database column."

    __metaclass__ = models.SubfieldBase

    def __init__(self, delimiter="|", *args, **kwargs):
        self.delimiter = delimiter
        super(TagField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        # If it's already a list, leave it
        if isinstance(value, list):
            return value

        # Otherwise, split by delimiter
        return value.split(self.delimiter)

    def get_prep_value(self, value):
        return self.delimiter.join(value)

To tell South about a custom field, you need to tell it two things; that this particular class is OK to use, and how to reconstruct the keyword arguments from a Field instance.

Keyword Arguments

South freezes fields by storing their class name and module (so it can get the field class itself) and the keyword arguments you used for that particular instance (for example, CharField(max_length=50) is a different database type to CharField(max_length=150)).

Since Python doesn’t store the keyword arguments a class was passed, South has to reconstruct them using the field instance. For example, we know that CharField‘s max_length attribute is stored as self.max_length, while ForeignKeys store their to attribute (the model they point to - also the first positional argument) as self.rel.to.

South knows all these rules for the core Django fields, but you need to tell it about your own ones. The good news is that South will trace the inheritance tree of your field class and add on rules from parent classes it knows about - thus, you only need tell South about extra keyword arguments you’ve added, not every possible argument the field could have.

In our example, we’ve only specified one extra keyword: delimiter. Here’s the code we’d add for South to work with our new field; I’ll explain it in a minute:

from south.modelsinspector import add_introspection_rules
add_introspection_rules([
    (
        [TagField], # Class(es) these apply to
        [],         # Positional arguments (not used)
        {           # Keyword argument
            "delimiter": ["delimiter", {"default": "|"}],
        },
    ),
], ["^southtut\.fields\.TagField"])

As you can see, to tell South about your new fields, you need to call the south.modelsinspector.add_introspection_rules function. You should put this code next to the definition of your field; the last thing you want is for the field to get imported, but for this code to not run.

add_introspection_rules takes two arguments; a list of rules, and a list of regular expressions. The regular expressions are used by South to see if a field is allowed to be introspected; just having a rule that matches it isn’t enough, as rule inheritance means that any custom field class will have at least some rules on it (as they will inherit from Field, if not something more specific like CharField), and some custom fields can get by with only those inherited rules (more on that shortly).

The first argument is the list of rules. Each rule is a tuple (or list) with three items:

  • A list of classes these rules apply to. You’ll almost certainly have just [MyField] here.
  • Positional argument specification. This should always be left blank, as an empty list - [].
  • Keyword argument specification. This is a dictionary, with the key being the name of the keyword argument, and the value being a tuple or list of (attribute_name, options).

The attribute name says where the value of the keyword can be found - in our case, it’s 'delimiter', as we stored our keyword in self.delimiter. (If this was the ForeignKey rule, we’d put 'rel.to' here)

options is a dictionary. You can safely leave it blank, but to make things nicer, we can use it to specify the default value of this keyword - if the value South finds matches this, it will leave out this keyword from the frozen definition. This helps keep the frozen definitions shorter and more readable.

Simple Inheritance

If your field inherits directly from another Django field - say CharField - and doesn’t add any new keyword arguments, there’s no need to have any rules in your add_introspection_rules; you can just tell South that the field is alright as it is:

class UpperCaseField(models.TextField):
    "Makes sure its content is always upper-case."

    def to_python(self, value):
        return value.upper()

    def get_prep_value(self, value):
        return value.upper()

from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^southtut\.fields\.UpperCaseField"])

More Information

There’s more documentation on this subject, and on all the possible options, in the Extending Introspection section.