mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
802d28b4a7
29 changed files with 215 additions and 88 deletions
|
@ -55,6 +55,8 @@ class ActivityStream(RedisStore):
|
||||||
return (
|
return (
|
||||||
models.Status.objects.select_subclasses()
|
models.Status.objects.select_subclasses()
|
||||||
.filter(id__in=statuses)
|
.filter(id__in=statuses)
|
||||||
|
.select_related("user", "reply_parent")
|
||||||
|
.prefetch_related("mention_books", "mention_users")
|
||||||
.order_by("-published_date")
|
.order_by("-published_date")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -381,17 +381,16 @@ class AnnualGoal(BookWyrmModel):
|
||||||
return {r.book.id: r.rating for r in reviews}
|
return {r.book.id: r.rating for r in reviews}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def progress_percent(self):
|
def progress(self):
|
||||||
"""how close to your goal, in percent form"""
|
|
||||||
return int(float(self.book_count / self.goal) * 100)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def book_count(self):
|
|
||||||
"""how many books you've read this year"""
|
"""how many books you've read this year"""
|
||||||
return self.user.readthrough_set.filter(
|
count = self.user.readthrough_set.filter(
|
||||||
finish_date__year__gte=self.year,
|
finish_date__year__gte=self.year,
|
||||||
finish_date__year__lt=self.year + 1,
|
finish_date__year__lt=self.year + 1,
|
||||||
).count()
|
).count()
|
||||||
|
return {
|
||||||
|
"count": count,
|
||||||
|
"percent": int(float(count / self.goal) * 100),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
|
|
|
@ -43,6 +43,19 @@ body {
|
||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
width: 0.01em !important;
|
width: 0.01em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-0-mobile {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer.is-stacked-mobile {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer.is-stacked-mobile .card-footer-item:not(:last-child) {
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.is-transparent {
|
.button.is-transparent {
|
||||||
|
@ -331,6 +344,49 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Book list
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
ol.ordered-list {
|
||||||
|
list-style: none;
|
||||||
|
counter-reset: list-counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.ordered-list li {
|
||||||
|
counter-increment: list-counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.ordered-list li::before {
|
||||||
|
content: counter(list-counter);
|
||||||
|
position: absolute;
|
||||||
|
left: -20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
border-right: 0;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
ol.ordered-list li::before {
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border: 0;
|
||||||
|
border-right: 1px solid #dbdbdb;
|
||||||
|
border-bottom: 1px solid #dbdbdb;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Dimensions
|
/* Dimensions
|
||||||
* @todo These could be in rem.
|
* @todo These could be in rem.
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}
|
{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}
|
||||||
|
|
||||||
{% block title %}{{ book.title }}{% endblock %}
|
{% block title %}{{ book|title }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %}
|
{% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %}
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
|
|
||||||
{# user's relationship to the book #}
|
{# user's relationship to the book #}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% for shelf in user_shelves %}
|
{% for shelf in user_shelfbooks %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans with path=shelf.shelf.local_path shelf_name=shelf.shelf.name %}This edition is on your <a href="{{ path }}">{{ shelf_name }}</a> shelf.{% endblocktrans %}
|
{% blocktrans with path=shelf.shelf.local_path shelf_name=shelf.shelf.name %}This edition is on your <a href="{{ path }}">{{ shelf_name }}</a> shelf.{% endblocktrans %}
|
||||||
{% include 'snippets/shelf_selector.html' with current=shelf.shelf %}
|
{% include 'snippets/shelf_selector.html' with current=shelf.shelf %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="select is-small mt-1 mb-3">
|
<div class="select is-small mt-1 mb-3">
|
||||||
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
|
||||||
<option disabled selected value>Add to your books</option>
|
<option disabled selected value>Add to your books</option>
|
||||||
{% for shelf in request.user.shelf_set.all %}
|
{% for shelf in user_shelves %}
|
||||||
<option value="{{ shelf.id }}">{{ shelf.name }}</option>
|
<option value="{{ shelf.id }}">{{ shelf.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -134,12 +134,14 @@
|
||||||
<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 request.user.unread_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 request.user.has_unread_mentions %}is-danger {% endif %}tag is-medium transition-x"
|
||||||
data-poll-wrapper
|
data-poll-wrapper
|
||||||
>
|
>
|
||||||
<span data-poll="notifications">{{ request.user.unread_notification_count }}</span>
|
<span data-poll="notifications">{{ notification_count }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
{% endwith %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -193,8 +195,11 @@
|
||||||
|
|
||||||
<div class="section is-flex-grow-1">
|
<div class="section is-flex-grow-1">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{# almost every view needs to know the user shelves #}
|
||||||
|
{% with request.user.shelf_set.all as user_shelves %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
{% if not items.object_list.exists %}
|
{% if not items.object_list.exists %}
|
||||||
<p>{% trans "This list is currently empty" %}</p>
|
<p>{% trans "This list is currently empty" %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ol start="{{ items.start_index }}">
|
<ol start="{{ items.start_index }}" class="ordered-list">
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<li class="block mb-5">
|
<li class="block mb-5">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -35,16 +35,17 @@
|
||||||
<div
|
<div
|
||||||
class="
|
class="
|
||||||
card-content p-0 mb-0
|
card-content p-0 mb-0
|
||||||
columns is-mobile is-gapless
|
columns is-gapless
|
||||||
|
is-mobile
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="column is-2-mobile is-cover align to-t">
|
<div class="column is-3-mobile is-2-tablet is-cover align to-t">
|
||||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
||||||
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet' %}
|
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column mx-3 my-2">
|
||||||
<p>
|
<p>
|
||||||
{% include 'snippets/book_titleby.html' %}
|
{% include 'snippets/book_titleby.html' %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -59,7 +60,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<div class="card-footer has-background-white-bis is-align-items-baseline">
|
<div class="card-footer is-stacked-mobile has-background-white-bis is-align-items-stretch">
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<div>
|
<div>
|
||||||
<p>{% blocktrans with username=item.user.display_name user_path=user.local_path %}Added by <a href="{{ user_path }}">{{ username }}</a>{% endblocktrans %}</p>
|
<p>{% blocktrans with username=item.user.display_name user_path=user.local_path %}Added by <a href="{{ user_path }}">{{ username }}</a>{% endblocktrans %}</p>
|
||||||
|
@ -69,6 +70,9 @@
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<form name="set-position" method="post" action="{% url 'list-set-book-position' item.id %}">
|
<form name="set-position" method="post" action="{% url 'list-set-book-position' item.id %}">
|
||||||
<div class="field has-addons mb-0">
|
<div class="field has-addons mb-0">
|
||||||
|
<div class="control">
|
||||||
|
<label for="input-list-position" class="button is-transparent is-small">{% trans "List position" %}</label>
|
||||||
|
</div>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input id="input-list-position" class="input is-small" type="number" min="1" name="position" value="{{ item.order }}">
|
<input id="input-list-position" class="input is-small" type="number" min="1" name="position" value="{{ item.order }}">
|
||||||
|
@ -77,7 +81,6 @@
|
||||||
<button type="submit" class="button is-info is-small is-tablet">{% trans "Set" %}</button>
|
<button type="submit" class="button is-info is-small is-tablet">{% trans "Set" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label for="input-list-position" class="help">{% trans "List position" %}</label>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<form name="add-book" method="post" action="{% url 'list-remove-book' list.id %}" class="card-footer-item">
|
<form name="add-book" method="post" action="{% url 'list-remove-book' list.id %}" class="card-footer-item">
|
||||||
|
@ -96,24 +99,36 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="column is-one-quarter">
|
<section class="column is-one-quarter">
|
||||||
<h2>{% trans "Sort List" %}</h2>
|
<h2 class="title is-5">
|
||||||
|
{% trans "Sort List" %}
|
||||||
|
</h2>
|
||||||
<form name="sort" action="{% url 'list' list.id %}" method="GET" class="block">
|
<form name="sort" action="{% url 'list' list.id %}" method="GET" class="block">
|
||||||
|
<div class="field">
|
||||||
<label class="label" for="id_sort_by">{% trans "Sort By" %}</label>
|
<label class="label" for="id_sort_by">{% trans "Sort By" %}</label>
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
{{ sort_form.sort_by }}
|
{{ sort_form.sort_by }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
<label class="label" for="id_direction">{% trans "Direction" %}</label>
|
<label class="label" for="id_direction">{% trans "Direction" %}</label>
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
{{ sort_form.direction }}
|
{{ sort_form.direction }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
<button class="button is-primary is-fullwidth" type="submit">
|
<button class="button is-primary is-fullwidth" type="submit">
|
||||||
{% trans "Sort List" %}
|
{% trans "Sort List" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% if request.user.is_authenticated and not list.curation == 'closed' or request.user == list.user %}
|
{% if request.user.is_authenticated and not list.curation == 'closed' or request.user == list.user %}
|
||||||
<h2>{% if list.curation == 'open' or request.user == list.user %}{% trans "Add Books" %}{% else %}{% trans "Suggest Books" %}{% endif %}</h2>
|
<h2 class="title is-5 mt-6">
|
||||||
|
{% if list.curation == 'open' or request.user == list.user %}
|
||||||
|
{% trans "Add Books" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Suggest Books" %}
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
<form name="search" action="{% url 'list' list.id %}" method="GET" class="block">
|
<form name="search" action="{% url 'list' list.id %}" method="GET" class="block">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
{% for list in lists %}
|
{% for list in lists %}
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
<div class="card">
|
<div class="card is-stretchable">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<h4 class="card-header-title">
|
<h4 class="card-header-title">
|
||||||
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<p class="subtitle help">
|
<p class="subtitle help">
|
||||||
{% include 'lists/created_text.html' with list=list %}
|
{% include 'lists/created_text.html' with list=list %}
|
||||||
</p>
|
</p>
|
||||||
{% include 'snippets/trimmed_text.html' with full=list.description %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if request.user == list.user %}
|
{% if request.user == list.user %}
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
|
@ -20,6 +19,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="block content">
|
||||||
|
{% include 'snippets/trimmed_text.html' with full=list.description %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% include 'lists/edit_form.html' with controls_text="edit-list" %}
|
{% include 'lists/edit_form.html' with controls_text="edit-list" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<a href="{{ user.local_path }}">{% include 'snippets/avatar.html' with user=user %} {{ user.display_name }}</a>
|
<a href="{{ user.local_path }}">{% include 'snippets/avatar.html' with user=user %} {{ user.display_name }}</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="mr-2">
|
<p class="mr-2">
|
||||||
{% include 'snippets/block_button.html' with user=user %}
|
{% include 'snippets/block_button.html' with user=user blocks=True %}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if not user in request.user.blocks.all %}
|
{% if not blocks %}
|
||||||
<form name="blocks" method="post" action="/block/{{ user.id }}">
|
<form name="blocks" method="post" action="/block/{{ user.id }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-danger is-light is-small {{ class }}" type="submit">{% trans "Block" %}</button>
|
<button class="button is-danger is-light is-small {{ class }}" type="submit">{% trans "Block" %}</button>
|
||||||
|
|
|
@ -3,14 +3,31 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% with status.id|uuid as uuid %}
|
{% with status.id|uuid as uuid %}
|
||||||
<form name="boost" action="/boost/{{ status.id }}" method="post" class="interaction boost-{{ status.id }}-{{ uuid }} {% if request.user|boosted:status %}is-hidden{% endif %}" data-id="boost-{{ status.id }}-{{ uuid }}">
|
{% with request.user|boosted:status as boosted %}
|
||||||
|
<form
|
||||||
|
name="boost"
|
||||||
|
action="/boost/{{ status.id }}"
|
||||||
|
method="post"
|
||||||
|
class="interaction boost-{{ status.id }}-{{ uuid }} {% if boosted %}is-hidden{% endif %}"
|
||||||
|
data-id="boost-{{ status.id }}-{{ uuid }}"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-small is-light is-transparent" type="submit" {% if not status.boostable %}disabled{% endif %}>
|
<button
|
||||||
|
class="button is-small is-light is-transparent"
|
||||||
|
type="submit"
|
||||||
|
{% if not status.boostable %}disabled{% endif %}
|
||||||
|
>
|
||||||
<span class="icon icon-boost m-0-mobile" title="{% trans 'Boost' %}"></span>
|
<span class="icon icon-boost m-0-mobile" title="{% trans 'Boost' %}"></span>
|
||||||
<span class="is-sr-only-mobile">{% trans "Boost" %}</span>
|
<span class="is-sr-only-mobile">{% trans "Boost" %}</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form name="unboost" action="/unboost/{{ status.id }}" method="post" class="interaction boost-{{ status.id }}-{{ uuid }} active {% if not request.user|boosted:status %}is-hidden{% endif %}" data-id="boost-{{ status.id }}-{{ uuid }}">
|
<form
|
||||||
|
name="unboost"
|
||||||
|
action="/unboost/{{ status.id }}"
|
||||||
|
method="post"
|
||||||
|
class="interaction boost-{{ status.id }}-{{ uuid }} active {% if not boosted %}is-hidden{% endif %}"
|
||||||
|
data-id="boost-{{ status.id }}-{{ uuid }}"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-small is-light is-transparent" type="submit">
|
<button class="button is-small is-light is-transparent" type="submit">
|
||||||
<span class="icon icon-boost has-text-primary m-0-mobile" title="{% trans 'Un-boost' %}"></span>
|
<span class="icon icon-boost has-text-primary m-0-mobile" title="{% trans 'Un-boost' %}"></span>
|
||||||
|
@ -18,3 +35,4 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% with status.id|uuid as uuid %}
|
{% with status.id|uuid as uuid %}
|
||||||
<form name="favorite" action="/favorite/{{ status.id }}" method="POST" class="interaction fav-{{ status.id }}-{{ uuid }} {% if request.user|liked:status %}is-hidden{% endif %}" data-id="fav-{{ status.id }}-{{ uuid }}">
|
{% with request.user|liked:status as liked %}
|
||||||
|
<form name="favorite" action="/favorite/{{ status.id }}" method="POST" class="interaction fav-{{ status.id }}-{{ uuid }} {% if liked %}is-hidden{% endif %}" data-id="fav-{{ status.id }}-{{ uuid }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-small is-light is-transparent" type="submit">
|
<button class="button is-small is-light is-transparent" type="submit">
|
||||||
<span class="icon icon-heart m-0-mobile" title="{% trans 'Like' %}">
|
<span class="icon icon-heart m-0-mobile" title="{% trans 'Like' %}">
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
<span class="is-sr-only-mobile">{% trans "Like" %}</span>
|
<span class="is-sr-only-mobile">{% trans "Like" %}</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form name="unfavorite" action="/unfavorite/{{ status.id }}" method="POST" class="interaction fav-{{ status.id }}-{{ uuid }} active {% if not request.user|liked:status %}is-hidden{% endif %}" data-id="fav-{{ status.id }}-{{ uuid }}">
|
<form name="unfavorite" action="/unfavorite/{{ status.id }}" method="POST" class="interaction fav-{{ status.id }}-{{ uuid }} active {% if not liked %}is-hidden{% endif %}" data-id="fav-{{ status.id }}-{{ uuid }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-light is-transparent is-small" type="submit">
|
<button class="button is-light is-transparent is-small" type="submit">
|
||||||
<span class="icon icon-heart has-text-primary m-0-mobile" title="{% trans 'Un-like' %}"></span>
|
<span class="icon icon-heart has-text-primary m-0-mobile" title="{% trans 'Un-like' %}"></span>
|
||||||
|
@ -19,3 +20,4 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if request.user == user or not request.user.is_authenticated %}
|
{% if request.user == user or not request.user.is_authenticated %}
|
||||||
{% elif user in request.user.blocks.all %}
|
{% elif user in request.user.blocks.all %}
|
||||||
{% include 'snippets/block_button.html' %}
|
{% include 'snippets/block_button.html' with blocks=True %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
|
||||||
|
{% with goal.progress as progress %}
|
||||||
<p>
|
<p>
|
||||||
{% if goal.progress_percent >= 100 %}
|
{% if progress.percent >= 100 %}
|
||||||
{% trans "Success!" %}
|
{% trans "Success!" %}
|
||||||
{% elif goal.progress_percent %}
|
{% elif progress.percent %}
|
||||||
{% blocktrans with percent=goal.progress_percent %}{{ percent }}% complete!{% endblocktrans %}
|
{% blocktrans with percent=progress.percent %}{{ percent }}% complete!{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if goal.user == request.user %}
|
{% if goal.user == request.user %}
|
||||||
{% blocktrans with read_count=goal.book_count|intcomma goal_count=goal.goal|intcomma path=goal.local_path %}You've read <a href="{{ path }}">{{ read_count }} of {{ goal_count}} books</a>.{% endblocktrans %}
|
{% blocktrans with read_count=progress.count|intcomma goal_count=goal.goal|intcomma path=goal.local_path %}You've read <a href="{{ path }}">{{ read_count }} of {{ goal_count}} books</a>.{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% blocktrans with username=goal.user.display_name read_count=goal.book_count|intcomma goal_count=goal.goal|intcomma path=goal.local_path %}{{ username }} has read <a href="{{ path }}">{{ read_count }} of {{ goal_count}} books</a>.{% endblocktrans %}
|
{% blocktrans with username=goal.user.display_name read_count=progress.count|intcomma goal_count=goal.goal|intcomma path=goal.local_path %}{{ username }} has read <a href="{{ path }}">{{ read_count }} of {{ goal_count}} books</a>.{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<progress class="progress is-large" value="{{ goal.book_count }}" max="{{ goal.goal }}" aria-hidden="true">{{ goal.progress_percent }}%</progress>
|
<progress class="progress is-large" value="{{ progress.count }}" max="{{ goal.goal }}" aria-hidden="true">{{ progress.percent }}%</progress>
|
||||||
|
|
||||||
|
{% endwith %}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block dropdown-list %}
|
{% block dropdown-list %}
|
||||||
{% for shelf in request.user.shelf_set.all %}
|
{% for shelf in user_shelves %}
|
||||||
<li role="menuitem" class="dropdown-item p-0">
|
<li role="menuitem" class="dropdown-item p-0">
|
||||||
<form name="shelve" action="/shelve/" method="post">
|
<form name="shelve" action="/shelve/" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{% include 'snippets/shelve_button/shelve_button_options.html' with class="shelf-option is-small" shelves=request.user.shelf_set.all active_shelf=active_shelf button_uuid=uuid %}
|
{% include 'snippets/shelve_button/shelve_button_options.html' with class="shelf-option is-small" shelves=user_shelves active_shelf=active_shelf button_uuid=uuid %}
|
||||||
</div>
|
</div>
|
||||||
{% include 'snippets/shelve_button/shelve_button_dropdown.html' with class="is-small" button_uuid=uuid%}
|
{% include 'snippets/shelve_button/shelve_button_dropdown.html' with class="is-small" button_uuid=uuid%}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/finish_reading_modal.html' with book=active_shelf.book controls_text="finish-reading" controls_uid=uuid readthrough=readthrough %}
|
{% include 'snippets/shelve_button/finish_reading_modal.html' with book=active_shelf.book controls_text="finish-reading" controls_uid=uuid readthrough=readthrough %}
|
||||||
|
|
||||||
{% include 'snippets/shelve_button/progress_update_modal.html' with book=shelf_book.book controls_text="progress-update" controls_uid=uuid readthrough=readthrough %}
|
{% include 'snippets/shelve_button/progress_update_modal.html' with book=active_shelf_book.book controls_text="progress-update" controls_uid=uuid readthrough=readthrough %}
|
||||||
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block dropdown-list %}
|
{% block dropdown-list %}
|
||||||
{% include 'snippets/shelve_button/shelve_button_options.html' with active_shelf=active_shelf shelves=request.user.shelf_set.all dropdown=True class="shelf-option is-fullwidth is-small is-radiusless is-white" %}
|
{% include 'snippets/shelve_button/shelve_button_options.html' with active_shelf=active_shelf shelves=user_shelves dropdown=True class="shelf-option is-fullwidth is-small is-radiusless is-white" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
|
|
||||||
{# moderation options #}
|
{# moderation options #}
|
||||||
<form name="delete-{{status.id}}" action="/delete-status/{{ status.id }}" method="post">
|
<form name="delete-{{ status.id }}" action="/delete-status/{{ status.id }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-danger is-light" type="submit">
|
<button class="button is-danger is-light" type="submit">
|
||||||
{% trans "Delete status" %}
|
{% trans "Delete status" %}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{% if status.user == request.user %}
|
{% if status.user == request.user %}
|
||||||
{# things you can do to your own statuses #}
|
{# things you can do to your own statuses #}
|
||||||
<li role="menuitem" class="dropdown-item p-0">
|
<li role="menuitem" class="dropdown-item p-0">
|
||||||
<form name="delete-{{status.id}}" action="/delete-status/{{ status.id }}" method="post">
|
<form name="delete-{{ status.id }}" action="/delete-status/{{ status.id }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-radiusless is-danger is-light is-fullwidth is-small" type="submit">
|
<button class="button is-radiusless is-danger is-light is-fullwidth is-small" type="submit">
|
||||||
{% trans "Delete status" %}
|
{% trans "Delete status" %}
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</li>
|
</li>
|
||||||
{% if status.status_type != 'GeneratedNote' and status.status_type != 'Rating' %}
|
{% if status.status_type != 'GeneratedNote' and status.status_type != 'Rating' %}
|
||||||
<li role="menuitem" class="dropdown-item p-0">
|
<li role="menuitem" class="dropdown-item p-0">
|
||||||
<form class="" name="delete-{{status.id}}" action="{% url 'redraft' status.id %}" method="post">
|
<form class="" name="delete-{{ status.id }}" action="{% url 'redraft' status.id %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-radiusless is-danger is-light is-fullwidth is-small" type="submit">
|
<button class="button is-radiusless is-danger is-light is-fullwidth is-small" type="submit">
|
||||||
{% trans "Delete & re-draft" %}
|
{% trans "Delete & re-draft" %}
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
{% include 'snippets/report_button.html' with user=status.user status=status %}
|
{% include 'snippets/report_button.html' with user=status.user status=status %}
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem" class="dropdown-item p-0">
|
<li role="menuitem" class="dropdown-item p-0">
|
||||||
{% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" %}
|
{% include 'snippets/block_button.html' with user=status.user class="is-fullwidth" blocks=False %}
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
{% include 'snippets/report_button.html' with user=user class="is-fullwidth" %}
|
{% include 'snippets/report_button.html' with user=user class="is-fullwidth" %}
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
{% include 'snippets/block_button.html' with user=user class="is-fullwidth" %}
|
{% include 'snippets/block_button.html' with user=user class="is-fullwidth" blocks=False %}
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,8 +3,6 @@ from django import template
|
||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
|
|
||||||
from bookwyrm import models, views
|
from bookwyrm import models, views
|
||||||
from bookwyrm.views.status import to_markdown
|
|
||||||
from bookwyrm.templatetags.utilities import get_user_identifier
|
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -71,9 +69,14 @@ def related_status(notification):
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def active_shelf(context, book):
|
def active_shelf(context, book):
|
||||||
"""check what shelf a user has a book on, if any"""
|
"""check what shelf a user has a book on, if any"""
|
||||||
shelf = models.ShelfBook.objects.filter(
|
shelf = (
|
||||||
shelf__user=context["request"].user, book__in=book.parent_work.editions.all()
|
models.ShelfBook.objects.filter(
|
||||||
).first()
|
shelf__user=context["request"].user,
|
||||||
|
book__in=book.parent_work.editions.all(),
|
||||||
|
)
|
||||||
|
.select_related("book", "shelf")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
return shelf if shelf else {"book": book}
|
return shelf if shelf else {"book": book}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,10 @@ register = template.Library()
|
||||||
@register.filter(name="liked")
|
@register.filter(name="liked")
|
||||||
def get_user_liked(user, status):
|
def get_user_liked(user, status):
|
||||||
"""did the given user fav a status?"""
|
"""did the given user fav a status?"""
|
||||||
try:
|
return models.Favorite.objects.filter(user=user, status=status).exists()
|
||||||
models.Favorite.objects.get(user=user, status=status)
|
|
||||||
return True
|
|
||||||
except models.Favorite.DoesNotExist:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="boosted")
|
@register.filter(name="boosted")
|
||||||
def get_user_boosted(user, status):
|
def get_user_boosted(user, status):
|
||||||
"""did the given user fav a status?"""
|
"""did the given user fav a status?"""
|
||||||
return user.id in status.boosters.all().values_list("user", flat=True)
|
return status.boosters.filter(user=user).exists()
|
||||||
|
|
|
@ -42,7 +42,12 @@ def get_parent(status):
|
||||||
@register.filter(name="boosted_status")
|
@register.filter(name="boosted_status")
|
||||||
def get_boosted(boost):
|
def get_boosted(boost):
|
||||||
"""load a boosted status. have to do this or it won't get foreign keys"""
|
"""load a boosted status. have to do this or it won't get foreign keys"""
|
||||||
return models.Status.objects.select_subclasses().get(id=boost.boosted_status.id)
|
return (
|
||||||
|
models.Status.objects.select_subclasses()
|
||||||
|
.select_related("user", "reply_parent")
|
||||||
|
.prefetch_related("mention_books", "mention_users")
|
||||||
|
.get(id=boost.boosted_status.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="published_date")
|
@register.filter(name="published_date")
|
||||||
|
|
|
@ -62,13 +62,16 @@ class Book(View):
|
||||||
queryset = queryset.filter(user=request.user)
|
queryset = queryset.filter(user=request.user)
|
||||||
else:
|
else:
|
||||||
queryset = reviews.exclude(Q(content__isnull=True) | Q(content=""))
|
queryset = reviews.exclude(Q(content__isnull=True) | Q(content=""))
|
||||||
|
queryset = queryset.select_related("user")
|
||||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"book": book,
|
"book": book,
|
||||||
"statuses": paginated.get_page(request.GET.get("page")),
|
"statuses": paginated.get_page(request.GET.get("page")),
|
||||||
"review_count": reviews.count(),
|
"review_count": reviews.count(),
|
||||||
"ratings": reviews.filter(Q(content__isnull=True) | Q(content=""))
|
"ratings": reviews.filter(
|
||||||
|
Q(content__isnull=True) | Q(content="")
|
||||||
|
).select_related("user")
|
||||||
if not user_statuses
|
if not user_statuses
|
||||||
else None,
|
else None,
|
||||||
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
||||||
|
@ -89,15 +92,15 @@ class Book(View):
|
||||||
)
|
)
|
||||||
data["readthroughs"] = readthroughs
|
data["readthroughs"] = readthroughs
|
||||||
|
|
||||||
data["user_shelves"] = models.ShelfBook.objects.filter(
|
data["user_shelfbooks"] = models.ShelfBook.objects.filter(
|
||||||
user=request.user, book=book
|
user=request.user, book=book
|
||||||
)
|
).select_related("shelf")
|
||||||
|
|
||||||
data["other_edition_shelves"] = models.ShelfBook.objects.filter(
|
data["other_edition_shelves"] = models.ShelfBook.objects.filter(
|
||||||
~Q(book=book),
|
~Q(book=book),
|
||||||
user=request.user,
|
user=request.user,
|
||||||
book__parent_work=book.parent_work,
|
book__parent_work=book.parent_work,
|
||||||
)
|
).select_related("shelf", "book")
|
||||||
|
|
||||||
data["user_statuses"] = {
|
data["user_statuses"] = {
|
||||||
"review_count": book.review_set.filter(user=request.user).count(),
|
"review_count": book.review_set.filter(user=request.user).count(),
|
||||||
|
|
|
@ -162,14 +162,15 @@ def get_suggested_books(user, max_books=5):
|
||||||
else max_books - book_count
|
else max_books - book_count
|
||||||
)
|
)
|
||||||
shelf = user.shelf_set.get(identifier=preset)
|
shelf = user.shelf_set.get(identifier=preset)
|
||||||
|
if not shelf.books.exists():
|
||||||
shelf_books = shelf.shelfbook_set.order_by("-updated_date")[:limit]
|
|
||||||
if not shelf_books:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
shelf_preview = {
|
shelf_preview = {
|
||||||
"name": shelf.name,
|
"name": shelf.name,
|
||||||
"identifier": shelf.identifier,
|
"identifier": shelf.identifier,
|
||||||
"books": [s.book for s in shelf_books],
|
"books": shelf.books.order_by("shelfbook").prefetch_related("authors")[
|
||||||
|
:limit
|
||||||
|
],
|
||||||
}
|
}
|
||||||
suggested_books.append(shelf_preview)
|
suggested_books.append(shelf_preview)
|
||||||
book_count += len(shelf_preview["books"])
|
book_count += len(shelf_preview["books"])
|
||||||
|
|
|
@ -13,6 +13,10 @@ from bookwyrm.utils import regex
|
||||||
|
|
||||||
def get_user_from_username(viewer, username):
|
def get_user_from_username(viewer, username):
|
||||||
"""helper function to resolve a localname or a username to a user"""
|
"""helper function to resolve a localname or a username to a user"""
|
||||||
|
if viewer.is_authenticated and viewer.localname == username:
|
||||||
|
# that's yourself, fool
|
||||||
|
return viewer
|
||||||
|
|
||||||
# raises 404 if the user isn't found
|
# raises 404 if the user isn't found
|
||||||
try:
|
try:
|
||||||
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.db.models import Count, OuterRef, Subquery, F, Q
|
from django.db.models import OuterRef, Subquery
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||||
|
@ -28,6 +28,11 @@ class Shelf(View):
|
||||||
"""display a shelf"""
|
"""display a shelf"""
|
||||||
user = get_user_from_username(request.user, username)
|
user = get_user_from_username(request.user, username)
|
||||||
|
|
||||||
|
is_self = user == request.user
|
||||||
|
|
||||||
|
if is_self:
|
||||||
|
shelves = user.shelf_set
|
||||||
|
else:
|
||||||
shelves = privacy_filter(request.user, user.shelf_set)
|
shelves = privacy_filter(request.user, user.shelf_set)
|
||||||
|
|
||||||
# get the shelf and make sure the logged in user should be able to see it
|
# get the shelf and make sure the logged in user should be able to see it
|
||||||
|
@ -53,26 +58,28 @@ class Shelf(View):
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
||||||
|
|
||||||
reviews = privacy_filter(
|
reviews = models.Review.objects.filter(
|
||||||
request.user,
|
|
||||||
models.Review.objects.filter(
|
|
||||||
user=user,
|
user=user,
|
||||||
rating__isnull=False,
|
rating__isnull=False,
|
||||||
book__id=OuterRef("id"),
|
book__id=OuterRef("id"),
|
||||||
),
|
|
||||||
).order_by("-published_date")
|
).order_by("-published_date")
|
||||||
|
|
||||||
books = books.annotate(rating=Subquery(reviews.values("rating")[:1]))
|
if not is_self:
|
||||||
|
reviews = privacy_filter(request.user, reviews)
|
||||||
|
|
||||||
|
books = books.annotate(
|
||||||
|
rating=Subquery(reviews.values("rating")[:1])
|
||||||
|
).prefetch_related("authors")
|
||||||
|
|
||||||
paginated = Paginator(
|
paginated = Paginator(
|
||||||
books.order_by("-updated_date"),
|
books.order_by("-shelfbook__updated_date"),
|
||||||
PAGE_LENGTH,
|
PAGE_LENGTH,
|
||||||
)
|
)
|
||||||
|
|
||||||
page = paginated.get_page(request.GET.get("page"))
|
page = paginated.get_page(request.GET.get("page"))
|
||||||
data = {
|
data = {
|
||||||
"user": user,
|
"user": user,
|
||||||
"is_self": request.user == user,
|
"is_self": is_self,
|
||||||
"shelves": shelves.all(),
|
"shelves": shelves.all(),
|
||||||
"shelf": shelf,
|
"shelf": shelf,
|
||||||
"books": page,
|
"books": page,
|
||||||
|
|
|
@ -59,10 +59,15 @@ class User(View):
|
||||||
break
|
break
|
||||||
|
|
||||||
# user's posts
|
# user's posts
|
||||||
activities = privacy_filter(
|
activities = (
|
||||||
|
privacy_filter(
|
||||||
request.user,
|
request.user,
|
||||||
user.status_set.select_subclasses(),
|
user.status_set.select_subclasses(),
|
||||||
)
|
)
|
||||||
|
.select_related("reply_parent")
|
||||||
|
.prefetch_related("mention_books", "mention_users")
|
||||||
|
)
|
||||||
|
|
||||||
paginated = Paginator(activities, PAGE_LENGTH)
|
paginated = Paginator(activities, PAGE_LENGTH)
|
||||||
goal = models.AnnualGoal.objects.filter(
|
goal = models.AnnualGoal.objects.filter(
|
||||||
user=user, year=timezone.now().year
|
user=user, year=timezone.now().year
|
||||||
|
|
Loading…
Reference in a new issue