# Buggy code
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(
            _("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 = None
        # handle book title
        if book.title != to_save["book_title"].rstrip().strip():
            if to_save["book_title"] == "":
                to_save["book_title"] = _("unknown")
            book.title = to_save["book_title"].rstrip().strip()
            edited_books_id = book.id

        # handle author(s)
        input_authors = to_save["author_name"].split("&")
        input_authors = list(
            map(lambda it: it.strip().replace(",", "|"), input_authors)
        )
        # we have all author names now
        if input_authors == [""]:
            input_authors = [_("unknown")]  # prevent empty Author
        if book.authors:
            author0_before_edit = book.authors[0].name
        else:
            author0_before_edit = db.Authors(_("unknown"), "", 0)
        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 = book.id
                book.author_sort = helper.get_sorted_author(input_authors[0])

        if config.config_use_google_drive:
            gdriveutils.updateGdriveCalibreFromLocal()

        error = False
        if edited_books_id:
            error = helper.update_dir_stucture(
                edited_books_id, config.config_calibre_dir
            )

        if not error:
            if to_save["cover_url"]:
                if helper.save_cover(to_save["cover_url"], book.path) is True:
                    book.has_cover = 1
                else:
                    flash(_("Cover is not a jpg file, can't save"), category="error")

            if book.series_index != to_save["series_index"]:
                book.series_index = to_save["series_index"]

            # Handle book comments/description
            if len(book.comments):
                book.comments[0].text = to_save["description"]
            else:
                book.comments.append(
                    db.Comments(text=to_save["description"], book=book.id)
                )

            # Handle book tags
            input_tags = to_save["tags"].split(",")
            input_tags = list(map(lambda it: it.strip(), input_tags))
            modify_database_object(input_tags, book.tags, db.Tags, db.session, "tags")

            # Handle book series
            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"
            )

            if to_save["pubdate"]:
                try:
                    book.pubdate = datetime.datetime.strptime(
                        to_save["pubdate"], "%Y-%m-%d"
                    )
                except ValueError:
                    book.pubdate = db.Books.DEFAULT_PUBDATE
            else:
                book.pubdate = db.Books.DEFAULT_PUBDATE

            if to_save["publisher"]:
                publisher = to_save["publisher"].rstrip().strip()
                if len(book.publishers) == 0 or (
                    len(book.publishers) > 0 and publisher != book.publishers[0].name
                ):
                    modify_database_object(
                        [publisher],
                        book.publishers,
                        db.Publishers,
                        db.session,
                        "publisher",
                    )
            elif len(book.publishers):
                modify_database_object(
                    [], book.publishers, db.Publishers, db.session, "publisher"
                )

            # handle book languages
            input_languages = to_save["languages"].split(",")
            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()
            ]
            for lang in input_languages:
                try:
                    res = list(language_table[get_locale()].keys())[
                        invers_lang_table.index(lang)
                    ]
                    input_l.append(res)
                except ValueError:
                    app.logger.error("%s is not a valid language" % lang)
                    flash(
                        _("%(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:
                    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])

            # handle cc data
            edit_cc_data(book_id, book, to_save)

            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:
                flash(_("Metadata successfully updated"), category="success")
                return render_edit_book(book_id)
        else:
            db.session.rollback()
            flash(error, category="error")
            return render_edit_book(book_id)
    except Exception as e:
        app.logger.exception(e)
        db.session.rollback()
        flash(
            _("Error editing book, please check logfile for details"), category="error"
        )
        return redirect(url_for("show_book", book_id=book.id))


# Traceback (OperationalError)
ERROR:cps.web:Exception on /book/125 [GET]
Traceback (most recent call last):
File "/Users/jgillman/Code/calibre-web/vendor/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/Users/jgillman/Code/calibre-web/vendor/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/jgillman/Code/calibre-web/vendor/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/jgillman/Code/calibre-web/vendor/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/jgillman/Code/calibre-web/vendor/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/jgillman/Code/calibre-web/vendor/flask_login.py", line 717, in decorated_view
return func(*args, **kwargs)
File "/Users/jgillman/Code/calibre-web/cps/web.py", line 381, in show_book
return render_template('detail.html', entry=entries,  cc=cc, title=entries.title, books_shelfs=book_in_shelfs)
File "/Users/jgillman/Code/calibre-web/vendor/flask/templating.py", line 128, in render_template
context, ctx.app)
File "/Users/jgillman/Code/calibre-web/vendor/flask/templating.py", line 110, in _render
rv = template.render(context)
File "/Users/jgillman/Code/calibre-web/vendor/jinja2/environment.py", line 969, in render
return self.environment.handle_exception(exc_info, True)
File "/Users/jgillman/Code/calibre-web/vendor/jinja2/environment.py", line 742, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/jgillman/Code/calibre-web/cps/templates/detail.html", line 1, in top-level template code
{% extends "layout.html" %}
File "/Users/jgillman/Code/calibre-web/cps/templates/layout.html", line 136, in top-level template code
{% block body %}{% endblock %}
File "/Users/jgillman/Code/calibre-web/cps/templates/detail.html", line 66, in block "body"
{% if entry['custom_column_' ~ c.id]|length > 0 %}
File "/Users/jgillman/Code/calibre-web/vendor/jinja2/environment.py", line 387, in getitem
return getattr(obj, attr)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/attributes.py", line 316, in __get__
return self.impl.get(instance_state(instance), dict_)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/attributes.py", line 613, in get
value = self.callable_(state, passive)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/strategies.py", line 524, in _load_for_state
return self._emit_lazyload(session, state, ident_key, passive)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/strategies.py", line 585, in _emit_lazyload
result = q.all()
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/query.py", line 2241, in all
return list(self)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/query.py", line 2353, in __iter__
return self._execute_and_instances(context)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/orm/query.py", line 2368, in _execute_and_instances
result = conn.execute(querycontext.statement, self._params)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/base.py", line 662, in execute
params)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/base.py", line 761, in _execute_clauseelement
compiled_sql, distilled_params
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/base.py", line 874, in _execute_context
context)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/base.py", line 1024, in _handle_dbapi_exception
exc_info
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/util/compat.py", line 196, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/base.py", line 867, in _execute_context
context)
File "/Users/jgillman/Code/calibre-web/vendor/sqlalchemy/engine/default.py", line 324, in do_execute
cursor.execute(statement, parameters)
OperationalError: (OperationalError) no such table: books_custom_column_1_link u'SELECT custom_column_1.id AS custom_column_1_id, custom_column_1.value AS custom_column_1_value \
nFROM custom_column_1, books_custom_column_1_link \nWHERE ? = books_custom_column_1_link.book AND custom_column_1.id = books_custom_column_1_link.value' (125,)
ERROR:tornado.access:500 GET /book/125 (::1) 89.60ms

