I'm quite new with Flask and I still didn't understand how to make different FLASK WTforms to interact differently with the users.
In the following code, I'm showing the route where I show 3 different forms to the users:
Distribution Form(1): After the user input start_date and end_date, I will generate two plots
Keywords Form(2): based on other user inputs, I will generate a plot. What I expect here is the user to interact and generate new plots as much as they want.
Search Pattern Form(3): based on other user inputs, I will show the reviews where the patterns are present. As in (2), even here I want the user to interact with the form as much as he wants.
@app.route('/report', methods=['GET', 'POST']) def report():
distribution_form = DistributionForm() keywords_form = KeywordsForm() search_pattern_form = SearchPatternForm() subset_df = None plot_keywords = None weekly_reviews_volume_plot = None monthly_reviews_volume_plot = None if distribution_form.validate_on_submit(): start_date = distribution_form.start_date.data end_date = distribution_form.end_date.data if not start_date or not end_date: flash('Please fill out all fields before submitting', category='error') return redirect(url_for('report')) reviews_filepath = os.path.abspath(os.path.join(reviews_dir, f'app_reviews_extraction_{start_date}_{end_date}.xlsx')) ratings_filepath = os.path.abspath(os.path.join(ratings_dir, f'daily_cumulative_average_{start_date}_{end_date}.xlsx')) if not os.path.isfile(reviews_filepath): flash('Please download the dataset in the Reviews section before creating the report', category='error') return redirect(url_for('report')) # Do the same for ratings if not os.path.isfile(ratings_filepath): flash('Please download the dataset in the Reviews section before creating the report', category='error') return redirect(url_for('report')) df_reviews = pd.read_excel(reviews_filepath) df_ratings = pd.read_excel(ratings_filepath) session['start_date'] = start_date session['end_date'] = end_date # daily_distribution_dict = timing.get_daily_reviews_distribution(df_reviews, df_ratings) weekly_distribution_dict = timing.get_weekly_reviews_distribution(df_reviews, df_ratings) monthly_distribution_dict = timing.get_monthly_reviews_distribution(df_reviews, df_ratings) monthly_reviews_volume_plot = timing.monthly_reviews_distribution(monthly_distribution_dict['ios'], monthly_distribution_dict['android']) weekly_reviews_volume_plot = timing.weekly_reviews_distribution(weekly_distribution_dict['ios'], weekly_distribution_dict['android']) if keywords_form.validate_on_submit(): start_date = session.get('start_date') end_date = session.get('end_date') reviews_filepath = os.path.abspath(os.path.join(reviews_dir, f'app_reviews_extraction_{start_date}_{end_date}.xlsx')) ratings_filepath = os.path.abspath(os.path.join(ratings_dir, f'daily_cumulative_average_{start_date}_{end_date}.xlsx')) df_reviews = pd.read_excel(reviews_filepath) df_ratings = pd.read_excel(ratings_filepath) #text preprocessing reviews_preprocessed = text.preprocess_only_english(df_reviews) exclude_word = ['philips', 'philip','phillip', 'app', 'light','lighting', 'hue', 'bulb', 'product'] exclude_pair = ['philips hue', 'hue app', 'philip hue', 'phillip hue', 'hue bulb', 'phillips hue', 'hue lighting', 'hue light', 'do not'] #keywords count keywords_dict = topic_extraction.get_counts_english(reviews_preprocessed, exclude_word, exclude_pair) plot_keywords = topic_extraction.hbar_words(keywords_dict, keywords_form.form.data, keywords_form.polar.data, keywords_form.platform.data, keywords_form.n.data, keywords_form.date.data) if search_pattern_form.validate_on_submit(): start_date = session.get('start_date') end_date = session.get('end_date') reviews_filepath = os.path.abspath(os.path.join(reviews_dir, f'app_reviews_extraction_{start_date}_{end_date}.xlsx')) ratings_filepath = os.path.abspath(os.path.join(ratings_dir, f'daily_cumulative_average_{start_date}_{end_date}.xlsx')) df_reviews = pd.read_excel(reviews_filepath) df_ratings = pd.read_excel(ratings_filepath) #text preprocessing reviews_preprocessed = text.preprocess_only_english(df_reviews) pattern = r'\b' + search_pattern_form.pattern.data + r'\b' platform = search_pattern_form.platform.data mask = reviews_preprocessed[platform]['text_preprocessed'].str.contains(pattern) subset_df = reviews_preprocessed[platform][mask] subset_df = subset_df.loc[:, ['english_translation', 'rating']] subset_df = subset_df.to_html(classes='table table-striped') return render_template('report.html', distribution_form=distribution_form, keywords_form=keywords_form, search_pattern_form=search_pattern_form, monthly_reviews=monthly_reviews_volume_plot, weekly_reviews=weekly_reviews_volume_plot, plot_keywords=plot_keywords, subset_df=subset_df)
What is happening right now instead is that the user input the data for the form (1), the plots are shown, then after the inputs for form (2) the page is just refreshed. This is the report.html template:
{% extends "base.html"%} {% block title %}Report{% endblock %}
{% block content %}
<form method="POST">
{{ distribution_form.hidden_tag() }}
{{ distribution_form.start_date.label }} {{ distribution_form.start_date() }}
{{ distribution_form.end_date.label }} {{ distribution_form.end_date() }}
<input type="submit" name="distribution_form_submit" value="Get Distributions">
</form>
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
<div id='monthly_chart' class='chart'></div>
<div id='weekly_chart' class='chart'></div>
<div id='keywords' class='chart'></div>
<script type='text/javascript'>
var monthly = {{monthly_reviews|safe}};
Plotly.plot('monthly_chart',monthly,{});
</script>
<script type='text/javascript'>
var weekly = {{weekly_reviews|safe}};
Plotly.plot('weekly_chart',weekly,{});
</script>
<form method="POST">
{{ keywords_form.hidden_tag() }}
{{ keywords_form.form.label }} {{ keywords_form.form() }}
{{ keywords_form.polar.label }} {{ keywords_form.polar() }}
{{ keywords_form.platform.label }} {{ keywords_form.platform() }}
{{ keywords_form.n.label }} {{ keywords_form.n() }}
{{ keywords_form.date.label }} {{ keywords_form.date() }}
<input type="submit" name="keywords_form_submit", value="Get Keywords">
</form>
<script type='text/javascript'>
var keywords = {{plot_keywords|safe}};
Plotly.plot('keywords',keywords,{});
</script>
<form method="POST">
{{ search_pattern_form.hidden_tag() }}
{{ search_pattern_form.pattern.label }} {{ search_pattern_form.pattern() }}
{{ search_pattern_form.platform.label }} {{ search_pattern_form.platform() }}
<input type="submit" name="search_pattern_form" value="Get Reviews">
</form>
<div>
{{ subset_df|safe }}
</div>
{% endblock %}
I'm clearly missing the logic, but I couldn't find any helpful information until now. Any help is much appreciated! I post also the forms class I made:
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, IntegerField, DateField, SubmitField
from wtforms.validators import DataRequired, NumberRange
class KeywordsForm(FlaskForm):
form = SelectField('Form', choices=[('word', 'Word'), ('pair', 'Pair')], validators=[DataRequired()])
polar = SelectField('Polarity', choices=[('positive', 'Positive'), ('negative', 'Negative')], validators=[DataRequired()])
platform = SelectField('Platform', choices=[('ios', 'iOS'), ('android', 'Android')], validators=[DataRequired()])
n = IntegerField('N', validators=[NumberRange(min=5, max=20)])
date = StringField('Date', validators=[DataRequired()])
submit = SubmitField('Submit')
class DistributionForm(FlaskForm):
start_date = DateField('Start Date', format='%Y-%m-%d', validators=[DataRequired()])
end_date = DateField('End Date', format='%Y-%m-%d', validators=[DataRequired()])
submit = SubmitField('Create Report')
class SearchPatternForm(FlaskForm):
pattern = StringField('Pattern', validators=[DataRequired()])
platform = StringField('Platform', validators=[DataRequired()])
submit = SubmitField('Submit')
As your html template is currently set up you have three separate forms that can be submitted independently but, as your flask server has no memory of what was submitted previously, each time you make a post request the server will only see one completed form and two empty forms. The simple solution to this is to only have a single form tag on your page encompassing all three of your current forms. This way all of the data will be submitted whichever button you press and as far as your server is concerned the three forms will have been submitted simultaneously. If you have styling problems with this then you could keep the three form tags but point all of the inputs at only one of them with the html form attribute on each of the inputs. This approach does mean that all three forms will be reevaluated every time you submit one of them and a more efficient approach would be to use ajax but this would require the addition of javascript and changes to your code structure.