Commit 9c1b3f13 authored by Ozzieisaacs's avatar Ozzieisaacs

Improved sorting for rated,random, hot books, read/unread book

parent a66873a9
...@@ -25,8 +25,8 @@ import os ...@@ -25,8 +25,8 @@ import os
from flask import Blueprint, flash, redirect, url_for from flask import Blueprint, flash, redirect, url_for
from flask import abort, request, make_response from flask import abort, request, make_response
from flask_login import login_required, current_user, logout_user from flask_login import login_required, current_user, logout_user
from web import admin_required, render_title_template, before_request, speaking_language, unconfigured, \ from web import admin_required, render_title_template, before_request, unconfigured, \
login_required_if_no_ano, check_valid_domain login_required_if_no_ano
from cps import db, ub, Server, get_locale, config, app, updater_thread, babel from cps import db, ub, Server, get_locale, config, app, updater_thread, babel
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
...@@ -37,6 +37,7 @@ from babel import Locale as LC ...@@ -37,6 +37,7 @@ from babel import Locale as LC
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
import helper import helper
from helper import speaking_language, check_valid_domain
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
try: try:
from imp import reload from imp import reload
......
...@@ -31,8 +31,9 @@ import json ...@@ -31,8 +31,9 @@ import json
from flask_babel import gettext as _ from flask_babel import gettext as _
from uuid import uuid4 from uuid import uuid4
import helper import helper
from helper import order_authors, common_filters
from flask_login import current_user from flask_login import current_user
from web import login_required_if_no_ano, common_filters, order_authors, render_title_template, edit_required, \ from web import login_required_if_no_ano, render_title_template, edit_required, \
upload_required, login_required, EXTENSIONS_UPLOAD upload_required, login_required, EXTENSIONS_UPLOAD
import gdriveutils import gdriveutils
from shutil import move, copyfile from shutil import move, copyfile
......
...@@ -32,9 +32,15 @@ from flask import send_from_directory, make_response, redirect, abort ...@@ -32,9 +32,15 @@ from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user from flask_login import current_user
from babel.dates import format_datetime from babel.dates import format_datetime
from babel.core import UnknownLocaleError
from datetime import datetime from datetime import datetime
from babel import Locale as LC
import shutil import shutil
import requests import requests
from sqlalchemy.sql.expression import true, and_, false, text, func
from iso639 import languages as isoLanguages
from pagination import Pagination
try: try:
import gdriveutils as gd import gdriveutils as gd
except ImportError: except ImportError:
...@@ -558,3 +564,109 @@ def render_task_status(tasklist): ...@@ -558,3 +564,109 @@ def render_task_status(tasklist):
renderedtasklist.append(task) renderedtasklist.append(task)
return renderedtasklist return renderedtasklist
# Language and content filters for displaying in the UI
def common_filters():
if current_user.filter_language() != "all":
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
lang_filter = true()
content_rating_filter = false() if current_user.mature_content else \
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
return and_(lang_filter, ~content_rating_filter)
# Creates for all stored languages a translated speaking name in the array for the UI
def speaking_language(languages=None):
if not languages:
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 UnknownLocaleError:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
return languages
# checks if domain is in database (including wildcards)
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
def check_valid_domain(domain_text):
domain_text = domain_text.split('@', 1)[-1].lower()
sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
return len(result)
# Orders all Authors in the list according to authors sort
def order_authors(entry):
sort_authors = entry.author_sort.split('&')
authors_ordered = list()
error = False
for auth in sort_authors:
# ToDo: How to handle not found authorname
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
if not result:
error = True
break
authors_ordered.append(result)
if not error:
entry.authors = authors_ordered
return entry
# Fill indexpage with all requested data from database
def fill_indexpage(page, database, db_filter, order, *join):
if current_user.show_detail_random():
randm = db.session.query(db.Books).filter(common_filters())\
.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()).\
order_by(*order).offset(off).limit(config.config_books_per_page).all()
for book in entries:
book = order_authors(book)
return entries, randm, pagination
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(term):
q = list()
authorterms = re.split("[, ]+", term)
for authorterm in authorterms:
q.append(db.Books.authors.any(db.Authors.name.ilike("%" + authorterm + "%")))
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
db.Books.authors.any(db.Authors.name.ilike("%" + term + "%"))
return db.session.query(db.Books).filter(common_filters()).filter(
db.or_(db.Books.tags.any(db.Tags.name.ilike("%" + term + "%")),
db.Books.series.any(db.Series.name.ilike("%" + term + "%")),
db.Books.authors.any(and_(*q)),
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
db.Books.title.ilike("%" + term + "%"))).all()
def get_unique_other_books(library_books, author_books):
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
# Note: Not all images will be shown, even though they're available on Goodreads.com.
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
library_books, [])
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
author_books)
# Fuzzy match book titles
if feature_support['levenshtein']:
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
other_books = filter(lambda author_book: not filter(
lambda library_book:
# Remove items in parentheses before comparing
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
library_titles
), other_books)
return other_books
...@@ -30,12 +30,12 @@ import datetime ...@@ -30,12 +30,12 @@ import datetime
import ub import ub
from flask_login import current_user from flask_login import current_user
from functools import wraps from functools import wraps
from web import login_required_if_no_ano, fill_indexpage, common_filters, get_search_results, render_read_books from web import login_required_if_no_ano, common_filters, get_search_results, render_read_books, download_required
from sqlalchemy.sql.expression import func, text from sqlalchemy.sql.expression import func, text
import helper import helper
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from web import download_required from helper import fill_indexpage
import sys import sys
try: try:
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
<div class="discover load-more"> <div class="discover load-more">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<div class="row"> <div class="row">
{% for entry in entries %} {% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book"> <div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">
......
...@@ -58,14 +58,14 @@ web. {% for entry in random %} ...@@ -58,14 +58,14 @@ web. {% for entry in random %}
<div class="discover load-more"> <div class="discover load-more">
<h2 class="{{title}}">{{_(title)}}</h2> <h2 class="{{title}}">{{_(title)}}</h2>
<div class="filterheader hidden-xs hidden-sm"> <div class="filterheader hidden-xs hidden-sm">
<a id="new" class="btn btn-primary" href="{{url_for('web.newest_books')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a> <a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.oldest_books')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> <a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.titles_ascending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a> <a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a> <a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></a> <a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></a> <a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<div class="btn-group character" role="group"> <div class="btn-group character" role="group">
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a> <a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a>
<div id="all" class="btn btn-primary">{{_('All')}}</div> <div id="all" class="btn btn-primary">{{_('All')}}</div>
</div> </div>
</div> </div>
......
...@@ -121,22 +121,10 @@ ...@@ -121,22 +121,10 @@
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav"> <ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<li class="nav-head hidden-xs">{{_('Browse')}}</li> <li class="nav-head hidden-xs">{{_('Browse')}}</li>
{% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate --> {% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate -->
<!--li id="nav_sort" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-sort-by-attributes"></span>{{_('Sorted Books')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li id="nav_sort_old" {% if page == 'newest' %}class="active"{% endif %}><a href="{{url_for('web.newest_books')}}">{{_('Sort By')}} {{_('Newest')}}</a></li>
<li id="nav_sort_new" {% if page == 'oldest' %}class="active"{% endif %}><a href="{{url_for('web.oldest_books')}}">{{_('Sort By')}} {{_('Oldest')}}</a></li>
<li id="nav_sort_asc" {% if page == 'a-z' %}class="active"{% endif %}><a href="{{url_for('web.titles_ascending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Ascending')}})</a></li>
<li id="nav_sort_desc" {% if page == 'z-a' %}class="active"{% endif %}><a href="{{url_for('web.titles_descending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Descending')}})</a></li>
</ul>
</li-->
{%endif%} {%endif%}
{% for element in sidebar %} {% for element in sidebar %}
{% if g.user.check_visibility(element['visibility']) and element['public'] %} {% if g.user.check_visibility(element['visibility']) and element['public'] %}
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'])}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li> <li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort='new')}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
......
...@@ -107,21 +107,21 @@ def get_sidebar_config(kwargs=[]): ...@@ -107,21 +107,21 @@ def get_sidebar_config(kwargs=[]):
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new", sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
"visibility": SIDEBAR_RECENT, 'public': True, "page": "root", "visibility": SIDEBAR_RECENT, 'public': True, "page": "root",
"show_text": _('Show recent books'), "config_show":True}) "show_text": _('Show recent books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.hot_books', "id": "hot", sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
"visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'), "visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
"config_show":True}) "config_show":True})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.best_rated_books', "id": "rated", {"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.books_list', "id": "rated",
"visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated", "visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
"show_text": _('Show best rated books'), "config_show":True}) "show_text": _('Show best rated books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.read_books', "id": "read", sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read", "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
"show_text": _('Show read and unread'), "config_show": content}) "show_text": _('Show read and unread'), "config_show": content})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.unread_books', "id": "unread", {"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read", "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
"show_text": _('Show unread'), "config_show":False}) "show_text": _('Show unread'), "config_show":False})
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.discover', "id": "rand", sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
"visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover", "visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover",
"show_text": _('Show random books'), "config_show":True}) "show_text": _('Show random books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat", sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment