Search code examples
pythonflaskhttp-redirecthttp-status-code-405

405 Method Not Allowed When Redirecting in Flask within POST route


I'm hoping to shed some light on why I am getting a 405 Method Not Allowed error.

Expected Outcome - User should be redirected to manage.html template when they attempt to add a review to a game they have already added a review to.

Actual Outcome - 405 Error (Method Not Allowed) Displayed.

Manage Route:

@app.route('/manage')
def manage():
    """Renders manage.html template."""
    return render_template('manage.html')

Submit Review Route:

@app.route('/submit_review/<game_id>', methods=['POST'])
def submit_review(game_id):
    """
    Add users review to database.
    """
    user = User.query.filter_by(username=session['username']).first()
    existing_review = Review.query.filter_by(user_id=user.id,
                                             game_id=game.id).first()

    if existing_review:
        flash('You have already created a review for this game')
        return redirect(url_for('manage'))
...

What I Have Tried: I've done some reading of the flask documentation, specifically around flask.redirect (docs), and searched for other examples, but I have been unable to find something that resolves my issue.

I have a hunch that when the user submits the form, and they have already have a review for that particular game, the POST request is also being redirected to the "manage" route.

I checked the network tab in dev tools and it is a GET request, which is correct for that URL.

I guess what I'm trying to say is... I have no idea why this is happening, so don't know how to search for a resolution.

Screenshot of Network -> Headers tab in dev tools: Screenshot of Network -> Headers tab in dev tools

Server Console:

[10/May/2022 18:43:49] "POST /submit_review/11198 HTTP/1.1" 302 -

[10/May/2022 18:43:49] "review-rating=0&review-heading=&liked-text=&disliked-text=&review-hours=1&game-name=Rocket+League&igdb-id=&igdb-summary=&igdb-cover-url=&action=GET /manage HTTP/1.1" 405 -


Solution

  • Firstly, Thank you @Henry for the inspiration to finding a solution.

    Secondly, I have learnt that including the entire function in the original question, and not just what I feel may be relevant, may have resolved this much sooner.

    The Answer.

    @Henry mentioned that as per the docs for url_for() - "Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments."

    This is my take on the situation.

    The submit_review() function was returning before the form data was being used, meaning that the form data was unknown to the target endpoint.

    Function Before Fix:

    @app.route('/submit_review/<game_id>', methods=['POST'])
    def submit_review(game_id):
        """
        Adds users review to database.
        """
    
        existing_game = Game.query.filter_by(igdb_id=game_id).first()
    
        if not existing_game:
            igdb_game_data = get_game_data_by_id(game_id)[0]
            igdb_game_artwork = get_game_artwork(game_id)
            igdb_game_cover = get_game_cover_art(game_id)
    
            game = Game(
                name=igdb_game_data['name'],
                artwork=json.dumps(igdb_game_artwork),
                summary=igdb_game_data['summary'],
                igdb_id=igdb_game_data['id'],
                cover_art=igdb_game_cover
            )
    
            db.session.add(game)
            db.session.commit()
    
        user = User.query.filter_by(username=session['username']).first()
        game = Game.query.filter_by(igdb_id=game_id).first()
    
        existing_review = Review.query.filter_by(user_id=user.id,
                                                 game_id=game.id).first()
    
        if existing_review:
            print(request)
            flash('You have already created a review for this game')
            return redirect(url_for('manage'))
    
        review = Review(
            user_id=user.id,
            game_id=game.id,
            rating=float(request.form.get('review-rating')),
            heading=request.form.get('review-heading'),
            liked_text=request.form.get('liked-text'),
            disliked_text=request.form.get('disliked-text'),
            hours=int(request.form.get('review-hours')),
        )
    
        db.session.add(review)
        db.session.commit()
    
        flash('Review added successfully')
        return redirect(url_for('home'))
    

    By moving where the form data is used, I got the expected results, functionality is correct, as the review is not added to the database if a review for that game, by the same user is present.

    Function After Fix:

    @app.route('/submit_review/<game_id>', methods=['POST'])
    def submit_review(game_id):
        """
        Adds users review to database.
        """
    
        existing_game = Game.query.filter_by(igdb_id=game_id).first()
    
        if not existing_game:
            igdb_game_data = get_game_data_by_id(game_id)[0]
            igdb_game_artwork = get_game_artwork(game_id)
            igdb_game_cover = get_game_cover_art(game_id)
    
            game = Game(
                name=igdb_game_data['name'],
                artwork=json.dumps(igdb_game_artwork),
                summary=igdb_game_data['summary'],
                igdb_id=igdb_game_data['id'],
                cover_art=igdb_game_cover
            )
    
            db.session.add(game)
            db.session.commit()
    
        user = User.query.filter_by(username=session['username']).first()
        game = Game.query.filter_by(igdb_id=game_id).first()
    
        existing_review = Review.query.filter_by(user_id=user.id,
                                                 game_id=game.id).first()
    
        review = Review(
            user_id=user.id,
            game_id=game.id,
            rating=float(request.form.get('review-rating')),
            heading=request.form.get('review-heading'),
            liked_text=request.form.get('liked-text'),
            disliked_text=request.form.get('disliked-text'),
            hours=int(request.form.get('review-hours')),
        )
    
        if existing_review:
            print(request)
            flash('You have already created a review for this game')
            return redirect(url_for('manage'))
    
        db.session.add(review)
        db.session.commit()
    
        flash('Review added successfully')
        return redirect(url_for('home'))