Flask WTForm: add new field based on previous input - python

I have a form with a SelectField and if a specific option in there is selected I want to show a different field where a user can input more options.
So my form class looks like
class QuestionForm(FlaskForm):
type = SelectField("Question Type")
options = SelectField("options", validators=[Optional()])
Then my views\questions.py blueprint renders the form like
def question():
...
form = QuestionCreationForm()
form.type.choices = [
(type, type) for type in ["Type 1", "Type 2"]
]
form.options.choices = ["option 1", "option 2"]
if form.validate_on_submit():
...
else:
flash_form_errors(form)
return render_template(
"question.html",
form=form,
)
And finally the html:
{% extends "base.html" %}
{% block page_title %}Questionnaire{% endblock %}
{% block content %}
...
<form method="POST" action="{{ url_for('questionnaire.questionnaire') }}">
<div id="type_selection" class="select is-medium is-expanded" onchange="WHAT HERE?">
{{ form.type }}
</div>
<div id="option_selection" class="select is-medium is-expanded DISPLAY NONE HERE?" onchange="WHAT HERE?">
{{ form.options }}
</div>
</form>
....
{% endblock %}
<script>
function myFunction() {
// ??
}
</script>
So my guess is to have an onchange for my typesform field and check if the selected value is let's say Type 2 in order to change the rendering of the optionsfield to be displayed. However, I wasn`t able to stick together all the parts correctly.
This questions is a potential duplicate, but I cannot comment there and the answer provided was not specific enough the help a newby like me. Thanks!
Flask WTF forms: can a new field be shown if a user selects a specific choice in SelectMultipleField?

To not make you all fuzzy about the answer on this one. You will need some javascript too. There is pretty straight to the point tutorial on youtube that is no longer than 10 - 15 mins , so search for flask dynamic select field. Hope it will help you out.

Related

How can I generate buttons or fields (forms) in Flask according to a list of strings?

Well, suppose we have a list of strings (objects with a toString() method respectively) and a jinja2 template that shall have selection forms (buttons or something alike) that agree in number and label to the list. This list may alter during the session. So far, I tried to work with submit buttons and radio buttons. Problems are: submit buttons vary in size because of different string length and I dislike that radio buttons force the user to first make a choice and then submit it.
The jinja2 markup looks like this:
<form method = 'post' action= "{{ url_for('add_dialogue_turn') }}">
{% if questions %}
{% for q in questions %}
<input type="radio" name="question" value={{q}}> {{q}} <br>
{% endfor %}
{% endif %}
<input type="submit" /><br /><br />
</form>
The flask function looks like this:
#app.route("/turn", methods=['POST'])
def add_dialogue_turn():
label = request.form["question"]
print(label)
return render_template("sometemplate.html", questions=aListOfQuestions, answers = aListOfAnswers)
Can I make the radio buttons submit the value directly after ticking off the circle? Or can I define some field that returns the string when clicking on it?
Thank you for your help in advance!
This is a Front end problem. You would need either JavaScript to submit your form when a button/radio is ticked. And it also depends on how you submit your form but if you want just the data to be passed into the server without page reloading, I'd suggest Ajax. And if you just want to pass the input value into the server, you do not have to use post.
A simple example would be,
-HTML
<input type="radio" name="question" value={{q}} id="{{something_unique_for_each_iterable}}" onclick="submitFunction(this)">
-JavaScript
function submitFunction(event){
id_of_radio_ticked = '#' + event.id;
$.ajax({
url: "{{url_for('to_your_flask_view_function')}}",
type: 'GET',
data: {'radio_value':$(id_of_radio_ticked).val()},
success: function(resp){
alert('do something with returned data')
}
});
}
I found another solution within the jinja template:
<nav>
<ul>
<div class="sideMenuL">
<form method = 'post' action= "{{ url_for('add_dialogue_turn') }}">
{% if questions %}
{% for q in questions %}
{% autoescape false %}
<input type="submit" name="question" value="{{q}}"><br>
{% endautoescape %}
{% endfor %}
{% endif %}
</form>
</div>
</ul>
</nav>

Why is my flask form validation returning Not a valid choice?

