CharField with fixed length, how?

Django

Django Problem Overview


I wold like to have in my model a CharField with fixed length. In other words I want that only a specified length is valid.

I tried to do something like

volumenumber = models.CharField('Volume Number', max_length=4, min_length=4)

but it gives me an error (it seems that I can use both max_length and min_length at the same time).

Is there another quick way?

My model is this:

class Volume(models.Model):
    vid = models.AutoField(primary_key=True)
    jid = models.ForeignKey(Journals, db_column='jid', null=True, verbose_name = "Journal")
    volumenumber = models.CharField('Volume Number')
    date_publication = models.CharField('Date of Publication', max_length=6, blank=True)
    class Meta:
        db_table = u'volume'
        verbose_name = "Volume"
        ordering = ['jid', 'volumenumber']
        unique_together = ('jid', 'volumenumber')
    def __unicode__(self):
        return (str(self.jid) + ' - ' + str(self.volumenumber))

What I want is that the volumenumber must be exactly 4 characters.

I.E. if someone insert '4b' django gives an error because it expects a string of 4 characters.

So I tried with

volumenumber = models.CharField('Volume Number', max_length=4, min_length=4)

but it gives me this error:

Validating models...
Unhandled exception in thread started by <function inner_run at 0x70feb0>
Traceback (most recent call last):
  File "/Library/Python/2.5/site-packages/django/core/management/commands/runserver.py", line 48, in inner_run
    self.validate(display_num_errors=True)
  File "/Library/Python/2.5/site-packages/django/core/management/base.py", line 249, in validate
    num_errors = get_validation_errors(s, app)
  File "/Library/Python/2.5/site-packages/django/core/management/validation.py", line 28, in get_validation_errors
    for (app_name, error) in get_app_errors().items():
  File "/Library/Python/2.5/site-packages/django/db/models/loading.py", line 131, in get_app_errors
    self._populate()
  File "/Library/Python/2.5/site-packages/django/db/models/loading.py", line 58, in _populate
    self.load_app(app_name, True)
  File "/Library/Python/2.5/site-packages/django/db/models/loading.py", line 74, in load_app
    models = import_module('.models', app_name)
  File "/Library/Python/2.5/site-packages/django/utils/importlib.py", line 35, in import_module
    __import__(name)
  File "/Users/Giovanni/src/djangoTestSite/../djangoTestSite/journaldb/models.py", line 120, in <module>
    class Volume(models.Model):
  File "/Users/Giovanni/src/djangoTestSite/../djangoTestSite/journaldb/models.py", line 123, in Volume
    volumenumber = models.CharField('Volume Number', max_length=4, min_length=4)
TypeError: __init__() got an unexpected keyword argument 'min_length'

That obviously doesn't appear if I use only "max_length" OR "min_length".

I read the documentation on the django web site and it seems that I'm right (I cannot use both together) so I'm asking if there is another way to solve the problem.

Django Solutions


Solution 1 - Django

Kind of along the same lines as above, but for what it's worth you could also go ahead with MinLengthValidator which django supplies. Worked for me. The code would look something like this:

from django.core.validators import MinLengthValidator
...
class Volume(models.Model):
volumenumber = models.CharField('Volume Number', max_length=4, validators=[MinLengthValidator(4)])
...

Solution 2 - Django

You don't even have to write a custom one. Just use the RegexValidator which Django supplies.

from django.core.validators import RegexValidator

class MyModel(models.Model):
    myfield = models.CharField(validators=[RegexValidator(regex='^.{4}$', message='Length has to be 4', code='nomatch')])

From the Django Docs: class RegexValidator(\[regex=None, message=None, code=None\])

regex: A valid regular expression to match. For more on regex in Python check this excellent HowTo: http://docs.python.org/howto/regex.html

message: The message returned to the user in case of failure.

code: error code returned by ValidationError. Not important for your usage case, you can leave it out.

Watch out, the regex suggested by me will allow any characters including whitespace. To allow only alphanumeric characters, substitute the '.' with '\w' in the regex argument. For other requirements, ReadTheDocs ;).

Solution 3 - Django

CharField database model field instances only have a max_length parameter, as indicated in the docs. This is probably because there is only a max character length contraint equivalent in SQL.

Form Field CharField objects, on the other hand, do have a min_length parameter. So you'd have to write a custom ModelForm for this specific model and override the default admin model form with the custom one.

Something like that:

# admin.py

from django import forms

...

class VolumeForm(forms.ModelForm):
    volumenumber = forms.CharField(max_length=4, min_length=4)

    class Meta:
        model = Volume


class VolumeAdmin(admin.ModelAdmin):
    form = VolumeForm

...

admin.site.register(Volume, VolumeAdmin)

Solution 4 - Django

You can write a custom Validator as suggested by @Ben. As of the date of this answer the instructions for doing this can be found at https://docs.djangoproject.com/en/dev/ref/validators/

The code would be something like this (copying from the link):

from django.core.exceptions import ValidationError

def validate_length(value,length=6):
    if len(str(value))!=length:
        raise ValidationError(u'%s is not the correct length' % value)

from django.db import models

class MyModel(models.Model):
    constraint_length_charField = models.CharField(validators=[validate_length])

Solution 5 - Django

Yet another implementation using custom model field:

from django.core.validators import BaseValidator
from django.db import models
from django.utils.deconstruct import deconstructible


@deconstructible
class FixedLengthValidator(BaseValidator):
    message = 'Ensure this value has %(limit_value)d character (it has %(show_value)d).'
    code = 'length'

    def compare(self, a, b):
        return a != b

    def clean(self, x):
        return len(x)


class FixedLengthCharField(models.CharField):
    def __init__(self, *args, length, **kwargs):
        self.length = length
        kwargs['max_length'] = length
        super().__init__(*args, **kwargs)
        self.validators.insert(0, FixedLengthValidator(length))

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        del kwargs['max_length']
        kwargs['length'] = self.length
        return name, path, args, kwargs

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGiovanni Di MiliaView Question on Stackoverflow
Solution 1 - DjangoChetanView Answer on Stackoverflow
Solution 2 - DjangotBuLiView Answer on Stackoverflow
Solution 3 - DjangoHaesView Answer on Stackoverflow
Solution 4 - DjangotjbView Answer on Stackoverflow
Solution 5 - DjangodtatarkinView Answer on Stackoverflow