Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically change WTForms field type between SelectField and HiddenField

I have a WTForms field (value_currency) that I want to sometimes be a SelectField and sometimes a HiddenField. I use the same view and template for a page that both creates new items and edits existing items. If I load the page to create a new item, I want this field to be a SelectField, and if I load the page to edit an existing item, I want this field to be a HiddenField because it's a non-editable field.

Here is what I have so far:

FORM

class PromoForm(Form):
    value = StringField('value')
    currencies = Currency.query.order_by(Currency.id).all()
    currency_choices = []
    for currency in currencies:
        currency_choice = (currency.id, currency.name)
        currency_choices.append(currency_choice)
    value_currency = SelectField('value_currency', choices=currency_choices)

VIEW

@app.route('/promo/<id>', methods=['GET', 'POST'])
@login_required
def promo(id):
    form = PromoForm()
    # Existing promo will pass in its id
    # id = 0 if a new promo is to be created
    if id != str(0):
        # Load existing promo
        promo = Promo.query.get(id)
        # display value in decimal format
        form.value.default = "{0}.{1:0>2}".format(
            promo.value_cents//100, promo.value_cents%100)
        form.process()
        return render_template('promo.html', promo=promo, form=form)
    else:
        # New promo
        audit_log('GET', client, session=session)
        return render_template('promo.html', form=form)

TEMPLATE

{% extends "base.html" %}
{% block content %}
    {% if promo is defined %}
        <form action="{{ url_for('.promo', id=promo.id) }}" method="post">
    {% else %}
        <form action="{{ url_for('.promo', id=0) }}" method="post">
    {% endif %}
    {{ form.hidden_tag() }}
    <div>
        <label for="value">Promo Value</label>
        {% if promo is defined %}
            {{ form.value() }}
        {% else %}
            {{ form.value() }}
        {% endif %}
        {% for error in form.value.errors %}
            <span class="error">[{{ error }}]</span>
        {% endfor %}
        {% if promo is defined %}
            # ----> Promo.value_currency should be a hidden field here (Doesn't work)
            {{ promo.value_currency }}
        {% else %}
            # ----> Promo.value_currency is a select field here (Currently works)
            {{ form.value_currency() }}
        {% endif %}
    </div>
    <div class="submit_btn">
        {% if promo is defined %}
            <input type="submit" value="Update Promo">
        {% else %}
            <input type="submit" value="Create Promo">
        {% endif %}
    </div>
{% endblock %}

I know I can just simply hardcode the hidden input element and drop in the value with Jinja, but I prefer to do it with WTForms and not do any form element hard coding. Is that possible?

like image 513
Kelly Keller-Heikkila Avatar asked Sep 26 '22 20:09

Kelly Keller-Heikkila


1 Answers

See (duplicated) question: Flask, WTForms: Is there a way to make a StringField in the form _temporarily_ hidden?.

You cannot just omit the field, and you cannot change its object type (from SelectField to HiddenField) either.

However, you can change its widget object dynamically. Replace this with a HiddenInput.

from wtforms.widgets import HiddenInput

class PromoForm(Form):
    value = StringField('value')
    currencies = Currency.query.order_by(Currency.id).all()
    currency_choices = []
    for currency in currencies:
        currency_choice = (currency.id, currency.name)
        currency_choices.append(currency_choice)
    value_currency = SelectField('value_currency', choices=currency_choices)
    
    def hide_value_currency(self, value):
        """
        Hide the value_currency field by morping it into a 
        HiddenInput.    
        """ 
        self.value_currency.widget = HiddenInput()
        # wtforms chokes if the data attribute is not present
        self.value_currency.data = value
        # wtforms chokes on SelectField with HiddenInput widget
        # if there is no _data() callable
        self.value_currency._value = lambda: value
        

Call form.hide_value_currency(pre_set_value) in your view when required.

No logic in your template necessary.

like image 192
florisla Avatar answered Sep 30 '22 07:09

florisla