I have been trying to figure out why my Flask form will not properly validate my select field choices even though the choices are coming from the select field options.
My assumption is that the select option when passed back from the server is unicode and is being compared to the choice which is a string, however, I thought coerce=str would fix that. I printed out the form data and request data which is the output below. Why isn't it working?
My code is attached below, removed csrf token key from the output dict. It seems like a very simple thing, but I can't figure it out.
forms.py
class PlatformForm(FlaskForm):
platform_options = [('test', 'Test'), ('test2','Test2')]
platforms = wtforms.SelectField('Platforms', choices=platform_options, coerce=str, validators=[DataRequired()])
views.py
#app.route('/', methods=['POST', 'GET'])
def index():
form = forms.PlatformForm()
if form.is_submitted():
print form.data
print request.form
if form.errors:
print form.errors
return render_template('home.html', form=form)
index.html
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.csrf_token }}
<select class="custom-select" name="platform">
{% for value, text in form.platforms.choices %}<br>
<option value="{{ value }}">{{ text }}</option>
{% endfor %}
</select>
<button id="submit_inputs" type="submit" class="btn btn-default">Submit</button>
</form>
{% endblock %}
output
{'platforms': 'None'}
ImmutableMultiDict([('platform', u'test')])
{'platforms': [u'Not a valid choice']}
EDIT:
I figured out the problem. It's the way I'm creating the Select drop down through HTML and Jinja. Iterating through the choices and creating option tags doesn't seem to instantiate anything in the form data itself when passed back into Python. Changing that whole for loop to just
{{form.platforms}}
created a select drop down field that actually works.
You have a name mismatch. In the form, you named your select field platforms (plural). In the HTML, you use platform (singular).
I recommend that instead of manually rendering the fields in your template, you let WTForms generate the HTML for you. For the form label, you can use {{ form.platforms.label }}, and for the actual field {{ form.platforms() }}. You can pass any attributes you want to field to have as keyword arguments.
I think something might be going wrong because of the way you are rendering the form in your html file. If my hunch is right, try this:
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.hidden_tag() }}
Select: {{ form.plaforms}}
{{ form.submit(class="btn btn-default") }}
</form>
{% endblock %}
and then try if form.validate_on_submit() in your views.py file
taken from this stack overflow answer by pjcunningham:
"validate_on_submit() is a shortcut for is_submitted() and validate().
From the source code, line 89, is_submitted() returns True if the form
submitted is an active request and the method is POST, PUT, PATCH, or
DELETE.
Generally speaking, it is used when a route can accept both GET and
POST methods and you want to validate only on a POST request."

How to update multiple objects at once in Django?

As the image presents I have to update multiple objects at once, e.g. to update the status of each object. If the status is active, the object will display on another page and vice versa. The goal is to change the status of one object (or all) with one button.
At the moment if I click on 'Update_all' I got only one value.
Django's Admin Page to take action on multiple objects would be a nice solution, but I have no idea, how this template is constructed although I considered the html code of the template.
Another try I attempted - similiar to the image above - was this one:
My template
<div class="container">
{% if forms.items %}
{% for key,value in forms.items %}
<form class="custom-form-manage-habis" method="post" action="">
{% csrf_token %}
<div class="container custom-div-manage-habits">
{{key}}
 
{{value}}
</div>
<hr>
{% endfor %}
{% else %}
<p>There are no habits to manage.</p>
{% endif %}
<input type="submit" value="Apply changes" {% if not forms.items
%}style="display:none"{% endif %} ></input>
</form>
</div>
...and in my view:
def post(self, request):
print('postFuction')
print(request.POST)
form_collection = {}
habit_update_list = request.POST.getlist('is_active')
print(habit_update_list)
habits = Habit.objects.filter(created_by=request.user.userprofile)
i = 0
for habit in habits:
print('I: ' + str(i))
form = HabitToManageForm(request.POST, instance=habit)
if form.is_valid():
habit = form.save(commit=False)
habit.is_active = habit_update_list[i]
print(habit)
habit.save()
else:
print('Error while check if form is valid')
i += 1
return redirect('manage_habits')
The problem here that I get indeed values of the objects, but unsorted and therefore it may be true that I get a value of object2, which will saved in object4.
So, is there a common practice or best way to handle this problem? Or may someone has any hints how I adopted the django admin template approach "select an object, then change it".
EDIT
Form:
class HabitToManageForm(forms.ModelForm):
class Meta:
model = Habit
fields = ('is_active',)

Django form template customization

