Package: wtforms-bootstrap5
WTForms-Bootstrap5Simple library for rendering WTForms in HTML as Bootstrap 5 form controlsNotice: this project is still in very early stage, the API may change a lots rapidlyFeaturesMIT licensed- it doesn't infect your codeLight weight- not much code and little dependenciesLatest Bootstrap 5- generates forms in latest Bootstrap 5 styleHighly customizable- you can generate different kind of Bootstrap 5 form controls and layoutsTemplate engine friendly- chained method calls making it easy to integrate with template engineCovered with automatic tests- yep, we have test casesWhy?Everytime I build a website withWTForms, I spend way too much time in writing HTML andJinja templatefor rendering a form asBootstrap 5 form controls.
Work smart is an important value we have here atLaunch Platform, so I wonder why not make a library for making rendering Bootstrap 5 style WTForms controls easily?
So here you go, wtforms-bootstrap5 is created, open sourced under MIT license.
It's a simple Python library for rendering WTForms in Bootstrap 5 favor.InstallTo install the formatter, simply runpipinstallwtforms-bootstrap5ExampleFirst, you define your form as you would usually do with WTForms:fromwtformsimportFormfromwtformsimportEmailFieldfromwtformsimportPasswordFieldfromwtformsimportSelectFieldfromwtformsimportBooleanFieldfromwtformsimportSubmitFieldclassMyForm(Form):email=EmailField("Email",render_kw=dict(placeholder="Foobar"))password=PasswordField("Password",description="Your super secret password")city=SelectField("City",choices=["Los Angle","San Francisco","New York"])agree_terms=BooleanField("I agrees to terms and service")submit=SubmitField()Then you can useRenderContextfor rendering your form like thisfromwtforms_bootstrap5importRendererContextform=MyForm()context=RendererContext()html=context.render(form)The form will be rendered as HTML like<formmethod="POST"><divclass="mb-3"><labelclass="form-label"for="email">Email</label><inputclass="form-control"id="email"name="email"type="email"value=""></div><divclass="mb-3"><labelclass="form-label"for="password">Password</label><inputclass="form-control"id="password"name="password"type="password"value=""><divclass="form-text">Your super secret password</div></div><divclass="mb-3"><labelclass="form-label"for="city">City</label><selectclass="form-select"id="city"name="city"><optionvalue="Los Angle">Los Angle</option><optionvalue="San Francisco">San Francisco</option><optionvalue="New York">New York</option></select></div><divclass="mb-3"><divclass="form-check"><labelclass="form-check-label"for="agree_terms">I agrees to terms and service</label><inputclass="form-check-input"id="agree_terms"name="agree_terms"type="checkbox"value="y"></div></div><divclass="mb-3"><inputclass="btn btn-primary"id="submit"name="submit"type="submit"value="Submit"></div></form>And it will look like thisBy default, a sensible simple layout style will be used.Customize the formThere are many similar open source libraries out there, but most of them are very hard to customize.
Once you adopt it, then you can only render your form in a specific style.
As a result, I found myself writing HTML manually without using the library to save time.To avoid the same mistake, we want to make wtforms-bootstrap5 very easy to customize without compromising too much of its reusability.
Here's an example how you can turn the example above into a column based form.html=(renderer_context.form(action="/sign-up").default_field(row_class="row mb-3",label_class="form-label col-2",field_wrapper_class="col-10",field_wrapper_enabled=True,).field("agree_terms",wrapper_class="offset-2",wrapper_enabled=True,field_wrapper_enabled=False,).field("submit",field_wrapper_class="offset-2",field_wrapper_enabled=True,)).render(form)And this is how it looks likeAs you can see in the example, we usedefault_fieldmethod for overwriting the options of all fields by default.
We also usefieldmethod for overwriting the options for a specific field.
Thefieldmethod takes multiple input name arguments, so that you can overwrite options for multiple fields at once like thishtml=(context.field("email","password",label_class="my-custom-class",...))Please notice that,the order ofdefault_fieldandfieldmethod calls matter.
Whenfieldis called, the current default field options will be used as the default values.
So if you make the calls in order like thishtml=(context.field("email",row_class="row").default_field(label_class="my-custom-class"))Thelabel_classforemailfield here will beform-labelinstead ofmy-custom-classsince when it's called, the original default value was stillform-label.To customize the form element, you can use theformmethod like thishtml=(context.form(method="POST",action="/sign-up",enctype="application/x-www-form-urlencoded",form_class="my-form",form_attrs=dict(custom="value")))Add submit button or other fieldsSometimes, define a submit button for each form is not desirable.
Or, the form could be automatically generated and doesn't come with a submit button.
In those cases, you can useadd_fieldto add any extra fields into the end of the form.
Like this:html=(renderer_context.add_field("submit",SubmitField()).field("submit",field_wrapper_class="offset-2",field_wrapper_enabled=True,)).render(form)Since adding a submit button is very common, so you can also useadd_submitinstead if the field to add is aSubmitField.
The default submit field name issubmit, so you don't need to provide it if you want to make itsubmit.
Arguments ofSubmitFieldcan be passed in, such aslabel.html=(renderer_context.add_submit(label="Update").field("submit",field_wrapper_class="offset-2",field_wrapper_enabled=True,)).render(form)As you can see in the example, the decorate options also work for the newly added submit field.
If you want to change the default submit field class, you can pass insubmit_field_clsargument when creating the context like this.html=(RenderContext(submit_field_cls=SubmitButton).add_submit()).render(form)Field HTML structureIn general, the field HTML structure can be controlled by the option values and it looks like this<!-- enabled by .row_enabled, default: true --><divclass=".row_class"{.row_attrs}><!-- enabled by .wrapper_enabled, default: false --><divclass=".wrapper_class"{.wrapper_attrs}><!-- enabled by .label_enabled, default: true --><labelclass=".label_class"for="email"{.label_attrs}>Email</label><!-- enabled by .field_wrapper_enabled, default: false --><divclass=".field_wrapper"{.field_wrapper_attrs}><inputclass=".field_class"id="email"name="email"type="email"value=""{.field_attrs}><!-- enabled by .help_enabled, default: true --><divclass=".help_class"{.helper_attrs}>Your super secret password</div><!-- enabled by .error_enabled, default: true --><divclass=".error_class"{.error_attrs}>Bad password</div></div></div></div>Integrate with template engineWe want to make it as easy as possible to integrate with template engine such asJinja.
That's why we use chained method calls for customizing the form.
You should be able to pass theformandRenderContextobjects and write all your form customization from the template.
This way, you don't get your view relative code pollute your controller code.
For example, after passingformandrender_contextobject, you can write this in Jinja:<h1>New user</h1>{{
    renderer_context
        .default_field(
            row_class="row mb-3",
            label_class="form-label col-2",
            field_wrapper_class="col-10",
            field_wrapper_enabled=True,
        )
        .field(
            "agree_terms",
            wrapper_class="offset-2",
            wrapper_enabled=True,
            field_wrapper_enabled=False,
        )
        .field(
            "submit",
            field_wrapper_class="offset-2",
            field_wrapper_enabled=True,
        )
    ).render(form)
}}FeedbacksFeedbacks, bugs reporting or feature requests are welcome 🙌, just please open an issue.
No guarantee we have time to deal with them, but will see what we can do.