1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-22 17:06:17 +00:00

WIP: Add some style to web frontend

This commit is contained in:
Óscar García Amor 2017-07-09 21:45:59 +02:00
parent b42d0e7af2
commit 8a91e23043
14 changed files with 329 additions and 127 deletions

View File

@ -31,7 +31,7 @@ def login_check():
return
if request.path.startswith('/static/'):
return
return
if request.endpoint != 'login':
should_login = False

View File

@ -38,12 +38,12 @@ def check_admin():
@app.route('/folder')
def folder_index():
return render_template('folders.html', folders = store.find(Folder, Folder.root == True))
return render_template('folders.html', folders = store.find(Folder, Folder.root == True), admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/folder/add', methods = [ 'GET', 'POST' ])
def add_folder():
if request.method == 'GET':
return render_template('addfolder.html')
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
error = False
(name, path) = map(request.form.get, [ 'name', 'path' ])
@ -54,12 +54,12 @@ def add_folder():
flash('The path is required.')
error = True
if error:
return render_template('addfolder.html')
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
ret = FolderManager.add(store, name, path)
if ret != FolderManager.SUCCESS:
flash(FolderManager.error_str(ret))
return render_template('addfolder.html')
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
flash("Folder '%s' created. You should now run a scan" % name)

View File

@ -37,12 +37,12 @@ def check_admin():
@app.route('/user')
def user_index():
return render_template('users.html', users = store.find(User))
return render_template('users.html', users = store.find(User), admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/me')
def user_profile():
prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid')))
return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs)
return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/me', methods = [ 'POST' ])
def update_clients():
@ -74,7 +74,7 @@ def change_mail():
store.commit()
return redirect(url_for('user_profile'))
return render_template('change_mail.html', user = user)
return render_template('change_mail.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/changepass', methods = [ 'GET', 'POST' ])
def change_password():
@ -99,15 +99,15 @@ def change_password():
flash('Password changed')
return redirect(url_for('user_profile'))
return render_template('change_pass.html', user = UserManager.get(store, session.get('userid'))[1].name)
return render_template('change_pass.html', user = UserManager.get(store, session.get('userid'))[1].name, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/add', methods = [ 'GET', 'POST' ])
def add_user():
if request.method == 'GET':
return render_template('adduser.html')
return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
error = False
(name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'name', 'passwd', 'passwd_confirm', 'mail', 'admin' ])
(name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'user', 'passwd', 'passwd_confirm', 'mail', 'admin' ])
if name in (None, ''):
flash('The name is required.')
error = True
@ -154,12 +154,12 @@ def export_users():
@app.route('/user/import')
def import_users():
return render_template('importusers.html')
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/import', methods = [ 'POST' ])
def do_user_import():
if not request.files['file']:
return render_template('importusers.html')
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
users = []
reader = csv.reader(request.files['file'])

View File

@ -26,6 +26,14 @@ body {
margin-bottom: 0px;
}
#adduserform label {
margin-bottom: 0;
}
#clients td {
vertical-align: middle;
}
.page-header {
margin-top: 20px;
}

View File

@ -18,10 +18,30 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_folders %}
<li class="active"><a href="{{ url_for('folder_index') }}">Folders <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<div class="page-header">
<h2>Add Folder</h2>
</div>
<form method="post">
<label for="name">Name</label><input type="text" id="name" name="name" value="{{ request.form.name }}" /><br />
<label for="path">Path</label><input type="text" id="path" name="path" value="{{ request.form.path }}" /><br />
<input type="submit" />
<div class="form-group">
<label class="sr-only" for="user">Name</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-pencil"></i></span>
<input type="text" class="form-control" id="name" name="name" value="{{ request.form.name }}" placeholder="Name" />
</div>
</div>
<div class="form-group">
<label class="sr-only" for="user">Path</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-folder-open"></i></span>
<input type="text" class="form-control" id="path" name="path" value="{{ request.form.path }}" placeholder="Path" />
</div>
</div>
<input type="submit" class="btn btn-default" />
</form>
{% endblock %}