New to django (and in general python). I'm trying to customize one of my forms a bit and would like to know the best way to do this.
Take for example the following, a form with 3 fields, two fields for weight entry and one for a target date.
class BasicFitnessGoalForm(forms.ModelForm):
class Meta:
model = BasicFitnessGoal
fields = ('currentWeightKg','targetWeightKg','targetDate')
widgets = {
'targetDate': forms.DateInput(attrs={'class':'formdatepicker'}),
}
labels = {
'currentWeightKg' : "Current Weight",
'targetWeightKg' : "Goal Weight",
'targetDate' : 'Goal Date'
}
I want to be able to display this form in my templates using something like goalForm.as_p() which generates html like the following:
<p>
<label></label><input><input>...
What I am looking to do is on the weight fields insert an extra element after the input tags and customize the text and css classes. So I end up with something like this:
<p>
<label>Current Weight</label><input type=Number...><span class="customCss">Kg (or text I enter</span>
</p>
So is there anyway to accomplish this inside the model class? I know I could do this with javascript or by looping through the fields in the template instead of using the form.as_p tag. But I want to reuse this so it's cleanest if I can create a method to output the desired html wherever I want to use it.
I've seen some examples where you can set a method on the form like as_foobar where the self.html_output is returned. But I see that that only allows to specify values like "normal_row", "error_row" etc. I want to code a custom template for this form and return the html from a method on the model that I can then access from a template. (or override the as_p method for this model to return custom html depending on field).
Hopefully that makes sense.
Using a custom widget as suggested in the comment is one option, another is to loop through the fields manually.
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% if field.id_label = id_currentWeightKg" %}
<span class="customCss">Kg (or text I enter</span>
{% endif %}
</div>
{% endfor %}
<input type="submit" value="Submit" />
</form>
For additional information refer to the Looping over form fields section in the manual.
before you do this, view the HTML source and confirm that id_currentWeightKg is the correct id for the field in question. If not use replace with the correct one.

How do I use the same view with two different forms and don’t loose the data provided by the user with Flask?

I have a form with several input fields about a meeting.
#app.route('/Alt_Reuniao/<WCodigoChaveP>', methods=['GET', 'POST'])
def Alt_Reuniao(WCodigoChaveP):
# This function returns a list with de data from the Database about a specific meeting
WReuniao = Executa_Query_Leitura("0005", WCodigoChaveP, "O")
if not WReuniao:
flash('Error.......')
return redirect(url_for('Cad_Reunioes'))
else:
if WReuniao[8]:
# this function returns a list with de data from the Database about the topic of the meeting
WAssunto = Executa_Query_Leitura("0002", WReuniao[8], "O")
Wform = Cad_Reunioes_Form()
if request.method == "POST":
if Wform.validate():
# save the data ......
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
else:
Wform.WCPO_Reuniao.data = WReuniao[1]
Wform.WCPO_Desc_Reuniao.data = WReuniao[2]
Wform.WCPO_Nro_Part.data = WReuniao[3]
Wform.WCPO_Cod_Assunto.data = WReuniao[8]
if WReuniao[8]:
if WAssunto:
Wform.WCPO_Assunto.data = WAssunto[1]
return render_template('Reuniao.html', Wformulario=Wform)
This is my Reuniao.html template:
{% extends "Base_Cad_2.html" %}
{% block dados %}
{{ Wformulario.WCPO_Reuniao.label(id="WCPO_Reuniao", class="lab1") }} {{ Wformulario.WCPO_Reuniao(size = 100, maxlength=30, id="WCPO_Reuniao") }}
<p id="PL"> {{ Wformulario.WCPO_L_Desc_Reuniao(id="WCPO_L_Desc_Reuniao", class="lab1") }} </p>
{{ Wformulario.WCPO_Desc_Reuniao(rows=5, cols=100, id="WCPO_Desc_Reuniao") }}
{{ Wformulario.WCPO_Nro_Part.label(id="WCPO_Nro_Part", class="lab1") }} {{ Wformulario.WCPO_Nro_Part(size = 5, id="WCPO_Nro_Part") }}
{{ Wformulario.WCPO_Cod_Assunto.label(id="WCPO_Cod_Assunto", class="lab1") }} {{ Wformulario.WCPO_Cod_Assunto(size=10, readonly='readonly', id="WCPO_Cod_Assunto") }}
{{ Wformulario.WCPO_Assunto.label(id="WCPO_Assunto", class="lab1") }} {{ Wformulario.WCPO_Assunto(size=95, readonly='readonly', id="WCPO_Assunto") }}
<button id="Selec_Assunto" name="Selec_Assunto" value="Selec_Assunto" type="button"> <a class="botoes" href="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}" hreflang="pt-br"> Seleciona </a> </button>
{% endblock %}
{% block botoes %}
<button id="gravar" name="gravar" value="Gravar" type="submit" class="botoes" > Gravar </button>
{% endblock %}
Basically, this view works fine.
When I select a meeting from a list in a previous template the view method is a GET and the data from the database is passed to the form and the template renders correctly.
When the method is a POST the data from the form is saved correctly in the Database,…
On the form there is a button Selec_Assunto. When the user click on that button I point to a view witch renders a template with a list of all possible topics for the meeting. These topics come from the database. There can be a lot of them so I can’t just use a combo. That’s why I use a template.
When the user select an topic from the list I have to render Alt_Reuniao view again and I have to pass to the view the selected topic.
This is working fine.
My problem is this: the method again is a GET. If before hitting the Selec_Assunto button the user alter or input data on the other fields in the form I loose these data from the user when the topic is selected. The view render the template with the data from the database again.
Everything seems to be working fine. I just want to maintain the data the user already change in the form before clicking on the Selec_Assunto button.
As you can see I’m new in Web developing, Python, Flask,…
Thanks a lot in advance for your help.
In this case you could update the "Selec_Assunto" button behavior to post the form data back to the server, but also include a redirect variable. When the redirect variable is included, the server would save the form changes and then redirect to the "Selec_Assuntos_Inicio" view, whereas it would follow the previous/normal form posting behavior if the redirect variable isn't present. For example:
if request.method == "POST":
if Wform.validate():
# save the data ……
if Wform.redirect.data:
return redirect(Wform.redirect.data)
else:
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
It's worth noting that this approach requires you to use JavaScript to override the "Selec_Assunto" button behavior (as you'd be forcing it to perform a form submission essentially). Here's one way you could do that using jQuery:
$('button#Selec_Assunto').on('click', function() {
$('form#formId').append('<input type="hidden" name="redirect" value="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}">');
$('form#formId').submit();
});
That said, a potentially better option from both a coding and usability perspective would be to asynchronously load the topic data into the existing page so that you don't have to reload the view at all. That would enable you to avoid needing to do an interim save of the form data.

Categories