Merge branch 'main' into page-range

This commit is contained in:
Giebisch 2023-02-06 14:02:05 +01:00
commit 248eab22ed
22 changed files with 109 additions and 40 deletions

View file

@ -13,3 +13,5 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
- uses: psf/black@22.12.0 - uses: psf/black@22.12.0
with:
version: 22.12.0

View file

@ -1,5 +1,6 @@
""" functionality outline for a book data connector """ """ functionality outline for a book data connector """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from urllib.parse import quote_plus
import imghdr import imghdr
import logging import logging
import re import re
@ -48,7 +49,7 @@ class AbstractMinimalConnector(ABC):
return f"{self.isbn_search_url}{normalized_query}" return f"{self.isbn_search_url}{normalized_query}"
# NOTE: previously, we tried searching isbn and if that produces no results, # NOTE: previously, we tried searching isbn and if that produces no results,
# searched as free text. This, instead, only searches isbn if it's isbn-y # searched as free text. This, instead, only searches isbn if it's isbn-y
return f"{self.search_url}{query}" return f"{self.search_url}{quote_plus(query)}"
def process_search_response(self, query, data, min_confidence): def process_search_response(self, query, data, min_confidence):
"""Format the search results based on the formt of the query""" """Format the search results based on the formt of the query"""

View file

@ -52,7 +52,7 @@ class AnnualGoal(BookWyrmModel):
user=self.user, user=self.user,
book__in=book_ids, book__in=book_ids,
) )
return {r.book.id: r.rating for r in reviews} return {r.book_id: r.rating for r in reviews}
@property @property
def progress(self): def progress(self):

View file

@ -32,7 +32,7 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""update user active time""" """update user active time"""
cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") cache.delete(f"latest_read_through-{self.user_id}-{self.book_id}")
self.user.update_active_date() self.user.update_active_date()
# an active readthrough must have an unset finish date # an active readthrough must have an unset finish date
if self.finish_date or self.stopped_date: if self.finish_date or self.stopped_date:

View file

@ -107,7 +107,7 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
# remove all caches related to all editions of this book # remove all caches related to all editions of this book
cache.delete_many( cache.delete_many(
[ [
f"book-on-shelf-{book.id}-{self.shelf.id}" f"book-on-shelf-{book.id}-{self.shelf_id}"
for book in self.book.parent_work.editions.all() for book in self.book.parent_work.editions.all()
] ]
) )
@ -117,7 +117,7 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
if self.id and self.user.local: if self.id and self.user.local:
cache.delete_many( cache.delete_many(
[ [
f"book-on-shelf-{book}-{self.shelf.id}" f"book-on-shelf-{book}-{self.shelf_id}"
for book in self.book.parent_work.editions.values_list( for book in self.book.parent_work.editions.values_list(
"id", flat=True "id", flat=True
) )

View file

@ -80,7 +80,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""save and notify""" """save and notify"""
if self.reply_parent: if self.reply_parent:
self.thread_id = self.reply_parent.thread_id or self.reply_parent.id self.thread_id = self.reply_parent.thread_id or self.reply_parent_id
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -6,16 +6,16 @@
@use 'bulma/bulma.sass'; @use 'bulma/bulma.sass';
.shepherd-button { .shepherd-button {
@extend .button.mr-2; @extend .button, .mr-2;
} }
.shepherd-button.shepherd-button-secondary { .shepherd-button.shepherd-button-secondary {
@extend .button.is-light; @extend .button, .is-light;
} }
.shepherd-footer { .shepherd-footer {
@extend .message-body; @extend .message-body;
@extend .is-info.is-light; @extend .is-info, .is-light;
border-color: $info-light; border-color: $info-light;
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
} }
@ -29,7 +29,7 @@
.shepherd-text { .shepherd-text {
@extend .message-body; @extend .message-body;
@extend .is-info.is-light; @extend .is-info, .is-light;
border-radius: 0; border-radius: 0;
} }

View file