View File

@ -18,13 +18,48 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<form method="post">
<label for="name">Name</label><input type="text" id="name" name="name" value="{{ request.form.name }}" /><br />
<label for="passwd">Password</label><input type="password" id="passwd" name="passwd" /><br />
<label for="passwd_confirm">Confirm</label><input type="password" id="passwd_confirm" name="passwd_confirm" /><br />
<label for="mail">EMail</label><input type="text" id="mail" name="mail" value="{{ request.form.mail }}" /><br />
<label for="admin">Admin</label><input type="checkbox" id="admin" name="admin" {{ 'checked="checked"' if 'admin' in request.form }} /><br />
<input type="submit" />
<div class="page-header">
<h2>Add User</h2>
</div>
<form id="adduserform" method="post">
<div class="form-group">
<label class="sr-only" for="user">Username</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input type="text" class="form-control" id="user" name="user" value="{{ request.form.name }}" placeholder="Username" />
<span class="input-group-addon">
<label for="admin">Admin</label>
<input type="checkbox" id="admin" name="admin" {{ 'checked="checked"' if 'admin' in request.form }} />
</span>
</div>
</div>
<div class="form-group">
<label class="sr-only" for="passwd">Password</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input type="password" class="form-control" id="passwd" name="passwd" placeholder="Password" />
</div>
</div>
<div class="form-group">
<label class="sr-only" for="passwd_confirm">Confirm</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input type="password" class="form-control" id="passwd_confirm" name="passwd_confirm" placeholder="Confirm" />
</div>
</div>
<div class="form-group">
<label class="sr-only" for="mail">eMail</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i></span>
<input type="text" class="form-control" id="mail" name="mail" value="{{ request.form.mail }}" placeholder="eMail" />
</div>
</div>
<input type="submit" class="btn btn-default" />
</form>
{% endblock %}

View File

@ -18,11 +18,24 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_profile %}
<li class="active"><a href="{{ url_for('user_profile') }}">{{ session.username }} <span class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<h2>{{ user.name }}</h2>
<div class="page-header">
<h2>{{ user.name }}</h2>
</div>
<form method="post">
<label for="mail">Email</label><input type="text" name="mail" id="mail" value="{{ request.form.mail or user.mail }}" /><br />
<input type="submit" />
<div class="form-group">
<label class="sr-only" for="mail">eMail</label>
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i></span>
<input type="text" class="form-control" id="mail" name="mail" value="{{ request.form.mail or user.mail }}" placeholder="eMail" />
</div>
</div>
<input type="submit" class="btn btn-default" />
</form>
{% endblock %}

View File

@ -18,13 +18,38 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_profile %}
<li class="active"><a href="{{ url_for('user_profile') }}">{{ session.username }} <span class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<h2>{{ user }}</h2>
<div class="page-header">
<h2>{{ user }}</h2>
</div>
<form method="post">
<label for="current">Current password</label><input type="password" name="current" id="current" /><br />
<label for="new">New password</label><input type="password" name="new" id="new" /><br />
<label for="confirm">Confirm</label><input type="password" name="confirm" id="confirm" /><br />
<input type="submit" />
<div class="form-group">
<label class="sr-only" for="current">Current password</label>
<div class="input-group">
<div class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control" id="current" name="current" placeholder="Current password" />
</div>
</div>
<div class="form-group">
<label class="sr-only" for="new">New password</label>
<div class="input-group">
<div class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control" id="new" name="new" placeholder="New password" />
</div>
</div>
<div class="form-group">
<label class="sr-only" for="confirm">Confirm password</label>
<div class="input-group">
<div class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></div>
<input type="password" class="form-control" id="confirm" name="confirm" placeholder="Confirm password" />
</div>
</div>
<input type="submit" class="btn btn-default" />
</form>
{% endblock %}

View File

