Package: django-clean-fields
This Django utility allows the definition of methods or functions to
clean model object field values on save.InstallationSince django-clean-fields is not a Django app, simply include it on yourPYTHONPATH. The easiest way to do this is installing viapip:pipinstalldjango-clean-fieldsNo changes to the project’s settings are necessary.UsageTwo alternate implementation options are available: an extended model
class that closely resembles conventions used by theDjango forms
API,
and a decorator that registers a callable with thepre_save
signal.
Which approach to use is a decision left to the developer. The former
option may provide the most familiarity with existing conventions; the
latter offers more flexibility.CleanFieldsModelAny model inheriting from the abstractclean_fields.models.CleanFieldsModelwill check for and run cleaner
methods as its first action when saving. Such methods should match the
conventions used byform field
validators,
namely:methods must be namedclean_<field_name>methods must accept no parametersmethods must return the “cleaned” value, ready to be written to the
databasemethod may raise an exception to interrupt savingExample:fromdjango.core.exceptionsimportValidationErrorfromdjango.dbimportmodelsfromclean_fields.modelsimportCleanFieldsModelclassArticle(CleanFieldsModel):title=models.CharField(max_length=30)defclean_title(self):if"you'll never believe"inself.title.lower():raiseValidationError('Sensationalist Clickbait Not Allowed')returnself.title.title()DecoratorsTheclean_fields.decorators.cleans_fielddecorator can be applied to
any callable, which will then be invoked when thepre_save
signalis sent by the corresponding model. The decorator requires a single
argument: a reference string identifying the field to clean, which must
follow the pattern “app_name.ModelName.field_name”. Note that the full
reference must be provided even if the callable is within the model
class itself.Any decorated callable must accept the current field value and return
the “cleaned” value. The code below has the identical effect as the
above example.Example:fromdjango.core.exceptionsimportValidationErrorfromdjango.dbimportmodelsfromclean_fields.decoratorsimportcleans_fieldclassArticle(models.Model):title=models.CharField(max_length=30)@cleans_field('your_app.Article.title')defensure_title_case(self,unsaved_title):returnunsaved_title.title()# Multiple cleaners can be defined for a single field.# Also, they needn't be instance methods on the model object.@cleans_field('your_app.Article.title')defvalidate_dignified_title(unsaved_title):if"you'll never believe"inunsaved_title.lower():raiseValidationError('Sensationalist Clickbait Not Allowed')returnunsaved_titleIf references to other fields on the model instance are necessary, theclean_fields.decorators.cleans_field_with_contextdecorator should
be used instead. This decorator works the same ascleans_field, but
passes an additional parameter to the cleaner: a dictionary containing
the current field names and values.Example:fromdjango.dbimportmodelsfromclean_fields.decoratorsimportcleans_field_with_contextclassArticle(models.Model):title=models.CharField(max_length=30)is_published=models.BooleanField()@cleans_field_with_context('your_app.Article.title')defensure_title_case_when_unpublished(self,unsaved_title,data):ifdata['is_published']:returnunsaved_titleelse:returnunsaved_title.title()DiscussionThere is solid reasoning behind the omission of similar behavior in
Django’s core. For one, it might create a feeling of false security.
Validation runs on save, but that does not prevent “uncleaned” data from
being committed to the database (for instance, via the ORM’s`bulk_create<https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create>`__
or`update<https://docs.djangoproject.com/en/dev/ref/models/querysets/#update>`__
methods, which circumventsave()). Furthermore, a lack of
model-level validation encourages a separation between a user’s
interaction with model objects and a developer’s interaction with model
objects. This rigorous definition of user roles is usually a Good Thing,
but it can impose an unnecessary burden on projects that don’t require
user-driven interfaces. Be sure that this workflow benefits your project
before installing it.If in doubt, it’s worth noting some built-in alternative means to
accomplish similar cleaning behavior. For instance:The forms APIDjango form-field
validationallows cleaning both specific values or the entirety of a submitted
form. Used with aModelForm,
this is the best way to scrub data delivered via the user interface.However, forms and their validation are intended to be used within
the context of a web page. They lose much of their simplicity when
handled entirely on the backend.Model field constraints and validatorsModel fields provide two ways to avoid committing erroneous values to
the database. The first arefield
options;
passed as keyword arguments to your fields declarations, these will
enforce value contraints on the database level (eg. CharField’s
max_length). The second is the ability to definevalidators.
These functions, more flexible in Python than at the database level,
will raise errors if the values to be saved to not adhere to some
defined pattern or convention.While both these options keep the validation at the model level,
their benefit is merely error prevention. Neither allow the ability
to “massage” data into an acceptable format.Model ValidationDjango does provide some support custommodel object
validationvia the`Model.clean()method <https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.clean>`__.
This allows modifying attributes, allows access to multiple fields,
and will be called via the`Model.full_clean()method <https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.full_clean>`__.This sort of custom validation sounds ideal, except that it is not
called when a model object is saved.full_clean()orclean()must be invoked manually by any object other than aModelForm.
Moreover, from a design perspective, it’s preferable to have methods
with as narrow a focus as possible: a single method to clean a single
aspect of a single field is better thanclean(), which must
handle all validation on all fields.Signal handlingThecleans_fielddecorator already leveragesbuilt-in Django
signals(specifically, thepre_save
signal).
It is possible to handle field scrubbing directly by defining your
own signal handlers and connecting them to the appropriate signal.The greatest shortcoming of this approach is that it encourages bad
OO design: signal handlers of this nature would easily be defined
apart from the models which they are meant to modify. Even
implemented as staticmethods on the appropriate models, their method
signature is obtuse, and therefore difficult to use outside of the
context of signals.This project intends to pick up the slack where the above built-in
methods fall short, providing a simple interface to support streamlined
model design. It’s not uncircumventable, socaveat emptor, but aims to
make your life easier.