Search code examples
pythonhtmlflaskbootstrap-4flask-wtforms

Difficulties with having a Flask-wtforms in the main layout template of the website


I have been working on a website for the past while using flask, python and bootstrap. I added footer to my website and I would like a "contact me" form in the footer. Below is my current code for creating a contact form and displaying this form.

class ContactForm(FlaskForm):
    email = StringField("", validators=[DataRequired(), Email()])
    content = TextAreaField("", validators=[DataRequired(), length(min=2, max=500)])

    submit = SubmitField("Submit")
<form method="POST" action="">
    <div class="form-group">
        {{ form.email.label(class="form-control-label") }}
        {% if form.email.errors %}
            {{ form.email(class="form-control form-control-lg is-invalid") }}
            <div class="invalid-feedback">
                {% for error in form.email.errors %}
                    <span>{{ error }}</span>
                {% endfor %}
            </div>
        {% else %}
            {{ form.email(class="form-control form-control-lg") }}
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.content.label(class="form-control-label") }}
        {% if form.content.errors %}
            {{ form.content(class="form-control form-control-lg is-invalid") }}
            <div class="invalid-feedback">
                {% for error in form.content.errors %}
                    <span>{{ error }}</span>
                {% endfor %}
            </div>
        {% else %}
            {{ form.content(class="form-control form-control-lg") }}
        {% endif %}
    </div>
    <div class="form-group">
        {{ form.submit(class="btn btn-outline-info") }}
    </div>
</form>

I created this footer in my "layout.html" file which is the base file that all other html documents extend from so that all pages will have this same footer.

The issue is that then I need to create and instance of this form object on every page of my application and handle the validate_on_submit on every page. (Sample code below)

@app.route("/random_page", methods=["GET", "POST"])
def random_page():
    form = ContactForm()

    if form.validate_on_submit():
        # code to send the email goes here

    return render_template("random_page")

I am looking for an easier way to do this so that I don't need to repeat code on every page and so that It can be considerably simpler. I am not very experienced with flask and would highly appreciate the help.


Solution

  • Use the @app.context_processor decorator, documentation to inject an instance of your contact form into the template context for all templates. For example:

    @app.context_processor
    def inject():
        return dict(
            contact_form=ContactForm(),
        )
    

    Then re-write the form's HTML to use the contact_form template variable, also adding in an appropriate action route.

    <form method="POST" action="{{url_for('contact')}}">
        <div class="form-group">
            {{ contact_form.email.label(class="form-control-label") }}
            {% if contact_form.email.errors %}
                {{ contact_form.email(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form.email.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ contact_form.email(class="form-control form-control-lg") }}
            {% endif %}
        </div>
        <div class="form-group">
            {{ contact_form.content.label(class="form-control-label") }}
            {% if contact_form.content.errors %}
                {{ contact_form.content(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in contact_form.content.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ contact_form.content(class="form-control form-control-lg") }}
            {% endif %}
        </div>
        <div class="form-group">
            {{ contact_form.submit(class="btn btn-outline-info") }}
        </div>
    </form>  
    

    Then add a route to solely handle this form's postback:

    @app.route("/contact", methods=["POST"])
    def contact():
        form = ContactForm()
    
        if form.validate_on_submit():
            # code to send the email goes here
    
        # blah blah