@ -18,16 +18,33 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block body %}
<h2>Music folders</h2>
<table>
<tr><th>Name</th><th>Path</th><th></th><th></th></tr>
{% for folder in folders %}
<tr>
<td>{{ folder.name }}</td><td>{{ folder.path }}</td><td><a href="{{ url_for('del_folder', id = folder.id) }}">X</a></td>
<td><a href="{{ url_for('scan_folder', id = folder.id) }}">Scan</a></td>
</tr>
{% endfor %}
</table>
<a href="{{ url_for('add_folder') }}">Add</a> <a href="{{ url_for('scan_folder') }}">Scan all</a>
{% block navbar_folders %}
<li class="active"><a href="{{ url_for('folder_index') }}">Folders <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<div class="page-header">
<h2>Music folders</h2>
</div>
<table class="table table-striped table-hover">
<thead>
<tr><th>Name</th><th>Path</th><th></th><th></th></tr>
</thead>
<tbody>
{% for folder in folders %}
<tr>
<td>{{ folder.name }}</td><td>{{ folder.path }}</td><td>
<a href="{{ url_for('del_folder', id = folder.id) }}" aria-label="Delete folder">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete folder"></a></td>
<td><a href="{{ url_for('scan_folder', id = folder.id) }}" aria-label="Scan folder">
<span class="glyphicon glyphicon-search" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Scan folder"></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="btn-toolbar" role="toolbar">
<a href="{{ url_for('add_folder') }}" class="btn btn-default">Add</a>
<a href="{{ url_for('scan_folder') }}" class="btn btn-default">Scan all</a>
</div>
{% endblock %}

View File

@ -18,30 +18,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar %}
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="supysonic-navbar-collapse">
{% if session.userid %}
<ul class="nav navbar-nav">
<li class="active"><a href="{{ url_for('index') }}">Home <span
class="sr-only">(current)</span></a></li>
<li><a href="{{ url_for('playlist_index') }}">Playlists</a></li>
{% if admin %}
<li><a href="{{ url_for('user_index') }}">Users</a></li>
<li><a href="{{ url_for('folder_index') }}">Folders</a></li>
{% endif %}
<li><a href="{{ url_for('user_profile') }}">{{ session.username }}</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('logout') }}">Log out</a></li>
</ul>
{% else %}
<ul class="nav navbar-nav">
<li class="active"><a href="{{ url_for('login') }}">Log in <span
class="sr-only">(current)</span></a></li>
</ul>
{% endif %}
</div><!-- /.navbar-collapse -->
{% block navbar_index %}
<li class="active"><a href="{{ url_for('index') }}">Home <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -18,11 +18,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<h2>Import users</h2>
<p><strong>Warning:</strong> this will overwrite any existing users, even you!</p>
<div class="page-header">
<h2>Import users</h2>
</div>
<div class="alert alert-warning" role="alert">
<strong>Warning!</strong>
this will overwrite any existing users, even you!
</div>
<form enctype="multipart/form-data" method="post">
<input type="file" name="file" />
<input type="submit" />
<div class="form-group">
<label for="file">Users file</label>
<input type="file" name="file" id="file" />
</div>
<input class="btn btn-default" type="submit" />
</form>
{% endblock %}

View File

@ -55,8 +55,35 @@
{% endif %}
<span class="navbar-brand">Supysonic</span>
</div>
{% block navbar %}{% endblock %}
</div><!-- /.container-fluid -->
{% if session.userid %}
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse"
id="supysonic-navbar-collapse">
<ul class="nav navbar-nav">
{% block navbar_index %}
<li><a href="{{ url_for('index') }}">Home</a></li>
{% endblock %}
{% block navbar_playlists %}
<li><a href="{{ url_for('playlist_index') }}">Playlists</a></li>
{% endblock %}
{% if admin %}
{% block navbar_users %}
<li><a href="{{ url_for('user_index') }}">Users</a></li>
{% endblock %}
{% block navbar_folders %}
<li><a href="{{ url_for('folder_index') }}">Folders</a></li>
{% endblock %}
{% endif %}
{% block navbar_profile %}
<li><a href="{{ url_for('user_profile') }}">{{ session.username }}</a></li>
{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('logout') }}">Log out</a></li>
</ul>
</div><!-- /.navbar-collapse -->
{% endif %}
</div><!-- /.container -->
</nav>
<div class="container">
@ -86,5 +113,11 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="/static/js/bootstrap.min.js"></script>
<!-- Activate tooltips -->
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
</body>
</html>

