mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-25 17:40:31 +00:00
Merge remote-tracking branch 'kobo_book_delete' into Develop
# Conflicts: # cps/kobo.py # cps/services/SyncToken.py # cps/templates/book_edit.html # cps/ub.py
This commit is contained in:
commit
09e7d76c6f
@ -71,7 +71,7 @@ class _Settings(_Base):
|
||||
config_kobo_sync = Column(Boolean, default=False)
|
||||
|
||||
config_default_role = Column(SmallInteger, default=0)
|
||||
config_default_show = Column(SmallInteger, default=6143)
|
||||
config_default_show = Column(SmallInteger, default=38911)
|
||||
config_columns_to_ignore = Column(String)
|
||||
|
||||
config_denied_tags = Column(String, default="")
|
||||
|
@ -80,9 +80,10 @@ MATURE_CONTENT = 1 << 11
|
||||
SIDEBAR_PUBLISHER = 1 << 12
|
||||
SIDEBAR_RATING = 1 << 13
|
||||
SIDEBAR_FORMAT = 1 << 14
|
||||
SIDEBAR_ARCHIVED = 1 << 15
|
||||
|
||||
ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_EDIT_SHELFS & ~ROLE_ANONYMOUS
|
||||
ADMIN_USER_SIDEBAR = (SIDEBAR_FORMAT << 1) - 1
|
||||
ADMIN_USER_SIDEBAR = (SIDEBAR_ARCHIVED << 1) - 1
|
||||
|
||||
UPDATE_STABLE = 0 << 0
|
||||
AUTO_UPDATE_STABLE = 1 << 0
|
||||
|
@ -179,6 +179,7 @@ def delete_book(book_id, book_format):
|
||||
# delete book from Shelfs, Downloads, Read list
|
||||
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
|
||||
ub.session.query(ub.ReadBook).filter(ub.ReadBook.book_id == book_id).delete()
|
||||
ub.session.query(ub.ArchivedBook).filter(ub.ReadBook.book_id == book_id).delete()
|
||||
ub.delete_download(book_id)
|
||||
ub.session.commit()
|
||||
|
||||
@ -261,6 +262,7 @@ def render_edit_book(book_id):
|
||||
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,
|
||||
config=config,
|
||||
source_formats=valid_source_formats)
|
||||
|
||||
|
||||
|
@ -684,7 +684,19 @@ def render_task_status(tasklist):
|
||||
|
||||
|
||||
# Language and content filters for displaying in the UI
|
||||
def common_filters():
|
||||
def common_filters(allow_show_archived=False):
|
||||
if not allow_show_archived:
|
||||
archived_books = (
|
||||
ub.session.query(ub.ArchivedBook)
|
||||
.filter(ub.ArchivedBook.user_id == int(current_user.id))
|
||||
.filter(ub.ArchivedBook.is_archived == True)
|
||||
.all()
|
||||
)
|
||||
archived_book_ids = [archived_book.book_id for archived_book in archived_books]
|
||||
archived_filter = db.Books.id.notin_(archived_book_ids)
|
||||
else:
|
||||
archived_filter = true()
|
||||
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
@ -706,7 +718,7 @@ def common_filters():
|
||||
pos_content_cc_filter = true()
|
||||
neg_content_cc_filter = false()
|
||||
return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter,
|
||||
pos_content_cc_filter, ~neg_content_cc_filter)
|
||||
pos_content_cc_filter, ~neg_content_cc_filter, archived_filter)
|
||||
|
||||
|
||||
def tags_filters():
|
||||
@ -764,15 +776,19 @@ def order_authors(entry):
|
||||
|
||||
# Fill indexpage with all requested data from database
|
||||
def fill_indexpage(page, database, db_filter, order, *join):
|
||||
return fill_indexpage_with_archived_books(page, database, db_filter, order, False, *join)
|
||||
|
||||
|
||||
def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_show_archived, *join):
|
||||
if current_user.show_detail_random():
|
||||
randm = db.session.query(db.Books).filter(common_filters())\
|
||||
randm = db.session.query(db.Books).filter(common_filters(allow_show_archived))\
|
||||
.order_by(func.random()).limit(config.config_random_books)
|
||||
else:
|
||||
randm = false()
|
||||
off = int(int(config.config_books_per_page) * (page - 1))
|
||||
pagination = Pagination(page, config.config_books_per_page,
|
||||
len(db.session.query(database).filter(db_filter).filter(common_filters()).all()))
|
||||
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters()).\
|
||||
len(db.session.query(database).filter(db_filter).filter(common_filters(allow_show_archived)).all()))
|
||||
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters(allow_show_archived)).\
|
||||
order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
||||
for book in entries:
|
||||
book = order_authors(book)
|
||||
|
80
cps/kobo.py
80
cps/kobo.py
@ -23,6 +23,7 @@ import base64
|
||||
import os
|
||||
import uuid
|
||||
from time import gmtime, strftime
|
||||
|
||||
try:
|
||||
from urllib import unquote
|
||||
except ImportError:
|
||||
@ -38,10 +39,10 @@ from flask import (
|
||||
redirect,
|
||||
abort
|
||||
)
|
||||
from flask_login import current_user, login_required
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.datastructures import Headers
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.sql.expression import and_
|
||||
from sqlalchemy.sql.expression import or_
|
||||
import requests
|
||||
|
||||
from . import config, logger, kobo_auth, db, helper, ub
|
||||
@ -58,6 +59,7 @@ kobo_auth.register_url_value_preprocessor(kobo)
|
||||
|
||||
log = logger.create()
|
||||
|
||||
|
||||
def get_store_url_for_current_request():
|
||||
# Programmatically modify the current url to point to the official Kobo store
|
||||
base, sep, request_path_with_auth_token = request.full_path.rpartition("/kobo/")
|
||||
@ -99,9 +101,6 @@ def redirect_or_proxy_request():
|
||||
if config.config_kobo_proxy:
|
||||
if request.method == "GET":
|
||||
return redirect(get_store_url_for_current_request(), 307)
|
||||
if request.method == "DELETE":
|
||||
log.info('Delete Book')
|
||||
return make_response(jsonify({}))
|
||||
else:
|
||||
# The Kobo device turns other request types into GET requests on redirects, so we instead proxy to the Kobo store ourselves.
|
||||
store_response = make_request_to_kobo_store()
|
||||
@ -141,13 +140,35 @@ def HandleSyncRequest():
|
||||
# in case of external changes (e.g: adding a book through Calibre).
|
||||
db.reconnect_db(config)
|
||||
|
||||
archived_books = (
|
||||
ub.session.query(ub.ArchivedBook)
|
||||
.filter(ub.ArchivedBook.user_id == int(current_user.id))
|
||||
.all()
|
||||
)
|
||||
|
||||
# We join-in books that have had their Archived bit recently modified in order to either:
|
||||
# * Restore them to the user's device.
|
||||
# * Delete them from the user's device.
|
||||
# (Ideally we would use a join for this logic, however cross-database joins don't look trivial in SqlAlchemy.)
|
||||
recently_restored_or_archived_books = []
|
||||
archived_book_ids = {}
|
||||
new_archived_last_modified = datetime.min
|
||||
for archived_book in archived_books:
|
||||
if archived_book.last_modified > sync_token.archive_last_modified:
|
||||
recently_restored_or_archived_books.append(archived_book.book_id)
|
||||
if archived_book.is_archived:
|
||||
archived_book_ids[archived_book.book_id] = True
|
||||
new_archived_last_modified = max(
|
||||
new_archived_last_modified, archived_book.last_modified)
|
||||
|
||||
# sqlite gives unexpected results when performing the last_modified comparison without the datetime cast.
|
||||
# It looks like it's treating the db.Books.last_modified field as a string and may fail
|
||||
# the comparison because of the +00:00 suffix.
|
||||
changed_entries = (
|
||||
db.session.query(db.Books)
|
||||
.join(db.Data)
|
||||
.filter(func.datetime(db.Books.last_modified) > sync_token.books_last_modified)
|
||||
.filter(or_(func.datetime(db.Books.last_modified) > sync_token.books_last_modified,
|
||||
db.Books.id.in_(recently_restored_or_archived_books)))
|
||||
.filter(db.Data.format.in_(KOBO_FORMATS))
|
||||
.all()
|
||||
)
|
||||
@ -156,7 +177,7 @@ def HandleSyncRequest():
|
||||
for book in changed_entries:
|
||||
kobo_reading_state = get_or_create_reading_state(book.id)
|
||||
entitlement = {
|
||||
"BookEntitlement": create_book_entitlement(book),
|
||||
"BookEntitlement": create_book_entitlement(book, archived=(book.id in archived_book_ids)),
|
||||
"BookMetadata": get_metadata(book),
|
||||
}
|
||||
|
||||
@ -191,7 +212,7 @@ def HandleSyncRequest():
|
||||
|
||||
sync_token.books_last_created = new_books_last_created
|
||||
sync_token.books_last_modified = new_books_last_modified
|
||||
sync_token.reading_state_last_modified = new_reading_state_last_modified
|
||||
sync_token.archive_last_modified = new_archived_last_modified
|
||||
|
||||
if config.config_kobo_proxy:
|
||||
return generate_sync_response(request, sync_token, sync_results)
|
||||
@ -256,7 +277,7 @@ def get_download_url_for_book(book, book_format):
|
||||
)
|
||||
|
||||
|
||||
def create_book_entitlement(book):
|
||||
def create_book_entitlement(book, archived):
|
||||
book_uuid = book.uuid
|
||||
return {
|
||||
"Accessibility": "Full",
|
||||
@ -264,17 +285,20 @@ def create_book_entitlement(book):
|
||||
"Created": convert_to_kobo_timestamp_string(book.timestamp),
|
||||
"CrossRevisionId": book_uuid,
|
||||
"Id": book_uuid,
|
||||
"IsRemoved": archived,
|
||||
"IsHiddenFromArchive": False,
|
||||
"IsLocked": False,
|
||||
# Setting this to true removes from the device.
|
||||
"IsRemoved": False,
|
||||
"LastModified": convert_to_kobo_timestamp_string(book.last_modified),
|
||||
"LastModified": book.last_modified,
|
||||
"OriginCategory": "Imported",
|
||||
"RevisionId": book_uuid,
|
||||
"Status": "Active",
|
||||
}
|
||||
|
||||
|
||||
def current_time():
|
||||
return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
|
||||
|
||||
|
||||
def get_description(book):
|
||||
if not book.comments:
|
||||
return None
|
||||
@ -526,13 +550,39 @@ def TopLevelEndpoint():
|
||||
return make_response(jsonify({}))
|
||||
|
||||
|
||||
@kobo.route("/v1/library/<book_uuid>", methods=["DELETE"])
|
||||
@login_required
|
||||
def HandleBookDeletionRequest(book_uuid):
|
||||
log.info("Kobo book deletion request received for book %s" % book_uuid)
|
||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
||||
if not book:
|
||||
log.info(u"Book %s not found in database", book_uuid)
|
||||
return redirect_or_proxy_request()
|
||||
|
||||
book_id = book.id
|
||||
archived_book = (
|
||||
ub.session.query(ub.ArchivedBook)
|
||||
.filter(ub.ArchivedBook.book_id == book_id)
|
||||
.first()
|
||||
)
|
||||
if not archived_book:
|
||||
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
|
||||
archived_book.is_archived = True
|
||||
archived_book.last_modified = datetime.utcnow()
|
||||
|
||||
ub.session.merge(archived_book)
|
||||
ub.session.commit()
|
||||
|
||||
return ("", 204)
|
||||
|
||||
|
||||
# TODO: Implement the following routes
|
||||
@kobo.route("/v1/library/<dummy>", methods=["DELETE", "GET"])
|
||||
@kobo.route("/v1/library/<book_uuid>/state", methods=["PUT"])
|
||||
@kobo.route("/v1/library/tags", methods=["POST"])
|
||||
@kobo.route("/v1/library/tags/<shelf_name>", methods=["POST"])
|
||||
@kobo.route("/v1/library/tags/<tag_id>", methods=["DELETE"])
|
||||
def HandleUnimplementedRequest(dummy=None, book_uuid=None, shelf_name=None, tag_id=None):
|
||||
log.debug("Unimplemented Library Request received: %s", request.base_url)
|
||||
def HandleUnimplementedRequest(book_uuid=None, shelf_name=None, tag_id=None):
|
||||
log.debug("Alternative Request received:")
|
||||
return redirect_or_proxy_request()
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ class SyncToken():
|
||||
"raw_kobo_store_token": {"type": "string"},
|
||||
"books_last_modified": {"type": "string"},
|
||||
"books_last_created": {"type": "string"},
|
||||
"reading_state_last_modified": {"type": "string"},
|
||||
"archive_last_modified": {"type": "string"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -85,13 +85,12 @@ class SyncToken():
|
||||
raw_kobo_store_token="",
|
||||
books_last_created=datetime.min,
|
||||
books_last_modified=datetime.min,
|
||||
reading_state_last_modified=datetime.min,
|
||||
archive_last_modified=datetime.min,
|
||||
):
|
||||
self.raw_kobo_store_token = raw_kobo_store_token
|
||||
self.books_last_created = books_last_created
|
||||
self.books_last_modified = books_last_modified
|
||||
self.reading_state_last_modified = reading_state_last_modified
|
||||
|
||||
self.archive_last_modified = archive_last_modified
|
||||
|
||||
@staticmethod
|
||||
def from_headers(headers):
|
||||
@ -123,7 +122,7 @@ class SyncToken():
|
||||
try:
|
||||
books_last_modified = get_datetime_from_json(data_json, "books_last_modified")
|
||||
books_last_created = get_datetime_from_json(data_json, "books_last_created")
|
||||
reading_state_last_modified = get_datetime_from_json(data_json, "reading_state_last_modified")
|
||||
archive_last_modified = get_datetime_from_json(data_json, "archive_last_modified")
|
||||
except TypeError:
|
||||
log.error("SyncToken timestamps don't parse to a datetime.")
|
||||
return SyncToken(raw_kobo_store_token=raw_kobo_store_token)
|
||||
@ -132,7 +131,7 @@ class SyncToken():
|
||||
raw_kobo_store_token=raw_kobo_store_token,
|
||||
books_last_created=books_last_created,
|
||||
books_last_modified=books_last_modified,
|
||||
reading_state_last_modified=reading_state_last_modified
|
||||
archive_last_modified=archive_last_modified
|
||||
)
|
||||
|
||||
def set_kobo_store_header(self, store_headers):
|
||||
@ -153,7 +152,7 @@ class SyncToken():
|
||||
"raw_kobo_store_token": self.raw_kobo_store_token,
|
||||
"books_last_modified": to_epoch_timestamp(self.books_last_modified),
|
||||
"books_last_created": to_epoch_timestamp(self.books_last_created),
|
||||
"reading_state_last_modified": to_epoch_timestamp(self.reading_state_last_modified)
|
||||
"archive_last_modified": to_epoch_timestamp(self.archive_last_modified)
|
||||
},
|
||||
}
|
||||
return b64encode_json(token)
|
||||
|
@ -216,6 +216,8 @@ if ( $( 'body.book' ).length > 0 ) {
|
||||
.prependTo( '[aria-label^="Download, send"]' );
|
||||
$( '#have_read_cb' )
|
||||
.after( '<label class="block-label readLbl" for="#have_read_cb"></label>' );
|
||||
$( '#archived_cb' )
|
||||
.after( '<label class="block-label readLbl" for="#archived_cb"></label>' );
|
||||
$( '#shelf-actions' ).prependTo( '[aria-label^="Download, send"]' );
|
||||
|
||||
|
||||
@ -586,6 +588,20 @@ $( '#have_read_cb:checked' ).attr({
|
||||
'data-viewport': '.btn-toolbar' })
|
||||
.addClass('readunread-btn-tooltip');
|
||||
|
||||
$( '#archived_cb' ).attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'title': $( '#archived_cb').attr('data-unchecked'),
|
||||
'data-placement': 'bottom',
|
||||
'data-viewport': '.btn-toolbar' })
|
||||
.addClass('readunread-btn-tooltip');
|
||||
|
||||
$( '#archived_cb:checked' ).attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'title': $( '#archived_cb').attr('data-checked'),
|
||||
'data-placement': 'bottom',
|
||||
'data-viewport': '.btn-toolbar' })
|
||||
.addClass('readunread-btn-tooltip');
|
||||
|
||||
$( 'button#delete' ).attr({
|
||||
'data-toggle-two': 'tooltip',
|
||||
'title': $( 'button#delete' ).text(), //'Delete'
|
||||
@ -601,6 +617,14 @@ $( '#have_read_cb' ).click(function() {
|
||||
}
|
||||
});
|
||||
|
||||
$( '#archived_cb' ).click(function() {
|
||||
if ( $( '#archived_cb:checked' ).length > 0 ) {
|
||||
$( this ).attr('data-original-title', $('#archived_cb').attr('data-checked'));
|
||||
} else {
|
||||
$( this).attr('data-original-title', $('#archived_cb').attr('data-unchecked'));
|
||||
}
|
||||
});
|
||||
|
||||
$( '.btn-group[aria-label="Edit/Delete book"] a' ).attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'title': $( '#edit_book' ).text(), // 'Edit'
|
||||
|
@ -25,6 +25,14 @@ $("#have_read_cb").on("change", function() {
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#archived_form").ajaxForm();
|
||||
});
|
||||
|
||||
$("#archived_cb").on("change", function() {
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
|
||||
(function() {
|
||||
var templates = {
|
||||
add: _.template(
|
||||
|
@ -200,8 +200,16 @@
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<p>
|
||||
<span>{{_('This book will be permanently erased from database')}}</span>
|
||||
<span>{{_('and hard disk')}}</span>
|
||||
</p>
|
||||
{% if config.config_kobo_sync %}
|
||||
<p>
|
||||
<span>{{_('Important Kobo Note: deleted books will remain on any paired Kobo device.')}}</span>
|
||||
<span>{{_('Books must first be archived and the device synced before a book can safely be deleted.')}}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
@ -202,6 +202,14 @@
|
||||
</label>
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
|
||||
<label class="block-label">
|
||||
<input id="archived_cb" data-checked="{{_('Restore from archive')}}" data-unchecked="{{_('Add to archive')}}" type="checkbox" {% if is_archived %}checked{% endif %} >
|
||||
<span>{{_('Archived')}}</span>
|
||||
</label>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
17
cps/ub.py
17
cps/ub.py
@ -98,10 +98,13 @@ def get_sidebar_config(kwargs=None):
|
||||
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
|
||||
"visibility": constants.SIDEBAR_FORMAT, 'public': True,
|
||||
"page": "format", "show_text": _('Show file formats selection'), "config_show":True})
|
||||
sidebar.append(
|
||||
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
||||
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
|
||||
"show_text": _('Show archived books'), "config_show": True})
|
||||
return sidebar
|
||||
|
||||
|
||||
|
||||
class UserBase:
|
||||
|
||||
@property
|
||||
@ -310,6 +313,16 @@ class Bookmark(Base):
|
||||
format = Column(String(collation='NOCASE'))
|
||||
bookmark_key = Column(String)
|
||||
|
||||
# Baseclass representing books that are archived on the user's Kobo device.
|
||||
class ArchivedBook(Base):
|
||||
__tablename__ = 'archived_book'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
book_id = Column(Integer)
|
||||
is_archived = Column(Boolean, unique=False)
|
||||
last_modified = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
# The Kobo ReadingState API keeps track of 4 timestamped entities:
|
||||
# ReadingState, StatusInfo, Statistics, CurrentBookmark
|
||||
@ -417,6 +430,8 @@ def migrate_Database(session):
|
||||
KoboBookmark.__table__.create(bind=engine)
|
||||
if not engine.dialect.has_table(engine.connect(), "kobo_statistics"):
|
||||
KoboStatistics.__table__.create(bind=engine)
|
||||
if not engine.dialect.has_table(engine.connect(), "archived_book"):
|
||||
ArchivedBook.__table__.create(bind=engine)
|
||||
if not engine.dialect.has_table(engine.connect(), "registration"):
|
||||
ReadBook.__table__.create(bind=engine)
|
||||
conn = engine.connect()
|
||||
|
57
cps/web.py
57
cps/web.py
@ -45,10 +45,10 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from . import constants, logger, isoLanguages, services, worker
|
||||
from . import searched_ids, lm, babel, db, ub, config, get_locale, app
|
||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||
from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
|
||||
order_authors, get_typeahead, render_task_status, json_serial, get_cc_columns, \
|
||||
get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \
|
||||
check_send_to_kindle, check_read_formats, lcase, tags_filters, reset_password
|
||||
from .helper import common_filters, get_search_results, fill_indexpage, fill_indexpage_with_archived_books, \
|
||||
speaking_language, check_valid_domain, order_authors, get_typeahead, render_task_status, json_serial, \
|
||||
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||
send_registration_mail, check_send_to_kindle, check_read_formats, lcase, tags_filters, reset_password
|
||||
from .pagination import Pagination
|
||||
from .redirect import redirect_back
|
||||
|
||||
@ -347,6 +347,22 @@ def toggle_read(book_id):
|
||||
return ""
|
||||
|
||||
|
||||
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
|
||||
@login_required
|
||||
def toggle_archived(book_id):
|
||||
archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id),
|
||||
ub.ArchivedBook.book_id == book_id)).first()
|
||||
if archived_book:
|
||||
archived_book.is_archived = not archived_book.is_archived
|
||||
archived_book.last_modified = datetime.datetime.utcnow()
|
||||
else:
|
||||
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
|
||||
archived_book.is_archived = True
|
||||
ub.session.merge(archived_book)
|
||||
ub.session.commit()
|
||||
return ""
|
||||
|
||||
|
||||
'''
|
||||
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
|
||||
@login_required
|
||||
@ -542,6 +558,8 @@ def books_list(data, sort, book_id, page):
|
||||
return render_category_books(page, book_id, order)
|
||||
elif data == "language":
|
||||
return render_language_books(page, book_id, order)
|
||||
elif data == "archived":
|
||||
return render_archived_books(page, order)
|
||||
else:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
@ -1018,6 +1036,26 @@ def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs)
|
||||
title=name, page=pagename)
|
||||
|
||||
|
||||
def render_archived_books(page, order):
|
||||
order = order or []
|
||||
archived_books = (
|
||||
ub.session.query(ub.ArchivedBook)
|
||||
.filter(ub.ArchivedBook.user_id == int(current_user.id))
|
||||
.filter(ub.ArchivedBook.is_archived == True)
|
||||
.all()
|
||||
)
|
||||
archived_book_ids = [archived_book.book_id for archived_book in archived_books]
|
||||
|
||||
archived_filter = db.Books.id.in_(archived_book_ids)
|
||||
|
||||
entries, random, pagination = fill_indexpage_with_archived_books(page, db.Books, archived_filter, order,
|
||||
allow_show_archived=True)
|
||||
|
||||
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
|
||||
pagename = "archived"
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=name, page=pagename)
|
||||
|
||||
# ################################### Download/Send ##################################################################
|
||||
|
||||
|
||||
@ -1435,7 +1473,8 @@ def read_book(book_id, book_format):
|
||||
@web.route("/book/<int:book_id>")
|
||||
@login_required_if_no_ano
|
||||
def show_book(book_id):
|
||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||
entries = db.session.query(db.Books).filter(and_(db.Books.id == book_id,
|
||||
common_filters(allow_show_archived=True))).first()
|
||||
if entries:
|
||||
for index in range(0, len(entries.languages)):
|
||||
try:
|
||||
@ -1464,8 +1503,14 @@ def show_book(book_id):
|
||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||
have_read = None
|
||||
|
||||
archived_book = ub.session.query(ub.ArchivedBook).\
|
||||
filter(and_(ub.ArchivedBook.user_id == int(current_user.id),
|
||||
ub.ArchivedBook.book_id == book_id)).first()
|
||||
is_archived = archived_book and archived_book.is_archived
|
||||
|
||||
else:
|
||||
have_read = None
|
||||
is_archived = None
|
||||
|
||||
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
||||
|
||||
@ -1481,7 +1526,7 @@ def show_book(book_id):
|
||||
|
||||
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
|
||||
is_xhr=request.headers.get('X-Requested-With')=='XMLHttpRequest', title=entries.title, books_shelfs=book_in_shelfs,
|
||||
have_read=have_read, kindle_list=kindle_list, reader_list=reader_list, page="book")
|
||||
have_read=have_read, is_archived=is_archived, kindle_list=kindle_list, reader_list=reader_list, page="book")
|
||||
else:
|
||||
log.debug(u"Error opening eBook. File does not exist or file is not accessible:")
|
||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||
|
Loading…
Reference in New Issue
Block a user