Refactored books detail page
This commit is contained in:
parent
b4262b1317
commit
917909cfdb
52
cps/db.py
52
cps/db.py
|
@ -39,9 +39,8 @@ except ImportError:
|
||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
from sqlalchemy.sql.expression import and_, true, false, text, func, or_
|
from sqlalchemy.sql.expression import and_, true, false, text, func, or_
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from babel import Locale as LC
|
|
||||||
from babel.core import UnknownLocaleError
|
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask import flash
|
from flask import flash
|
||||||
|
|
||||||
|
@ -338,15 +337,15 @@ class Books(Base):
|
||||||
isbn = Column(String(collation='NOCASE'), default="")
|
isbn = Column(String(collation='NOCASE'), default="")
|
||||||
flags = Column(Integer, nullable=False, default=1)
|
flags = Column(Integer, nullable=False, default=1)
|
||||||
|
|
||||||
authors = relationship('Authors', secondary=books_authors_link, backref='books')
|
authors = relationship(Authors, secondary=books_authors_link, backref='books')
|
||||||
tags = relationship('Tags', secondary=books_tags_link, backref='books', order_by="Tags.name")
|
tags = relationship(Tags, secondary=books_tags_link, backref='books', order_by="Tags.name")
|
||||||
comments = relationship('Comments', backref='books')
|
comments = relationship(Comments, backref='books')
|
||||||
data = relationship('Data', backref='books')
|
data = relationship(Data, backref='books')
|
||||||
series = relationship('Series', secondary=books_series_link, backref='books')
|
series = relationship(Series, secondary=books_series_link, backref='books')
|
||||||
ratings = relationship('Ratings', secondary=books_ratings_link, backref='books')
|
ratings = relationship(Ratings, secondary=books_ratings_link, backref='books')
|
||||||
languages = relationship('Languages', secondary=books_languages_link, backref='books')
|
languages = relationship(Languages, secondary=books_languages_link, backref='books')
|
||||||
publishers = relationship('Publishers', secondary=books_publishers_link, backref='books')
|
publishers = relationship(Publishers, secondary=books_publishers_link, backref='books')
|
||||||
identifiers = relationship('Identifiers', backref='books')
|
identifiers = relationship(Identifiers, backref='books')
|
||||||
|
|
||||||
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover,
|
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover,
|
||||||
authors, tags, languages=None):
|
authors, tags, languages=None):
|
||||||
|
@ -602,6 +601,33 @@ class CalibreDB():
|
||||||
return self.session.query(Books).filter(Books.id == book_id). \
|
return self.session.query(Books).filter(Books.id == book_id). \
|
||||||
filter(self.common_filters(allow_show_archived)).first()
|
filter(self.common_filters(allow_show_archived)).first()
|
||||||
|
|
||||||
|
def get_book_read_archived(self, book_id, read_column, allow_show_archived=False):
|
||||||
|
# Add missing relationships for inter database joins
|
||||||
|
#setattr(Books, "is_archived",
|
||||||
|
# relationship(ub.ArchivedBook,
|
||||||
|
# uselist=False,
|
||||||
|
# foreign_keys=ub.ArchivedBook.book_id,
|
||||||
|
# primaryjoin=and_(Books.id == ub.ArchivedBook.book_id,
|
||||||
|
# int(current_user.id) == ub.ArchivedBook.user_id)))
|
||||||
|
if not read_column:
|
||||||
|
bd = (self.session.query(Books, ub.ReadBook.read_status, ub.ArchivedBook.is_archived).select_from(Books)
|
||||||
|
.join(ub.ReadBook, and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id),
|
||||||
|
isouter=True))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
read_column = cc_classes[read_column]
|
||||||
|
bd = (self.session.query(Books, read_column.value, ub.ArchivedBook.is_archived).select_from(Books)
|
||||||
|
.join(read_column, read_column.book == book_id,
|
||||||
|
isouter=True))
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
log.error("Custom Column No.%d is not existing in calibre database", read_column)
|
||||||
|
# Skip linking read column and return None instead of read status
|
||||||
|
bd = self.session.query(Books, None, ub.ArchivedBook.is_archived)
|
||||||
|
return (bd.filter(Books.id == book_id)
|
||||||
|
.join(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,
|
||||||
|
int(current_user.id) == ub.ArchivedBook.user_id), isouter=True)
|
||||||
|
.filter(self.common_filters(allow_show_archived)).first())
|
||||||
|
|
||||||
def get_book_by_uuid(self, book_uuid):
|
def get_book_by_uuid(self, book_uuid):
|
||||||
return self.session.query(Books).filter(Books.uuid == book_uuid).first()
|
return self.session.query(Books).filter(Books.uuid == book_uuid).first()
|
||||||
|
|
||||||
|
@ -709,8 +735,6 @@ class CalibreDB():
|
||||||
entries = query.order_by(*order).offset(off).limit(pagesize).all()
|
entries = query.order_by(*order).offset(off).limit(pagesize).all()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.debug_or_exception(ex)
|
log.debug_or_exception(ex)
|
||||||
#for book in entries:
|
|
||||||
# book = self.order_authors(book)
|
|
||||||
return entries, randm, pagination
|
return entries, randm, pagination
|
||||||
|
|
||||||
# Orders all Authors in the list according to authors sort
|
# Orders all Authors in the list according to authors sort
|
||||||
|
@ -730,7 +754,7 @@ class CalibreDB():
|
||||||
authors_ordered.append(r)
|
authors_ordered.append(r)
|
||||||
if not error:
|
if not error:
|
||||||
entry.authors = authors_ordered
|
entry.authors = authors_ordered
|
||||||
return entry
|
return authors_ordered
|
||||||
|
|
||||||
def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()):
|
def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()):
|
||||||
query = query or ''
|
query = query or ''
|
||||||
|
|
|
@ -376,7 +376,7 @@ def render_edit_book(book_id):
|
||||||
for lang in book.languages:
|
for lang in book.languages:
|
||||||
lang.language_name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
|
lang.language_name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
|
||||||
|
|
||||||
book = calibre_db.order_authors(book)
|
book.authors = calibre_db.order_authors(book)
|
||||||
|
|
||||||
author_names = []
|
author_names = []
|
||||||
for authr in book.authors:
|
for authr in book.authors:
|
||||||
|
@ -1249,9 +1249,9 @@ def table_xchange_author_title():
|
||||||
modif_date = False
|
modif_date = False
|
||||||
book = calibre_db.get_book(val)
|
book = calibre_db.get_book(val)
|
||||||
authors = book.title
|
authors = book.title
|
||||||
entries = calibre_db.order_authors(book)
|
book.authors = calibre_db.order_authors(book)
|
||||||
author_names = []
|
author_names = []
|
||||||
for authr in entries.authors:
|
for authr in book.authors:
|
||||||
author_names.append(authr.name.replace('|', ','))
|
author_names.append(authr.name.replace('|', ','))
|
||||||
|
|
||||||
title_change = handle_title_on_edit(book, " ".join(author_names))
|
title_change = handle_title_on_edit(book, " ".join(author_names))
|
||||||
|
|
|
@ -36,9 +36,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if g.user.kindle_mail and kindle_list %}
|
{% if g.user.kindle_mail and entry.kindle_list %}
|
||||||
{% if kindle_list.__len__() == 1 %}
|
{% if entry.kindle_list.__len__() == 1 %}
|
||||||
<a href="{{url_for('web.send_to_kindle', book_id=entry.id, book_format=kindle_list[0]['format'], convert=kindle_list[0]['convert'])}}" id="sendbtn" data-text="{{_('Send to Kindle')}}" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{kindle_list[0]['text']}}</a>
|
<a href="{{url_for('web.send_to_kindle', book_id=entry.id, book_format=entry.kindle_list[0]['format'], convert=entry.kindle_list[0]['convert'])}}" id="sendbtn" data-text="{{_('Send to Kindle')}}" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-send"></span> {{entry.kindle_list[0]['text']}}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button id="sendbtn2" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button id="sendbtn2" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@ -46,52 +46,52 @@
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="send-to-kindle">
|
<ul class="dropdown-menu" aria-labelledby="send-to-kindle">
|
||||||
{% for format in kindle_list %}
|
{% for format in entry.kindle_list %}
|
||||||
<li><a href="{{url_for('web.send_to_kindle', book_id=entry.id, book_format=format['format'], convert=format['convert'])}}">{{format['text']}}</a></li>
|
<li><a href="{{url_for('web.send_to_kindle', book_id=entry.id, book_format=format['format'], convert=format['convert'])}}">{{format['text']}}</a></li>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if reader_list and g.user.role_viewer() %}
|
{% if entry.reader_list and g.user.role_viewer() %}
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
{% if reader_list|length > 1 %}
|
{% if entry.reader_list|length > 1 %}
|
||||||
<button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}}
|
<span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="read-in-browser">
|
<ul class="dropdown-menu" aria-labelledby="read-in-browser">
|
||||||
{% for format in reader_list %}
|
{% for format in entry.reader_list %}
|
||||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{format}}</a></li>
|
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{format}}</a></li>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=reader_list[0])}}" id="readbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}} - {{reader_list[0]}}</a>
|
<a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=entry.reader_list[0])}}" id="readbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-book"></span> {{_('Read in Browser')}} - {{entry.reader_list[0]}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if audioentries|length > 0 and g.user.role_viewer() %}
|
{% if entry.audioentries|length > 0 and g.user.role_viewer() %}
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
{% if audioentries|length > 1 %}
|
{% if entry.audioentries|length > 1 %}
|
||||||
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="glyphicon glyphicon-music"></span> {{_('Listen in Browser')}}
|
<span class="glyphicon glyphicon-music"></span> {{_('Listen in Browser')}}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||||
{% for format in reader_list %}
|
{% for format in entry.reader_list %}
|
||||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{format}}</a></li>
|
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{format}}</a></li>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||||
|
|
||||||
{% for format in entry.data %}
|
{% for format in entry.data %}
|
||||||
{% if format.format|lower in audioentries %}
|
{% if format.format|lower in entry.audioentries %}
|
||||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format.format|lower) }}">{{format.format|lower }}</a></li>
|
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format.format|lower) }}">{{format.format|lower }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=audioentries[0])}}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-music"></span> {{_('Listen in Browser')}} - {{audioentries[0]}}</a>
|
<a target="_blank" href="{{url_for('web.read_book', book_id=entry.id, book_format=entry.audioentries[0])}}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-music"></span> {{_('Listen in Browser')}} - {{entry.audioentries[0]}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
<form id="have_read_form" action="{{ url_for('web.toggle_read', book_id=entry.id)}}" method="POST">
|
<form id="have_read_form" action="{{ url_for('web.toggle_read', book_id=entry.id)}}" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<label class="block-label">
|
<label class="block-label">
|
||||||
<input id="have_read_cb" data-checked="{{_('Mark As Unread')}}" data-unchecked="{{_('Mark As Read')}}" type="checkbox" {% if have_read %}checked{% endif %} >
|
<input id="have_read_cb" data-checked="{{_('Mark As Unread')}}" data-unchecked="{{_('Mark As Read')}}" type="checkbox" {% if entry.read_status %}checked{% endif %} >
|
||||||
<span>{{_('Read')}}</span>
|
<span>{{_('Read')}}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
@ -226,7 +226,7 @@
|
||||||
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
|
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<label class="block-label">
|
<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 %} >
|
<input id="archived_cb" data-checked="{{_('Restore from archive')}}" data-unchecked="{{_('Add to archive')}}" type="checkbox" {% if entry.is_archived.is_archived %}checked{% endif %} >
|
||||||
<span>{{_('Archived')}}</span>
|
<span>{{_('Archived')}}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
|
63
cps/web.py
63
cps/web.py
|
@ -187,7 +187,7 @@ def toggle_read(book_id):
|
||||||
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400
|
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400
|
||||||
except (OperationalError, InvalidRequestError) as e:
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
log.error(u"Read status could not set: %e", e)
|
log.error(u"Read status could not set: {}".format(e))
|
||||||
return "Read status could not set: {}".format(e), 400
|
return "Read status could not set: {}".format(e), 400
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -1756,63 +1756,40 @@ def read_book(book_id, book_format):
|
||||||
@web.route("/book/<int:book_id>")
|
@web.route("/book/<int:book_id>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def show_book(book_id):
|
def show_book(book_id):
|
||||||
entries = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
entries = calibre_db.get_book_read_archived(book_id, config.config_read_column, allow_show_archived=True)
|
||||||
if entries:
|
if entries:
|
||||||
for index in range(0, len(entries.languages)):
|
read_book = entries[1]
|
||||||
entries.languages[index].language_name = isoLanguages.get_language_name(get_locale(), entries.languages[
|
archived_book = entries[2]
|
||||||
|
entry = entries[0]
|
||||||
|
entry.read_status = read_book == ub.ReadBook.STATUS_FINISHED
|
||||||
|
entry.is_archived = archived_book
|
||||||
|
for index in range(0, len(entry.languages)):
|
||||||
|
entry.languages[index].language_name = isoLanguages.get_language_name(get_locale(), entry.languages[
|
||||||
index].lang_code)
|
index].lang_code)
|
||||||
cc = get_cc_columns(filter_config_custom_read=True)
|
cc = get_cc_columns(filter_config_custom_read=True)
|
||||||
book_in_shelfs = []
|
book_in_shelfs = []
|
||||||
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
||||||
for entry in shelfs:
|
for sh in shelfs:
|
||||||
book_in_shelfs.append(entry.shelf)
|
book_in_shelfs.append(sh.shelf)
|
||||||
|
|
||||||
if not current_user.is_anonymous:
|
entry.tags = sort(entry.tags, key=lambda tag: tag.name)
|
||||||
if not config.config_read_column:
|
|
||||||
matching_have_read_book = ub.session.query(ub.ReadBook). \
|
|
||||||
filter(and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
|
|
||||||
have_read = len(
|
|
||||||
matching_have_read_book) > 0 and matching_have_read_book[0].read_status == ub.ReadBook.STATUS_FINISHED
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
matching_have_read_book = getattr(entries, 'custom_column_' + str(config.config_read_column))
|
|
||||||
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
|
|
||||||
except (KeyError, AttributeError):
|
|
||||||
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).\
|
entry.authors = calibre_db.order_authors(entry)
|
||||||
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:
|
entry.kindle_list = check_send_to_kindle(entry)
|
||||||
have_read = None
|
entry.reader_list = check_read_formats(entry)
|
||||||
is_archived = None
|
|
||||||
|
|
||||||
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
entry.audioentries = []
|
||||||
|
for media_format in entry.data:
|
||||||
entries = calibre_db.order_authors(entries)
|
|
||||||
|
|
||||||
kindle_list = check_send_to_kindle(entries)
|
|
||||||
reader_list = check_read_formats(entries)
|
|
||||||
|
|
||||||
audioentries = []
|
|
||||||
for media_format in entries.data:
|
|
||||||
if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
|
if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
|
||||||
audioentries.append(media_format.format.lower())
|
entry.audioentries.append(media_format.format.lower())
|
||||||
|
|
||||||
return render_title_template('detail.html',
|
return render_title_template('detail.html',
|
||||||
entry=entries,
|
entry=entry,
|
||||||
audioentries=audioentries,
|
|
||||||
cc=cc,
|
cc=cc,
|
||||||
is_xhr=request.headers.get('X-Requested-With')=='XMLHttpRequest',
|
is_xhr=request.headers.get('X-Requested-With')=='XMLHttpRequest',
|
||||||
title=entries.title,
|
title=entry.title,
|
||||||
books_shelfs=book_in_shelfs,
|
books_shelfs=book_in_shelfs,
|
||||||
have_read=have_read,
|
|
||||||
is_archived=is_archived,
|
|
||||||
kindle_list=kindle_list,
|
|
||||||
reader_list=reader_list,
|
|
||||||
page="book")
|
page="book")
|
||||||
else:
|
else:
|
||||||
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
|
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user