View File

@ -18,53 +18,96 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block navbar_profile %}
<li class="active"><a href="{{ url_for('user_profile') }}">{{ session.username }} <span class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<h2>{{ user.name }}</h2>
<ul>
{% if user.admin %}<li>You're an admin!</li>{% endif %}
<li><strong>Email</strong>: {{ user.mail }} - <a href="{{ url_for('change_mail') }}">Change</a></li>
<li>
<strong>LastFM status</strong>:
{% if api_key %}
{% if user.lastfm_session %}
{% if user.lastfm_status %}Linked{% else %}Invalid session{% endif %} - <a href="{{ url_for('lastfm_unreg') }}">Unlink</a>
{% else %}
Unlinked - <a href="http://www.last.fm/api/auth/?api_key={{ api_key }}&cb={{ request.url_root[:-(request.script_root|length+1)] + url_for('lastfm_reg') }}">Link</a>
{% endif %}
{% else %}
Unavailable
{% endif %}
</li>
<li><a href="{{ url_for('change_password') }}">Change password</a></li>
</ul>
<div class="page-header">
<h2>{{ user.name }}{% if user.admin %} <small><span class="glyphicon
glyphicon-certificate" data-toggle="tooltip" data-placement="right" title="You're an admin!"></span></small>{% endif %}</h2>
</div>
<div class="row">
<div class="col-md-6">
<form>
<div class="form-group">
<label class="sr-only" for="email">User eMail</label>
<div class="input-group">
<div class="input-group-addon">User eMail</div>
<input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly>
<div class="input-group-btn">
<a href="{{ url_for('change_mail') }}" class="btn btn-default">Change eMail</a>
</div>
</div>
</div>
</form>
</div>
<div class="col-md-6">
<form>
<div class="form-group">
<label class="sr-only" for="lastfm">LastFM status</label>
<div class="input-group">
<div class="input-group-addon">LastFM status</div>
{% if api_key %}
{% if user.lastfm_session %}
<input type="text" class="form-control" id="lastfm" placeholder="{% if user.lastfm_status %}Linked{% else %}Invalid session{% endif %}" readonly>
<div class="input-group-btn">
<a href="{{ url_for('lastfm_unreg') }}" class="btn btn-default">Unlink</a>
</div>
{% else %}
<input type="text" class="form-control" id="lastfm" placeholder="Unlinked" readonly>
<div class="input-group-btn">
<a href="http://www.last.fm/api/auth/?api_key={{ api_key }}&cb={{ request.url_root[:-(request.script_root|length+1)] + url_for('lastfm_reg') }}" class="btn btn-default">Link</a>
</div>
{% endif %}
{% else %}
<input type="text" class="form-control" id="lastfm" placeholder="Unavailable" readonly>
{% endif %}
</div>
</div>
</form>
</div>
</div>
<a href="{{ url_for('change_password') }}" class="btn btn-default">Change password</a></li>
{% if clients.count() %}
<h2>Clients</h2>
<p>Here's a list of clients you used to stream music. If you want to use transcoding or downsampling with one of them (for instance using a low bitrate on
mobile connections to reduce used bandwidth), but the client doesn't provide options to do so, you can set default values here. They'll only be used if no
transcoding/downsampling is requested by the client.</p>
<form method="post">
<table>
<tr><th>Client</th><th>Format</th><th>Max bitrate</th><th>Forget</th></tr>
{% for client in clients %}
<tr>
<td>{{ client.client_name }}</td>
<td><input type="text" name="{{ client.client_name }}_format" value="{{ client.format if client.format else '' }}" /></td>
<td><select name="{{ client.client_name }}_bitrate">
<option />
<option {{ 'selected="selected"' if client.bitrate == 64 else '' }}>64</option>
<option {{ 'selected="selected"' if client.bitrate == 96 else '' }}>96</option>
<option {{ 'selected="selected"' if client.bitrate == 128 else '' }}>128</option>
<option {{ 'selected="selected"' if client.bitrate == 192 else '' }}>192</option>
<option {{ 'selected="selected"' if client.bitrate == 256 else '' }}>256</option>
<option {{ 'selected="selected"' if client.bitrate == 320 else '' }}>320</option>
</select></td>
<td><input type="checkbox" name="{{ client.client_name }}_delete" /></td>
</tr>
{% endfor %}
</table>
<input type="submit" value="Save" />
</form>
<div class="page-header">
<h2>Clients</h2>
</div>
<p>Here's a list of clients you used to stream music. If you want to use
transcoding or downsampling with one of them (for instance using a low
bitrate on mobile connections to reduce used bandwidth), but the client
doesn't provide options to do so, you can set default values here. They'll
only be used if no transcoding/downsampling is requested by the client.</p>
<form method="post">
<table id="clients" class="table table-striped table-hover">
<thead>
<tr><th>Client</th><th>Format</th><th>Max bitrate</th><th>Forget</th></tr>
</thead>
<tbody>
{% for client in clients %}
<tr>
<td><label>{{ client.client_name }}</label></td>
<td><input type="text" class="form-control" name="{{ client.client_name }}_format" value="{{ client.format if client.format else '' }}" /></td>
<td><select class="form-control" name="{{ client.client_name }}_bitrate">
<option />
<option {{ 'selected="selected"' if client.bitrate == 64 else '' }}>64</option>
<option {{ 'selected="selected"' if client.bitrate == 96 else '' }}>96</option>
<option {{ 'selected="selected"' if client.bitrate == 128 else '' }}>128</option>
<option {{ 'selected="selected"' if client.bitrate == 192 else '' }}>192</option>
<option {{ 'selected="selected"' if client.bitrate == 256 else '' }}>256</option>
<option {{ 'selected="selected"' if client.bitrate == 320 else '' }}>320</option>
</select></td>
<td><input type="checkbox" name="{{ client.client_name }}_delete" /></td>
</tr>
{% endfor %}
<tbody>
</table>
<input class="btn btn-default" type="submit" value="Save" />
</form>
{% endif %}
{% endblock %}