@ -215,10 +215,10 @@
{% endif %} {% endif %}
{% with work=book.parent_work %} {% with work=book.parent_work editions_count=book.parent_work.editions.count %}
<p> <p>
<a href="{{ work.local_path }}/editions" id="tour-other-editions-link"> <a href="{{ work.local_path }}/editions" id="tour-other-editions-link">
{% blocktrans trimmed count counter=work.editions.count with count=work.editions.count|intcomma %} {% blocktrans trimmed count counter=editions_count with count=editions_count|intcomma %}
{{ count }} edition {{ count }} edition
{% plural %} {% plural %}
{{ count }} editions {{ count }} editions

View file

@ -25,6 +25,7 @@
{% block body %} {% block body %}
<nav class="navbar" aria-label="main navigation"> <nav class="navbar" aria-label="main navigation">
<div class="container"> <div class="container">
{% with notification_count=request.user.unread_notification_count has_unread_mentions=request.user.has_unread_mentions %}
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="{% blocktrans with site_name=site.name %}{{ site_name }} home page{% endblocktrans %}"> <img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="{% blocktrans with site_name=site.name %}{{ site_name }} home page{% endblocktrans %}">
@ -67,9 +68,8 @@
> >
<i class="icon-dots-three-vertical" aria-hidden="true"></i> <i class="icon-dots-three-vertical" aria-hidden="true"></i>
{% with request.user.unread_notification_count as notification_count %}
<strong <strong
class="{% if not notification_count %}is-hidden {% elif request.user.has_unread_mentions %}is-danger {% else %}is-primary {% endif %} tag is-small px-1" class="{% if not notification_count %}is-hidden {% elif has_unread_mentions %}is-danger {% else %}is-primary {% endif %} tag is-small px-1"
data-poll-wrapper data-poll-wrapper
> >
<span class="is-sr-only">{% trans "Notifications" %}</span> <span class="is-sr-only">{% trans "Notifications" %}</span>
@ -77,7 +77,6 @@
{{ notification_count }} {{ notification_count }}
</strong> </strong>
</strong> </strong>
{% endwith %}
</button> </button>
</div> </div>
@ -108,14 +107,12 @@
<span class="is-sr-only">{% trans "Notifications" %}</span> <span class="is-sr-only">{% trans "Notifications" %}</span>
</span> </span>
</span> </span>
{% with request.user.unread_notification_count as notification_count %}
<span <span
class="{% if not notification_count %}is-hidden {% elif request.user.has_unread_mentions %}is-danger {% endif %}tag is-medium transition-x" class="{% if not notification_count %}is-hidden {% elif has_unread_mentions %}is-danger {% endif %}tag is-medium transition-x"
data-poll-wrapper data-poll-wrapper
> >
<span data-poll="notifications">{{ notification_count }}</span> <span data-poll="notifications">{{ notification_count }}</span>
</span> </span>
{% endwith %}
</a> </a>
</div> </div>
{% else %} {% else %}
@ -154,6 +151,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endwith %}
</div> </div>
</nav> </nav>

View file

@ -9,6 +9,14 @@
{% block panel %} {% block panel %}
<div class="notification">
<p>
{% trans "You can set up monitoring to check if Celery is running by querying:" %}
{% url "settings-celery-ping" as url %}
<a href="{{ url }}" target="_blank" rel="nofollow noopener noreferrer">{{ url }}</a>
</p>
</div>
{% if queues %} {% if queues %}
<section class="block content"> <section class="block content">
<h2>{% trans "Queues" %}</h2> <h2>{% trans "Queues" %}</h2>

View file

@ -15,6 +15,12 @@
</p> </p>
{% endif %} {% endif %}
{% if not user.is_active and user.deactivation_reason == "pending" %}
<form name="activate" method="post" action="{% url 'settings-activate-user' user.id %}" class="mr-1">
{% csrf_token %}
<button type="submit" class="button is-success is-light">{% trans "Activate user" %}</button>
</form>
{% endif %}
{% if user.is_active or user.deactivation_reason == "pending" %} {% if user.is_active or user.deactivation_reason == "pending" %}
<form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id %}" class="mr-1"> <form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id %}" class="mr-1">
{% csrf_token %} {% csrf_token %}

