Multiple forms in Flask with different behaviors

225 Views Asked by At

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')
1

There are 1 best solutions below

1
EAW On

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.