View File

@ -18,14 +18,31 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-#}
{% extends "layout.html" %}
{% block body %}
<h2>Users</h2>
<table>
<tr><th>Name</th><th>EMail</th><th>Admin</th><th>Last play date</th><th></th></tr>
{% for user in users %}
<tr><td>{{ user.name }}</td><td>{{ user.mail }}</td><td>{{ user.admin }}</td><td>{{ user.last_play_date }}</td><td><a href="{{ url_for('del_user', uid = user.id) }}">X</a></td></tr>
{% endfor %}
</table>
<a href="{{ url_for('add_user') }}">Add</a> - <a href="{{ url_for('export_users') }}">Export</a> - <a href="{{ url_for('import_users') }}">Import</a>
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
<div class="page-header">
<h2>Users</h2>
</div>
<table class="table table-striped table-hover">
<thead>
<tr><th>Name</th><th>EMail</th><th>Admin</th><th>Last play date</th><th></th></tr>
</thead>
<tbody>
{% for user in users %}
<tr><td>{{ user.name }}</td><td>{{ user.mail }}</td><td>{{ user.admin }}</td><td>{{ user.last_play_date }}</td><td>
<a href="{{ url_for('del_user', uid = user.id) }}" aria-label="Delete user">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete user"></a></td></tr>
{% endfor %}
</tbody>
</table>
<div class="btn-toolbar" role="toolbar">
<a href="{{ url_for('add_user') }}" class="btn btn-default">Add</a>
<a href="{{ url_for('export_users') }}" class="btn btn-default">Export</a>
<a href="{{ url_for('import_users') }}" class="btn btn-default">Import</a>
</div>
{% endblock %}