Let’s attempt to create a basic Django model that allows whitespace as a valid value. Here’s an example models.py
:
class Example(models.Model):
title = models.CharField(max_length=100)
def __str__(self):
return self.title
Using the Django shell, everything works as expected:
>>> from example.models import Example
>>> created = Example.objects.create(title=' ')
>>> created.title == ' '
True
But if we add the Example
model to admin.py
:
from django.contrib import admin
from .models import Example
@admin.register(Example)
class ExampleAdmin(admin.ModelAdmin):
ordering = ('title',)
And then try to create the same model via the Django admin, using an empty space for the title, we receive an error:
Adding blank=True
to the title
property makes the result even more confusing:
class Example(models.Model):
title = models.CharField(max_length=100, blank=True)
The model will be created, but the title
property is empty!
The Django admin is doing something hidden and non-obvious when creating the example model’s admin form: it’s enabling whitespace stripping by default! This stripping process removes our whitespace character and - in the case of blank=False
(the default for fields) - fails validation!
This default can be seen in the keyword arguments of the form CharField
that the Django admin uses to represent the title
model field.
Even worse, it’s not clear how to change this behavior. Adding a strip=False
keyword to the model CharField
results in an unexpected keyword argument
error. We could build a custom form for the admin, but we’d be re-creating an entire ModelForm
to change a single keyword of one property. The ModelAdmin
has a property called formfield_overrides
, but since it operates via classes, we’d be altering the behavior of all CharField
inputs - and still need to create a custom field input class!
The easiest solution is to override the behavior of the ModelAdmin
method formfield_for_dbfield
in admin.py
, which determines which form class should be used to represent each model property. We just need to add strip=False
to the field keywords when it encounters our title
property:
@admin.register(Example)
class ExampleAdmin(admin.ModelAdmin):
ordering = ('title',)
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == 'title':
kwargs['strip'] = False
return super().formfield_for_dbfield(db_field, request, **kwargs)
Success! The admin has an undesirable behavior, and that’s where our override lives - no changes to models.py
needed!