1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-20 03:11:04 +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. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # 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 # 2017 Óscar García Amor
# #
# Distributed under terms of the GNU AGPLv3 license. # 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 functools import wraps
from pony.orm import db_session from pony.orm import db_session
from ..db import Artist, Album, Track from ..db import Artist, Album, Track
from ..managers.user import UserManager from ..managers.user import UserManager
@app.before_request frontend = Blueprint('frontend', __name__)
@frontend.before_request
def login_check(): def login_check():
if request.path.startswith('/rest/'):
return
if request.path.startswith('/static/'):
return
request.user = None request.user = None
should_login = True should_login = True
if session.get('userid'): if session.get('userid'):
@ -34,11 +31,11 @@ def login_check():
request.user = user request.user = user
should_login = False should_login = False
if should_login and request.endpoint != 'login': if should_login and request.endpoint != 'frontend.login':
flash('Please 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 @db_session
def index(): def index():
stats = { stats = {
@ -52,10 +49,11 @@ def admin_only(f):
@wraps(f) @wraps(f)
def decorated_func(*args, **kwargs): def decorated_func(*args, **kwargs):
if not request.user or not request.user.admin: 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 f(*args, **kwargs)
return decorated_func return decorated_func
from .user import * from .user import *
from .folder import * from .folder import *
from .playlist import * from .playlist import *

View File

@ -3,7 +3,7 @@
# This file is part of Supysonic. # This file is part of Supysonic.
# #
# Supysonic is a Python implementation of the Subsonic server API. # 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 # 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 # it under the terms of the GNU Affero General Public License as published by
@ -21,7 +21,7 @@
import os.path import os.path
import uuid 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 pony.orm import db_session
from ..db import Folder from ..db import Folder
@ -29,20 +29,20 @@ from ..managers.user import UserManager
from ..managers.folder import FolderManager from ..managers.folder import FolderManager
from ..scanner import Scanner from ..scanner import Scanner
from . import admin_only from . import admin_only, frontend
@app.route('/folder') @frontend.route('/folder')
@admin_only @admin_only
@db_session @db_session
def folder_index(): def folder_index():
return render_template('folders.html', folders = Folder.select(lambda f: f.root)) return render_template('folders.html', folders = Folder.select(lambda f: f.root))
@app.route('/folder/add') @frontend.route('/folder/add')
@admin_only @admin_only
def add_folder_form(): def add_folder_form():
return render_template('addfolder.html') return render_template('addfolder.html')
@app.route('/folder/add', methods = [ 'POST' ]) @frontend.route('/folder/add', methods = [ 'POST' ])
@admin_only @admin_only
def add_folder_post(): def add_folder_post():
error = False error = False
@ -63,16 +63,16 @@ def add_folder_post():
flash("Folder '%s' created. You should now run a scan" % name) 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 @admin_only
def del_folder(id): def del_folder(id):
try: try:
idid = uuid.UUID(id) idid = uuid.UUID(id)
except ValueError: except ValueError:
flash('Invalid folder id') flash('Invalid folder id')
return redirect(url_for('folder_index')) return redirect(url_for('frontend.folder_index'))
ret = FolderManager.delete(idid) ret = FolderManager.delete(idid)
if ret != FolderManager.SUCCESS: if ret != FolderManager.SUCCESS:
@ -80,14 +80,14 @@ def del_folder(id):
else: else:
flash('Deleted folder') flash('Deleted folder')
return redirect(url_for('folder_index')) return redirect(url_for('frontend.folder_index'))
@app.route('/folder/scan') @frontend.route('/folder/scan')
@app.route('/folder/scan/<id>') @frontend.route('/folder/scan/<id>')
@admin_only @admin_only
@db_session @db_session
def scan_folder(id = None): def scan_folder(id = None):
extensions = app.config['BASE']['scanner_extensions'] extensions = current_app.config['BASE']['scanner_extensions']
if extensions: if extensions:
extensions = extensions.split(' ') extensions = extensions.split(' ')
@ -100,7 +100,7 @@ def scan_folder(id = None):
status, folder = FolderManager.get(id) status, folder = FolderManager.get(id)
if status != FolderManager.SUCCESS: if status != FolderManager.SUCCESS:
flash(FolderManager.error_str(status)) flash(FolderManager.error_str(status))
return redirect(url_for('folder_index')) return redirect(url_for('frontend.folder_index'))
scanner.scan(folder) scanner.scan(folder)
scanner.finish() scanner.finish()
@ -112,5 +112,5 @@ def scan_folder(id = None):
flash('Errors in:') flash('Errors in:')
for err in stats.errors: for err in stats.errors:
flash('- ' + err) 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. # This file is part of Supysonic.
# #
# Supysonic is a Python implementation of the Subsonic server API. # 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 # 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 # it under the terms of the GNU Affero General Public License as published by
@ -20,51 +20,53 @@
import uuid 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 db_session
from pony.orm import ObjectNotFound from pony.orm import ObjectNotFound
from ..db import Playlist from ..db import Playlist
from ..managers.user import UserManager from ..managers.user import UserManager
@app.route('/playlist') from . import frontend
@frontend.route('/playlist')
@db_session @db_session
def playlist_index(): def playlist_index():
return render_template('playlists.html', return render_template('playlists.html',
mine = Playlist.select(lambda p: p.user == request.user), mine = Playlist.select(lambda p: p.user == request.user),
others = Playlist.select(lambda p: p.user != request.user and p.public)) others = Playlist.select(lambda p: p.user != request.user and p.public))
@app.route('/playlist/<uid>') @frontend.route('/playlist/<uid>')
@db_session @db_session
def playlist_details(uid): def playlist_details(uid):
try: try:
uid = uuid.UUID(uid) uid = uuid.UUID(uid)
except ValueError: except ValueError:
flash('Invalid playlist id') flash('Invalid playlist id')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
try: try:
playlist = Playlist[uid] playlist = Playlist[uid]
except ObjectNotFound: except ObjectNotFound:
flash('Unknown playlist') flash('Unknown playlist')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
return render_template('playlist.html', playlist = playlist) return render_template('playlist.html', playlist = playlist)
@app.route('/playlist/<uid>', methods = [ 'POST' ]) @frontend.route('/playlist/<uid>', methods = [ 'POST' ])
@db_session @db_session
def playlist_update(uid): def playlist_update(uid):
try: try:
uid = uuid.UUID(uid) uid = uuid.UUID(uid)
except ValueError: except ValueError:
flash('Invalid playlist id') flash('Invalid playlist id')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
try: try:
playlist = Playlist[uid] playlist = Playlist[uid]
except ObjectNotFound: except ObjectNotFound:
flash('Unknown playlist') flash('Unknown playlist')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
if playlist.user.id != request.user.id: if playlist.user.id != request.user.id:
flash("You're not allowed to edit this playlist") flash("You're not allowed to edit this playlist")
@ -77,20 +79,20 @@ def playlist_update(uid):
return playlist_details(str(uid)) return playlist_details(str(uid))
@app.route('/playlist/del/<uid>') @frontend.route('/playlist/del/<uid>')
@db_session @db_session
def playlist_delete(uid): def playlist_delete(uid):
try: try:
uid = uuid.UUID(uid) uid = uuid.UUID(uid)
except ValueError: except ValueError:
flash('Invalid playlist id') flash('Invalid playlist id')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
try: try:
playlist = Playlist[uid] playlist = Playlist[uid]
except ObjectNotFound: except ObjectNotFound:
flash('Unknown playlist') flash('Unknown playlist')
return redirect(url_for('playlist_index')) return redirect(url_for('frontend.playlist_index'))
if playlist.user.id != request.user.id: if playlist.user.id != request.user.id:
flash("You're not allowed to delete this playlist") flash("You're not allowed to delete this playlist")
@ -98,5 +100,5 @@ def playlist_delete(uid):
playlist.delete() playlist.delete()
flash('Playlist deleted') 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 # 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/>. # 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 functools import wraps
from pony.orm import db_session from pony.orm import db_session
@ -27,7 +28,7 @@ from ..lastfm import LastFm
from ..managers.user import UserManager from ..managers.user import UserManager
from ..py23 import dict from ..py23 import dict
from . import admin_only from . import admin_only, frontend
def me_or_uuid(f, arg = 'uid'): def me_or_uuid(f, arg = 'uid'):
@db_session @db_session
@ -41,12 +42,12 @@ def me_or_uuid(f, arg = 'uid'):
if uid == 'me': if uid == 'me':
user = User[request.user.id] # Refetch user from previous transaction user = User[request.user.id] # Refetch user from previous transaction
elif not request.user.admin: elif not request.user.admin:
return redirect(url_for('index')) return redirect(url_for('frontend.index'))
else: else:
code, user = UserManager.get(uid) code, user = UserManager.get(uid)
if code != UserManager.SUCCESS: if code != UserManager.SUCCESS:
flash(UserManager.error_str(code)) flash(UserManager.error_str(code))
return redirect(url_for('index')) return redirect(url_for('frontend.index'))
if kwargs: if kwargs:
kwargs['user'] = user kwargs['user'] = user
@ -57,18 +58,18 @@ def me_or_uuid(f, arg = 'uid'):
return decorated_func return decorated_func
@app.route('/user') @frontend.route('/user')
@admin_only @admin_only
@db_session @db_session
def user_index(): def user_index():
return render_template('users.html', users = User.select()) return render_template('users.html', users = User.select())
@app.route('/user/<uid>') @frontend.route('/user/<uid>')
@me_or_uuid @me_or_uuid
def user_profile(uid, user): 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 @me_or_uuid
def update_clients(uid, user): def update_clients(uid, user):
clients_opts = dict() clients_opts = dict()
@ -86,7 +87,7 @@ def update_clients(uid, user):
clients_opts[client] = dict([ (opt, value) ]) clients_opts[client] = dict([ (opt, value) ])
else: else:
clients_opts[client][opt] = value clients_opts[client][opt] = value
app.logger.debug(clients_opts) current_app.logger.debug(clients_opts)
for client, opts in clients_opts.items(): for client, opts in clients_opts.items():
prefs = user.clients.select(lambda c: c.client_name == client).first() prefs = user.clients.select(lambda c: c.client_name == client).first()
@ -103,23 +104,23 @@ def update_clients(uid, user):
flash('Clients preferences updated.') flash('Clients preferences updated.')
return user_profile(uid, user) return user_profile(uid, user)
@app.route('/user/<uid>/changeusername') @frontend.route('/user/<uid>/changeusername')
@admin_only @admin_only
def change_username_form(uid): def change_username_form(uid):
code, user = UserManager.get(uid) code, user = UserManager.get(uid)
if code != UserManager.SUCCESS: if code != UserManager.SUCCESS:
flash(UserManager.error_str(code)) flash(UserManager.error_str(code))
return redirect(url_for('index')) return redirect(url_for('frontend.index'))
return render_template('change_username.html', user = user) return render_template('change_username.html', user = user)
@app.route('/user/<uid>/changeusername', methods = [ 'POST' ]) @frontend.route('/user/<uid>/changeusername', methods = [ 'POST' ])
@admin_only @admin_only
@db_session @db_session
def change_username_post(uid): def change_username_post(uid):
code, user = UserManager.get(uid) code, user = UserManager.get(uid)
if code != UserManager.SUCCESS: if code != UserManager.SUCCESS:
return redirect(url_for('index')) return redirect(url_for('frontend.index'))
username = request.form.get('user') username = request.form.get('user')
if username in ('', None): if username in ('', None):
@ -141,27 +142,27 @@ def change_username_post(uid):
else: else:
flash("No changes for '%s'." % username) 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 @me_or_uuid
def change_mail_form(uid, user): def change_mail_form(uid, user):
return render_template('change_mail.html', user = 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 @me_or_uuid
def change_mail_post(uid, user): def change_mail_post(uid, user):
mail = request.form.get('mail', '') mail = request.form.get('mail', '')
# No validation, lol. # No validation, lol.
user.mail = mail 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 @me_or_uuid
def change_password_form(uid, user): def change_password_form(uid, user):
return render_template('change_pass.html', user = 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 @me_or_uuid
def change_password_post(uid, user): def change_password_post(uid, user):
error = False error = False
@ -190,16 +191,16 @@ def change_password_post(uid, user):
flash(UserManager.error_str(status)) flash(UserManager.error_str(status))
else: else:
flash('Password changed') 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) return change_password_form(uid, user)
@app.route('/user/add') @frontend.route('/user/add')
@admin_only @admin_only
def add_user_form(): def add_user_form():
return render_template('adduser.html') return render_template('adduser.html')
@app.route('/user/add', methods = [ 'POST' ]) @frontend.route('/user/add', methods = [ 'POST' ])
@admin_only @admin_only
def add_user_post(): def add_user_post():
error = False error = False
@ -222,13 +223,13 @@ def add_user_post():
status = UserManager.add(name, passwd, mail, admin) status = UserManager.add(name, passwd, mail, admin)
if status == UserManager.SUCCESS: if status == UserManager.SUCCESS:
flash("User '%s' successfully added" % name) flash("User '%s' successfully added" % name)
return redirect(url_for('user_index')) return redirect(url_for('frontend.user_index'))
else: else:
flash(UserManager.error_str(status)) flash(UserManager.error_str(status))
return add_user_form() return add_user_form()
@app.route('/user/del/<uid>') @frontend.route('/user/del/<uid>')
@admin_only @admin_only
def del_user(uid): def del_user(uid):
status = UserManager.delete(uid) status = UserManager.delete(uid)
@ -237,33 +238,33 @@ def del_user(uid):
else: else:
flash(UserManager.error_str(status)) 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 @me_or_uuid
def lastfm_reg(uid, user): def lastfm_reg(uid, user):
token = request.args.get('token') token = request.args.get('token')
if token in ('', None): if token in ('', None):
flash('Missing LastFM auth token') 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) status, error = lfm.link_account(token)
flash(error if not status else 'Successfully linked LastFM account') 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 @me_or_uuid
def lastfm_unreg(uid, user): 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() lfm.unlink_account()
flash('Unlinked LastFM 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(): 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: if request.user:
flash('Already logged in') flash('Already logged in')
return redirect(return_url) return redirect(return_url)
@ -291,9 +292,9 @@ def login():
return render_template('login.html') return render_template('login.html')
@app.route('/user/logout') @frontend.route('/user/logout')
def logout(): def logout():
session.clear() session.clear()
flash('Logged out!') 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. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_folders %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -2,7 +2,7 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% block navbar_users %}
{% if request.user.id != user.id %} {% 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> class="sr-only">(current)</span></a></li>
{% else %} {% else %}
{{ super() }} {{ super() }}
@ -18,7 +18,7 @@
{% endblock %} {% endblock %}
{% block navbar_profile %} {% block navbar_profile %}
{% if request.user.id == user.id %} {% 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 %} {% else %}
{{ super() }} {{ super() }}
{% endif %} {% endif %}

View File

@ -2,7 +2,7 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% block navbar_users %}
{% if request.user.id != user.id %} {% 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> class="sr-only">(current)</span></a></li>
{% else %} {% else %}
{{ super() }} {{ super() }}
@ -18,7 +18,7 @@
{% endblock %} {% endblock %}
{% block navbar_profile %} {% block navbar_profile %}
{% if request.user.id == user.id %} {% 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 %} {% else %}
{{ super() }} {{ super() }}
{% endif %} {% endif %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

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

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_index %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% 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. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
@ -51,25 +51,25 @@
id="supysonic-navbar-collapse"> id="supysonic-navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
{% block navbar_index %} {% block navbar_index %}
<li><a href="{{ url_for('index') }}">Home</a></li> <li><a href="{{ url_for('frontend.index') }}">Home</a></li>
{% endblock %} {% endblock %}
{% block navbar_playlists %} {% 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 %} {% endblock %}
{% if request.user.admin %} {% if request.user.admin %}
{% block navbar_users %} {% 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 %} {% endblock %}
{% block navbar_folders %} {% 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 %} {% endblock %}
{% endif %} {% endif %}
{% block navbar_profile %} {% 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 %} {% endblock %}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <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> </ul>
</div><!-- /.navbar-collapse --> </div><!-- /.navbar-collapse -->
{% endif %} {% endif %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_playlists %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_playlists %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
@ -26,13 +26,13 @@
<tbody> <tbody>
{% for p in mine %} {% for p in mine %}
<tr> <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>{{ p.get_tracks()|length }}</td>
<td>{% if p.public %}<span class="glyphicon glyphicon-check" <td>{% if p.public %}<span class="glyphicon glyphicon-check"
aria-label="Public playlist"></span>{% else %}<span aria-label="Public playlist"></span>{% else %}<span
class="glyphicon glyphicon-unchecked" class="glyphicon glyphicon-unchecked"
aria-label="Private playlist"></span>{% endif %}</td> 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> <span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete playlist"></span></button></td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -50,7 +50,7 @@
<tbody> <tbody>
{% for p in others %} {% for p in others %}
<tr> <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.user.name }}</td>
<td>{{ p.get_tracks()|length }}</td> <td>{{ p.get_tracks()|length }}</td>
</tr> </tr>

View File

@ -2,7 +2,7 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
@ -10,7 +10,7 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% block navbar_users %}
{% if request.user.id != user.id %} {% 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> class="sr-only">(current)</span></a></li>
{% else %} {% else %}
{{ super() }} {{ super() }}
@ -18,7 +18,7 @@
{% endblock %} {% endblock %}
{% block navbar_profile %} {% block navbar_profile %}
{% if request.user.id == user.id %} {% 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 %} {% else %}
{{ super() }} {{ super() }}
{% endif %} {% endif %}
@ -39,9 +39,9 @@
<input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly> <input type="text" class="form-control" id="email" placeholder="{{ user.mail }}" readonly>
<div class="input-group-btn"> <div class="input-group-btn">
{% if request.user.id == user.id %} {% 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 %} {% 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 %} {% endif %}
</div> </div>
</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> <input type="text" class="form-control" id="lastfm" placeholder="{% if user.lastfm_status %}Linked{% else %}Invalid session{% endif %}" readonly>
<div class="input-group-btn"> <div class="input-group-btn">
{% if request.user.id == user.id %} {% 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 %} {% 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 %} {% endif %}
</div> </div>
{% else %} {% else %}
<input type="text" class="form-control" id="lastfm" placeholder="Unlinked" readonly> <input type="text" class="form-control" id="lastfm" placeholder="Unlinked" readonly>
<div class="input-group-btn"> <div class="input-group-btn">
{% if request.user.id == user.id %} {% 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 %} {% 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 %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -83,10 +83,10 @@
</div> </div>
</div> </div>
{% if request.user.id == user.id %} {% 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 %} {% 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('frontend.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_password_form', uid = user.id) }}" class="btn btn-default">Change password</a></li>
{% endif %} {% endif %}
{% if clients.count() %} {% if clients.count() %}
<div class="page-header"> <div class="page-header">

View File

@ -2,14 +2,14 @@
This file is part of Supysonic. This file is part of Supysonic.
Supysonic is a Python implementation of the Subsonic server API. 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 2017 Óscar García Amor
Distributed under terms of the GNU AGPLv3 license. Distributed under terms of the GNU AGPLv3 license.
-#} -#}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block navbar_users %} {% 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> class="sr-only">(current)</span></a></li>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
@ -24,15 +24,15 @@
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{% if request.user.id == user.id %}{{ user.name }}{% else %} <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> <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> <span class="glyphicon glyphicon-remove-circle" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="Delete user"></span></button>{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="btn-toolbar" role="toolbar"> <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>
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog"> <div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">

View File

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