# Fixed code
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(
            _("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 = None
        # handle book title
        if book.title != to_save["book_title"].rstrip().strip():
            if to_save["book_title"] == "":
                to_save["book_title"] = _("unknown")
            book.title = to_save["book_title"].rstrip().strip()
            edited_books_id = book.id

        # handle author(s)
        input_authors = to_save["author_name"].split("&")
        input_authors = list(
            map(lambda it: it.strip().replace(",", "|"), input_authors)
        )
        # we have all author names now
        if input_authors == [""]:
            input_authors = [_("unknown")]  # prevent empty Author

        modify_database_object(
            input_authors, book.authors, db.Authors, db.session, "author"
        )

        # Search for each author if author is in database, if not, authorname and sorted authorname is generated new
        # everything then is assembled for sorted author field in database
        sort_authors_list = list()
        for inp in input_authors:
            stored_author = (
                db.session.query(db.Authors).filter(db.Authors.name == inp).first()
            )
            if not stored_author:
                stored_author = helper.get_sorted_author(inp)
            else:
                stored_author = stored_author.sort
            sort_authors_list.append(helper.get_sorted_author(stored_author))
        sort_authors = " & ".join(sort_authors_list)
        if book.author_sort != sort_authors:
            edited_books_id = book.id
            book.author_sort = sort_authors

        if config.config_use_google_drive:
            gdriveutils.updateGdriveCalibreFromLocal()

        error = False
        if edited_books_id:
            error = helper.update_dir_stucture(
                edited_books_id, config.config_calibre_dir, input_authors[0]
            )

        if not error:
            if to_save["cover_url"]:
                if helper.save_cover(to_save["cover_url"], book.path) is True:
                    book.has_cover = 1
                else:
                    flash(_("Cover is not a jpg file, can't save"), category="error")

            if book.series_index != to_save["series_index"]:
                book.series_index = to_save["series_index"]

            # Handle book comments/description
            if len(book.comments):
                book.comments[0].text = to_save["description"]
            else:
                book.comments.append(
                    db.Comments(text=to_save["description"], book=book.id)
                )

            # Handle book tags
            input_tags = to_save["tags"].split(",")
            input_tags = list(map(lambda it: it.strip(), input_tags))
            modify_database_object(input_tags, book.tags, db.Tags, db.session, "tags")

            # Handle book series
            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"
            )

            if to_save["pubdate"]:
                try:
                    book.pubdate = datetime.datetime.strptime(
                        to_save["pubdate"], "%Y-%m-%d"
                    )
                except ValueError:
                    book.pubdate = db.Books.DEFAULT_PUBDATE
            else:
                book.pubdate = db.Books.DEFAULT_PUBDATE

            if to_save["publisher"]:
                publisher = to_save["publisher"].rstrip().strip()
                if len(book.publishers) == 0 or (
                    len(book.publishers) > 0 and publisher != book.publishers[0].name
                ):
                    modify_database_object(
                        [publisher],
                        book.publishers,
                        db.Publishers,
                        db.session,
                        "publisher",
                    )
            elif len(book.publishers):
                modify_database_object(
                    [], book.publishers, db.Publishers, db.session, "publisher"
                )

            # handle book languages
            input_languages = to_save["languages"].split(",")
            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()
            ]
            for lang in input_languages:
                try:
                    res = list(language_table[get_locale()].keys())[
                        invers_lang_table.index(lang)
                    ]
                    input_l.append(res)
                except ValueError:
                    app.logger.error("%s is not a valid language" % lang)
                    flash(
                        _("%(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:
                    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])

            # handle cc data
            edit_cc_data(book_id, book, to_save)

            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:
                flash(_("Metadata successfully updated"), category="success")
                return render_edit_book(book_id)
        else:
            db.session.rollback()
            flash(error, category="error")
            return render_edit_book(book_id)
    except Exception as e:
        app.logger.exception(e)
        db.session.rollback()
        flash(
            _("Error editing book, please check logfile for details"), category="error"
        )
        return redirect(url_for("show_book", book_id=book.id))