1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-19 19:01:03 +00:00

Frontend as blueprint

Ref #76
This commit is contained in:
spl0k 2018-01-29 21:37:19 +01:00
parent 0de87e64b0
commit 7455711b60
18 changed files with 131 additions and 159 deletions

View File

@ -4,26 +4,23 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
# 2017 Óscar García Amor
#
# Distributed under terms of the GNU AGPLv3 license.
from flask import session, request, redirect, url_for, current_app as app
from flask import redirect, request, session, url_for
from flask import Blueprint
from functools import wraps
from pony.orm import db_session
from ..db import Artist, Album, Track
from ..managers.user import UserManager
@app.before_request
frontend = Blueprint('frontend', __name__)
@frontend.before_request
def login_check():
if request.path.startswith('/rest/'):
return
if request.path.startswith('/static/'):
return
request.user = None
should_login = True
if session.get('userid'):
@ -34,11 +31,11 @@ def login_check():
request.user = user
should_login = False
if should_login and request.endpoint != 'login':
if should_login and request.endpoint != 'frontend.login':
flash('Please login')
return redirect(url_for('login', returnUrl = request.script_root + request.url[len(request.url_root)-1:]))
return redirect(url_for('frontend.login', returnUrl = request.script_root + request.url[len(request.url_root)-1:]))
@app.route('/')
@frontend.route('/')
@db_session
def index():
stats = {
@ -52,10 +49,11 @@ def admin_only(f):
@wraps(f)
def decorated_func(*args, **kwargs):
if not request.user or not request.user.admin:
return redirect(url_for('index'))
return redirect(url_for('frontend.index'))
return f(*args, **kwargs)
return decorated_func
from .user import *
from .folder import *
from .playlist import *

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic.
#
# Supysonic is a Python implementation of the Subsonic server API.
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -21,7 +21,7 @@
import os.path
import uuid
from flask import request, flash, render_template, redirect, url_for, current_app as app
from flask import current_app, flash, redirect, render_template, request, url_for
from pony.orm import db_session
from ..db import Folder
@ -29,20 +29,20 @@ from ..managers.user import UserManager
from ..managers.folder import FolderManager
from ..scanner import Scanner
from . import admin_only
from . import admin_only, frontend
@app.route('/folder')
@frontend.route('/folder')
@admin_only
@db_session
def folder_index():
return render_template('folders.html', folders = Folder.select(lambda f: f.root))
@app.route('/folder/add')
@frontend.route('/folder/add')
@admin_only
def add_folder_form():
return render_template('addfolder.html')
@app.route('/folder/add', methods = [ 'POST' ])
@frontend.route('/folder/add', methods = [ 'POST' ])
@admin_only
def add_folder_post():
error = False
@ -63,16 +63,16 @@ def add_folder_post():
flash("Folder '%s' created. You should now run a scan" % name)
return redirect(url_for('folder_index'))
return redirect(url_for('frontend.folder_index'))
@app.route('/folder/del/<id>')
@frontend.route('/folder/del/<id>')
@admin_only
def del_folder(id):
try:
idid = uuid.UUID(id)
except ValueError:
flash('Invalid folder id')
return redirect(url_for('folder_index'))
return redirect(url_for('frontend.folder_index'))
ret = FolderManager.delete(idid)
if ret != FolderManager.SUCCESS:
@ -80,14 +80,14 @@ def del_folder(id):
else:
flash('Deleted folder')
return redirect(url_for('folder_index'))
return redirect(url_for('frontend.folder_index'))
@app.route('/folder/scan')
@app.route('/folder/scan/<id>')
@frontend.route('/folder/scan')
@frontend.route('/folder/scan/<id>')
@admin_only
@db_session
def scan_folder(id = None):
extensions = app.config['BASE']['scanner_extensions']
extensions = current_app.config['BASE']['scanner_extensions']
if extensions:
extensions = extensions.split(' ')
@ -100,7 +100,7 @@ def scan_folder(id = None):
status, folder = FolderManager.get(id)
if status != FolderManager.SUCCESS:
flash(FolderManager.error_str(status))
return redirect(url_for('folder_index'))
return redirect(url_for('frontend.folder_index'))
scanner.scan(folder)
scanner.finish()
@ -112,5 +112,5 @@ def scan_folder(id = None):
flash('Errors in:')
for err in stats.errors:
flash('- ' + err)
return redirect(url_for('folder_index'))
return redirect(url_for('frontend.folder_index'))

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic.
#
# Supysonic is a Python implementation of the Subsonic server API.
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
# Copyright (C) 2013-2018 Alban 'spl0k' Féron
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -20,51 +20,53 @@
import uuid
from flask import request, flash, render_template, redirect, url_for, current_app as app
from flask import flash, redirect, render_template, request, url_for
from pony.orm import db_session
from pony.orm import ObjectNotFound
from ..db import Playlist
from ..managers.user import UserManager
@app.route('/playlist')
from . import frontend
@frontend.route('/playlist')
@db_session
def playlist_index():
return render_template('playlists.html',
mine = Playlist.select(lambda p: p.user == request.user),
others = Playlist.select(lambda p: p.user != request.user and p.public))
@app.route('/playlist/<uid>')
@frontend.route('/playlist/<uid>')
@db_session
def playlist_details(uid):
try:
uid = uuid.UUID(uid)
except ValueError:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
try:
playlist = Playlist[uid]
except ObjectNotFound:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
return render_template('playlist.html', playlist = playlist)
@app.route('/playlist/<uid>', methods = [ 'POST' ])
@frontend.route('/playlist/<uid>', methods = [ 'POST' ])
@db_session
def playlist_update(uid):
try:
uid = uuid.UUID(uid)
except ValueError:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
try:
playlist = Playlist[uid]
except ObjectNotFound:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
if playlist.user.id != request.user.id:
flash("You're not allowed to edit this playlist")
@ -77,20 +79,20 @@ def playlist_update(uid):
return playlist_details(str(uid))
@app.route('/playlist/del/<uid>')
@frontend.route('/playlist/del/<uid>')
@db_session
def playlist_delete(uid):
try:
uid = uuid.UUID(uid)
except ValueError:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
try:
playlist = Playlist[uid]
except ObjectNotFound:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))
if playlist.user.id != request.user.id:
flash("You're not allowed to delete this playlist")
@ -98,5 +100,5 @@ def playlist_delete(uid):
playlist.delete()
flash('Playlist deleted')
return redirect(url_for('playlist_index'))
return redirect(url_for('frontend.playlist_index'))

