Package: paper-forms
paper-formsThis library provides tools to simplify and customize the form rendering process in Django.
It includes theBaseComposerclass for centralized management of form settings and extends
the functionality ofBoundFieldfor convenient customization of form fields.Compatibilitypython>= 3.9django>= 3.2FeaturesJinja2support.django-jinjasupport.Table of ContentsInstallationBasic UsageComposer ConfigurationSpecifying Custom Template NamesCustomizing Form Field Rendering in ComposerCustomizing Composer ClassTemplate TagsCommon Issues and WorkaroundsInstallationTo get started withpaper-forms, you need to install the library usingpip.
Ensure you have Python 3.9 or later installed in your environment.pipinstallpaper-formsNext, add "paper_forms" to theINSTALLED_APPSin your Django project'ssettings.py:INSTALLED_APPS=(# ..."paper_forms",)Basic UsageStart by creating a simple Django form. No mixins or third-party classes are required.
Just define your form as you normally would using Django'sformsmodule.# forms.pyfromdjangoimportformsclassExampleForm(forms.Form):name=forms.CharField()age=forms.IntegerField()Now, let's render the form in your template using the{% field %}template tag
provided bypaper-forms. This tag allows you to render form fields with enhanced
customization.<!-- example_template.html -->{% load paper_forms %}<formmethod="post">{% field form.name %}
  {% field form.age %}</form>The rendered HTML will be similar to the standard Django form rendering, but with
