FormHelper()
and Layout()
objects to design reusable forms in code. This requires some additional work upfront, but the end result should be a form that can be modified in one location (the forms.py file) and reused in multiple views and templates.FormHelper()
and Layout()
objects:models.ModelForm
class. This makes it simpler to create a form for most, if not all, of Ghostwriter's use cases for a form.ClientContactCreate
) because the forms should be reusable. A form used to create a new model entry should be reusable to update that same entry.Meta
class of every instance of ModelForm
must declare the model as the variablemodel
and then provide values for either fields
or exclude
. Use exclude
to declare which fields should not be included in the form.exclude
would be a bigger list than fields
, then use fields
to declare which fields to include.fields = "__all__"
.from .forms import FORM_NAME
.Client
and Project
models and their associated child models pushed the single forms.py file beyond 1,000 lines. To make it easier to maintain each set of forms, the project moved them into files named forms._client.py and forms_project.py.FormHelper()
and Layout()
objects control the <form></form>
tags and the layout of the fields, respectively.ModelForm
instances, this is done in the form's __init__
method. Attributes should be set at the top of the __init__
method, like so:placeholder
attribute should be set for all fields. The placeholder text should provide an example of the intended content or an example of the proper input format. In more freeform fields, the placeholder should identify the field and guide the user towards its intended purpose (e.g., the note
field in the above example).autocomplete
to off
unless it is explicitly needed. Otherwise, autocomplete behavior can negatively impact user experience (e.g., autocomplete lists appearing and covering datepickers) or display non-public information when clicking on the field. The latter is mostly an issue for demonstrations of Ghostwriter.FormHelper()
object named self.helper
. Create a FormHelper()
object named in the form's __init__
method. At a minimum, Ghostwriter form helpers set several values. These values ensure the form appears correctly in the content of the rendered webpage.FormHelper()
configuration. See the next section for more information.Layout()
object assigned to the FormHelper()
object's layout
attribute, self.helper.layout
. This object controls the form's HTML. Crispy uses a collection of HTML templates assigned to different crispy_forms.layout
and crispy_forms.bootstrap
classes to generate elements when the form is rendered.id="my-div-id"
to an instance of Div()
will result in Crispy using this to the render the div and set the div's id
attribute to my-div-id.class
, so they require using a different argument. To set the class attribute, use css_class
.DEBUG
mode for development, every save action will restart the server. Plan form changes carefully to avoid excessive wait times for restarting the server.ClientNote
form might look like this example. This form display a single TextArea
for a note. The only other fields are hidden fields used for associating the entry with an individual Client
and individual Users
.Div()
class so they appear wrapped in <div></div>
tags. While not used in this example, this allows for specifying a CSS class and other attributes for the div.css_class="btn btn-primary col-md-4"
css_class="btn btn-outline-secondary col-md-4
Submit()
class. This class accepts a name and a value. In most examples online the name
parameter is set to submit. The name can be anything, but using submit-button is generally preferred. The value should always be Submit.value
parameter is set to submit
. This works in most cases, but can cause unintended issues if the form is ever used with JavaScript. Setting the name of any field or button to submit masks the form's submit()
method.cancel_link
context variable to templates with forms. This variable contains a pre-defined URL that will return the user to a sensible location if they choose to abandon the form.HTML()
class to create a button with an onclick
attribute that uses the cancel_link
context variable and sets the other necessary values for the button.Button()
with an onclick
kwarg, but Django will not render context variables passed to the template in this way.Tabholder()
class. This class must contain tabs that hold different sections of the form.Tab()
class. Instead, there is a CustomTab()
class available in the project that allows for additional customization of the tabs.