From 66ee5ccacfe1d0b8bd97ad89779dcb3afb95ca53 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Tue, 21 Dec 2021 17:24:15 +0100
Subject: [PATCH 01/19] Fix tests?
---
bookwyrm/tests/views/test_annual_summary.py | 23 +++++++++++++--------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/bookwyrm/tests/views/test_annual_summary.py b/bookwyrm/tests/views/test_annual_summary.py
index e50731c7f..d191395c4 100644
--- a/bookwyrm/tests/views/test_annual_summary.py
+++ b/bookwyrm/tests/views/test_annual_summary.py
@@ -12,6 +12,11 @@ from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
+def make_date(*args):
+ """helper function to easily generate a date obj"""
+ return datetime(*args, tzinfo=pytz.UTC)
+
+
class AnnualSummary(TestCase):
"""views"""
@@ -91,18 +96,18 @@ class AnnualSummary(TestCase):
validate_html(result.render())
self.assertEqual(result.status_code, 200)
+ @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
+ @patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
def test_annual_summary_page(self, *_):
"""there are so many views, this just makes sure it LOADS"""
- shelf = self.local_user.shelf_set.get(identifier="read")
-
- with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
- models.ShelfBook.objects.create(
- book=self.book,
- user=self.local_user,
- shelf=shelf,
- shelved_date=datetime(2020, 1, 1),
- )
+ shelf = self.local_user.shelf_set.filter(identifier="read").first()
+ models.ShelfBook.objects.create(
+ book=self.book,
+ user=self.local_user,
+ shelf=shelf,
+ shelved_date=make_date(2020, 1, 1),
+ )
view = views.AnnualSummary.as_view()
request = self.factory.get("")
From 67092fd3e32538bc7c513da1b0e50689c9e8c25a Mon Sep 17 00:00:00 2001
From: Joachim
Date: Tue, 21 Dec 2021 17:35:57 +0100
Subject: [PATCH 02/19] :facepalm:
---
bookwyrm/tests/views/test_annual_summary.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/bookwyrm/tests/views/test_annual_summary.py b/bookwyrm/tests/views/test_annual_summary.py
index d191395c4..2245adbc5 100644
--- a/bookwyrm/tests/views/test_annual_summary.py
+++ b/bookwyrm/tests/views/test_annual_summary.py
@@ -1,5 +1,6 @@
"""testing the annual summary page"""
from datetime import datetime
+import pytz
from unittest.mock import patch
from django.contrib.auth.models import AnonymousUser
From 0da0a626609794c9bed84dac823e151de6cc4728 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 11:18:26 +0100
Subject: [PATCH 03/19] Don't crash if there's no ratings
---
bookwyrm/views/annual_summary.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 26374ea93..cfdd6093f 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -105,7 +105,9 @@ class AnnualSummary(View):
"book_pages_highest": book_list_by_pages.last(),
"no_page_number": no_page_list,
"ratings_total": len(ratings),
- "rating_average": round(ratings_stats["rating__avg"], 2),
+ "rating_average": round(
+ ratings_stats["rating__avg"] if ratings_stats["rating__avg"] else 0, 2
+ ),
"book_rating_highest": ratings.order_by("-rating").first(),
"best_ratings_books_ids": [
review.book.id for review in ratings.filter(rating=5)
From ce9c6f1727014be3336150194506eabbb5cbedbf Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 14:53:34 +0100
Subject: [PATCH 04/19] Color links in green
---
bookwyrm/templates/annual_summary/layout.html | 16 ++++++++--------
bookwyrm/templates/snippets/authors.html | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index f994e9787..99c5343db 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -67,13 +67,13 @@
- {% endif %}
+{% endif %}
+{% endwith %}
{% endblock %}
diff --git a/bookwyrm/templates/feed/summary_card.html b/bookwyrm/templates/feed/summary_card.html
index e726792e9..a5bc4643a 100644
--- a/bookwyrm/templates/feed/summary_card.html
+++ b/bookwyrm/templates/feed/summary_card.html
@@ -15,7 +15,7 @@
-
+
{% blocktrans %}Discover your stats for {{ year }}!{% endblocktrans %}
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py
index 7b7bc3740..30e54bc75 100644
--- a/bookwyrm/urls.py
+++ b/bookwyrm/urls.py
@@ -479,7 +479,11 @@ urlpatterns = [
),
# annual summary
re_path(
- r"^my-year-in-the-books/(?P\d{4})/?$",
+ r"^my-year-in-the-books/(?P\d+)/?$",
+ views.personal_annual_summary,
+ ),
+ re_path(
+ rf"{LOCAL_USER_PATH}/(?P\d+)-in-the-books/?$",
views.AnnualSummary.as_view(),
name="annual-summary",
),
diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py
index 30195fad6..2eeda1148 100644
--- a/bookwyrm/views/__init__.py
+++ b/bookwyrm/views/__init__.py
@@ -96,4 +96,7 @@ from .status import edit_readthrough
from .updates import get_notification_count, get_unread_status_count
from .user import User, Followers, Following, hide_suggestions
from .wellknown import *
-from .annual_summary import AnnualSummary
+from .annual_summary import (
+ AnnualSummary,
+ personal_annual_summary,
+)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index cfdd6093f..5a98c009e 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -1,13 +1,17 @@
"""end-of-year read books stats"""
from datetime import date
+from uuid import uuid4
+from django.contrib.auth.decorators import login_required
from django.db.models import Case, When, Avg, Sum
from django.http import Http404
-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.views import View
+from django.views.decorators.http import require_POST
from bookwyrm import models
+from .helpers import get_user_from_username
# December day of first availability
@@ -16,6 +20,100 @@ FIRST_DAY = 15
LAST_DAY = 15
+# pylint: disable= no-self-use
+class AnnualSummary(View):
+ """display a summary of the year for the current user"""
+
+ def get(self, request, username, year):
+ """get response"""
+
+ if not is_year_available(year):
+ raise Http404(f"The summary for {year} is unavailable")
+
+ paginated_years = (
+ int(year) - 1,
+ int(year) + 1 if is_year_available(int(year) + 1) else None,
+ )
+
+ user = get_user_from_username(request.user, username)
+
+ year_key = None
+ if user.summary_keys and year in user.summary_keys:
+ year_key = user.summary_keys[year]
+
+ # verify key
+ if user != request.user:
+ request_key = None
+ if "key" in request.GET:
+ request_key = request.GET["key"]
+
+ if not request_key or request_key != year_key:
+ raise Http404(f"The summary for {year} is unavailable")
+
+ # get data
+ read_book_ids_in_year = get_read_book_ids_in_year(user, year)
+
+ if len(read_book_ids_in_year) == 0:
+ data = {
+ "summary_user": user,
+ "year": year,
+ "book_total": 0,
+ "books": [],
+ "paginated_years": paginated_years,
+ }
+ return TemplateResponse(request, "annual_summary/layout.html", data)
+
+ read_books_in_year = get_books_from_shelfbooks(read_book_ids_in_year)
+
+ # pages stats queries
+ page_stats = read_books_in_year.aggregate(Sum("pages"), Avg("pages"))
+ book_list_by_pages = read_books_in_year.filter(pages__gte=0).order_by("pages")
+
+ # books with no pages
+ no_page_list = len(read_books_in_year.filter(pages__exact=None))
+
+ # rating stats queries
+ ratings = (
+ models.Review.objects.filter(user=user)
+ .exclude(deleted=True)
+ .exclude(rating=None)
+ .filter(book_id__in=read_book_ids_in_year)
+ )
+ ratings_stats = ratings.aggregate(Avg("rating"))
+
+ data = {
+ "summary_user": user,
+ "year": year,
+ "books_total": len(read_books_in_year),
+ "books": read_books_in_year,
+ "pages_total": page_stats["pages__sum"],
+ "pages_average": round(
+ page_stats["pages__avg"] if page_stats["pages__avg"] else 0
+ ),
+ "book_pages_lowest": book_list_by_pages.first(),
+ "book_pages_highest": book_list_by_pages.last(),
+ "no_page_number": no_page_list,
+ "ratings_total": len(ratings),
+ "rating_average": round(
+ ratings_stats["rating__avg"] if ratings_stats["rating__avg"] else 0, 2
+ ),
+ "book_rating_highest": ratings.order_by("-rating").first(),
+ "best_ratings_books_ids": [
+ review.book.id for review in ratings.filter(rating=5)
+ ],
+ "paginated_years": paginated_years,
+ }
+
+ return TemplateResponse(request, "annual_summary/layout.html", data)
+
+
+@login_required
+def personal_annual_summary(request, year):
+ """redirect simple URL to URL with username"""
+
+ return redirect("annual-summary", request.user.localname, year)
+
+
def get_annual_summary_year():
"""return the latest available annual summary year or None"""
@@ -44,80 +142,6 @@ def is_year_available(year):
return False
-# pylint: disable= no-self-use
-class AnnualSummary(View):
- """display a summary of the year for the current user"""
-
- def get(self, request, year):
- """get response"""
-
- if not is_year_available(year):
- raise Http404(f"The summary for {year} is unavailable")
-
- paginated_years = (
- int(year) - 1,
- int(year) + 1 if is_year_available(int(year) + 1) else None,
- )
-
- user = request.user
-
- if not user.is_authenticated:
- raise Http404(f"Login or register {year} to access this page")
-
- read_book_ids_in_year = get_read_book_ids_in_year(user, year)
-
- if len(read_book_ids_in_year) == 0:
- data = {
- "year": year,
- "book_total": 0,
- "books": [],
- "paginated_years": paginated_years,
- }
- return TemplateResponse(request, "annual_summary/layout.html", data)
-
- read_books_in_year = get_books_from_shelfbooks(read_book_ids_in_year)
-
- # pages stats queries
- page_stats = read_books_in_year.aggregate(Sum("pages"), Avg("pages"))
- book_list_by_pages = read_books_in_year.filter(pages__gte=0).order_by("pages")
-
- # books with no pages
- no_page_list = len(read_books_in_year.filter(pages__exact=None))
-
- # rating stats queries
- ratings = (
- models.Review.objects.filter(user=user)
- .exclude(deleted=True)
- .exclude(rating=None)
- .filter(book_id__in=read_book_ids_in_year)
- )
- ratings_stats = ratings.aggregate(Avg("rating"))
-
- data = {
- "year": year,
- "books_total": len(read_books_in_year),
- "books": read_books_in_year,
- "pages_total": page_stats["pages__sum"],
- "pages_average": round(
- page_stats["pages__avg"] if page_stats["pages__avg"] else 0
- ),
- "book_pages_lowest": book_list_by_pages.first(),
- "book_pages_highest": book_list_by_pages.last(),
- "no_page_number": no_page_list,
- "ratings_total": len(ratings),
- "rating_average": round(
- ratings_stats["rating__avg"] if ratings_stats["rating__avg"] else 0, 2
- ),
- "book_rating_highest": ratings.order_by("-rating").first(),
- "best_ratings_books_ids": [
- review.book.id for review in ratings.filter(rating=5)
- ],
- "paginated_years": paginated_years,
- }
-
- return TemplateResponse(request, "annual_summary/layout.html", data)
-
-
def get_read_book_ids_in_year(user, year):
"""return an ordered QuerySet of the read book ids"""
From af9c983145171a02928978d0004079b718dfb33a Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 15:11:55 +0100
Subject: [PATCH 07/19] Change voice for no read book
---
bookwyrm/templates/annual_summary/layout.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index f20a8f00c..46f9c0b32 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -44,9 +44,9 @@
{% blocktrans %}{{ display_name }}’s year of reading{% endblocktrans %}
- {% if not books %}
- {% blocktrans %}Sadly you didn't finish any book in {{ year }}{% endblocktrans %}
- {% else %}
+{% if not books %}
+ {% blocktrans %}Sadly {{ display_name }} didn’t finish any book in {{ year }}{% endblocktrans %}
+{% else %}
From 45dd39d370f3b371103fb2ef993ebb41e8904b2d Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 15:12:56 +0100
Subject: [PATCH 08/19] Add key creation/revocation
---
bookwyrm/templates/annual_summary/layout.html | 45 +++++++++++++++++++
bookwyrm/urls.py | 4 ++
bookwyrm/views/__init__.py | 2 +
bookwyrm/views/annual_summary.py | 42 +++++++++++++++++
4 files changed, 93 insertions(+)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index 46f9c0b32..0b46e725f 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -44,6 +44,51 @@
{% blocktrans %}{{ display_name }}’s year of reading{% endblocktrans %}
+
+
+ Share this page
+
+
+
+
+ {% if user == summary_user %}
+ {% if year_key %}
+
+
+
Sharing status: public with key
+
The page can be seen by anyone with the complete address.
+
+
+
+ {% else %}
+
+
+
Sharing status: private
+
The page is private, only you can see it.
+
+
+
+ {% endif %}
+
When you make your page private, the old key won’t give access to the page anymore. A new key will be created if the page is once again made public.
+ {% endif %}
+
+
+
+
+
+
{% if not books %}
{% blocktrans %}Sadly {{ display_name }} didn’t finish any book in {{ year }}{% endblocktrans %}
{% else %}
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py
index 30e54bc75..7220b545c 100644
--- a/bookwyrm/urls.py
+++ b/bookwyrm/urls.py
@@ -487,4 +487,8 @@ urlpatterns = [
views.AnnualSummary.as_view(),
name="annual-summary",
),
+ re_path(r"^summary_add_key/?$", views.summary_add_key, name="summary-add-key"),
+ re_path(
+ r"^summary_revoke_key/?$", views.summary_revoke_key, name="summary-revoke-key"
+ ),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py
index 2eeda1148..8b1f56488 100644
--- a/bookwyrm/views/__init__.py
+++ b/bookwyrm/views/__init__.py
@@ -99,4 +99,6 @@ from .wellknown import *
from .annual_summary import (
AnnualSummary,
personal_annual_summary,
+ summary_add_key,
+ summary_revoke_key,
)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 5a98c009e..03d724c09 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -57,6 +57,7 @@ class AnnualSummary(View):
data = {
"summary_user": user,
"year": year,
+ "year_key": year_key,
"book_total": 0,
"books": [],
"paginated_years": paginated_years,
@@ -84,6 +85,7 @@ class AnnualSummary(View):
data = {
"summary_user": user,
"year": year,
+ "year_key": year_key,
"books_total": len(read_books_in_year),
"books": read_books_in_year,
"pages_total": page_stats["pages__sum"],
@@ -114,6 +116,46 @@ def personal_annual_summary(request, year):
return redirect("annual-summary", request.user.localname, year)
+@login_required
+@require_POST
+def summary_add_key(request):
+ """add summary key"""
+
+ year = request.POST["year"]
+ user = request.user
+
+ new_key = uuid4().hex
+
+ if not user.summary_keys:
+ user.summary_keys = {
+ year: new_key,
+ }
+ else:
+ user.summary_keys[year] = new_key
+
+ user.save()
+
+ response = redirect("annual-summary", user.localname, year)
+ response["Location"] += f"?key={str(new_key)}"
+ return response
+
+
+@login_required
+@require_POST
+def summary_revoke_key(request):
+ """revoke summary key"""
+
+ year = request.POST["year"]
+ user = request.user
+
+ if user.summary_keys and year in user.summary_keys:
+ user.summary_keys.pop(year)
+
+ user.save()
+
+ return redirect("annual-summary", user.localname, year)
+
+
def get_annual_summary_year():
"""return the latest available annual summary year or None"""
From 8de2bca2c530da5872d63cb8575f2b2847eac524 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 15:13:42 +0100
Subject: [PATCH 09/19] Add + adjust text copy component
---
bookwyrm/static/css/bookwyrm.css | 35 +++++++++++++++++++
bookwyrm/static/js/bookwyrm.js | 2 --
bookwyrm/templates/annual_summary/layout.html | 6 ++++
bookwyrm/templates/lists/list.html | 4 ++-
4 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css
index b63ac0725..19a7cac89 100644
--- a/bookwyrm/static/css/bookwyrm.css
+++ b/bookwyrm/static/css/bookwyrm.css
@@ -136,6 +136,10 @@ summary::marker {
content: none;
}
+summary {
+ cursor: pointer;
+}
+
.detail-pinned-button summary {
position: absolute;
right: 0;
@@ -586,6 +590,37 @@ ol.ordered-list li::before {
min-height: calc(2 * var(--height-basis));
}
+/* Copy
+ ******************************************************************************/
+
+.horizontal-copy {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: .75rem;
+}
+
+.horizontal-copy textarea {
+ min-width: initial;
+ white-space: nowrap;
+}
+
+.horizontal-copy button {
+ align-self: stretch;
+ height: unset;
+}
+
+.vertical-copy {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: .75rem;
+}
+
+.vertical-copy button {
+ width: 100%;
+}
+
/* Dimensions
* @todo These could be in rem.
******************************************************************************/
diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js
index 2b78bf518..2e446a856 100644
--- a/bookwyrm/static/js/bookwyrm.js
+++ b/bookwyrm/static/js/bookwyrm.js
@@ -465,10 +465,8 @@ let BookWyrm = new class {
copyButtonEl.textContent = textareaEl.dataset.copytextLabel;
copyButtonEl.classList.add(
- "mt-2",
"button",
"is-small",
- "is-fullwidth",
"is-primary",
"is-light"
);
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index 0b46e725f..4b7572244 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -51,6 +51,12 @@
+ {% if year_key %}
+
+
+
+ {% endif %}
+
{% if user == summary_user %}
{% if year_key %}
diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html
index 412ca470f..836ca864e 100644
--- a/bookwyrm/templates/lists/list.html
+++ b/bookwyrm/templates/lists/list.html
@@ -190,7 +190,9 @@
{% trans "Embed this list on a website" %}
-
+
+
+
From 357eddf16e443eda210ac1f863c04ca3f5164e10 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 16:52:42 +0100
Subject: [PATCH 10/19] Limit page availability to earliest completed
readthrough / shelving
---
bookwyrm/templates/annual_summary/layout.html | 2 +
bookwyrm/views/annual_summary.py | 62 ++++++++++++++++---
2 files changed, 54 insertions(+), 10 deletions(-)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index 4b7572244..b4089e8bc 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -14,12 +14,14 @@
{% if user == summary_user %}
{% with year=paginated_years|first %}
+ {% if year %}
+ {% endif %}
{% endwith %}
{% with year=paginated_years|last %}
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 03d724c09..b63c30a0d 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -27,14 +27,6 @@ class AnnualSummary(View):
def get(self, request, username, year):
"""get response"""
- if not is_year_available(year):
- raise Http404(f"The summary for {year} is unavailable")
-
- paginated_years = (
- int(year) - 1,
- int(year) + 1 if is_year_available(int(year) + 1) else None,
- )
-
user = get_user_from_username(request.user, username)
year_key = None
@@ -50,6 +42,14 @@ class AnnualSummary(View):
if not request_key or request_key != year_key:
raise Http404(f"The summary for {year} is unavailable")
+ if not is_year_available(user, year):
+ raise Http404(f"The summary for {year} is unavailable")
+
+ paginated_years = (
+ int(year) - 1 if is_year_available(user, int(year) - 1) else None,
+ int(year) + 1 if is_year_available(user, int(year) + 1) else None,
+ )
+
# get data
read_book_ids_in_year = get_read_book_ids_in_year(user, year)
@@ -171,12 +171,13 @@ def get_annual_summary_year():
return None
-def is_year_available(year):
+def is_year_available(user, year):
"""return boolean"""
+ earliest_year = get_earliest_year(user)
today = date.today()
year = int(year)
- if year < today.year:
+ if year < today.year and year >= earliest_year:
return True
if year == today.year and today >= date(today.year, 12, FIRST_DAY):
return True
@@ -184,6 +185,36 @@ def is_year_available(year):
return False
+def get_earliest_year(user):
+ """return the earliest finish_date or shelved_date year for user books in read shelf"""
+
+ read_shelfbooks = models.ShelfBook.objects.filter(user__id=user.id).filter(
+ shelf__identifier__exact="read"
+ )
+ read_shelfbooks_list = list(read_shelfbooks.values("book", "shelved_date"))
+
+ book_dates = []
+
+ for book in read_shelfbooks_list:
+ earliest_finished = (
+ models.ReadThrough.objects.filter(user__id=user.id)
+ .filter(book_id=book["book"])
+ .exclude(finish_date__exact=None)
+ .order_by("finish_date")
+ .values("finish_date")
+ .first()
+ )
+
+ if earliest_finished:
+ book_dates.append(
+ min(earliest_finished["finish_date"], book["shelved_date"])
+ )
+ else:
+ book_dates.append(book["shelved_date"])
+
+ return min(book_dates).year
+
+
def get_read_book_ids_in_year(user, year):
"""return an ordered QuerySet of the read book ids"""
@@ -196,6 +227,17 @@ def get_read_book_ids_in_year(user, year):
.values_list("book", flat=True)
)
return read_book_ids_in_year
+ models.ReadThrough.objects.filter(user__id=user.id)
+ .filter(book_id=book[0])
+ .exists()
+ )
+ if not has_other_year_readthrough and book[1].year == int(year):
+ # No readthrough but shelved this year
+ book_dates.append(book)
+
+ book_dates = sorted(book_dates, key=lambda tup: tup[1])
+
+ return [book[0] for book in book_dates]
def get_books_from_shelfbooks(books_ids):
From 13ee7e7a65e685fe68a2f1d9b5050b14f34e4ca2 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 16:53:10 +0100
Subject: [PATCH 11/19] Use readthrough finish date instead of shelving date
---
bookwyrm/views/annual_summary.py | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index b63c30a0d..2ab99d23b 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -219,14 +219,28 @@ def get_read_book_ids_in_year(user, year):
"""return an ordered QuerySet of the read book ids"""
read_shelf = get_object_or_404(user.shelf_set, identifier="read")
- read_book_ids_in_year = (
+ shelved_book_ids = (
models.ShelfBook.objects.filter(shelf=read_shelf)
.filter(user=user)
- .filter(shelved_date__year=year)
- .order_by("shelved_date", "created_date", "updated_date")
- .values_list("book", flat=True)
+ .values_list("book", "shelved_date")
)
- return read_book_ids_in_year
+
+ book_dates = []
+
+ for book in shelved_book_ids:
+ finished_in_year = (
+ models.ReadThrough.objects.filter(user__id=user.id)
+ .filter(book_id=book[0])
+ .filter(finish_date__year=year)
+ .values("finish_date")
+ .first()
+ )
+
+ if finished_in_year:
+ # Finished a readthrough in the year
+ book_dates.append((book[0], finished_in_year["finish_date"]))
+ else:
+ has_other_year_readthrough = (
models.ReadThrough.objects.filter(user__id=user.id)
.filter(book_id=book[0])
.exists()
From d4b8aa51f698df69a53d6c54173de56711d76a33 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 16:55:02 +0100
Subject: [PATCH 12/19] lint styles
---
bookwyrm/static/css/bookwyrm.css | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css
index 19a7cac89..43a59a628 100644
--- a/bookwyrm/static/css/bookwyrm.css
+++ b/bookwyrm/static/css/bookwyrm.css
@@ -594,10 +594,10 @@ ol.ordered-list li::before {
******************************************************************************/
.horizontal-copy {
- display: flex;
+ display: flex;
flex-direction: row;
align-items: center;
- gap: .75rem;
+ gap: 0.75rem;
}
.horizontal-copy textarea {
@@ -611,10 +611,10 @@ ol.ordered-list li::before {
}
.vertical-copy {
- display: flex;
+ display: flex;
flex-direction: column;
align-items: stretch;
- gap: .75rem;
+ gap: 0.75rem;
}
.vertical-copy button {
From 04d51cde3f9c434e7e6f9efe5453992168b93624 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 17:09:12 +0100
Subject: [PATCH 13/19] pylint
---
bookwyrm/views/annual_summary.py | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 2ab99d23b..5c4b04eef 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -33,17 +33,7 @@ class AnnualSummary(View):
if user.summary_keys and year in user.summary_keys:
year_key = user.summary_keys[year]
- # verify key
- if user != request.user:
- request_key = None
- if "key" in request.GET:
- request_key = request.GET["key"]
-
- if not request_key or request_key != year_key:
- raise Http404(f"The summary for {year} is unavailable")
-
- if not is_year_available(user, year):
- raise Http404(f"The summary for {year} is unavailable")
+ privacy_verification(request, user, year, year_key)
paginated_years = (
int(year) - 1 if is_year_available(user, int(year) - 1) else None,
@@ -171,13 +161,27 @@ def get_annual_summary_year():
return None
+def privacy_verification(request, user, year, year_key):
+ if user != request.user:
+ request_key = None
+ if "key" in request.GET:
+ request_key = request.GET["key"]
+
+ if not request_key or request_key != year_key:
+ raise Http404(f"The summary for {year} is unavailable")
+
+ if not is_year_available(user, year):
+ raise Http404(f"The summary for {year} is unavailable")
+
+
+
def is_year_available(user, year):
"""return boolean"""
earliest_year = get_earliest_year(user)
today = date.today()
year = int(year)
- if year < today.year and year >= earliest_year:
+ if earliest_year <= year < today.year:
return True
if year == today.year and today >= date(today.year, 12, FIRST_DAY):
return True
From b9265bdd29daa150e9af54532309a926d9b00b12 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 17:10:47 +0100
Subject: [PATCH 14/19] Update annual_summary.py
---
bookwyrm/views/annual_summary.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 5c4b04eef..350090a65 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -174,7 +174,6 @@ def privacy_verification(request, user, year, year_key):
raise Http404(f"The summary for {year} is unavailable")
-
def is_year_available(user, year):
"""return boolean"""
From b03b6f6d6f0d0331227b016b41ecb71253d3064f Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 17:14:21 +0100
Subject: [PATCH 15/19] Add docstring
---
bookwyrm/views/annual_summary.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index 350090a65..eeecf2d7e 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -162,6 +162,7 @@ def get_annual_summary_year():
def privacy_verification(request, user, year, year_key):
+ """raises a 404 error if the user should not access the page"""
if user != request.user:
request_key = None
if "key" in request.GET:
From 07f2d9a11c25f2c963cbb1af4dad9e86870dd4f0 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 18:15:05 +0100
Subject: [PATCH 16/19] Update tests
---
bookwyrm/tests/views/test_annual_summary.py | 88 +++++++++++++--------
bookwyrm/views/annual_summary.py | 9 ++-
2 files changed, 63 insertions(+), 34 deletions(-)
diff --git a/bookwyrm/tests/views/test_annual_summary.py b/bookwyrm/tests/views/test_annual_summary.py
index 2245adbc5..d62540288 100644
--- a/bookwyrm/tests/views/test_annual_summary.py
+++ b/bookwyrm/tests/views/test_annual_summary.py
@@ -34,6 +34,7 @@ class AnnualSummary(TestCase):
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
+ summary_keys={"2020": "0123456789"},
)
self.work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create(
@@ -42,18 +43,11 @@ class AnnualSummary(TestCase):
parent_work=self.work,
pages=300,
)
- with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
- self.review = models.Review.objects.create(
- name="Review name",
- content="test content",
- rating=3.0,
- user=self.local_user,
- book=self.book,
- )
+
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
- self.year = 2020
+ self.year = "2020"
models.SiteSettings.objects.create()
def test_annual_summary_not_authenticated(self, *_):
@@ -62,12 +56,24 @@ class AnnualSummary(TestCase):
request = self.factory.get("")
request.user = self.anonymous_user
- with patch(
- "bookwyrm.views.annual_summary.is_year_available"
- ) as is_year_available:
- is_year_available.return_value = True
- with self.assertRaises(Http404):
- view(request, self.year)
+ with self.assertRaises(Http404):
+ view(request, self.local_user.localname, self.year)
+
+ def test_annual_summary_not_authenticated_with_key(self, *_):
+ """there are so many views, this just makes sure it DOES LOAD"""
+ key = self.local_user.summary_keys[self.year]
+ view = views.AnnualSummary.as_view()
+ request_url = (
+ f"user/{self.local_user.localname}/{self.year}-in-the-books?key={key}"
+ )
+ request = self.factory.get(request_url)
+ request.user = self.anonymous_user
+
+ result = view(request, self.local_user.localname, self.year)
+
+ self.assertIsInstance(result, TemplateResponse)
+ validate_html(result.render())
+ self.assertEqual(result.status_code, 200)
def test_annual_summary_wrong_year(self, *_):
"""there are so many views, this just makes sure it DOESN’T LOAD"""
@@ -75,12 +81,8 @@ class AnnualSummary(TestCase):
request = self.factory.get("")
request.user = self.anonymous_user
- with patch(
- "bookwyrm.views.annual_summary.is_year_available"
- ) as is_year_available:
- is_year_available.return_value = False
- with self.assertRaises(Http404):
- view(request, self.year)
+ with self.assertRaises(Http404):
+ view(request, self.local_user.localname, self.year)
def test_annual_summary_empty_page(self, *_):
"""there are so many views, this just makes sure it LOADS"""
@@ -88,11 +90,8 @@ class AnnualSummary(TestCase):
request = self.factory.get("")
request.user = self.local_user
- with patch(
- "bookwyrm.views.annual_summary.is_year_available"
- ) as is_year_available:
- is_year_available.return_value = True
- result = view(request, self.year)
+ result = view(request, self.local_user.localname, self.year)
+
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
@@ -114,11 +113,38 @@ class AnnualSummary(TestCase):
request = self.factory.get("")
request.user = self.local_user
- with patch(
- "bookwyrm.views.annual_summary.is_year_available"
- ) as is_year_available:
- is_year_available.return_value = True
- result = view(request, self.year)
+ result = view(request, self.local_user.localname, self.year)
+
+ self.assertIsInstance(result, TemplateResponse)
+ validate_html(result.render())
+ self.assertEqual(result.status_code, 200)
+
+ @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
+ @patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
+ def test_annual_summary_page_with_review(self, *_):
+ """there are so many views, this just makes sure it LOADS"""
+
+ self.review = models.Review.objects.create(
+ name="Review name",
+ content="test content",
+ rating=3.0,
+ user=self.local_user,
+ book=self.book,
+ )
+
+ shelf = self.local_user.shelf_set.filter(identifier="read").first()
+ models.ShelfBook.objects.create(
+ book=self.book,
+ user=self.local_user,
+ shelf=shelf,
+ shelved_date=make_date(2020, 1, 1),
+ )
+
+ view = views.AnnualSummary.as_view()
+ request = self.factory.get("")
+ request.user = self.local_user
+
+ result = view(request, self.local_user.localname, self.year)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
diff --git a/bookwyrm/views/annual_summary.py b/bookwyrm/views/annual_summary.py
index eeecf2d7e..bfebf7f5a 100644
--- a/bookwyrm/views/annual_summary.py
+++ b/bookwyrm/views/annual_summary.py
@@ -178,7 +178,7 @@ def privacy_verification(request, user, year, year_key):
def is_year_available(user, year):
"""return boolean"""
- earliest_year = get_earliest_year(user)
+ earliest_year = int(get_earliest_year(user, year))
today = date.today()
year = int(year)
if earliest_year <= year < today.year:
@@ -189,7 +189,7 @@ def is_year_available(user, year):
return False
-def get_earliest_year(user):
+def get_earliest_year(user, year):
"""return the earliest finish_date or shelved_date year for user books in read shelf"""
read_shelfbooks = models.ShelfBook.objects.filter(user__id=user.id).filter(
@@ -216,7 +216,10 @@ def get_earliest_year(user):
else:
book_dates.append(book["shelved_date"])
- return min(book_dates).year
+ if book_dates:
+ return min(book_dates).year
+
+ return year
def get_read_book_ids_in_year(user, year):
From df7b40359a6ede2974ff9da4dfbbdc4f47c4ab96 Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 18:15:20 +0100
Subject: [PATCH 17/19] Add translation calls
---
bookwyrm/templates/annual_summary/layout.html | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index b4089e8bc..f9d289f73 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -48,7 +48,9 @@
- Share this page
+
+ {% trans "Share this page" %}
+
@@ -63,29 +65,29 @@
{% if year_key %}
-
Sharing status: public with key
-
The page can be seen by anyone with the complete address.
+
{% trans "Sharing status: public with key" %}
+
{% trans "The page can be seen by anyone with the complete address." %}
{% else %}
-
Sharing status: private
-
The page is private, only you can see it.
+
{% trans "Sharing status: private" %}
+
{% trans "The page is private, only you can see it." %}
{% endif %}
-
When you make your page private, the old key won’t give access to the page anymore. A new key will be created if the page is once again made public.
+
{% trans "When you make your page private, the old key won’t give access to the page anymore. A new key will be created if the page is once again made public." %}
{% endif %}
From 53146816418c8b1c90d42a569b5ecb6dcb84700d Mon Sep 17 00:00:00 2001
From: Joachim
Date: Wed, 22 Dec 2021 19:47:39 +0100
Subject: [PATCH 18/19] Update layout.html
---
bookwyrm/templates/annual_summary/layout.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index f9d289f73..7423d8e4d 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -47,8 +47,8 @@
-
-
+
+
{% trans "Share this page" %}
From 85486dcfad512b6d6f5d531f98c60b10fa167437 Mon Sep 17 00:00:00 2001
From: Mouse Reeve
Date: Mon, 27 Dec 2021 13:29:47 -0800
Subject: [PATCH 19/19] Update bookwyrm.css
---
bookwyrm/static/css/bookwyrm.css | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css
index 3c8386dc2..4d9aabb4a 100644
--- a/bookwyrm/static/css/bookwyrm.css
+++ b/bookwyrm/static/css/bookwyrm.css
@@ -125,6 +125,10 @@ input[type=file]::file-selector-button:hover {
/** General `details` element styles
******************************************************************************/
+summary {
+ cursor: pointer;
+}
+
summary::-webkit-details-marker {
display: none;
}
@@ -133,10 +137,6 @@ summary::marker {
content: none;
}
-summary {
- cursor: pointer;
-}
-
.detail-pinned-button summary {
position: absolute;
right: 0;