forked from mirrors/bookwyrm
Feature: Create annual summary page
This commit is contained in:
parent
f7c6cb3598
commit
c1459dbcf9
5 changed files with 263 additions and 0 deletions
|
@ -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.
|
||||
******************************************************************************/
|
||||
|
|
152
bookwyrm/templates/annual_summary/layout.html
Normal file
152
bookwyrm/templates/annual_summary/layout.html
Normal 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 "That’s 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 don’t 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 %}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
79
bookwyrm/views/annual_summary.py
Normal file
79
bookwyrm/views/annual_summary.py
Normal 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)
|
Loading…
Reference in a new issue