Feature: Create annual summary page

This commit is contained in:
Joachim 2021-12-20 23:37:45 +01:00
parent f7c6cb3598
commit c1459dbcf9
5 changed files with 263 additions and 0 deletions

View file

@ -555,6 +555,35 @@ ol.ordered-list li::before {
padding: 0 0.75em;
}
/* Breadcrumbs
******************************************************************************/
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));
gap: 2em;
align-items: end;
justify-items: center;
}
.books-grid > .is-big {
grid-column: span 2;
grid-row: span 2;
justify-self: stretch;
}
.books-grid .book-cover {
width: 100%;
}
.books-grid .book-title {
--height-basis: 1.35rem;
display: block;
margin-top: 0.5rem;
line-height: var(--height-basis);
min-height: calc(2 * var(--height-basis));
}
/* Dimensions
* @todo These could be in rem.
******************************************************************************/

View file

@ -0,0 +1,152 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load static %}
{% block title %}{% blocktrans %}{{ year }} in the books{% endblocktrans %}{% endblock %}
{% block content %}
<h1 class="title is-1 has-text-centered mb-5">{% blocktrans %}{{ year }} <em>in the books</em>{% endblocktrans %}</h1>
{% if not books %}
{% blocktrans %}Sadly you didn't finish any book in {{ year }}{% endblocktrans %}
{% else %}
<div class="columns is-mobile">
<div class="column is-8 is-offset-2 has-text-centered">
<h2 class="title is-3">
{% blocktrans %}In {{ year }}, you read {{ books_total }} books<br />for a total of {{ pages_total }} pages!{% endblocktrans %}
</h2>
<p class="subtitle is-5">{% trans "Thats great!" %}</p>
<p class="title is-4">
{% blocktrans %}That makes an average of {{ pages_average }} pages per book.{% endblocktrans %}
</p>
{% if no_page_number %}
<p class="subtitle is-6">{% blocktrans %}({{ no_page_number }} books dont have pages){% endblocktrans %}</p>
{% endif %}
</div>
</div>
<div class="columns is-mobile is-align-items-center mt-5">
<div class="column is-2 is-offset-1">
<a href="{{ book_pages_lowest.local_path }}">{% include 'snippets/book_cover.html' with book=book_pages_lowest cover_class='is-w-auto-tablet is-h-l-mobile' %}</a>
</div>
<div class="column is-3">
{% trans "Your shortest read this year" %}
<p class="title is-4 is-italic">
<a href="{{ book_pages_lowest.local_path }}" class="has-text-link-dark">
{{ book_pages_lowest.title }}
</a>
</p>
{% if book_pages_lowest.authors.exists %}
<p class="subtitle is-5 mb-2">{% trans "by" %}
{% include 'snippets/authors.html' with book=book_pages_lowest %}
</p>
{% endif %}
<p class="subtitle is-6">
{% with pages=book_pages_lowest.pages %}
{% blocktrans %}<strong>{{ pages }}</strong> pages{% endblocktrans%}
{% endwith %}
</p>
</div>
<div class="column is-2">
<a href="{{ book_pages_highest.local_path }}">{% include 'snippets/book_cover.html' with book=book_pages_highest cover_class='is-w-auto-tablet is-h-l-mobile' %}</a>
</div>
<div class="column is-3">
{% trans "and the longest read" %}
<p class="title is-4 is-italic">
<a href="{{ book_pages_lowest.local_path }}" class="has-text-link-dark">
{{ book_pages_highest.title }}
</a>
</p>
{% if book_pages_highest.authors.exists %}
<p class="subtitle is-5 mb-2">{% trans "by" %}
{% include 'snippets/authors.html' with book=book_pages_highest %}
</p>
{% endif %}
<p class="subtitle is-6">
{% with pages=book_pages_highest.pages %}
{% blocktrans %}<strong>{{ pages }}</strong> pages{% endblocktrans%}
{% endwith %}
</p>
</div>
</div>
<div class="columns">
<div class="column is-one-fifth is-offset-two-fifths">
<hr />
</div>
</div>
<div class="columns">
<div class="column has-text-centered">
<h2 class="title is-3">
{% blocktrans %}You rated {{ ratings_total }} books and your average rating is {{ rating_average }}{% endblocktrans %}
</h2>
</div>
</div>
<div class="columns is-align-items-center">
<div class="column is-3 is-offset-3">
<a href="{{ book_rating_highest.book.local_path }}">{% include 'snippets/book_cover.html' with book=book_rating_highest.book cover_class='is-w-auto-tablet is-h-l-mobile' %}</a>
</div>
<div class="column is-3">
{% trans "Your best rated review" %}
<p class="title is-4 is-italic">
<a href="{{ book_rating_highest.book.local_path }}" class="has-text-link-dark">
{{ book_rating_highest.book.title }}
</a>
</p>
{% if book_rating_highest.book.authors.exists %}
<p class="subtitle is-5 mb-2">{% trans "by" %}
{% include 'snippets/authors.html' with book=book_rating_highest.book %}
</p>
{% endif %}
<p class="subtitle is-6">
{% with rating=book_rating_highest.rating|floatformat %}
{% blocktrans %}Your rating: <strong>{{ rating }}</strong>{% endblocktrans%}
{% endwith %}
</p>
</div>
</div>
<div class="columns">
<div class="column is-one-fifth is-offset-two-fifths">
<hr />
</div>
</div>
<div class="columns">
<div class="column has-text-centered">
<h2 class="title is-3">
{% blocktrans %}All the books you read in 2021{% endblocktrans %}
</h2>
</div>
</div>
<div class="columns">
<div class="column is-10 is-offset-1">
<div class="books-grid">
{% for book in books %}
{% if book.id in best_ratings_books_ids %}
<a href="{{ book.local_path }}" class="has-text-centered is-big">
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto' %}
<span class="book-title is-size-5">
{{ book.title }}
</span>
</a>
{% else %}
<a href="{{ book.local_path }}" class="has-text-centered">
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto' %}
<span class="book-title is-size-6">
{{ book.title }}
</span>
</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endblock %}

