From ffe3dd3de3963086fb6ff0f146ccfdf4ae6b7d2c Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sat, 29 Sep 2018 19:21:04 +0200 Subject: [PATCH 1/2] Show version of Werkzeug and Jinja2 in stats page --- cps/web.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cps/web.py b/cps/web.py index 5de0a730..c93ca00d 100644 --- a/cps/web.py +++ b/cps/web.py @@ -41,6 +41,8 @@ from flask import (Flask, render_template, request, Response, redirect, url_for, send_from_directory, make_response, g, flash, abort, Markup) from flask import __version__ as flaskVersion +from werkzeug import __version__ as werkzeugVersion +from jinja2 import __version__ as jinja2Version import cache_buster import ub from ub import config @@ -1665,8 +1667,10 @@ def stats(): categorys = db.session.query(db.Tags).count() series = db.session.query(db.Series).count() versions = uploader.book_formats.get_versions() - versions['Babel'] = 'v'+babelVersion - versions['Sqlalchemy'] = 'v'+sqlalchemyVersion + versions['Babel'] = 'v' + babelVersion + versions['Sqlalchemy'] = 'v' + sqlalchemyVersion + versions['Werkzeug'] = 'v' + werkzeugVersion + versions['Jinja2'] = 'v' + jinja2Version versions['Flask'] = 'v'+flaskVersion versions['Flask Login'] = 'v'+flask_loginVersion versions['Flask Principal'] = 'v'+flask_principalVersion From 5a2ed58d06a21f7623904260620e0002fcdbb86d Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sun, 30 Sep 2018 09:43:20 +0200 Subject: [PATCH 2/2] Improved message for non configured ebook converter Gdrive cleared interface (drive variable) refactored web -> reverseproxy outsourced, edit_books split, removed unused md5 function rearanged imports added Wand and pytz Version info to stats Page --- cps/book_formats.py | 6 +- cps/converter.py | 2 +- cps/gdriveutils.py | 27 +- cps/reverseproxy.py | 39 +++ cps/server.py | 9 +- cps/translations/de/LC_MESSAGES/messages.po | 2 +- cps/web.py | 334 ++++++++++---------- 7 files changed, 229 insertions(+), 190 deletions(-) create mode 100644 cps/reverseproxy.py diff --git a/cps/book_formats.py b/cps/book_formats.py index 1e0a08bd..58d21df5 100644 --- a/cps/book_formats.py +++ b/cps/book_formats.py @@ -123,9 +123,11 @@ def pdf_preview(tmp_file_path, tmp_dir): def get_versions(): if not use_generic_pdf_cover: - IVersion=ImageVersion.MAGICK_VERSION + IVersion = ImageVersion.MAGICK_VERSION + WVersion = ImageVersion.VERSION else: IVersion = _(u'not installed') + WVersion = _(u'not installed') if use_pdf_meta: PVersion='v'+PyPdfVersion else: @@ -134,4 +136,4 @@ def get_versions(): XVersion = 'v'+'.'.join(map(str, lxmlversion)) else: XVersion = _(u'not installed') - return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion} + return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion, 'Wand Version': WVersion} diff --git a/cps/converter.py b/cps/converter.py index 8967d3e5..666a4b56 100644 --- a/cps/converter.py +++ b/cps/converter.py @@ -45,5 +45,5 @@ def versioncheck(): elif ub.config.config_ebookconverter == 2: return versionCalibre() else: - return {'ebook_converter':''} + return {'ebook_converter':_(u'not configured')} diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index d8df9587..8da2d282 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -149,19 +149,19 @@ def getDrive(drive=None, gauth=None): drive.auth.Refresh() return drive -def listRootFolders(drive=None): - drive = getDrive(drive) +def listRootFolders(): + drive = getDrive(Gdrive.Instance().drive) folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" fileList = drive.ListFile({'q': folder}).GetList() return fileList -def getEbooksFolder(drive=None): +def getEbooksFolder(drive): return getFolderInFolder('root',config.config_google_drive_folder,drive) -def getFolderInFolder(parentId, folderName,drive=None): - drive = getDrive(drive) +def getFolderInFolder(parentId, folderName, drive): + # drive = getDrive(drive) query="" if folderName: query = "title = '%s' and " % folderName.replace("'", "\\'") @@ -190,7 +190,6 @@ def getEbooksFolderId(drive=None): def getFile(pathId, fileName, drive): - # drive = getDrive(Gdrive.Instance().drive) metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'")) fileList = drive.ListFile({'q': metaDataFile}).GetList() @@ -200,8 +199,8 @@ def getFile(pathId, fileName, drive): return fileList[0] -def getFolderId(path, drive=None): - drive = getDrive(drive) +def getFolderId(path, drive): + # drive = getDrive(drive) currentFolderId = getEbooksFolderId(drive) sqlCheckPath = path if path[-1] == '/' else path + '/' storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first() @@ -249,7 +248,7 @@ def getFileFromEbooksFolder(path, fileName): return None -def copyDriveFileRemote(drive, origin_file_id, copy_title): +'''def copyDriveFileRemote(drive, origin_file_id, copy_title): drive = getDrive(drive) copied_file = {'title': copy_title} try: @@ -258,7 +257,7 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title): return drive.CreateFile({'id': file_data['id']}) except errors.HttpError as error: print ('An error occurred: %s' % error) - return None + return None''' # Download metadata.db from gdrive @@ -347,7 +346,6 @@ def uploadFileToEbooksFolder(destFile, f): def watchChange(drive, channel_id, channel_type, channel_address, channel_token=None, expiration=None): - # drive = getDrive(drive) # Watch for all changes to a user's Drive. # Args: # service: Drive API service instance. @@ -390,8 +388,6 @@ def watchFile(drive, file_id, channel_id, channel_type, channel_address, Raises: apiclient.errors.HttpError: if http request to create channel fails. """ - # drive = getDrive(drive) - body = { 'id': channel_id, 'type': channel_type, @@ -413,8 +409,6 @@ def stopChannel(drive, channel_id, resource_id): Raises: apiclient.errors.HttpError: if http request to create channel fails. """ - # drive = getDrive(drive) - # service=drive.auth.service body = { 'id': channel_id, 'resourceId': resource_id @@ -423,7 +417,6 @@ def stopChannel(drive, channel_id, resource_id): def getChangeById (drive, change_id): - # drive = getDrive(drive) # Print a single Change resource information. # # Args: @@ -454,11 +447,13 @@ def updateDatabaseOnEdit(ID,newPath): storedPathName.path = newPath session.commit() + # Deletes the hashes in database of deleted book def deleteDatabaseEntry(ID): session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete() session.commit() + # Gets cover file from gdrive def get_cover_via_gdrive(cover_path): df = getFileFromEbooksFolder(cover_path, 'cover.jpg') diff --git a/cps/reverseproxy.py b/cps/reverseproxy.py new file mode 100644 index 00000000..db2c4a3b --- /dev/null +++ b/cps/reverseproxy.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +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, application): + self.app = application + + 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 + servr = environ.get('HTTP_X_FORWARDED_SERVER', '') + if servr: + environ['HTTP_HOST'] = servr + return self.app(environ, start_response) diff --git a/cps/server.py b/cps/server.py index bf1c1923..6dd48ae7 100644 --- a/cps/server.py +++ b/cps/server.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - from socket import error as SocketError import sys import os import signal +import web try: from gevent.pywsgi import WSGIServer @@ -19,8 +19,6 @@ except ImportError: from tornado import version as tornadoVersion gevent_present = False -import web - class server: @@ -29,7 +27,7 @@ class server: def __init__(self): signal.signal(signal.SIGINT, self.killServer) - signal.signal(signal.SIGTERM, self.killServer) + signal.signal(signal.SIGTERM, self.killServer) def start_gevent(self): try: @@ -68,7 +66,8 @@ class server: ssl_options=ssl) http_server.listen(web.ub.config.config_port) self.wsgiserver=IOLoop.instance() - self.wsgiserver.start() # wait for stop signal + self.wsgiserver.start() + # wait for stop signal self.wsgiserver.close(True) if self.restart == True: diff --git a/cps/translations/de/LC_MESSAGES/messages.po b/cps/translations/de/LC_MESSAGES/messages.po index cdc34cfc..c358ec05 100644 --- a/cps/translations/de/LC_MESSAGES/messages.po +++ b/cps/translations/de/LC_MESSAGES/messages.po @@ -1173,7 +1173,7 @@ msgstr "Pfad zu Konvertertool" #: cps/templates/config_edit.html:199 msgid "Location of Unrar binary" -msgstr "Ofad zum UnRar Programm" +msgstr "Pfad zum UnRar Programm" #: cps/templates/config_edit.html:215 cps/templates/layout.html:82 #: cps/templates/login.html:4 diff --git a/cps/web.py b/cps/web.py index c93ca00d..2d1e5f3f 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1,38 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -try: - from googleapiclient.errors import HttpError -except ImportError: - pass - -try: - from goodreads.client import GoodreadsClient - goodreads_support = True -except ImportError: - goodreads_support = False - -try: - import Levenshtein - levenshtein_support = True -except ImportError: - levenshtein_support = False - -try: - from functools import reduce -except ImportError: - pass # We're not using Python 3 - -try: - import rarfile - rar_support=True -except ImportError: - rar_support=False - -try: - from natsort import natsorted as sort -except ImportError: - sort=sorted # Just use regular sort then - # may cause issues with badly named pages in cbz/cbr files import mimetypes import logging @@ -74,6 +41,7 @@ import json import datetime from iso639 import languages as isoLanguages from iso639 import __version__ as iso639Version +from pytz import __version__ as pytzVersion from uuid import uuid4 import os.path import sys @@ -83,10 +51,43 @@ from shutil import move, copyfile import gdriveutils import converter import tempfile -import hashlib from redirect import redirect_back import time import server +from reverseproxy import ReverseProxied +try: + from googleapiclient.errors import HttpError +except ImportError: + pass + +try: + from goodreads.client import GoodreadsClient + goodreads_support = True +except ImportError: + goodreads_support = False + +try: + import Levenshtein + levenshtein_support = True +except ImportError: + levenshtein_support = False + +try: + from functools import reduce +except ImportError: + pass # We're not using Python 3 + +try: + import rarfile + rar_support=True +except ImportError: + rar_support=False + +try: + from natsort import natsorted as sort +except ImportError: + sort=sorted # Just use regular sort then + # may cause issues with badly named pages in cbz/cbr files try: import cPickle except ImportError: @@ -103,12 +104,10 @@ try: except ImportError: from flask_login.__about__ import __version__ as flask_loginVersion -current_milli_time = lambda: int(round(time.time() * 1000)) - # Global variables +current_milli_time = lambda: int(round(time.time() * 1000)) gdrive_watch_callback_token = 'target=calibreweb-watch_files' - EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx', 'fb2', 'html', 'rtf', 'odt'} EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'html', 'rtf', 'odt'} @@ -116,15 +115,7 @@ EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit' # EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else [])) -def md5(fname): - hash_md5 = hashlib.md5() - with open(fname, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash_md5.update(chunk) - return hash_md5.hexdigest() - - -class ReverseProxied(object): +'''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 @@ -159,7 +150,7 @@ class ReverseProxied(object): servr = environ.get('HTTP_X_FORWARDED_SERVER', '') if servr: environ['HTTP_HOST'] = servr - return self.app(environ, start_response) + return self.app(environ, start_response)''' # Main code @@ -1671,13 +1662,15 @@ def stats(): versions['Sqlalchemy'] = 'v' + sqlalchemyVersion versions['Werkzeug'] = 'v' + werkzeugVersion versions['Jinja2'] = 'v' + jinja2Version - versions['Flask'] = 'v'+flaskVersion - versions['Flask Login'] = 'v'+flask_loginVersion - versions['Flask Principal'] = 'v'+flask_principalVersion - versions['Iso 639'] = 'v'+iso639Version - versions['Requests'] = 'v'+requests.__version__ - versions['pySqlite'] = 'v'+db.engine.dialect.dbapi.version - versions['Sqlite'] = 'v'+db.engine.dialect.dbapi.sqlite_version + versions['Flask'] = 'v' + flaskVersion + versions['Flask Login'] = 'v' + flask_loginVersion + versions['Flask Principal'] = 'v' + flask_principalVersion + versions['Iso 639'] = 'v' + iso639Version + versions['pytz'] = 'v' + pytzVersion + + versions['Requests'] = 'v' + requests.__version__ + versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version + versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version versions.update(converter.versioncheck()) versions.update(server.Server.getNameVersion()) versions['Python'] = sys.version @@ -3362,24 +3355,19 @@ def reset_password(user_id): return redirect(url_for('admin')) -@app.route("/admin/book/", methods=['GET', 'POST']) -@login_required_if_no_ano -@edit_required -def edit_book(book_id): - # create the function for sorting... +def render_edit_book(book_id): 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() book = db.session.query(db.Books)\ .filter(db.Books.id == book_id).filter(common_filters()).first() - author_names = [] - # Book not found if not book: flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") return redirect(url_for("index")) for indx in range(0, len(book.languages)): book.languages[indx].language_name = language_table[get_locale()][book.languages[indx].lang_code] + author_names = [] for authr in book.authors: author_names.append(authr.name.replace('|', ',')) @@ -3398,14 +3386,92 @@ def edit_book(book_id): except Exception: app.logger.warning(file.format.lower() + ' already removed from list.') - app.logger.debug('Allowed conversion formats: '+ ', '.join(allowed_conversion_formats)) + return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, + title=_(u"edit metadata"), page="editbook", + conversion_formats=allowed_conversion_formats, + source_formats=valid_source_formats) - # Show form - if request.method != 'POST': - return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, - title=_(u"edit metadata"), page="editbook", - conversion_formats=allowed_conversion_formats, - source_formats=valid_source_formats) + +def edit_cc_data(book_id, book, to_save): + cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() + 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) + elif c.datatype == 'int': + if to_save[cc_string] == 'None': + to_save[cc_string] = None + 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 is not 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 is None: + new_cc = cc_class(value=to_save[cc_string].strip()) + db.session.add(new_cc) + db.session.flush() + 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 is not 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 = list(map(lambda it: it.strip(), input_tags)) + modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session, + 'custom') + return cc + +def upload_single_file(request, book, book_id): # Check and handle Uploaded file if 'btn-upload-format' in request.files: requested_file = request.files['btn-upload-format'] @@ -3452,9 +3518,10 @@ def edit_book(book_id): # Queue uploader info uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) - helper.global_WorkerThread.add_upload(current_user.nickname, + helper.global_WorkerThread.add_upload(current_user.nickname, "" + uploadText + "") +def upload_cover(request, book): if 'btn-upload-cover' in request.files: requested_file = request.files['btn-upload-cover'] # check for empty request @@ -3480,15 +3547,37 @@ def edit_book(book_id): except IOError: flash(_(u"Cover-file is not a valid image file" % saved_filename), category="error") return redirect(url_for('show_book', book_id=book.id)) - to_save = request.form.to_dict() +@app.route("/admin/book/", methods=['GET', 'POST']) +@login_required_if_no_ano +@edit_required +def edit_book(book_id): + # Show form + if request.method != 'POST': + return render_edit_book(book_id) + + # create the function for sorting... + db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) + book = db.session.query(db.Books)\ + .filter(db.Books.id == book_id).filter(common_filters()).first() + + # Book not found + if not book: + flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") + return redirect(url_for("index")) + + upload_single_file(request, book, book_id) + upload_cover(request, book) try: + to_save = request.form.to_dict() # Update book - edited_books_id = set() + edited_books_id = None #handle book title if book.title != to_save["book_title"]: + if to_save["book_title"] == '': + to_save["book_title"] = _(u'unknown') book.title = to_save["book_title"] - edited_books_id.add(book.id) + edited_books_id = book.id # handle author(s) input_authors = to_save["author_name"].split('&') @@ -3503,18 +3592,17 @@ def edit_book(book_id): modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author') if book.authors: if author0_before_edit != book.authors[0].name: - edited_books_id.add(book.id) + edited_books_id = book.id book.author_sort = helper.get_sorted_author(input_authors[0]) if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() error = False - for b in edited_books_id: - error = helper.update_dir_stucture(b, config.config_calibre_dir) + if edited_books_id: + error = helper.update_dir_stucture(edited_books_id, config.config_calibre_dir) if error: # stop on error flash(error, category="error") - break if not error: if to_save["cover_url"]: @@ -3557,7 +3645,6 @@ def edit_book(book_id): # handle book languages input_languages = to_save["languages"].split(',') - # input_languages = list(map(lambda it: it.strip().lower(), input_languages)) input_languages = [x.strip().lower() for x in input_languages if x != ''] input_l = [] invers_lang_table = [x.lower() for x in language_table[get_locale()].values()] @@ -3570,6 +3657,7 @@ def edit_book(book_id): flash(_(u"%(langname)s is not a valid language", langname=lang), category="error") modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages') + # handle book ratings if to_save["rating"].strip(): old_rating = False if len(book.ratings) > 0: @@ -3588,104 +3676,20 @@ def edit_book(book_id): 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) - elif c.datatype == 'int': - if to_save[cc_string] == 'None': - to_save[cc_string] = None - 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) + # handle cc data + edit_cc_data(book_id, book, to_save) - 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 is not 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 is None: - new_cc = cc_class(value=to_save[cc_string].strip()) - db.session.add(new_cc) - db.session.flush() - 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 is not 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 = list(map(lambda it: it.strip(), input_tags)) - modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session, - 'custom') db.session.commit() if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() if "detail_view" in to_save: return redirect(url_for('show_book', book_id=book.id)) else: - for indx in range(0, len(book.languages)): - try: - book.languages[indx].language_name = LC.parse(book.languages[indx].lang_code).get_language_name( - get_locale()) - except UnknownLocaleError: - book.languages[indx].language_name = _( - isoLanguages.get(part3=book.languages[indx].lang_code).name) - author_names = [] - for authr in book.authors: - author_names.append(authr.name) - return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, - title=_(u"edit metadata"), page="editbook") + return render_edit_book(book_id) else: db.session.rollback() flash(error, category="error") - return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, - title=_(u"edit metadata"), page="editbook") + return render_edit_book(book_id) except Exception as e: app.logger.exception(e) db.session.rollback()