View File

@ -18,7 +18,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask import request, session, flash, render_template, redirect, url_for, current_app as app
from flask import flash, redirect, render_template, request, session, url_for
from flask import current_app
from functools import wraps
from pony.orm import db_session
@ -27,7 +28,7 @@ from ..lastfm import LastFm
from ..managers.user import UserManager
from ..py23 import dict
from . import admin_only
from . import admin_only, frontend
def me_or_uuid(f, arg = 'uid'):
@db_session
@ -41,12 +42,12 @@ def me_or_uuid(f, arg = 'uid'):
if uid == 'me':
user = User[request.user.id] # Refetch user from previous transaction
elif not request.user.admin:
return redirect(url_for('index'))
return redirect(url_for('frontend.index'))
else:
code, user = UserManager.get(uid)
if code != UserManager.SUCCESS:
flash(UserManager.error_str(code))
return redirect(url_for('index'))
return redirect(url_for('frontend.index'))
if kwargs:
kwargs['user'] = user
@ -57,18 +58,18 @@ def me_or_uuid(f, arg = 'uid'):
return decorated_func
@app.route('/user')
@frontend.route('/user')
@admin_only
@db_session
def user_index():
return render_template('users.html', users = User.select())
@app.route('/user/<uid>')
@frontend.route('/user/<uid>')
@me_or_uuid
def user_profile(uid, user):
return render_template('profile.html', user = user, has_lastfm = app.config['LASTFM']['api_key'] != None, clients = user.clients)
return render_template('profile.html', user = user, has_lastfm = current_app.config['LASTFM']['api_key'] != None, clients = user.clients)
@app.route('/user/<uid>', methods = [ 'POST' ])
@frontend.route('/user/<uid>', methods = [ 'POST' ])
@me_or_uuid
def update_clients(uid, user):
clients_opts = dict()
@ -86,7 +87,7 @@ def update_clients(uid, user):
clients_opts[client] = dict([ (opt, value) ])
else:
clients_opts[client][opt] = value
app.logger.debug(clients_opts)
current_app.logger.debug(clients_opts)
for client, opts in clients_opts.items():
prefs = user.clients.select(lambda c: c.client_name == client).first()
@ -103,23 +104,23 @@ def update_clients(uid, user):
flash('Clients preferences updated.')
return user_profile(uid, user)
@app.route('/user/<uid>/changeusername')
@frontend.route('/user/<uid>/changeusername')
@admin_only
def change_username_form(uid):
code, user = UserManager.get(uid)
if code != UserManager.SUCCESS:
flash(UserManager.error_str(code))
return redirect(url_for('index'))
return redirect(url_for('frontend.index'))
return render_template('change_username.html', user = user)
@app.route('/user/<uid>/changeusername', methods = [ 'POST' ])
@frontend.route('/user/<uid>/changeusername', methods = [ 'POST' ])
@admin_only
@db_session
def change_username_post(uid):
code, user = UserManager.get(uid)
if code != UserManager.SUCCESS:
return redirect(url_for('index'))
return redirect(url_for('frontend.index'))
username = request.form.get('user')
if username in ('', None):
@ -141,27 +142,27 @@ def change_username_post(uid):
else:
flash("No changes for '%s'." % username)
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
@app.route('/user/<uid>/changemail')
@frontend.route('/user/<uid>/changemail')
@me_or_uuid
def change_mail_form(uid, user):
return render_template('change_mail.html', user = user)
@app.route('/user/<uid>/changemail', methods = [ 'POST' ])
@frontend.route('/user/<uid>/changemail', methods = [ 'POST' ])
@me_or_uuid
def change_mail_post(uid, user):
mail = request.form.get('mail', '')
# No validation, lol.
user.mail = mail
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
@app.route('/user/<uid>/changepass')
@frontend.route('/user/<uid>/changepass')
@me_or_uuid
def change_password_form(uid, user):
return render_template('change_pass.html', user = user)
@app.route('/user/<uid>/changepass', methods = [ 'POST' ])
@frontend.route('/user/<uid>/changepass', methods = [ 'POST' ])
@me_or_uuid
def change_password_post(uid, user):
error = False
@ -190,16 +191,16 @@ def change_password_post(uid, user):
flash(UserManager.error_str(status))
else:
flash('Password changed')
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
return change_password_form(uid, user)
@app.route('/user/add')
@frontend.route('/user/add')
@admin_only
def add_user_form():
return render_template('adduser.html')
@app.route('/user/add', methods = [ 'POST' ])
@frontend.route('/user/add', methods = [ 'POST' ])
@admin_only
def add_user_post():
error = False
@ -222,13 +223,13 @@ def add_user_post():
status = UserManager.add(name, passwd, mail, admin)
if status == UserManager.SUCCESS:
flash("User '%s' successfully added" % name)
return redirect(url_for('user_index'))
return redirect(url_for('frontend.user_index'))
else:
flash(UserManager.error_str(status))
return add_user_form()
@app.route('/user/del/<uid>')
@frontend.route('/user/del/<uid>')
@admin_only
def del_user(uid):
status = UserManager.delete(uid)
@ -237,33 +238,33 @@ def del_user(uid):
else:
flash(UserManager.error_str(status))
return redirect(url_for('user_index'))
return redirect(url_for('frontend.user_index'))
@app.route('/user/<uid>/lastfm/link')
@frontend.route('/user/<uid>/lastfm/link')
@me_or_uuid
def lastfm_reg(uid, user):
token = request.args.get('token')
if token in ('', None):
flash('Missing LastFM auth token')
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
lfm = LastFm(app.config['LASTFM'], user, app.logger)
lfm = LastFm(current_app.config['LASTFM'], user, current_app.logger)
status, error = lfm.link_account(token)
flash(error if not status else 'Successfully linked LastFM account')
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
@app.route('/user/<uid>/lastfm/unlink')
@frontend.route('/user/<uid>/lastfm/unlink')
@me_or_uuid
def lastfm_unreg(uid, user):
lfm = LastFm(app.config['LASTFM'], user, app.logger)
lfm = LastFm(current_app.config['LASTFM'], user, current_app.logger)
lfm.unlink_account()
flash('Unlinked LastFM account')
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('frontend.user_profile', uid = uid))
@app.route('/user/login', methods = [ 'GET', 'POST'])
@frontend.route('/user/login', methods = [ 'GET', 'POST'])
def login():
return_url = request.args.get('returnUrl') or url_for('index')
return_url = request.args.get('returnUrl') or url_for('frontend.index')
if request.user:
flash('Already logged in')
return redirect(return_url)
@ -291,9 +292,9 @@ def login():
return render_template('login.html')
@app.route('/user/logout')
@frontend.route('/user/logout')
def logout():
session.clear()
flash('Logged out!')
return redirect(url_for('login'))
return redirect(url_for('frontend.login'))

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_folders %}
<li class="active"><a href="{{ url_for('folder_index') }}">Folders <span
<li class="active"><a href="{{ url_for('frontend.folder_index') }}">Folders <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -2,7 +2,7 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %}
{% block navbar_users %}
{% if request.user.id != user.id %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
@ -18,7 +18,7 @@
{% endblock %}
{% block navbar_profile %}
{% if request.user.id == user.id %}
<li class="active"><a href="{{ url_for('user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
<li class="active"><a href="{{ url_for('frontend.user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
{% endif %}

View File

@ -2,7 +2,7 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %}
{% block navbar_users %}
{% if request.user.id != user.id %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
@ -18,7 +18,7 @@
{% endblock %}
{% block navbar_profile %}
{% if request.user.id == user.id %}
<li class="active"><a href="{{ url_for('user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
<li class="active"><a href="{{ url_for('frontend.user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
{% endif %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_folders %}
<li class="active"><a href="{{ url_for('folder_index') }}">Folders <span
<li class="active"><a href="{{ url_for('frontend.folder_index') }}">Folders <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
@ -24,17 +24,17 @@
{% for folder in folders %}
<tr>
<td>{{ folder.name }}</td><td>{{ folder.path }}</td><td>
<button class="btn btn-danger btn-xs" data-href="{{ url_for('del_folder', id = folder.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete folder">
<button class="btn btn-danger btn-xs" data-href="{{ url_for('frontend.del_folder', id = folder.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete folder">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete folder"></span></button></td>
<td><a class="btn btn-default btn-xs" href="{{ url_for('scan_folder', id = folder.id) }}" aria-label="Scan folder">
<td><a class="btn btn-default btn-xs" href="{{ url_for('frontend.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"></span></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="btn-toolbar" role="toolbar">
<a href="{{ url_for('add_folder_form') }}" class="btn btn-default">Add</a>
<a href="{{ url_for('scan_folder') }}" class="btn btn-default">Scan all</a>
<a href="{{ url_for('frontend.add_folder_form') }}" class="btn btn-default">Add</a>
<a href="{{ url_for('frontend.scan_folder') }}" class="btn btn-default">Scan all</a>
</div>
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_index %}
<li class="active"><a href="{{ url_for('index') }}">Home <span
<li class="active"><a href="{{ url_for('frontend.index') }}">Home <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -1,30 +0,0 @@
{#-
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% 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 %}
<div class="page-header first-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">
<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

