Search code examples
pythonflasklithiumsingle-responsibility-principle

Flask view methods and SRP


I've seen a pattern in Flask (and Lithium) where a single view method corresponds to multiple HTTP verbs; example:

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password' 
    else:
        session['logged_in'] = True
        flash('You were logged in')
        return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)

I think this leads to bloated view methods that do more than one thing.

I was going to ask why this was the default pattern in Flask, but now I see that method-based views exist.

Still, why have non-method based views in the framework at all? Because not all client-server protocols talk through REST, and method-based views are inherently REST-flavored?


Solution

  • The simple answer is it's easier, and often cleaner. The majority of views are GET methods, and so Flask makes that very simple, with decorated views such as:

    @app.route('/')
    def home():
        thing = do_fun_stuff()
        return render_template('home.html', foo=thing)
    

    If I wanted to represent using a MethodView that you find less bloated, I'd end up with:

    class HomeView(MethodView):
    
        def get(self):
            thing = do_fun_stuff()
            return render_template('home', foo=thing)
    
    app.add_url_rule('/', view_func=HomeView.as_view('home'))
    

    In reality, your login() code could be cleared up, and made less bloated by using better form handling and validation using Flask-WTF and Flask-Login, which would lead you to a login process such as:

    app.route('/login', methods=['GET', 'POST'])
    def login():
        form = LoginForm()
        if form.validate_on_submit():
            login_user(form.user)
            return redirect(url_for('show_entries'))
        return render_template('login.html', form=form)
    

    Which is a long-way around of saying, it's nice to have options. Flask doesn't force a particular narrative on your development, and what is bloated on one project might be concise on another that solves a different goal.