View file

@ -19,9 +19,9 @@ uuid: a unique identifier used to make html "id" attributes unique and clarify j
{# Supplemental fields #} {# Supplemental fields #}
<div> <div>
{% active_shelf book as active_shelf %} {% active_shelf book as active_shelf %}
{% if active_shelf.shelf.identifier == 'reading' and book.latest_readthrough %} {% if active_shelf.shelf.identifier == 'reading' %}
{% with readthrough=book.latest_readthrough %} {% with readthrough=book.latest_readthrough %}
{% if readthrough %}
<div class="field"> <div class="field">
<input type="hidden" name="id" value="{{ readthrough.id }}"/> <input type="hidden" name="id" value="{{ readthrough.id }}"/>
<label class="label" for="progress_{{ uuid }}">{% trans "Progress:" %}</label> <label class="label" for="progress_{{ uuid }}">{% trans "Progress:" %}</label>
@ -66,6 +66,7 @@ uuid: a unique identifier used to make html "id" attributes unique and clarify j
<p class="help">{% blocktrans with pages=book.pages %}of {{ pages }} pages{% endblocktrans %}</p> <p class="help">{% blocktrans with pages=book.pages %}of {{ pages }} pages{% endblocktrans %}</p>
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</div> </div>

View file

@ -4,11 +4,11 @@
{# Three day cache #} {# Three day cache #}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% cache 259200 generated_note_header LANGUAGE_CODE status.id %} {% cache 259200 generated_note_header LANGUAGE_CODE status.id %}
{% if status.content == 'wants to read' %} {% if status.content == 'wants to read' or status.content == '<p>wants to read</p>' %}
{% include 'snippets/status/headers/to_read.html' with book=status.mention_books.first %} {% include 'snippets/status/headers/to_read.html' with book=status.mention_books.first %}
{% elif status.content == 'finished reading' %} {% elif status.content == 'finished reading' or status.content == '<p>finished reading</p>' %}
{% include 'snippets/status/headers/read.html' with book=status.mention_books.first %} {% include 'snippets/status/headers/read.html' with book=status.mention_books.first %}
{% elif status.content == 'started reading' %} {% elif status.content == 'started reading' or status.content == '<p>started reading</p>' %}
{% include 'snippets/status/headers/reading.html' with book=status.mention_books.first %} {% include 'snippets/status/headers/reading.html' with book=status.mention_books.first %}
{% else %} {% else %}
{{ status.content }} {{ status.content }}

View file

@ -145,6 +145,11 @@ urlpatterns = [
views.UserAdmin.as_view(), views.UserAdmin.as_view(),
name="settings-user", name="settings-user",
), ),
re_path(
r"^settings/users/(?P<user>\d+)/activate/?$",
views.ActivateUserAdmin.as_view(),
name="settings-activate-user",
),
re_path( re_path(
r"^settings/federation/(?P<status>(federated|blocked))?/?$", r"^settings/federation/(?P<status>(federated|blocked))?/?$",
views.Federation.as_view(), views.Federation.as_view(),
@ -329,6 +334,9 @@ urlpatterns = [
re_path( re_path(
r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery" r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery"
), ),
re_path(
r"^settings/celery/ping/?$", views.celery_ping, name="settings-celery-ping"
),
re_path( re_path(
r"^settings/email-config/?$", r"^settings/email-config/?$",
views.EmailConfig.as_view(), views.EmailConfig.as_view(),

View file

@ -4,7 +4,7 @@ from .admin.announcements import Announcements, Announcement
from .admin.announcements import EditAnnouncement, delete_announcement from .admin.announcements import EditAnnouncement, delete_announcement
from .admin.automod import AutoMod, automod_delete, run_automod from .admin.automod import AutoMod, automod_delete, run_automod
from .admin.automod import schedule_automod_task, unschedule_automod_task from .admin.automod import schedule_automod_task, unschedule_automod_task
from .admin.celery_status import CeleryStatus from .admin.celery_status import CeleryStatus, celery_ping
from .admin.dashboard import Dashboard from .admin.dashboard import Dashboard
from .admin.federation import Federation, FederatedServer from .admin.federation import Federation, FederatedServer
from .admin.federation import AddFederatedServer, ImportServerBlocklist from .admin.federation import AddFederatedServer, ImportServerBlocklist
@ -31,7 +31,7 @@ from .admin.reports import (
) )
from .admin.site import Site, Registration, RegistrationLimited from .admin.site import Site, Registration, RegistrationLimited
from .admin.themes import Themes, delete_theme from .admin.themes import Themes, delete_theme
from .admin.user_admin import UserAdmin, UserAdminList from .admin.user_admin import UserAdmin, UserAdminList, ActivateUserAdmin
# user preferences # user preferences
from .preferences.change_password import ChangePassword from .preferences.change_password import ChangePassword

View file

@ -1,8 +1,10 @@
""" celery status """ """ celery status """
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.http import HttpResponse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from django.views.decorators.http import require_GET
import redis import redis
from celerywyrm import settings from celerywyrm import settings
@ -50,3 +52,18 @@ class CeleryStatus(View):
"errors": errors, "errors": errors,
} }
return TemplateResponse(request, "settings/celery.html", data) return TemplateResponse(request, "settings/celery.html", data)
@require_GET
# pylint: disable=unused-argument
def celery_ping(request):
"""Just tells you if Celery is on or not"""
try:
ping = celery.control.inspect().ping()
if ping:
return HttpResponse()
# pylint: disable=broad-except
except Exception:
pass
return HttpResponse(status=500)

View file

@ -1,7 +1,7 @@
""" manage user """ """ manage user """
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
@ -95,3 +95,19 @@ class UserAdmin(View):
form.save(request) form.save(request)
data = {"user": user, "group_form": form} data = {"user": user, "group_form": form}
return TemplateResponse(request, "settings/users/user.html", data) return TemplateResponse(request, "settings/users/user.html", data)
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.moderate_user", raise_exception=True),
name="dispatch",
)
class ActivateUserAdmin(View):
"""activate a user manually"""
# pylint: disable=unused-argument
def post(self, request, user):
"""activate user"""
user = get_object_or_404(models.User, id=user)
user.reactivate()
return redirect("settings-user", user.id)

View file

@ -68,7 +68,7 @@ class AnnualSummary(View):
book_list_by_pages = read_books_in_year.filter(pages__gte=0).order_by("pages") book_list_by_pages = read_books_in_year.filter(pages__gte=0).order_by("pages")
# books with no pages # books with no pages
no_page_list = len(read_books_in_year.filter(pages__exact=None)) no_page_list = read_books_in_year.filter(pages__exact=None).count()
# rating stats queries # rating stats queries
ratings = ( ratings = (
@ -95,13 +95,13 @@ class AnnualSummary(View):
"book_pages_lowest": book_list_by_pages.first(), "book_pages_lowest": book_list_by_pages.first(),
"book_pages_highest": book_list_by_pages.last(), "book_pages_highest": book_list_by_pages.last(),
"no_page_number": no_page_list, "no_page_number": no_page_list,
"ratings_total": len(ratings), "ratings_total": ratings.count(),
"rating_average": round( "rating_average": round(
ratings_stats["rating__avg"] if ratings_stats["rating__avg"] else 0, 2 ratings_stats["rating__avg"] if ratings_stats["rating__avg"] else 0, 2
), ),
"book_rating_highest": ratings.order_by("-rating").first(), "book_rating_highest": ratings.order_by("-rating").first(),
"best_ratings_books_ids": [ "best_ratings_books_ids": [
review.book.id for review in ratings.filter(rating=5) review.book_id for review in ratings.filter(rating=5)
], ],
"paginated_years": paginated_years, "paginated_years": paginated_years,
"goal_status": goal_status, "goal_status": goal_status,

View file

@ -237,16 +237,24 @@ def feed_page_data(user):
def get_suggested_books(user, max_books=5): def get_suggested_books(user, max_books=5):
"""helper to get a user's recent books""" """helper to get a user's recent books"""
book_count = 0 book_count = 0
preset_shelves = [("reading", max_books), ("read", 2), ("to-read", max_books)] preset_shelves = {"reading": max_books, "read": 2, "to-read": max_books}
suggested_books = [] suggested_books = []
for (preset, shelf_max) in preset_shelves:
user_shelves = {
shelf.identifier: shelf
for shelf in user.shelf_set.filter(
identifier__in=preset_shelves.keys()
).exclude(books__isnull=True)
}
for preset, shelf_max in preset_shelves.items():
limit = ( limit = (
shelf_max shelf_max
if shelf_max < (max_books - book_count) if shelf_max < (max_books - book_count)
else max_books - book_count else max_books - book_count
) )
shelf = user.shelf_set.get(identifier=preset) shelf = user_shelves.get(preset, None)
if not shelf.books.exists(): if not shelf:
continue continue
shelf_preview = { shelf_preview = {

View file

@ -1 +1 @@
black==22.3.0 black==22.12.0

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-26 16:43+0000\n" "POT-Creation-Date: 2023-01-30 08:21+0000\n"
"PO-Revision-Date: 2021-02-28 17:19-0800\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n"
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n" "Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\n" "Language-Team: English <LL@li.org>\n"
@ -851,7 +851,7 @@ msgstr ""
#: bookwyrm/templates/settings/registration.html:96 #: bookwyrm/templates/settings/registration.html:96
#: bookwyrm/templates/settings/registration_limited.html:76 #: bookwyrm/templates/settings/registration_limited.html:76
#: bookwyrm/templates/settings/site.html:144 #: bookwyrm/templates/settings/site.html:144
#: bookwyrm/templates/settings/users/user_moderation_actions.html:69 #: bookwyrm/templates/settings/users/user_moderation_actions.html:75
#: bookwyrm/templates/shelf/form.html:25 #: bookwyrm/templates/shelf/form.html:25
#: bookwyrm/templates/snippets/reading_modals/layout.html:18 #: bookwyrm/templates/snippets/reading_modals/layout.html:18
msgid "Save" msgid "Save"
@ -5448,7 +5448,7 @@ msgid "Remove theme"
msgstr "" msgstr ""
#: bookwyrm/templates/settings/users/delete_user_form.html:5 #: bookwyrm/templates/settings/users/delete_user_form.html:5
#: bookwyrm/templates/settings/users/user_moderation_actions.html:32 #: bookwyrm/templates/settings/users/user_moderation_actions.html:38
msgid "Permanently delete user" msgid "Permanently delete user"
msgstr "" msgstr ""
@ -5570,14 +5570,18 @@ msgid "User Actions"
msgstr "" msgstr ""
#: bookwyrm/templates/settings/users/user_moderation_actions.html:21 #: bookwyrm/templates/settings/users/user_moderation_actions.html:21
msgid "Activate user"
msgstr ""
#: bookwyrm/templates/settings/users/user_moderation_actions.html:27
msgid "Suspend user" msgid "Suspend user"
msgstr "" msgstr ""
#: bookwyrm/templates/settings/users/user_moderation_actions.html:26 #: bookwyrm/templates/settings/users/user_moderation_actions.html:32
msgid "Un-suspend user" msgid "Un-suspend user"
msgstr "" msgstr ""
#: bookwyrm/templates/settings/users/user_moderation_actions.html:48 #: bookwyrm/templates/settings/users/user_moderation_actions.html:54
msgid "Access level:" msgid "Access level:"
msgstr "" msgstr ""

View file

@ -2,7 +2,7 @@ aiohttp==3.8.3
bleach==5.0.1 bleach==5.0.1
celery==5.2.7 celery==5.2.7
colorthief==0.2.1 colorthief==0.2.1
Django==3.2.16 Django==3.2.17
django-celery-beat==2.4.0 django-celery-beat==2.4.0
django-compressor==4.3.1 django-compressor==4.3.1
django-imagekit==4.1.0 django-imagekit==4.1.0