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 - http://bit.ly/RaFx8j. 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
else:
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)
kwargs.update({
'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)
BVm9GE
>>> class RandomCharTestModelAlpha(models.Model):
>>> chars = RandomCharField(length=12, alpha_only=True)
CxPWKJHDPnNO
>>> class RandomCharTestModelDigits(models.Model):
>>> chars = RandomCharField(length=4, digits_only=True)
7097
>>> class RandomCharTestModelPunctuation(models.Model):
>>> chars = RandomCharField(length=12, include_punctuation=True)
k[ZS.TR,0LHO
>>> class RandomCharTestModelLower(models.Model):
>>> chars = RandomCharField(length=12, lower=True, alpha_only=True)
pzolbemetmok
>>> class RandomCharTestModelLowerAlphaDigits(models.Model):
>>> chars = RandomCharField(length=12, lower=True, include_punctuation=False)
wfaytk3msiin



