#!/usr/bin/env python # -*- coding: utf-8 -*- import mimetypes import logging from logging.handlers import RotatingFileHandler import textwrap mimetypes.add_type('application/xhtml+xml','.xhtml') from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, make_response, g, flash, abort import db, config, ub, helper import os import errno from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import false from sqlalchemy.exc import IntegrityError from math import ceil from flask_login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed from flask_babel import Babel from flask_babel import gettext as _ import requests, zipfile from werkzeug.security import generate_password_hash, check_password_hash from babel import Locale as LC from babel import negotiate_locale from functools import wraps import base64 from sqlalchemy.sql import * import json import urllib import datetime from iso639 import languages as isoLanguages from uuid import uuid4 import os.path import shutil import re try: from wand.image import Image use_generic_pdf_cover = False except ImportError, e: use_generic_pdf_cover = True from shutil import copyfile from cgi import escape class ReverseProxied(object): '''Wrap the application in this middleware and configure the front-end server to add these headers, to let you quietly bind this to a URL other than / and to an HTTP scheme that is different than what is used locally. Code courtesy of: http://flask.pocoo.org/snippets/35/ In nginx: location /myprefix { proxy_pass http://127.0.0.1:8083; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Scheme $scheme; proxy_set_header X-Script-Name /myprefix; } ''' def __init__(self, app): self.app = app def __call__(self, environ, start_response): script_name = environ.get('HTTP_X_SCRIPT_NAME', '') if script_name: environ['SCRIPT_NAME'] = script_name path_info = environ.get('PATH_INFO', '') if path_info and path_info.startswith(script_name): environ['PATH_INFO'] = path_info[len(script_name):] scheme = environ.get('HTTP_X_SCHEME', '') if scheme: environ['wsgi.url_scheme'] = scheme server = environ.get('HTTP_X_FORWARDED_SERVER', '') if server: environ['HTTP_HOST'] = server return self.app(environ, start_response) app = (Flask(__name__)) app.wsgi_app = ReverseProxied(app.wsgi_app) formatter = logging.Formatter( "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s") file_handler = RotatingFileHandler(os.path.join(config.LOG_DIR, "calibre-web.log"), maxBytes=10000, backupCount=1) file_handler.setLevel(logging.INFO) file_handler.setFormatter(formatter) app.logger.addHandler(file_handler) app.logger.info('Starting Calibre Web...') logging.getLogger("book_formats").addHandler(file_handler) logging.getLogger("book_formats").setLevel(logging.INFO) Principal(app) babel = Babel(app) class Anonymous(AnonymousUserMixin): def __init__(self): self.nickname = 'Guest' self.role = -1 def role_admin(self): return False def role_download(self): return False def role_upload(self): return False def role_edit(self): return False def filter_language(self): return 'all' def show_random_books(self): return True def show_hot_books(self): return True def show_series(self): return True def show_category(self): return True def show_language(self): return True def is_anonymous(self): return config.ANON_BROWSE lm = LoginManager(app) lm.init_app(app) lm.login_view = 'login' lm.anonymous_user = Anonymous app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' LANGUAGES = { 'en': 'English', 'de': 'Deutsch', 'fr': 'Français' } @babel.localeselector def get_locale(): # if a user is logged in, use the locale from the user settings user = getattr(g, 'user', None) if user is not None and hasattr(user, "locale"): return user.locale preferred = [x.replace('-', '_') for x in request.accept_languages.values()] return negotiate_locale(preferred, LANGUAGES.keys()) @babel.timezoneselector def get_timezone(): user = getattr(g, 'user', None) if user is not None: return user.timezone @lm.user_loader def load_user(id): return ub.session.query(ub.User).filter(ub.User.id == int(id)).first() @lm.header_loader def load_user_from_header(header_val): if header_val.startswith('Basic '): header_val = header_val.replace('Basic ', '', 1) try: header_val = base64.b64decode(header_val) basic_username = header_val.split(':')[0] basic_password = header_val.split(':')[1] except TypeError: pass user = ub.session.query(ub.User).filter(ub.User.nickname == basic_username).first() if user and check_password_hash(user.password, basic_password): return user return def check_auth(username, password): user = ub.session.query(ub.User).filter(ub.User.nickname == username).first() if user and check_password_hash(user.password, password): return True else: return False def authenticate(): return Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) def requires_basic_auth_if_no_ano(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if config.ANON_BROWSE != 1: if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated #simple pagination for the feed class Pagination(object): def __init__(self, page, per_page, total_count): self.page = page self.per_page = per_page self.total_count = total_count @property def pages(self): return int(ceil(self.total_count / float(self.per_page))) @property def has_prev(self): return self.page > 1 @property def has_next(self): return self.page < self.pages def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): last = 0 for num in xrange(1, self.pages + 1): if num <= left_edge or \ (num > self.page - left_current - 1 and \ num < self.page + right_current) or \ num > self.pages - right_edge: if last + 1 != num: yield None yield num last = num ##pagination links in jinja def url_for_other_page(page): args = request.view_args.copy() args['page'] = page return url_for(request.endpoint, **args) app.jinja_env.globals['url_for_other_page'] = url_for_other_page def login_required_if_no_ano(func): if config.ANON_BROWSE == 1: return func return login_required(func) ## custom jinja filters @app.template_filter('shortentitle') def shortentitle_filter(s): if len(s) > 60: s = s.split(':', 1)[0] if len(s) > 60: s = textwrap.wrap(s, 60, break_long_words=False)[0]+' [...]' return s def admin_required(f): """ Checks if current_user.role == 1 """ @wraps(f) def inner(*args, **kwargs): if current_user.role_admin(): return f(*args, **kwargs) abort(403) return inner def download_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_download() or current_user.role_admin(): return f(*args, **kwargs) abort(403) return inner def upload_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_upload() or current_user.role_admin(): return f(*args, **kwargs) abort(403) return inner def edit_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_edit() or current_user.role_admin(): return f(*args, **kwargs) abort(403) return inner # Fill indexpage with all requested data from database def fill_indexpage(page,database, db_filter, order) : if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter= True if current_user.show_random_books(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) else : random = false off = int(int(config.NEWEST_BOOKS) * (page-1)) pagination = Pagination(page, config.NEWEST_BOOKS, len(db.session.query(database).filter(db_filter).filter(filter).all())) entries = db.session.query(database).filter(db_filter).filter(filter).order_by(order).offset(off).limit(config.NEWEST_BOOKS) return entries, random, pagination def modify_database_object(input_elements, db_book_object, db_object, db_session,type): input_elements = [x for x in input_elements if x != ''] # we have all input element (authors, series, tags) names now # 1. search for elements to remove del_elements = [] for c_elements in db_book_object: found = False for inp_element in input_elements: if inp_element == c_elements.name: found = True break; # if the element was not found in the new list, add it to remove list if not found: del_elements.append(c_elements) # 2. search for elements that need to be added add_elements = [] for inp_element in input_elements: found = False for c_elements in db_book_object: if inp_element == c_elements.name: found = True break; if not found: add_elements.append(inp_element) # if there are elements to remove, we remove them now if len(del_elements) > 0: for del_element in del_elements: db_book_object.remove(del_element) if len(del_element.books) == 0: db_session.delete(del_element) # if there are elements to add, we add them now! if len(add_elements) > 0: if type == 'languages': db_filter=db_object.lang_code else: db_filter=db_object.name for add_element in add_elements: # check if a element with that name exists new_element = db_session.query(db_object).filter(db_filter == add_element).first() # if no element is found add it if new_element == None: if type=='author': new_element = db_object(add_element, add_element, "") else: if type=='series': new_element = db_object(add_element, add_element) else: # type should be tag, or languages new_element = db_object(add_element) db_session.add(new_element) new_element = db.session.query(db_object).filter(db_filter == add_element).first() # add element to book db_book_object.append(new_element) @app.before_request def before_request(): g.user = current_user g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).all() g.allow_registration = config.PUBLIC_REG g.allow_upload = config.UPLOADING @app.route("/feed") @requires_basic_auth_if_no_ano def feed_index(): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True xml = render_template('index.xml') response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/osd") @requires_basic_auth_if_no_ano def feed_osd(): xml = render_template('osd.xml') response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/search", methods=["GET"]) @requires_basic_auth_if_no_ano def feed_search(): term = request.args.get("query").strip() if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if term: entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%"+term+"%")),db.Books.authors.any(db.Authors.name.like("%"+term+"%")),db.Books.title.like("%"+term+"%"))).filter(filter).all() xml = render_template('feed.xml', searchterm=term, entries=entries, Last_Updated=Last_Updated) else: xml = render_template('feed.xml', searchterm="") response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/new") @requires_basic_auth_if_no_ano def feed_new(): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter= True if not off: off=0 entries = db.session.query(db.Books).filter(filter).order_by(db.Books.last_modified.desc()).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries, next_url="/feed/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/discover") @requires_basic_auth_if_no_ano def feed_discover(): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries, next_url="/feed/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/hot") @requires_basic_auth_if_no_ano def feed_hot(): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries, next_url="/feed/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/author") @requires_basic_auth_if_no_ano def feed_authorindex(): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', authors=authors,next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/author/") @requires_basic_auth_if_no_ano def feed_author(name): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( filter).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries,next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/category") @requires_basic_auth_if_no_ano def feed_categoryindex(): off = request.args.get("start_index") if not off: off=0 entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', categorys=entries, next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/category/") @requires_basic_auth_if_no_ano def feed_category(name): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.name.like("%" + name + "%"))).order_by( db.Books.last_modified.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries, next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/series") @requires_basic_auth_if_no_ano def feed_seriesindex(): off = request.args.get("start_index") if not off: off=0 entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', series=entries, next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/series/") @requires_basic_auth_if_no_ano def feed_series(name): off = request.args.get("start_index") if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if not off: off=0 entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.name.like("%" + name + "%"))).order_by( db.Books.last_modified.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) xml = render_template('feed.xml', entries=entries, next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response= make_response(xml) response.headers["Content-Type"] = "application/xml" return response @app.route("/feed/download//") @requires_basic_auth_if_no_ano @download_required def get_opds_download_link(book_id, format): format = format.split(".")[0] book = db.session.query(db.Books).filter(db.Books.id == book_id).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() helper.update_download(book_id, int(current_user.id)) author = helper.get_normalized_author(book.author_sort) file_name = book.title if len(author) > 0: file_name = author+'-'+file_name file_name = helper.get_valid_filename(file_name) response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." +format)) response.headers["Content-Disposition"] = "attachment; filename=%s.%s" % (data.name, format) return response @app.route("/get_authors_json", methods = ['GET', 'POST']) @login_required_if_no_ano def get_authors_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from authors where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps @app.route("/get_tags_json", methods = ['GET', 'POST']) @login_required_if_no_ano def get_tags_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from tags where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps @app.route("/get_languages_json", methods = ['GET', 'POST']) @login_required_if_no_ano def get_languages_json(): if request.method == "GET": query = request.args.get('q').lower() # entries = db.session.execute("select lang_code from languages where lang_code like '%" + query + "%'") languages = db.session.query(db.Languages).all() for lang in languages: try: cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: lang.name=_(isoLanguages.get(part3=lang.lang_code).name) entries = [s for s in languages if query in s.name.lower()] json_dumps = json.dumps([dict(name=r.name) for r in entries]) return json_dumps @app.route("/get_series_json", methods = ['GET', 'POST']) @login_required_if_no_ano def get_series_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from series where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps @app.route("/get_matching_tags", methods = ['GET', 'POST']) @login_required_if_no_ano def get_matching_tags(): tag_dict = {'tags': []} if request.method == "GET": q = db.session.query(db.Books) author_input = request.args.get('author_name') title_input = request.args.get('book_title') include_tag_inputs = request.args.getlist('include_tag') exclude_tag_inputs = request.args.getlist('exclude_tag') q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_input + "%")), db.Books.title.like("%"+title_input+"%")) if len(include_tag_inputs) > 0: for tag in include_tag_inputs: q = q.filter(db.Books.tags.any(db.Tags.id == tag)) if len(exclude_tag_inputs) > 0: for tag in exclude_tag_inputs: q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag))) for book in q: for tag in book.tags: if tag.id not in tag_dict['tags']: tag_dict['tags'].append(tag.id) json_dumps = json.dumps(tag_dict) return json_dumps @app.route("/", defaults={'page': 1}) @app.route('/page/') @login_required_if_no_ano def index(page): entries, random, pagination =fill_indexpage(page,db.Books,True, db.Books.last_modified.desc()) return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Latest Books")) @app.route("/hot", defaults={'page': 1}) @app.route('/hot/page/') @login_required_if_no_ano def hot_books(page): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if current_user.show_random_books(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) else : random = false off = int(int(config.NEWEST_BOOKS) * (page - 1)) all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id) hot_books = all_books.offset(off).limit(config.NEWEST_BOOKS) entries = list() for book in hot_books: entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first()) numBooks = entries.__len__() pagination = Pagination(page, config.NEWEST_BOOKS, numBooks) return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Hot Books (most downloaded)")) @app.route("/discover", defaults={'page': 1}) @app.route('/discover/page/') @login_required_if_no_ano def discover(page): entries, random, pagination = fill_indexpage(page, db.Books, func.randomblob(2),db.Books.last_modified.desc()) return render_template('discover.html', entries=entries, pagination=pagination, title=_(u"Random Books")) @app.route("/author") @login_required_if_no_ano def author_list(): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True entries= db.session.query(db.Authors,func.count('books_authors_link.book').label('count')).join(db.books_authors_link).join(db.Books).filter( filter).group_by('books_authors_link.author').order_by(db.Authors.sort).all() return render_template('list.html', entries=entries, folder='author', title=_(u"Author list")) @app.route("/author/") @login_required_if_no_ano def author(name): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True if current_user.show_random_books(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) else: random = false entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(filter).all() return render_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s",nam=name)) @app.route("/series") @login_required_if_no_ano def series_list(): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True entries= db.session.query(db.Series,func.count('books_series_link.book').label('count')).join(db.books_series_link).join(db.Books).filter( filter).group_by('books_series_link.series').order_by(db.Series.sort).all() return render_template('list.html', entries=entries, folder='series', title=_(u"Series list")) @app.route("/series//", defaults={'page': 1}) @app.route("/series//'") @login_required_if_no_ano def series(name,page): entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.name == name ), db.Books.series_index) if entries : return render_template('index.html', random=random, pagination=pagination, entries=entries, title=_(u"Series: %(serie)s",serie=name)) else : flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) @app.route("/language") @login_required_if_no_ano def language_overview(): if current_user.filter_language() == u"all": languages = db.session.query(db.Languages).all() for lang in languages: try: cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: lang.name=_(isoLanguages.get(part3=lang.lang_code).name) else : try: langfound=1 cur_l = LC.parse(current_user.filter_language()) except: langfound=0 languages = db.session.query(db.Languages).filter(db.Languages.lang_code == current_user.filter_language()).all() if langfound: languages[0].name = cur_l.get_language_name(get_locale()) else : languages[0].name=_(isoLanguages.get(part3=languages[0].lang_code).name) lang_counter =db.session.query(db.books_languages_link,func.count('books_languages_link.book').label('bookcount')).group_by('books_languages_link.lang_code').all() return render_template('languages.html', languages=languages, lang_counter=lang_counter, title=_(u"Available languages")) @app.route("/language/",defaults={'page': 1}) @app.route('/language//page/') @login_required_if_no_ano def language(name,page): entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name ), db.Books.last_modified.desc()) try: cur_l = LC.parse(name) name = cur_l.get_language_name(get_locale()) except: name = _(isoLanguages.get(part3=name).name) return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Language: %(name)s", name=name)) @app.route("/category") @login_required_if_no_ano def category_list(): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True entries= db.session.query(db.Tags,func.count('books_tags_link.book').label('count')).join(db.books_tags_link).join(db.Books).filter( filter).group_by('books_tags_link.tag').all() return render_template('list.html', entries=entries, folder='category', title=_(u"Category list")) @app.route("/category/",defaults={'page': 1}) @app.route('/category//') @login_required_if_no_ano def category(name,page): entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.name == name ), db.Books.last_modified.desc()) return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Category: %(name)s",name=name)) @app.route("/book/") @login_required_if_no_ano def show_book(id): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True entries = db.session.query(db.Books).filter(db.Books.id == id).filter(filter).first() if entries : for index in range(0,len(entries.languages)) : try: entries.languages[index].language_name=LC.parse(entries.languages[index].lang_code).get_language_name(get_locale()) except: entries.languages[index].language_name = _(isoLanguages.get(part3=entries.languages[index].lang_code).name) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() book_in_shelfs = [] shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == id).all() for entry in shelfs: book_in_shelfs.append(entry.shelf) return render_template('detail.html', entry=entries, cc=cc, title=entries.title, books_shelfs=book_in_shelfs) else : flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) @app.route("/admin/") @login_required def admin(): #return "Admin ONLY!" abort(403) @app.route("/stats") @login_required def stats(): counter = len(db.session.query(db.Books).all()) authors = len(db.session.query(db.Authors).all()) return render_template('stats.html', bookcounter=counter, authorcounter=authors, title=_(u"Statistics")) @app.route("/search", methods=["GET"]) @login_required_if_no_ano def search(): term = request.args.get("query").strip() if term: if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%"+term+"%")),db.Books.series.any(db.Series.name.like("%"+term+"%")),db.Books.authors.any(db.Authors.name.like("%"+term+"%")),db.Books.title.like("%"+term+"%"))).filter(filter).all() return render_template('search.html', searchterm=term, entries=entries) else: return render_template('search.html', searchterm="") @app.route("/advanced_search", methods=["GET"]) @login_required_if_no_ano def advanced_search(): if request.method == 'GET': q = db.session.query(db.Books) include_tag_inputs = request.args.getlist('include_tag') exclude_tag_inputs = request.args.getlist('exclude_tag') author_name = request.args.get("author_name") book_title = request.args.get("book_title") if author_name: author_name=author_name.strip() if book_title : book_title=book_title.strip() if include_tag_inputs or exclude_tag_inputs or author_name or book_title: searchterm = [] searchterm.extend((author_name, book_title)) tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all() searchterm.extend(tag.name for tag in tag_names) searchterm = " + ".join(filter(None, searchterm)) q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_name + "%")), db.Books.title.like("%"+book_title+"%")) # random = db.session.query(db.Books).order_by(func.random()).limit(config.RANDOM_BOOKS) for tag in include_tag_inputs: q = q.filter(db.Books.tags.any(db.Tags.id == tag)) for tag in exclude_tag_inputs: q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag))) if current_user.filter_language() != "all": q = q.filter(db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())) q = q.all() return render_template('search.html', searchterm=searchterm, entries=q) tags = db.session.query(db.Tags).order_by(db.Tags.name).all() return render_template('search_form.html', tags=tags) @app.route("/cover/") @login_required_if_no_ano def get_cover(cover_path): return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") @app.route("/feed/cover/") @requires_basic_auth_if_no_ano def feed_get_cover(cover_path): return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") @app.route("/read//") @login_required def read_book(book_id,format): book = db.session.query(db.Books).filter(db.Books.id == book_id).first() if book : book_dir = os.path.join(config.MAIN_DIR, "cps","static", str(book_id)) if not os.path.exists(book_dir): os.mkdir(book_dir) if format.lower() == "epub": #check if mimetype file is exists mime_file = str(book_id) +"/mimetype" if os.path.exists(mime_file) == False: epub_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".epub" if not os.path.isfile(epub_file): raise ValueError('Error opening eBook. File does not exist: ', epub_file) zfile = zipfile.ZipFile(epub_file) for name in zfile.namelist(): (dirName, fileName) = os.path.split(name) newDir = os.path.join(book_dir, dirName) if not os.path.exists(newDir): try: os.makedirs(newDir) except OSError as exception: if exception.errno == errno.EEXIST: pass else: raise if fileName: fd = open(os.path.join(newDir, fileName), "wb") fd.write(zfile.read(name)) fd.close() zfile.close() return render_template('read.html', bookid=book_id, title=_(u"Read a Book")) elif format.lower() == "pdf": all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".pdf" tmp_file = os.path.join(book_dir,urllib.quote(book.data[0].name)) + ".pdf" if os.path.exists(tmp_file) == False: pdf_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".pdf" copyfile(pdf_file,tmp_file) return render_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book")) elif format.lower() == "txt": all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".txt" tmp_file = os.path.join(book_dir,urllib.quote(book.data[0].name)) + ".txt" if os.path.exists(all_name) == False: txt_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".txt" copyfile(txt_file,tmp_file) return render_template('readtxt.html', txtfile=all_name, title=_(u"Read a Book")) else : flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) @app.route("/download//") @login_required @download_required def get_download_link(book_id, format): format = format.split(".")[0] book = db.session.query(db.Books).filter(db.Books.id == book_id).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() helper.update_download(book_id, int(current_user.id)) author = helper.get_normalized_author(book.author_sort) file_name = book.title if len(author) > 0: file_name = author+'-'+file_name file_name = helper.get_valid_filename(file_name) response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." +format)) response.headers["Content-Disposition"] = \ "attachment; " \ "filename={utf_filename}.{suffix};" \ "filename*=UTF-8''{utf_filename}.{suffix}".format( utf_filename=file_name.encode('utf-8'), suffix=format ) return response @app.route('/register', methods = ['GET', 'POST']) def register(): error = None if not config.PUBLIC_REG: abort(404) if current_user is not None and current_user.is_authenticated: return redirect(url_for('index', _external=True)) if request.method == "POST": to_save = request.form.to_dict() if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: flash(_(u"Please fill out all fields!"), category="error") return render_template('register.html', title="register") existing_user = ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).first() existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"]).first() if not existing_user and not existing_email: content = ub.User() content.password = generate_password_hash(to_save["password"]) content.nickname = to_save["nickname"] content.email = to_save["email"] content.role = 0 try: ub.session.add(content) ub.session.commit() except: ub.session.rollback() flash(_(u"An unknown error occured. Please try again later."), category="error") return render_template('register.html', title="register") flash("Your account has been created. Please login.", category="success") return redirect(url_for('login', _external=True)) else: flash(_(u"This username or email address is already in use."), category="error") return render_template('register.html', title="register") return render_template('register.html', title=_(u"register")) @app.route('/login', methods = ['GET', 'POST']) def login(): error = None if current_user is not None and current_user.is_authenticated: return redirect(url_for('index', _external=True)) if request.method == "POST": form = request.form.to_dict() user = ub.session.query(ub.User).filter(ub.User.nickname == form['username'].strip()).first() if user and check_password_hash(user.password, form['password']): login_user(user, remember = True) flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") return redirect('/' or url_for("index", _external=True)) else: flash(_(u"Wrong Username or Password"), category="error") return render_template('login.html',title=_(u"login")) @app.route('/logout') @login_required def logout(): if current_user is not None and current_user.is_authenticated: logout_user() return redirect(url_for("index", _external=True)) @app.route('/send/') @login_required @download_required def send_to_kindle(book_id): settings = ub.get_mail_settings() if settings.get("mail_server", "mail.example.com") == "mail.example.com": flash(_(u"Please configure the SMTP mail settings first..."), category="error") elif current_user.kindle_mail: result = helper.send_mail(book_id, current_user.kindle_mail) if result is None: flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), category="success") helper.update_download(book_id, int(current_user.id)) else: flash(_(u"There was an error sending this book: %(res)s",res=result), category="error") else: flash(_(u"Please configure your kindle email address first..."), category="error") return redirect(request.environ["HTTP_REFERER"]) @app.route("/shelf/add//") @login_required def add_to_shelf(shelf_id, book_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() if not shelf.is_public and not shelf.user_id == int(current_user.id): flash("Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name) return redirect(url_for('index', _external=True)) ins = ub.BookShelf(shelf=shelf.id, book_id=book_id) ub.session.add(ins) ub.session.commit() flash(_(u"Book has been added to shelf: %(sname)s",sname=shelf.name), category="success") #return redirect(url_for('show_book', id=book_id)) return redirect(request.environ["HTTP_REFERER"]) @app.route("/shelf/remove//") @login_required def remove_from_shelf(shelf_id, book_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() if not shelf.is_public and not shelf.user_id == int(current_user.id): flash("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name) return redirect(url_for('index', _external=True)) book_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, ub.BookShelf.book_id == book_id).first() #rem = ub.BookShelf(shelf=shelf.id, book_id=book_id) ub.session.delete(book_shelf) ub.session.commit() flash(_(u"Book has been removed from shelf: %(sname)s",sname=shelf.name), category="success") return redirect(request.environ["HTTP_REFERER"]) @app.route("/shelf/create", methods=["GET", "POST"]) @login_required def create_shelf(): shelf = ub.Shelf() if request.method == "POST": to_save = request.form.to_dict() if "is_public" in to_save: shelf.is_public = 1 shelf.name = to_save["title"] shelf.user_id = int(current_user.id) existing_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.name == shelf.name).first() if existing_shelf: flash(_(u"A shelf with the name '%(title)s' already exists.",title=to_save["title"]), category="error") else: try: ub.session.add(shelf) ub.session.commit() flash(_(u"Shelf %(title)s created",title=to_save["title"]), category="success") except: flash(_(u"There was an error"), category="error") return render_template('shelf_edit.html',title=_(u"create a shelf")) else: return render_template('shelf_edit.html',title=_(u"create a shelf")) @app.route("/shelf/delete/") @login_required def delete_shelf(shelf_id): cur_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() deleted = 0 if current_user.role == ub.ROLE_ADMIN: deleted = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).delete() else: deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), ub.Shelf.id == shelf_id), ub.and_(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id))).delete() if deleted: ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete() ub.session.commit() flash( _("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success") ) return redirect(url_for('index')) @app.route("/shelf/") @login_required_if_no_ano def show_shelf(shelf_id): if current_user.is_anonymous(): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() else : shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), ub.Shelf.id == shelf_id), ub.and_(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id))).first() result = list() if shelf: books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all() for book in books_in_shelf: cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() result.append(cur_book) return render_template('shelf.html', entries=result, title=_(u"Shelf: '%(name)s'" ,name=shelf.name), shelf=shelf) @app.route("/me", methods = ["GET", "POST"]) @login_required def profile(): content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first() downloads = list() languages = db.session.query(db.Languages).all() for lang in languages: try: cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) translations=babel.list_translations()+[LC('en')] for book in content.downloads: downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) if request.method == "POST": to_save = request.form.to_dict() content.random_books = 0 if current_user.role_passwd() or current_user.role_admin(): if to_save["password"]: content.password = generate_password_hash(to_save["password"]) if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: content.kindle_mail = to_save["kindle_mail"] if to_save["email"] and to_save["email"] != content.email: content.email = to_save["email"] if "show_random" in to_save and to_save["show_random"] == "on": content.random_books = 1 if "default_language" in to_save: content.default_language = to_save["default_language"] if to_save["locale"]: content.locale = to_save["locale"] try: ub.session.commit() except IntegrityError: ub.session.rollback() flash(_(u"Found an existing account for this email address."), category="error") return render_template("user_edit.html", content=content, downloads=downloads, title=_(u"%(name)s's profile", name=current_user.nickname)) flash(_(u"Profile updated"), category="success") return render_template("user_edit.html", translations=translations, profile=1, languages=languages, content=content, downloads=downloads, title=_(u"%(name)s's profile", name=current_user.nickname)) @app.route("/admin/user") @login_required @admin_required def user_list(): content = ub.session.query(ub.User).all() settings = ub.session.query(ub.Settings).first() return render_template("user_list.html", content=content, email=settings, title=_(u"User list")) @app.route("/admin/user/new", methods = ["GET", "POST"]) @login_required @admin_required def new_user(): content = ub.User() languages = db.session.query(db.Languages).all() for lang in languages: try: cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) translations=babel.list_translations()+[LC('en')] if request.method == "POST": to_save = request.form.to_dict() if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: flash(_(u"Please fill out all fields!"), category="error") return render_template("user_edit.html", new_user=1, content=content, title=_(u"Add new user")) content.password = generate_password_hash(to_save["password"]) content.nickname = to_save["nickname"] content.email = to_save["email"] content.default_language = to_save["default_language"] content.locale = to_save["locale"] content.random_books = 0 content.language_books = 0 content.series_books = 0 content.category_books = 0 content.hot_books = 0 if "show_language" in to_save: content.language_books = to_save["show_language"] if "show_series" in to_save: content.series_books = to_save["show_series"] if "show_category" in to_save: content.category_books = to_save["show_category"] if "show_hot" in to_save: content.hot_books = to_save["show_hot"] content.role = 0 if "admin_role" in to_save: content.role = content.role + ub.ROLE_ADMIN if "download_role" in to_save: content.role = content.role + ub.ROLE_DOWNLOAD if "upload_role" in to_save: content.role = content.role + ub.ROLE_UPLOAD if "edit_role" in to_save: content.role = content.role + ub.ROLE_EDIT if "passwd_role" in to_save: content.role = content.role + ub.ROLE_PASSWD try: ub.session.add(content) ub.session.commit() flash(_("User '%(user)s' created" , user=content.nickname), category="success") return redirect(url_for('user_list', _external=True)) except IntegrityError: ub.session.rollback() flash(_(u"Found an existing account for this email address or nickname."), category="error") return render_template("user_edit.html", new_user=1, content=content, translations=translations, languages=languages, title="Add new user") @app.route("/admin/user/mailsettings", methods = ["GET", "POST"]) @login_required @admin_required def edit_mailsettings(): content = ub.session.query(ub.Settings).first() if request.method == "POST": to_save = request.form.to_dict() content.mail_server = to_save["mail_server"] content.mail_port = int(to_save["mail_port"]) content.mail_login = to_save["mail_login"] content.mail_password = to_save["mail_password"] content.mail_from = to_save["mail_from"] if "mail_use_ssl" in to_save: content.mail_use_ssl = 1 else: content.mail_use_ssl = 0 try: ub.session.commit() flash(_(u"Mail settings updated"), category="success") except (e): flash(e, category="error") return render_template("email_edit.html", content=content, title=_("Edit mail settings")) @app.route("/admin/user/", methods = ["GET", "POST"]) @login_required @admin_required def edit_user(user_id): content = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() downloads = list() languages = db.session.query(db.Languages).all() for lang in languages: try: cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) translations = babel.list_translations() + [LC('en')] for book in content.downloads: downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) if request.method == "POST": to_save = request.form.to_dict() if "delete" in to_save: ub.session.delete(content) flash(_(u"User '%(nick)s' deleted",nick=content.nickname), category="success") return redirect(url_for('user_list', _external=True)) else: if to_save["password"]: content.password = generate_password_hash(to_save["password"]) if "admin_role" in to_save and not content.role_admin(): content.role = content.role + ub.ROLE_ADMIN elif not "admin_role" in to_save and content.role_admin(): content.role = content.role - ub.ROLE_ADMIN if "download_role" in to_save and not content.role_download(): content.role = content.role + ub.ROLE_DOWNLOAD elif not "download_role" in to_save and content.role_download(): content.role = content.role - ub.ROLE_DOWNLOAD if "upload_role" in to_save and not content.role_upload(): content.role = content.role + ub.ROLE_UPLOAD elif not "upload_role" in to_save and content.role_upload(): content.role = content.role - ub.ROLE_UPLOAD if "edit_role" in to_save and not content.role_edit(): content.role = content.role + ub.ROLE_EDIT elif not "edit_role" in to_save and content.role_edit(): content.role = content.role - ub.ROLE_EDIT if "passwd_role" in to_save and not content.role_passwd(): content.role = content.role + ub.ROLE_PASSWD elif not "passwd_role" in to_save and content.role_passwd(): content.role = content.role - ub.ROLE_PASSWD content.random_books = 0 content.language_books = 0 content.series_books = 0 content.category_books = 0 content.hot_books = 0 if "show_random" in to_save and to_save["show_random"] == "on": content.random_books = 1 if "show_language" in to_save and to_save["show_language"] == "on": content.language_books = 1 if "show_series" in to_save and to_save["show_series"] == "on": content.series_books = 1 if "show_category" in to_save and to_save["show_category"] == "on": content.category_books = 1 if "show_hot" in to_save and to_save["show_hot"] == "on": content.hot_books = 1 if "default_language" in to_save: content.default_language = to_save["default_language"] if to_save["locale"]: content.locale = to_save["locale"] if to_save["email"] and to_save["email"] != content.email: content.email = to_save["email"] if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: content.kindle_mail = to_save["kindle_mail"] try: ub.session.commit() flash(_(u"User '%(nick)s' updated",nick= content.nickname), category="success") except IntegrityError: ub.session.rollback() flash(_(u"An unknown error occured."), category="error") return render_template("user_edit.html", translations=translations, languages=languages, new_user=0, content=content, downloads=downloads, title=_(u"Edit User %(nick)s",nick=content.nickname)) @app.route("/admin/book/", methods=['GET', 'POST']) @login_required @edit_required def edit_book(book_id): ## create the function for sorting... db.session.connection().connection.connection.create_function("title_sort",1,db.title_sort) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(filter).first() author_names = [] if book: for index in range(0, len(book.languages)): try: book.languages[index].language_name=LC.parse(book.languages[index].lang_code).get_language_name(get_locale()) except: book.languages[index].language_name=_(isoLanguages.get(part3=book.languages[index].lang_code).name) for author in book.authors: author_names.append(author.name) if request.method == 'POST': edited_books_id = set() to_save = request.form.to_dict() if book.title != to_save["book_title"]: book.title = to_save["book_title"] edited_books_id.add(book.id) input_authors = to_save["author_name"].split('&') input_authors = map(lambda it: it.strip(), input_authors) # we have all author names now author0_before_edit = book.authors[0].name modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author') if author0_before_edit != book.authors[0].name: edited_books_id.add(book.id) if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg": img = requests.get(to_save["cover_url"]) f = open(os.path.join(config.DB_ROOT, book.path, "cover.jpg"), "wb") f.write(img.content) f.close() if book.series_index != to_save["series_index"]: book.series_index = to_save["series_index"] if len(book.comments): book.comments[0].text = to_save["description"] else: book.comments.append(db.Comments(text=to_save["description"], book=book.id)) input_tags = to_save["tags"].split(',') input_tags = map(lambda it: it.strip(), input_tags) modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags') input_series = [to_save["series"].strip()] input_series = [x for x in input_series if x != ''] modify_database_object(input_series, book.series, db.Series, db.session, 'series') input_languages = to_save["languages"].split(',') input_languages = map(lambda it: it.strip().lower(), input_languages) # retranslate displayed text to language codes languages = db.session.query(db.Languages).all() input_l=[] for lang in languages: try: lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower() except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name).lower() for inp_lang in input_languages: if inp_lang == lang.name: input_l.append(lang.lang_code) modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages') if to_save["rating"].strip(): old_rating = False if len(book.ratings) > 0: old_rating = book.ratings[0].rating ratingx2 = int(float(to_save["rating"]) *2) if ratingx2 != old_rating: is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first() if is_rating: book.ratings.append(is_rating) else: new_rating = db.Ratings(rating=ratingx2) book.ratings.append(new_rating) if old_rating: book.ratings.remove(book.ratings[0]) else: if len(book.ratings) > 0: book.ratings.remove(book.ratings[0]) for c in cc: cc_string = "custom_column_" + str(c.id) if not c.is_multiple: if len(getattr(book, cc_string)) > 0: cc_db_value = getattr(book, cc_string)[0].value else: cc_db_value = None if to_save[cc_string].strip(): if c.datatype == 'bool': if to_save[cc_string] == 'None': to_save[cc_string]= None else: to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0 if to_save[cc_string] != cc_db_value: if cc_db_value is not None: if to_save[cc_string] is not None: setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string]) else : del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) db.session.delete(del_cc) else : cc_class = db.cc_classes[c.id] new_cc = cc_class(value=to_save[cc_string],book=book_id) db.session.add(new_cc) else: if c.datatype == 'rating': to_save[cc_string] = str(int(float(to_save[cc_string]) *2)) if to_save[cc_string].strip() != cc_db_value: if cc_db_value != None: #remove old cc_val del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) if len(del_cc.books) == 0: db.session.delete(del_cc) cc_class = db.cc_classes[c.id] new_cc = db.session.query(cc_class).filter(cc_class.value == to_save[cc_string].strip()).first() # if no cc val is found add it if new_cc == None: new_cc = cc_class(value=to_save[cc_string].strip()) db.session.add(new_cc) new_cc = db.session.query(cc_class).filter(cc_class.value == to_save[cc_string].strip()).first() # add cc value to book getattr(book, cc_string).append(new_cc) else: if cc_db_value != None: #remove old cc_val del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) if len(del_cc.books) == 0: db.session.delete(del_cc) else: input_tags = to_save[cc_string].split(',') input_tags = map(lambda it: it.strip(), input_tags) input_tags = [x for x in input_tags if x != ''] # we have all author names now # 1. search for tags to remove del_tags = [] for c_tag in getattr(book, cc_string): found = False for inp_tag in input_tags: if inp_tag == c_tag.value: found = True break; # if the tag was not found in the new list, add him to remove list if not found: del_tags.append(c_tag) # 2. search for tags that need to be added add_tags = [] for inp_tag in input_tags: found = False for c_tag in getattr(book, cc_string): if inp_tag == c_tag.value: found = True break; if not found: add_tags.append(inp_tag) # if there are tags to remove, we remove them now if len(del_tags) > 0: for del_tag in del_tags: getattr(book, cc_string).remove(del_tag) if len(del_tag.books) == 0: db.session.delete(del_tag) # if there are tags to add, we add them now! if len(add_tags) > 0: for add_tag in add_tags: # check if a tag with that name exists new_tag = db.session.query(db.cc_classes[c.id]).filter(db.cc_classes[c.id].value == add_tag).first() # if no tag is found add it if new_tag == None: new_tag = db.cc_classes[c.id](value=add_tag) db.session.add(new_tag) new_tag = db.session.query(db.cc_classes[c.id]).filter(db.cc_classes[c.id].value == add_tag).first() # add tag to book getattr(book, cc_string).append(new_tag) db.session.commit() author_names = [] for author in book.authors: author_names.append(author.name) for b in edited_books_id: helper.update_dir_stucture(b) if "detail_view" in to_save: return redirect(url_for('show_book', id=book.id, _external=True)) else: return render_template('edit_book.html', book=book, authors=author_names, cc=cc) else: return render_template('edit_book.html', book=book, authors=author_names, cc=cc) else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) import uploader from shutil import move @app.route("/upload", methods = ["GET", "POST"]) @login_required @upload_required def upload(): if not config.UPLOADING: abort(404) ## create the function for sorting... db.session.connection().connection.connection.create_function("title_sort",1,db.title_sort) db.session.connection().connection.connection.create_function('uuid4', 0, lambda : str(uuid4())) if request.method == 'POST' and 'btn-upload' in request.files: file = request.files['btn-upload'] meta = uploader.upload(file) title = meta.title.encode('utf-8') author = meta.author.encode('utf-8') title_dir = helper.get_valid_filename(title.decode('utf-8'), False) author_dir = helper.get_valid_filename(author.decode('utf-8'), False) data_name = title_dir filepath = config.DB_ROOT + os.sep + author_dir + os.sep + title_dir saved_filename = filepath + os.sep + data_name + meta.extension if not os.path.exists(filepath): try: os.makedirs(filepath) except OSError: flash(_(u"Failed to create path %s (Permission denied)." % filepath), category="error") return redirect(url_for('index', _external=True)) try: copyfile(meta.file_path, saved_filename) except OSError, e: flash(_(u"Failed to store file %s (Permission denied)." % saved_filename), category="error") return redirect(url_for('index', _external=True)) try: os.unlink(meta.file_path) except OSError, e: flash(_(u"Failed to delete file %s (Permission denied)." % meta.file_path), category="warning") file_size = os.path.getsize(saved_filename) if meta.cover is None: has_cover = 0 basedir = os.path.dirname(__file__) copyfile(os.path.join(basedir, "static/generic_cover.jpg"), os.path.join(filepath, "cover.jpg")) else: has_cover = 1 move(meta.cover, os.path.join(filepath, "cover.jpg")) is_author = db.session.query(db.Authors).filter(db.Authors.name == author).first() if is_author: db_author = is_author else: db_author = db.Authors(author, "", "") db.session.add(db_author) path = os.path.join(author_dir, title_dir) db_book = db.Books(title, "", "", datetime.datetime.now(), datetime.datetime(101, 01,01), 1, datetime.datetime.now(), path, has_cover, db_author, []) db_book.authors.append(db_author) db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, data_name) db_book.data.append(db_data) db.session.add(db_book) db.session.commit() author_names = [] for author in db_book.authors: author_names.append(author.name) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() if current_user.role_edit() or current_user.role_admin(): return render_template('edit_book.html', book=db_book, authors=author_names, cc=cc) book_in_shelfs = [] return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs)