Sometime ago I wrote about generating external ids. An external id is an unique id not related to the database id. For example something a link shortening service would use - The original post had the logic in a model Manager and I did get some feedback saying it would be better implemented as a Django Field. Finally after just a couple of years I had a chance to implement it as such. It is based on the AutoSlugField from the excellent django-extensions app.

class RandomCharField(BaseUniqueField):

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('blank', True)
        kwargs.setdefault('editable', False)

        self.lower = kwargs.pop('lower', False)
        self.digits_only = kwargs.pop('digits_only', False)
        self.alpha_only = kwargs.pop('alpha_only', False)
        self.include_punctuation = kwargs.pop('include_punctuation', False)
        self.length = kwargs.pop('length', 8)
        kwargs['max_length'] = self.length

        # legacy
        kwargs.pop('include_digits', False)

        if self.digits_only:
            self.valid_chars = string.digits
            self.valid_chars = string.lowercase

            if not self.lower:
                self.valid_chars += string.uppercase

            if not self.alpha_only:
                self.valid_chars += string.digits

                if self.include_punctuation:
                   self.valid_chars += string.punctuation

        super(RandomCharField, self).__init__(*args, **kwargs)

    def generate_chars(self, *args, **kwargs):
        return ''.join([random.choice(list(self.valid_chars)) for x in range(self.length)])

    def pre_save(self, model_instance, add):
        if not add:
            return getattr(model_instance, self.attname)

        initial = self.generate_chars()
        value = self.find_unique(model_instance, initial, self.generate_chars)
        setattr(model_instance, self.attname, value)
        return value

    def get_internal_type(self):
        return "CharField"

    def south_field_triple(self):
        "Returns a suitable description of this field for South."
        # We'll just introspect the _actual_ field.
        from south.modelsinspector import introspector
        field_class = '%s.RandomCharField' % (self.__module__)
        args, kwargs = introspector(self)
            'alpha_only': repr(self.alpha_only),
            'digits_only': repr(self.digits_only),
            'include_punctuation': repr(self.include_punctuation),
            'length': repr(self.length),
            'lower': repr(self.lower),
        # That's our definition!
        return (field_class, args, kwargs)

Below is some sample output:

>>> class RandomCharTestModel(models.Model):
>>>     chars = RandomCharField(length=6)

>>> class RandomCharTestModelAlpha(models.Model):
>>>     chars = RandomCharField(length=12, alpha_only=True)

>>> class RandomCharTestModelDigits(models.Model):
>>>     chars = RandomCharField(length=4, digits_only=True)

>>> class RandomCharTestModelPunctuation(models.Model):
>>>     chars = RandomCharField(length=12, include_punctuation=True)

>>> class RandomCharTestModelLower(models.Model):
>>>     chars = RandomCharField(length=12, lower=True, alpha_only=True)

>>> class RandomCharTestModelLowerAlphaDigits(models.Model):
>>>     chars = RandomCharField(length=12, lower=True, include_punctuation=False)
For the full implementation including tests see my django utilitiy library on github.