Django LowerCaseCharField

Can Burak Çilingir picture Can Burak Çilingir · Feb 28, 2010 · Viewed 6.9k times · Source

We implemented a LowerCaseCharField. We would be happy to hear better implementation suggestions.

from django.db.models.fields import CharField

class LowerCaseCharField(CharField):
    """
    Defines a charfield which automatically converts all inputs to
    lowercase and saves.
    """

    def pre_save(self, model_instance, add):
        """
        Converts the string to lowercase before saving.
        """
        current_value = getattr(model_instance, self.attname)
        setattr(model_instance, self.attname, current_value.lower())
        return getattr(model_instance, self.attname)

In fact we love to have is:

> modelinstance.field_name="TEST"
> print modelinstance.field_name
'test'

current implementation only converts to lowercase when it is saved.

Answer

Will Hardy picture Will Hardy · Feb 28, 2010

You may wish to override to_python, which will allow you to compare non-lowercase strings when doing database lookups. The actual method is get_prep_value, but as that calls to_python for CharField, it's more convenient to override that:

def to_python(self, value):
    value = super(LowerCaseCharField, self).to_python(value)
    if isinstance(value, basestring):
        return value.lower()
    return value

Now you can do queries like:

MyModel.objects.filter(lccf="MiXeD")

Edit:

Rereading your question, it looks like you want the lowering to take effect immediately. To do this, you'll need to create a descriptor (a new-style python object with __get__ and __set__ methods, see the python docs and the django code for related models) and override contribute_to_class in the field to set the model's field to your descriptor.

Here is a full example off the top of my head, which should be reusable for all fields that want to modify the value on setting.

class ModifyingFieldDescriptor(object):
    """ Modifies a field when set using the field's (overriden) .to_python() method. """
    def __init__(self, field):  
        self.field = field  
    def __get__(self, instance, owner=None):
        if instance is None:
            raise AttributeError('Can only be accessed via an instance.')  
        return instance.__dict__[self.field.name]
    def __set__(self, instance, value):
        instance.__dict__[self.field.name] = self.field.to_python(value)

class LowerCaseCharField(CharField):
    def to_python(self, value):
        value = super(LowerCaseCharField, self).to_python(value)
        if isinstance(value, basestring):
            return value.lower()
        return value
    def contribute_to_class(self, cls, name):
        super(LowerCaseCharField, self).contribute_to_class(cls, name)
        setattr(cls, self.name, ModifyingFieldDescriptor(self))