@ -2,7 +2,7 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
@ -51,25 +51,25 @@
id="supysonic-navbar-collapse">
<ul class="nav navbar-nav">
{% block navbar_index %}
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('frontend.index') }}">Home</a></li>
{% endblock %}
{% block navbar_playlists %}
<li><a href="{{ url_for('playlist_index') }}">Playlists</a></li>
<li><a href="{{ url_for('frontend.playlist_index') }}">Playlists</a></li>
{% endblock %}
{% if request.user.admin %}
{% block navbar_users %}
<li><a href="{{ url_for('user_index') }}">Users</a></li>
<li><a href="{{ url_for('frontend.user_index') }}">Users</a></li>
{% endblock %}
{% block navbar_folders %}
<li><a href="{{ url_for('folder_index') }}">Folders</a></li>
<li><a href="{{ url_for('frontend.folder_index') }}">Folders</a></li>
{% endblock %}
{% endif %}
{% block navbar_profile %}
<li><a href="{{ url_for('user_profile', uid = 'me')}}">{{ request.user.name }}</a></li>
<li><a href="{{ url_for('frontend.user_profile', uid = 'me')}}">{{ request.user.name }}</a></li>
{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('logout') }}">Log out</a></li>
<li><a href="{{ url_for('frontend.logout') }}">Log out</a></li>
</ul>
</div><!-- /.navbar-collapse -->
{% endif %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_playlists %}
<li class="active"><a href="{{ url_for('playlist_index') }}">Playlists <span
<li class="active"><a href="{{ url_for('frontend.playlist_index') }}">Playlists <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_playlists %}
<li class="active"><a href="{{ url_for('playlist_index') }}">Playlists <span
<li class="active"><a href="{{ url_for('frontend.playlist_index') }}">Playlists <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
@ -26,13 +26,13 @@
<tbody>
{% for p in mine %}
<tr>
<td><a href="{{ url_for('playlist_details', uid = p.id) }}">{{ p.name }}</a></td>
<td><a href="{{ url_for('frontend.playlist_details', uid = p.id) }}">{{ p.name }}</a></td>
<td>{{ p.get_tracks()|length }}</td>
<td>{% if p.public %}<span class="glyphicon glyphicon-check"
aria-label="Public playlist"></span>{% else %}<span
class="glyphicon glyphicon-unchecked"
aria-label="Private playlist"></span>{% endif %}</td>
<td><button class="btn btn-danger btn-xs" data-href="{{ url_for('playlist_delete', uid = p.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete playlist">
<td><button class="btn btn-danger btn-xs" data-href="{{ url_for('frontend.playlist_delete', uid = p.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete playlist">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete playlist"></span></button></td>
</tr>
{% endfor %}
@ -50,7 +50,7 @@
<tbody>
{% for p in others %}
<tr>
<td><a href="{{ url_for('playlist_details', uid = p.id) }}">{{ p.name }}</a></td>
<td><a href="{{ url_for('frontend.playlist_details', uid = p.id) }}">{{ p.name }}</a></td>
<td>{{ p.user.name }}</td>
<td>{{ p.get_tracks()|length }}</td>
</tr>

