November 25th 2014

Validating Django model attribute assignment

(Update: This snippet is now maintained as a dedicated Django package: django-validate-model-attribute-assignment)

Ever done the following?

>>> user = User.objects.get(pk=102)
>>> user.superuser = True

# Argh, why is this user now not a superuser...

Here's a dirty hack to validate these:

import sys

from django.db import models
from django.conf import settings

    'auth.User': ('backend',),

def setattr_validate(self, name, value):
    super(models.Model, self).__setattr__(name, value)

    # Real field names cannot start with underscores
    if name.startswith('_'):

    # Magic
    if name == 'pk':

    k = '%s.%s' % (self._meta.app_label, self._meta.object_name)
        fields = FIELDS[k]
    except KeyError:
        fields = FIELDS[k] = set(
            getattr(x, y) for x in self._meta.fields
            for y in ('attname', 'name')

    # Field is in allowed list
    if name in fields:

    # Field is in known exceptions
    if  name in EXCEPTIONS.get(k, ()):

    # Always allow Django internals to set values (eg. aggregates)
    if 'django/db/models' in sys._getframe().f_back.f_code.co_filename:

    raise ValueError(
        "Refusing to set unknown attribute '%s' on %s instance. "
        "(Did you misspell %s?)" % (name, k, ', '.join(fields))

# Let's assume we have good test coverage
if settings.DEBUG:
    models.Model.__setattr__ = setattr_validate


>>> user = User.objects.get(pk=102)
>>> user.superuser = True
ValueError: Refusing to set unknown attribute 'superuser' on auth.User instance. (Did you misspell 'username', 'first_name', 'last_name', 'is_active', 'email', 'is_superuser', 'is_staff', 'last_login', 'password', 'id', 'date_joined')

(Django can be a little schizophrenic on this —'s update_fields keyword argument validates its fields, as does prefetch_related, but it's taking select_related a little while to land.)

You can subscribe to new posts via email or RSS.