View file

@ -477,4 +477,6 @@ urlpatterns = [
re_path(
r"^ostatus_success/?$", views.ostatus_follow_success, name="ostatus-success"
),
# annual summary
re_path(r"^my-year-in-the-books/(?P<year>\d{4})/?$", views.AnnualSummary.as_view()),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -96,3 +96,4 @@ 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

View file

@ -0,0 +1,79 @@
from django.db.models import Case, When
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.views import View
from bookwyrm import models
class AnnualSummary(View):
"""display a summary of the year"""
def get(self, request, year):
"""get response"""
user = request.user
read_shelf = get_object_or_404(user.shelf_set, identifier="read")
read_shelf_books_in_year = (
models.ShelfBook.objects.filter(shelf=read_shelf)
.filter(user=user)
.filter(shelved_date__year=year)
.order_by("shelved_date", "created_date", "updated_date")
)
read_book_ids_in_year = [i.book.id for i in read_shelf_books_in_year]
preserved = Case(
*[When(pk=pk, then=pos) for pos, pk in enumerate(read_book_ids_in_year)]
)
read_books_in_year = models.Edition.objects.filter(
id__in=read_book_ids_in_year
).order_by(preserved)
"""pages stats queries"""
read_books_with_pages = read_books_in_year.filter(pages__gte=0).order_by(
"pages"
)
pages_list = [p.pages for p in read_books_with_pages]
pages_total = 0
pages_average = 0
book_pages_lowest = 0
book_pages_highest = 0
if len(pages_list) > 0:
pages_total = sum(pages_list)
pages_average = round(sum(pages_list) / len(pages_list))
book_pages_lowest = read_books_with_pages.first()
book_pages_highest = read_books_with_pages.last()
"""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)
)
best_ratings_books_ids = [review.book.id for review in ratings.filter(rating=5)]
rating_average = 0
if len(ratings) > 0:
ratings_list = [review.rating for review in ratings]
rating_average = round(sum(ratings_list) / len(ratings_list), 2)
data = {
"year": year,
"books_total": len(read_books_in_year),
"books": read_books_in_year,
"pages_total": pages_total,
"pages_average": pages_average,
"book_pages_lowest": book_pages_lowest,
"book_pages_highest": book_pages_highest,
"no_page_number": no_page_list,
"ratings_total": len(ratings),
"rating_average": rating_average,
"book_rating_highest": ratings.order_by("-rating").first(),
"best_ratings_books_ids": best_ratings_books_ids,
}
return TemplateResponse(request, "annual_summary/layout.html", data)