
############################
## This goes in forms.py  ##
############################

from django import forms
from django.utils.translation import ugettext_lazy as _
from django.forms.util import ValidationError
from django.forms.widgets import TextInput, PasswordInput

class FixedCharField(forms.CharField):
    default_error_messages = {
        'wrong_length': _(u'Ensure this value has exactly %(set_length)d characters (it has %(value_length)d).'),
    }

    def __init__(self, length, *args, **kwargs):
        self.length = length
        super(FixedCharField, self).__init__(*args, **kwargs)

    def clean(self, value):
        "Validates length. Returns a Unicode object."
        value = super(FixedCharField, self).clean(value)
        value_length = len(value)
        if value_length != self.length:
            raise ValidationError(self.error_messages['wrong_length'] % {'set_length': self.length, 'value_length': value_length})
        return value

    def widget_attrs(self, widget):
        if isinstance(widget, (TextInput, PasswordInput)):
            # The HTML attribute is maxlength, not max_length.
            return {'maxlength': str(self.length)}

############################
## This goes in models.py ##
############################

from myapp.forms import FixedCharField as FixedCharFormField

from django.db import models
from django.db import connection
from django.conf import settings
from django.utils.datastructures import DictWrapper

class FixedCharField(models.Field):
    def __init__(self, verbose_name=None, name=None, primary_key=False,
                 length=None, *args, **kwargs):
        if length == None:
            raise Exception("FixedCharField needs length attribute.")
        self.length = length
        
        # The kwargs will never include verbose_name nor name. (TODO: Really?)
        kwargs['verbose_name'] = verbose_name
        kwargs['name'] = name
        
        super(FixedCharField, self).__init__(*args, **kwargs)

    def db_type(self):
        data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
        
        if settings.DATABASE_ENGINE == 'mysql':
            return 'char(%(length)s)' % data
        elif settings.DATABASE_ENGINE == 'sqlite3':
            return 'char(%(length)s)' % data
        elif settings.DATABASE_ENGINE == 'oracle':
            return 'VARCHAR2(%(length)s)' % data
        else:
            return models.CharField.db_type(self)

#    def get_internal_type(self):
#        return "FixedCharField"

    def to_python(self, value):
        return models.CharField(self, value)

    def formfield(self, **kwargs):
        defaults = {'form_class': FixedCharFormField, 
                    'length': self.length}
        defaults.update(kwargs)
        return super(FixedCharField, self).formfield(**defaults)
