diff --git a/bookwyrm/templates/annual_summary/layout.html b/bookwyrm/templates/annual_summary/layout.html
index 99c5343db..f20a8f00c 100644
--- a/bookwyrm/templates/annual_summary/layout.html
+++ b/bookwyrm/templates/annual_summary/layout.html
@@ -10,12 +10,14 @@
{% endblock %}
{% block content %}
+{% with display_name=summary_user.display_name %}
+{% if user == summary_user %}
{% with year=paginated_years|first %}
{% endwith %}
@@ -23,20 +25,24 @@
{% with year=paginated_years|last %}
{% if year %}
{% endif %}
{% endwith %}
+{% endif %}
-
+
📚✨
{% blocktrans %}{{ year }} in the books{% endblocktrans %}
✨📚
+
+ {% blocktrans %}{{ display_name }}’s year of reading{% endblocktrans %}
+
{% if not books %}
{% blocktrans %}Sadly you didn't finish any book in {{ year }}{% endblocktrans %}
@@ -45,7 +51,7 @@
+ {% if ratings_total > 0 %}
- {% blocktrans %}You left {{ ratings_total }} ratings,
your average rating is {{ rating_average }}{% endblocktrans %}
+ {% blocktrans %}{{ display_name }} left {{ ratings_total }} ratings,
their average rating is {{ rating_average }}{% endblocktrans %}
@@ -124,7 +131,7 @@
{% include 'snippets/book_cover.html' with book=book_rating_highest.book cover_class='is-w-auto-tablet is-h-l-mobile' %}
@@ -148,11 +155,12 @@
+ {% endif %}
- {% blocktrans %}All the books you read in 2021{% endblocktrans %}
+ {% blocktrans %}All the books {{ display_name }} read in 2021{% endblocktrans %}
@@ -180,5 +188,6 @@
- {% 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"""