Search code examples
pythonjqueryajaxpython-3.xbottle

Including a bottle template in an AJAX request


Here is my scenario. I am trying to make a local web app to manipulate my movie files, both as a time saving tool and a python learning exercise.

Here is my server script:

from bottle import run, request, post, get, template, error, static_file
from pathlib import Path
import re


# Show a list of files ready to be moved
@get('/move_new_movies')
def move_new_movies():
    return template('move_new_movies')


#  Generate a list of files for move_new_movies page
@post('/generate_movie_list')
def generate_movie_list():
    ... do some stuff ...
    return movie_list


if __name__ == '__main__':
    run(debug=True, reloader=True)

generate_movie_list() returns a JSON array in the form of

{
    'total_results': ,
    'results': [{'folder': ,
                 'file_name':
                 }]
}

move_new_movies.tpl:

<!DOCTYPE html>
<html>
<head>
    <title>Move New Movies</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $('form').submit(function(e) {
                $.ajax({
                    type: 'POST',
                    url: '/generate_movie_list', 
                    data: $(this).serialize(), 
                    success: function(response) {
-----error------->      <%  movie_list = response['results']
                            movie_list_html = include('file_list', list='movie_list')
                            %>
                        $('#movie_list').html(movie_list_html);
                    }
                });
                e.preventDefault();
            });
        });
    </script>
</head>
<body>
    <h3>Enter path to new movies</h3>
    <form action="/move_new_movies" method="POST">
        <input type="text" name="movie_path">
        <input type="submit" name="Submit">
    </form>
    <div id='movie_list'></div>
</body>
</html>

And finally, file_list.tpl

<table>
    <tr>
        <th>Folder</th>
        <th>File Name</th>
    </tr>
    % for movie in list:
    <tr>
        <td>{{movie['folder']}}</td>
        <td>{{movie['file_name']}}</td>
    </tr>
    % end
</table>

Now here's my problem. When I browse to .../move_new_movies, I get an error

Traceback (most recent call last):
  ... ... ...
  File "\path\to\move_new_movies.tpl", line 15, in <module>
    <%  movie_list = response['results']
NameError: name 'response' is not defined

Originally, generate_movie_list() would return template('file_list'). There was no python in ajax.success(), and it would just update the page with whatever HTML it received. This worked just as expected; I entered a path, hit submit and the desired table would appear. However, I thought it would be better practice for the server to serve only the JSON data and have the client inset it into the appropriate template file.

I thought this because what if the list is not movies, but tv shows or music? I might want to use a different template and don't want to make a new rout for different media.

I feel like I'm getting this error because the template is being rendered before ajax.success() can define the results variable.

Any idea on how to make this work?


Solution

  • The syntax <% %> does not actually work in bottle. Always use % and the %end to encapsulate code. Make sure the % is the first non whitespace character of the line.

    I have run into this myself and have never gotten the above syntax to work either. It has not been a problem since.

    UPDATE with EXAMPLE:

    from bottle import run, request, post, get, template, error, static_file
    from pathlib import Path
    import re
    
    
    # Show a list of files ready to be moved
    @get('/move_new_movies')
    def move_new_movies():
        movies = generate_movie_list()
        return template('move_new_movies', movies = movies)
    
    
    #  Generate a list of files for move_new_movies page
    def generate_movie_list():
        ... do some stuff ...
        return movie_list
    
    
    if __name__ == '__main__':
        run(debug=True, reloader=True)
    

    And your template:

    </head>
    <body>
        <h3>Enter path to new movies</h3>
        <input type="text" name="movie_path">
    <table>
        <tr>
            <th>Folder</th>
            <th>File Name</th>
        </tr>
        % for movie in movies:
        <tr>
            <td>{{movie['folder']}}</td>
            <td>{{movie['file_name']}}</td>
        </tr>
        % end
    </table>
    </body>
    </html>
    

    Now you would need your javascript to just filter the table once they click enter on the input field. However you might find it easier to just use datatables.net and they can filter on whatever they want.