When you write a blog post, people of Internet will point out any mistakes they find. It’s a great opportunity to improve existing code and look into other solutions. After posting The tale of DRY with django-crispy-forms I found out about formulation - a template-based solution for rendering forms, in contrast to crispy-forms that builds the form structure in code.
Also @maraujop – crispy-forms developer – made several very helpful suggestions in the comments area, which brings us to Part II.
Mistakes of the past
The story of Foo people can be found in this archive. Their end was tragic. They knew truth was somewhere close, but they were unable to grasp it to its fullest and failed to survive this cruel software world.
FooForms had their
__init__ cluttered with the initialization
FormHelper object and the attachment of the
Nothing bad with
SubmitCancelFormActions itself, but as they grew in number
overriding the initialization started to look silly.
Decline of Foo marked the birth of powerful Bar people.
Assume that Bar model, view and template are similar to Foo. Bar people knew they should start with the API, and so they shaped their dreams first.
What Bar wants is to declare an alternative
FormHelper, which is responsible
for customizing the form. Also it wants to provide helper attributes, that are different
for each form - the cancel URL, for example.
There are 2 unknown creatures here -
SubmitCancelFormHelper as you might have guessed is of
FormHelper breed. It
tells you right to the face that it appends to the layout submit and cancel buttons.
See for yourself.
This class expects a
cancel_href named parameter. Afterwards it takes
advantage of the power it was given by
FormHelper and modifies the layout by
appending a button that we represent using HTML widget and a Submit widget.
SubmitCancelFormHelper serves us well.
ModelFormWithHelper mystery. This base class is a bit more tricky,
but still nothing out of this world.
Ohh, that’s a lot of code. Well, docstrings make it 1/3 of the listing. Let’s
ModelFormWithHelper is trying to tell us.
In the initialization phase it checks if the class
helper_class attribute. It’s mandatory to be there, otherwise there’s
no point in using this class at all. If there isn’t,
throw an exception to remind you, Hey, you forgot that attribute - helper_class!
Otherwise, it will initialize the form helper class and attach it
self.helper. But before that it extracts the helper kwargs.
Look at the dream
BarForm class. Bar people wanted to have a custom cancel URL
and used a meta attribute
with all the
helper_ attributes and strips them of that prefix to ultimately
feed them to the helper.
The class is not tied to any specific form helper, so you can create as many custom form helpers as your kingdom needs.
That’s it. Now forms will become more compact and readable due to the new meta attributes.