View File

@ -2,7 +2,7 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %}
{% block navbar_users %}
{% if request.user.id != user.id %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
@ -18,7 +18,7 @@
{% endblock %}
{% block navbar_profile %}
{% if request.user.id == user.id %}
<li class="active"><a href="{{ url_for('user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
<li class="active"><a href="{{ url_for('frontend.user_profile', uid = 'me') }}">{{ request.user.name }} <span class="sr-only">(current)</span></a></li>
{% else %}
{{ super() }}
{% endif %}
@ -39,9 +39,9 @@
<input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly>
<div class="input-group-btn">
{% if request.user.id == user.id %}
<a href="{{ url_for('change_mail_form', uid = 'me') }}" class="btn btn-default">Change eMail</a>
<a href="{{ url_for('frontend.change_mail_form', uid = 'me') }}" class="btn btn-default">Change eMail</a>
{% else %}
<a href="{{ url_for('change_mail_form', uid = user.id) }}" class="btn btn-default">Change eMail</a>
<a href="{{ url_for('frontend.change_mail_form', uid = user.id) }}" class="btn btn-default">Change eMail</a>
{% endif %}
</div>
</div>
@ -59,18 +59,18 @@
<input type="text" class="form-control" id="lastfm" placeholder="{% if user.lastfm_status %}Linked{% else %}Invalid session{% endif %}" readonly>
<div class="input-group-btn">
{% if request.user.id == user.id %}
<a href="{{ url_for('lastfm_unreg', uid = 'me') }}" class="btn btn-default">Unlink</a>
<a href="{{ url_for('frontend.lastfm_unreg', uid = 'me') }}" class="btn btn-default">Unlink</a>
{% else %}
<a href="{{ url_for('lastfm_unreg', uid = user.id) }}" class="btn btn-default">Unlink</a>
<a href="{{ url_for('frontend.lastfm_unreg', uid = user.id) }}" class="btn btn-default">Unlink</a>
{% endif %}
</div>
{% else %}
<input type="text" class="form-control" id="lastfm" placeholder="Unlinked" readonly>
<div class="input-group-btn">
{% if request.user.id == user.id %}
<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', uid = 'me') }}" class="btn btn-default">Link</a>
<a href="http://www.last.fm/api/auth/?api_key={{ api_key }}&cb={{ request.url_root[:-(request.script_root|length+1)] + url_for('frontend.lastfm_reg', uid = 'me') }}" class="btn btn-default">Link</a>
{% else %}
<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', uid = user.id) }}" class="btn btn-default">Link</a>
<a href="http://www.last.fm/api/auth/?api_key={{ api_key }}&cb={{ request.url_root[:-(request.script_root|length+1)] + url_for('frontend.lastfm_reg', uid = user.id) }}" class="btn btn-default">Link</a>
{% endif %}
</div>
{% endif %}
@ -83,10 +83,10 @@
</div>
</div>
{% if request.user.id == user.id %}
<a href="{{ url_for('change_password_form', uid = 'me') }}" class="btn btn-default">Change password</a></li>
<a href="{{ url_for('frontend.change_password_form', uid = 'me') }}" class="btn btn-default">Change password</a></li>
{% else %}
<a href="{{ url_for('change_username_form', uid = user.id) }}" class="btn btn-default">Change username or admin status</a></li>
<a href="{{ url_for('change_password_form', uid = user.id) }}" class="btn btn-default">Change password</a></li>
<a href="{{ url_for('frontend.change_username_form', uid = user.id) }}" class="btn btn-default">Change username or admin status</a></li>
<a href="{{ url_for('frontend.change_password_form', uid = user.id) }}" class="btn btn-default">Change password</a></li>
{% endif %}
{% if clients.count() %}
<div class="page-header">

View File

@ -2,14 +2,14 @@
This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API.
Copyright (C) 2013-2017 Alban 'spl0k' Féron
Copyright (C) 2013-2018 Alban 'spl0k' Féron
2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license.
-#}
{% extends "layout.html" %}
{% block navbar_users %}
<li class="active"><a href="{{ url_for('user_index') }}">Users <span
<li class="active"><a href="{{ url_for('frontend.user_index') }}">Users <span
class="sr-only">(current)</span></a></li>
{% endblock %}
{% block body %}
@ -24,15 +24,15 @@
{% for user in users %}
<tr>
<td>{% if request.user.id == user.id %}{{ user.name }}{% else %}
<a href="{{ url_for('user_profile', uid = user.id) }}">{{ user.name }}</a>{% endif %}</td>
<a href="{{ url_for('frontend.user_profile', uid = user.id) }}">{{ user.name }}</a>{% endif %}</td>
<td>{{ user.mail }}</td><td>{{ user.admin }}</td><td>{{ user.last_play_date }}</td><td>
{% if request.user.id != user.id %}<button class="btn btn-danger btn-xs" data-href="{{ url_for('del_user', uid = user.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete user">
{% if request.user.id != user.id %}<button class="btn btn-danger btn-xs" data-href="{{ url_for('frontend.del_user', uid = user.id) }}" data-toggle="modal" data-target="#confirm-delete" aria-label="Delete user">
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete user"></span></button>{% endif %}</td></tr>
{% endfor %}
</tbody>
</table>
<div class="btn-toolbar" role="toolbar">
<a href="{{ url_for('add_user_form') }}" class="btn btn-default">Add</a>
<a href="{{ url_for('frontend.add_user_form') }}" class="btn btn-default">Add</a>
</div>
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">

View File

@ -63,7 +63,8 @@ def create_application(config = None):
# Import app sections
with app.app_context():
if app.config['WEBAPP']['mount_webui']:
from . import frontend
from .frontend import frontend
app.register_blueprint(frontend)
if app.config['WEBAPP']['mount_api']:
from .api import api
app.register_blueprint(api, url_prefix = '/rest')