the added flexibility provided bypaper-forms. This sets the foundation for integratingpaper-formsseamlessly into your Django project.Composer ConfigurationThe real power ofpaper-formslies in the ability to customize form rendering
using theBaseComposerclass. Let's explore how you can leverage this class to tailor
the rendering of your forms.One of the key features ofpaper-formsis the ability to customize form widgets.
In your Django form, you can define aComposerclass that inherits fromBaseComposerto specify different widgets for specific fields.# forms.pyfromdjangoimportformsfrompaper_forms.composerimportBaseComposerclassExampleForm(forms.Form):name=forms.CharField()password=forms.CharField()classComposer(BaseComposer):widgets={"password":forms.PasswordInput,}In this example, theComposerclass defines a custom widget for the "password" field.
This allows you to have fine-grained control over the rendering of individual form fields.paper-formsalso allows you to set labels and help text for form fields, either
globally or on a per-field basis.# forms.pyfromdjangoimportformsfrompaper_forms.composerimportBaseComposerclassExampleForm(forms.Form):name=forms.CharField()age=forms.IntegerField()classComposer(BaseComposer):labels={"name":"Your Full Name","age":"Your Age",}help_texts={"name":"Enter your full name as it appears on official documents.","age":"Please provide your current age.",}Here, theComposerclass provides labels and help text for the "name" and "age" fields,
offering clear instructions to users interacting with your forms.Additionally, developers can enhance the customization of the form's appearance
by utilizing theerror_css_classandrequired_css_classattributes
within theComposerclass. These attributes allow you to define specific CSS classes
for handling errors and indicating required fields, respectively. Notably, any values
set for these attributes in theComposerclass take precedence over those specified
at the form level.Specifying Custom Template NamesWhen usingpaper-forms, you have the flexibility to create custom templates for
individual form fields. This can be achieved by leveraging theComposerclass
and specifying custom template names for each field. Let's go through an example
of how a custom form field template might look:Assume you have a Django form with a field named "name," and you want to create
a custom template for rendering this specific field.Create the Custom TemplateCreate a new HTML file, let's call itcustom_field.html, and place
it in your Django app's templates directory. Here's a simplified example of how
the template might look:<!-- custom_field.html --><divclass="form-field {{ css_classes }}"><labelfor="{{ widget.attrs.id }}">{{ label }}</label><!-- include default widget template -->{% include widget.template_name %}<!-- show field errors -->{% if errors %}<ul>{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>{% endif %}<!-- show help text -->{% if help_text %}<small>{{ help_text }}</small>{% endif %}</div>In this example, the template includes a custom structure for rendering
the form field, including a label, the actual form widget, error messages,
and help text.Update the Composer ClassNow, update theComposerclass in your Django form to specify the custom
template name for the "name" field:fromdjangoimportformsfrompaper_forms.composerimportBaseComposerclassExampleForm(forms.Form):name=forms.CharField()age=forms.IntegerField()classComposer(BaseComposer):template_names={"name":"path/to/custom_field.html",}Ensure that the path provided in template_names matches the location of your custom
template file.Render the Form in Your TemplateIn your Django template where you render the form, use the{% field %}template
tag for the "name" field:{% load paper_forms %}<formmethod="post">{% field form.name %}
    {% field form.age %}<buttontype="submit">Submit</button></form>When the form is rendered, the "name" field will use your custom template,
while other fields will follow the default rendering.By following these steps, you can create and apply custom templates for specific form
fields, providing a tailored look and feel for different parts of your Django forms.Customizing Form Field Rendering in ComposerThepaper-formslibrary provides extensive flexibility through theComposerclass,
allowing you to customize the rendering of form fields. In addition to specifying custom
templates, you can achieve advanced customization by overriding methods in theBaseComposerclass. Let's explore an example of how to override methods to replacelabelswithplaceholdersacross all form fields.Suppose you want to make a global change to the rendering of form fields, replacing
labels with placeholders.Step 1: Create a Custom Composer.Create a customComposersubclass with the desired customization for form
field rendering.# composers.pyfrompaper_forms.composerimportBaseComposerclassCustomComposer(BaseComposer):placeholders:dict=Nonedefget_label(self,name,widget):# Override get_label method to return an empty string# for all fields.return""defbuild_widget_attrs(self,name,attrs,widget):attrs=super().build_widget_attrs(name,attrs,widget)placeholder=attrs.get("placeholder")ifplaceholderisNoneandself.placeholders:placeholder=self.placeholders[name]ifplaceholder:attrs["placeholder"]=placeholderreturnattrsStep 2: Use Custom Composer in a Django Form.Apply the customCustomComposerclass to a Django form.# forms.pyfromdjangoimportformsfrom.composersimportCustomComposerclassExampleForm(forms.Form):name=forms.CharField()age=forms.IntegerField()classComposer(CustomComposer):placeholders={"name":"Enter your name","age":"Enter your age",}Step 3: Render the Form in Your Template.Use the{% field %}template tag to render the form in your template.{% load paper_forms %}<formmethod="post">{% field form.name %}
    {% field form.age %}<buttontype="submit">Submit</button></form>With this setup, the "name" and "age" fields will have placeholders instead of labels.Customizing Composer ClassIn addition to the specific methods discussed earlier, theBaseComposerclass
in thepaper-formslibrary provides several other methods that developers can override
to further customize form field rendering. Below are some of the additional methods
available for customization:get_widget(self, name: str) -> WidgetRetrieves a widget for a specific form field. By default, it looks up the widget
in thewidgetsdictionary. IfNoneis returned, the default widget for the field
will be used.get_template_name(self, name: str, widget: Widget) -> strDetermines the template name to be used for rendering a form field. It considers
hidden widgets, specific template names in the template_names dictionary, and
falls back to the default template name.get_default_template_name(self, name: str, widget: Widget) -> strThis method plays a crucial role in simplifying the creation of Composer classes for
frameworks like Bootstrap. This method is designed to facilitate the customization
of form field templates without affecting the ability to specify template paths via
thetemplate_namesattribute in theComposerclass.When rendering a form field, theget_default_template_namemethod is called
to determine the default template name based on the provided field name and widget.
This method can be overridden in custom Composer classes to set a default template name,
allowing for a more streamlined implementation of framework-specific Composers.fromdjango.forms.widgetsimportTextInput,NumberInputfrompaper_forms.composerimportBaseComposerclassFrameworkComposer(BaseComposer):defget_default_template_name(self,name,widget)->str:ifisinstance(widget,TextInput):returnf"bootstrap/text_field.html"elifisinstance(widget,NumberInput):returnf"bootstrap/number_field.html"# Fall back to the default behaviorreturnsuper().get_default_template_name(name,widget)get_label(self, name: str, widget: Widget) -> Optional[str]Retrieves the label for a form field. It looks up the label in thelabelsdictionary.
IfNoneis returned, the default label for the field will be used.get_help_text(self, name: str, widget: Widget) -> Optional[str]Retrieves the help text for a form field. It looks up the help text in thehelp_textsdictionary. IfNoneis returned, the default help text for the field will be used.get_css_classes(self, name: str, widget: Widget) -> Optional[str]Retrieves the CSS classes for a form field. It looks up the CSS classes
in thecss_classesdictionary.build_widget_attrs(self, name: str, attrs: Optional[dict], widget: Widget) -> dictBuilds and customizes the attributes for a form field's widget. Developers can add,
remove, or modify attributes based on field names or other criteria.build_context(self, name: str, context: Optional[dict], widget: Widget) -> dictBuilds the context to be passed to the form field template. Developers can add
or modify context variables based on field names or other conditions.Template Tagspaper-formsprovides template tags to simplify the integration of the library into
your Django templates. The primary tag is{% field %}, which allows you to render
form fields with enhanced customization options.The{% field %}tag is the key to leveraging the features provided bypaper-forms.
It allows you to render form fields with various attributes and context variables.{% load paper_forms %}<formmethod="post">{% field form.name placeholder="Enter your name" %}
  {% field form.age placeholder="Enter your age" _style="light" %}</form>In this example, the{% field %}tag is used to render the "name" and "age" fields
of the form. Attributes such asplaceholderandstyleare passed as parameters
to customize the rendering.The{% field %}tag supports adding variables to the form field template using
the "_" prefix in the template tag parameters. These variables become part
of the template context for the form field.In the example above, theplaceholderattribute is a widget attribute, while
the_styleis a template context variable. Parameters with a leading underscore,
such as_style, are treated as template context variables.Configurationpaper-formsprovides additional configuration options that you can set in your
Django project's settings.You can specify the defaultComposerclass to be used for any form that doesn't specify
a particular composer. This is set using thePAPER_FORMS_DEFAULT_COMPOSERsetting.# settings.pyPAPER_FORMS_DEFAULT_COMPOSER="myapp.composers.CustomComposer"Here,myapp.composers.CustomComposeris the custom composer class you want to use
as the default.The default form renderer can be set using thePAPER_FORMS_DEFAULT_FORM_RENDERERsetting.# settings.pyPAPER_FORMS_DEFAULT_FORM_RENDERER="django.forms.renderers.TemplatesSetting"In this example,django.forms.renderers.TemplatesSettingis used as the default
form renderer.Common Issues and WorkaroundsIn the course of usingpaper-forms, you may encounter some common issues. This section
provides explanations and workarounds for these issues to help you navigate potential
challenges.Dashes in Attribute NamesIf you are trying to specify an attribute with dashes in the{% field %}template tag,
likedata-src, you might face limitations. This is due to the restrictive nature of@simple_tag, whichdoes not allow dashesin kwargs names.To overcome this limitation, use double underscores. All double underscores in{% field %}arguments are replaced with single dashes. For example:{% field form.name data__original__name="Name" %}This would render to something like<input...data-original-name="Name"/>FORM_RENDERERSetting with Third-Party Template EnginesIf you are usingdjango-jinjaor any other third-party template engine as your default
template engine, and you want to use it for your form field templates, you might face
challenges. Django's form widgets are rendered using form renderers, and changing theFORM_RENDERERsetting can break the admin interface.Follow these steps to work around this problem:Make built-in widget templates searchable:# settings.pyfrompathlibimportPathfromdjangoimportformsTEMPLATES=[{"NAME":"jinja2","BACKEND":"django_jinja.backend.Jinja2","DIRS":[BASE_DIR/"templates",Path(forms.__file__).parent/"jinja2"],# ...}]UseTemplateSettingrenderer for your forms or implement your own. You can achieve
this in several ways:Set thePAPER_FORMS_DEFAULT_FORM_RENDERERsetting insettings.py:# settings.pyPAPER_FORMS_DEFAULT_FORM_RENDERER="django.forms.renderers.TemplatesSetting"Specify thedefault_rendererattribute in your form:fromdjangoimportformsfromdjango.forms.renderersimportTemplatesSettingclassExampleForm(forms.Form):default_renderer=TemplatesSetting# ...Set therendererfield in yourComposerclass:fromdjangoimportformsfrompaper_forms.composerimportBaseComposerclassExampleForm(forms.Form):name=forms.CharField()classComposer(BaseComposer):renderer="django.forms.renderers.TemplatesSetting"