forked from mirrors/bookwyrm
Merge branch 'main' into code-scanning
This commit is contained in:
commit
8c92869fc0
27 changed files with 234 additions and 97 deletions
|
@ -13,7 +13,7 @@ Social reading and reviewing, decentralized with ActivityPub
|
|||
- [Set up Bookwyrm](#set-up-bookwyrm)
|
||||
|
||||
## Joining BookWyrm
|
||||
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://docs.joinbookwyrm.com/instances.html) list.
|
||||
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list.
|
||||
|
||||
You can request an invite by entering your email address at https://bookwyrm.social.
|
||||
|
||||
|
|
|
@ -39,4 +39,5 @@ class Person(ActivityObject):
|
|||
bookwyrmUser: bool = False
|
||||
manuallyApprovesFollowers: str = False
|
||||
discoverable: str = False
|
||||
hideFollows: str = False
|
||||
type: str = "Person"
|
||||
|
|
|
@ -153,6 +153,7 @@ class EditUserForm(CustomForm):
|
|||
"manually_approves_followers",
|
||||
"default_post_privacy",
|
||||
"discoverable",
|
||||
"hide_follows",
|
||||
"preferred_timezone",
|
||||
"preferred_language",
|
||||
]
|
||||
|
|
19
bookwyrm/migrations/0142_user_hide_follows.py
Normal file
19
bookwyrm/migrations/0142_user_hide_follows.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.12 on 2022-02-28 19:44
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0141_alter_report_status"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="hide_follows",
|
||||
field=bookwyrm.models.fields.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -227,7 +227,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
@classmethod
|
||||
def privacy_filter(cls, viewer, privacy_levels=None):
|
||||
queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels)
|
||||
return queryset.filter(deleted=False)
|
||||
return queryset.filter(deleted=False, user__is_active=True)
|
||||
|
||||
@classmethod
|
||||
def direct_filter(cls, queryset, viewer):
|
||||
|
|
|
@ -136,6 +136,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
updated_date = models.DateTimeField(auto_now=True)
|
||||
last_active_date = models.DateTimeField(default=timezone.now)
|
||||
manually_approves_followers = fields.BooleanField(default=False)
|
||||
hide_follows = fields.BooleanField(default=False)
|
||||
|
||||
# options to turn features on and off
|
||||
show_goal = models.BooleanField(default=True)
|
||||
|
@ -478,10 +479,13 @@ def set_remote_server(user_id):
|
|||
get_remote_reviews.delay(user.outbox)
|
||||
|
||||
|
||||
def get_or_create_remote_server(domain):
|
||||
def get_or_create_remote_server(domain, refresh=False):
|
||||
"""get info on a remote server"""
|
||||
server = FederatedServer()
|
||||
try:
|
||||
return FederatedServer.objects.get(server_name=domain)
|
||||
server = FederatedServer.objects.get(server_name=domain)
|
||||
if not refresh:
|
||||
return server
|
||||
except FederatedServer.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
@ -496,13 +500,15 @@ def get_or_create_remote_server(domain):
|
|||
application_type = data.get("software", {}).get("name")
|
||||
application_version = data.get("software", {}).get("version")
|
||||
except ConnectorException:
|
||||
if server.id:
|
||||
return server
|
||||
application_type = application_version = None
|
||||
|
||||
server = FederatedServer.objects.create(
|
||||
server_name=domain,
|
||||
application_type=application_type,
|
||||
application_version=application_version,
|
||||
)
|
||||
server.server_name = domain
|
||||
server.application_type = application_type
|
||||
server.application_version = application_version
|
||||
|
||||
server.save()
|
||||
return server
|
||||
|
||||
|
||||
|
|
|
@ -352,7 +352,7 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if request.user.list_set.exists %}
|
||||
{% if list_options.exists %}
|
||||
<form name="list-add" method="post" action="{% url 'list-add-book' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
|
@ -361,7 +361,7 @@
|
|||
<div class="field has-addons">
|
||||
<div class="select control is-clipped">
|
||||
<select name="book_list" id="id_list">
|
||||
{% for list in user.list_set.all %}
|
||||
{% for list in list_options %}
|
||||
<option value="{{ list.id }}">{{ list.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
<footer class="card-footer">
|
||||
{% if user != request.user %}
|
||||
{% if user.mutuals %}
|
||||
{% if user.mutuals and not user.hide_follows %}
|
||||
<div class="card-footer-item">
|
||||
<div class="has-text-centered">
|
||||
<p class="title is-6 mb-0">{{ user.mutuals }}</p>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
||||
</a>
|
||||
{% include 'snippets/add_to_group_button.html' with user=user group=group %}
|
||||
{% if user.mutuals %}
|
||||
{% if user.mutuals and not user.hide_follows %}
|
||||
<p class="help">
|
||||
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
||||
{{ mutuals }} follower you follow
|
||||
|
|
|
@ -30,13 +30,23 @@
|
|||
|
||||
<div class="columns mt-3">
|
||||
<section class="column is-three-quarters">
|
||||
{% if request.GET.updated %}
|
||||
<div class="notification is-primary">
|
||||
{% if add_failed %}
|
||||
<div class="notification is-danger is-light">
|
||||
<span class="icon icon-x" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% trans "That book is already on this list." %}
|
||||
</span>
|
||||
</div>
|
||||
{% elif add_succeeded %}
|
||||
<div class="notification is-success is-light">
|
||||
<span class="icon icon-check" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% if list.curation != "open" and request.user != list.user and not list.group|is_member:request.user %}
|
||||
{% trans "You successfully suggested a book for this list!" %}
|
||||
{% else %}
|
||||
{% trans "You successfully added a book to this list!" %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -111,6 +111,12 @@
|
|||
{% trans "Manually approve followers" %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox label" for="id_hide_follows">
|
||||
{{ form.hide_follows }}
|
||||
{% trans "Hide followers and following on profile" %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="id_default_post_privacy">
|
||||
{% trans "Default post privacy:" %}
|
||||
|
|
|
@ -4,7 +4,19 @@
|
|||
|
||||
{% block header %}
|
||||
{% trans "Add instance" %}
|
||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to instance list" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">
|
||||
{% trans "Add instance" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
|
@ -73,9 +85,13 @@
|
|||
<label class="label" for="id_notes">
|
||||
{% trans "Notes:" %}
|
||||
</label>
|
||||
<textarea name="notes" cols="40" rows="5" class="textarea" id="id_notes">
|
||||
{{ form.notes.value|default:'' }}
|
||||
</textarea>
|
||||
<textarea
|
||||
name="notes"
|
||||
cols="40"
|
||||
rows="5"
|
||||
class="textarea"
|
||||
id="id_notes"
|
||||
>{{ form.notes.value|default:'' }}</textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button is-primary">
|
||||
|
|
|
@ -9,8 +9,26 @@
|
|||
|
||||
{% if server.status == "blocked" %}<span class="icon icon-x has-text-danger is-size-5" title="{% trans 'Blocked' %}"><span class="is-sr-only">{% trans "Blocked" %}</span></span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to list" %}</a>
|
||||
{% block edit-button %}
|
||||
<form name="reload" method="POST" action="{% url 'settings-federated-server-refresh' server.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="button" type="submit">{% trans "Refresh data" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">
|
||||
{{ server.server_name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
|
|
|
@ -4,7 +4,19 @@
|
|||
|
||||
{% block header %}
|
||||
{% trans "Import Blocklist" %}
|
||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to instance list" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">
|
||||
{% trans "Import Blocklist" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
<ul>
|
||||
{% url 'settings-federation' status='federated' as url %}
|
||||
<li {% if request.path in url %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Federated" %}</a>
|
||||
<a href="{{ url }}">{% trans "Federated" %} ({{ federated_count }})</a>
|
||||
</li>
|
||||
{% url 'settings-federation' status='blocked' as url %}
|
||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Blocked" %}</a>
|
||||
<a href="{{ url }}">{% trans "Blocked" %} ({{ blocked_count }})</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -71,7 +71,9 @@
|
|||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
<input type="hidden" name="shelf" value="{{ user_shelf.id }}">
|
||||
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">{% trans "Remove from" %} {{ user_shelf.name }}</button>
|
||||
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">
|
||||
{% blocktrans with name=user_shelf|translate_shelf_name %}Remove from {{ name }}{% endblocktrans %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<input type="hidden" name="book" value="{{ active_shelf.book.id }}">
|
||||
<input type="hidden" name="shelf" value="{{ active_shelf.shelf.id }}">
|
||||
<button class="button is-fullwidth is-small{% if dropdown %} is-radiusless{% endif %} is-danger is-light" type="submit">
|
||||
{% blocktrans with name=active_shelf.shelf.name %}Remove from {{ name }}{% endblocktrans %}
|
||||
{% blocktrans with name=active_shelf.shelf|translate_shelf_name %}Remove from {{ name }}{% endblocktrans %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
||||
</a>
|
||||
{% include 'snippets/follow_button.html' with user=user minimal=True %}
|
||||
{% if user.mutuals %}
|
||||
{% if user.mutuals and not user.hide_follows %}
|
||||
<p class="help">
|
||||
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
||||
{{ mutuals }} follower you follow
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
{% load i18n %}
|
||||
{% if shelf.identifier == 'all' %}
|
||||
{% trans "All books" %}
|
||||
{% elif shelf.identifier == 'to-read' %}
|
||||
{% trans "To Read" %}
|
||||
{% elif shelf.identifier == 'reading' %}
|
||||
{% trans "Currently Reading" %}
|
||||
{% elif shelf.identifier == 'read' %}
|
||||
{% trans "Read" %}
|
||||
{% else %}
|
||||
{{ shelf.name }}
|
||||
{% endif %}
|
||||
{% load shelf_tags %}
|
||||
|
||||
{{ shelf|translate_shelf_name }}
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
|
||||
{% elif request.user.is_authenticated %}
|
||||
|
||||
{% if user.hide_follows %}
|
||||
{% if request.user in user.following.all %}
|
||||
{% trans "Follows you" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% mutuals_count user as mutuals %}
|
||||
<a href="{% url 'user-followers' user|username %}">
|
||||
{% if mutuals %}
|
||||
|
@ -38,6 +43,7 @@
|
|||
{% trans "No followers you follow" %}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" Filters and tags related to shelving books """
|
||||
from django import template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.utils import cache
|
||||
|
@ -32,6 +33,24 @@ def get_next_shelf(current_shelf):
|
|||
return "to-read"
|
||||
|
||||
|
||||
@register.filter(name="translate_shelf_name")
|
||||
def get_translated_shelf_name(shelf):
|
||||
"""produced translated shelf nidentifierame"""
|
||||
if not shelf:
|
||||
return ""
|
||||
# support obj or dict
|
||||
identifier = shelf["identifier"] if isinstance(shelf, dict) else shelf.identifier
|
||||
if identifier == "all":
|
||||
return _("All books")
|
||||
if identifier == "to-read":
|
||||
return _("To Read")
|
||||
if identifier == "reading":
|
||||
return _("Currently Reading")
|
||||
if identifier == "read":
|
||||
return _("Read")
|
||||
return shelf["name"] if isinstance(shelf, dict) else shelf.name
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def active_shelf(context, book):
|
||||
"""check what shelf a user has a book on, if any"""
|
||||
|
|
|
@ -144,6 +144,11 @@ urlpatterns = [
|
|||
views.unblock_server,
|
||||
name="settings-federated-server-unblock",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/federation/(?P<server>\d+)/refresh/?$",
|
||||
views.refresh_server,
|
||||
name="settings-federated-server-refresh",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/federation/add/?$",
|
||||
views.AddFederatedServer.as_view(),
|
||||
|
|
|
@ -6,7 +6,7 @@ from .admin.automod import AutoMod, automod_delete, run_automod
|
|||
from .admin.dashboard import Dashboard
|
||||
from .admin.federation import Federation, FederatedServer
|
||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||
from .admin.federation import block_server, unblock_server
|
||||
from .admin.federation import block_server, unblock_server, refresh_server
|
||||
from .admin.email_blocklist import EmailBlocklist
|
||||
from .admin.ip_blocklist import IPBlocklist
|
||||
from .admin.invite import ManageInvites, Invite, InviteRequest
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.views.decorators.http import require_POST
|
|||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from bookwyrm.models.user import get_or_create_remote_server
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -37,6 +38,12 @@ class Federation(View):
|
|||
page = paginated.get_page(request.GET.get("page"))
|
||||
|
||||
data = {
|
||||
"federated_count": models.FederatedServer.objects.filter(
|
||||
status="federated"
|
||||
).count(),
|
||||
"blocked_count": models.FederatedServer.objects.filter(
|
||||
status="blocked"
|
||||
).count(),
|
||||
"servers": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
|
@ -157,3 +164,14 @@ def unblock_server(request, server):
|
|||
server = get_object_or_404(models.FederatedServer, id=server)
|
||||
server.unblock()
|
||||
return redirect("settings-federated-server", server.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.control_federation", raise_exception=True)
|
||||
# pylint: disable=unused-argument
|
||||
def refresh_server(request, server):
|
||||
"""unblock a server"""
|
||||
server = get_object_or_404(models.FederatedServer, id=server)
|
||||
get_or_create_remote_server(server.server_name, refresh=True)
|
||||
return redirect("settings-federated-server", server.id)
|
||||
|
|
|
@ -83,6 +83,7 @@ class Book(View):
|
|||
}
|
||||
|
||||
if request.user.is_authenticated:
|
||||
data["list_options"] = request.user.list_set.exclude(id__in=data["lists"])
|
||||
data["file_link_form"] = forms.FileLinkForm()
|
||||
readthroughs = models.ReadThrough.objects.filter(
|
||||
user=request.user,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
""" book list views"""
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db import transaction
|
||||
from django.db.models import Avg, DecimalField, Q, Max
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import HttpResponseBadRequest, HttpResponse
|
||||
|
@ -26,7 +25,7 @@ from bookwyrm.views.helpers import is_api_request
|
|||
class List(View):
|
||||
"""book list page"""
|
||||
|
||||
def get(self, request, list_id):
|
||||
def get(self, request, list_id, add_failed=False, add_succeeded=False):
|
||||
"""display a book list"""
|
||||
book_list = get_object_or_404(models.List, id=list_id)
|
||||
book_list.raise_visible_to_user(request.user)
|
||||
|
@ -37,33 +36,10 @@ class List(View):
|
|||
query = request.GET.get("q")
|
||||
suggestions = None
|
||||
|
||||
# sort_by shall be "order" unless a valid alternative is given
|
||||
sort_by = request.GET.get("sort_by", "order")
|
||||
if sort_by not in ("order", "title", "rating"):
|
||||
sort_by = "order"
|
||||
|
||||
# direction shall be "ascending" unless a valid alternative is given
|
||||
direction = request.GET.get("direction", "ascending")
|
||||
if direction not in ("ascending", "descending"):
|
||||
direction = "ascending"
|
||||
|
||||
directional_sort_by = {
|
||||
"order": "order",
|
||||
"title": "book__title",
|
||||
"rating": "average_rating",
|
||||
}[sort_by]
|
||||
if direction == "descending":
|
||||
directional_sort_by = "-" + directional_sort_by
|
||||
|
||||
items = book_list.listitem_set.prefetch_related("user", "book", "book__authors")
|
||||
if sort_by == "rating":
|
||||
items = items.annotate(
|
||||
average_rating=Avg(
|
||||
Coalesce("book__review__rating", 0.0),
|
||||
output_field=DecimalField(),
|
||||
items = book_list.listitem_set.filter(approved=True).prefetch_related(
|
||||
"user", "book", "book__authors"
|
||||
)
|
||||
)
|
||||
items = items.filter(approved=True).order_by(directional_sort_by)
|
||||
items = sort_list(request, items)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
|
||||
|
@ -106,10 +82,10 @@ class List(View):
|
|||
"suggested_books": suggestions,
|
||||
"list_form": forms.ListForm(instance=book_list),
|
||||
"query": query or "",
|
||||
"sort_form": forms.SortListForm(
|
||||
{"direction": direction, "sort_by": sort_by}
|
||||
),
|
||||
"sort_form": forms.SortListForm(request.GET),
|
||||
"embed_url": embed_url,
|
||||
"add_failed": add_failed,
|
||||
"add_succeeded": add_succeeded,
|
||||
}
|
||||
return TemplateResponse(request, "lists/list.html", data)
|
||||
|
||||
|
@ -131,6 +107,36 @@ class List(View):
|
|||
return redirect(book_list.local_path)
|
||||
|
||||
|
||||
def sort_list(request, items):
|
||||
"""helper to handle the surprisngly involved sorting"""
|
||||
# sort_by shall be "order" unless a valid alternative is given
|
||||
sort_by = request.GET.get("sort_by", "order")
|
||||
if sort_by not in ("order", "title", "rating"):
|
||||
sort_by = "order"
|
||||
|
||||
# direction shall be "ascending" unless a valid alternative is given
|
||||
direction = request.GET.get("direction", "ascending")
|
||||
if direction not in ("ascending", "descending"):
|
||||
direction = "ascending"
|
||||
|
||||
directional_sort_by = {
|
||||
"order": "order",
|
||||
"title": "book__title",
|
||||
"rating": "average_rating",
|
||||
}[sort_by]
|
||||
if direction == "descending":
|
||||
directional_sort_by = "-" + directional_sort_by
|
||||
|
||||
if sort_by == "rating":
|
||||
items = items.annotate(
|
||||
average_rating=Avg(
|
||||
Coalesce("book__review__rating", 0.0),
|
||||
output_field=DecimalField(),
|
||||
)
|
||||
)
|
||||
return items.order_by(directional_sort_by)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def save_list(request, list_id):
|
||||
|
@ -179,8 +185,8 @@ def add_book(request):
|
|||
|
||||
form = forms.ListItemForm(request.POST)
|
||||
if not form.is_valid():
|
||||
# this shouldn't happen, there aren't validated fields
|
||||
raise Exception(form.errors)
|
||||
return List().get(request, book_list.id, add_failed=True)
|
||||
|
||||
item = form.save(commit=False)
|
||||
|
||||
if book_list.curation == "curated":
|
||||
|
@ -196,17 +202,9 @@ def add_book(request):
|
|||
) or 0
|
||||
increment_order_in_reverse(book_list.id, order_max + 1)
|
||||
item.order = order_max + 1
|
||||
|
||||
try:
|
||||
item.save()
|
||||
except IntegrityError:
|
||||
# if the book is already on the list, don't flip out
|
||||
pass
|
||||
|
||||
path = reverse("list", args=[book_list.id])
|
||||
params = request.GET.copy()
|
||||
params["updated"] = True
|
||||
return redirect(f"{path}?{urlencode(params)}")
|
||||
return List().get(request, book_list.id, add_succeeded=True)
|
||||
|
||||
|
||||
@require_POST
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" non-interactive pages """
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q, Count
|
||||
from django.http import Http404
|
||||
|
@ -105,6 +106,9 @@ class Followers(View):
|
|||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
||||
|
||||
if user.hide_follows:
|
||||
raise PermissionDenied()
|
||||
|
||||
followers = annotate_if_follows(request.user, user.followers)
|
||||
paginated = Paginator(followers.all(), PAGE_LENGTH)
|
||||
data = {
|
||||
|
@ -125,6 +129,9 @@ class Following(View):
|
|||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
||||
|
||||
if user.hide_follows:
|
||||
raise PermissionDenied()
|
||||
|
||||
following = annotate_if_follows(request.user, user.following)
|
||||
paginated = Paginator(following.all(), PAGE_LENGTH)
|
||||
data = {
|
||||
|
|
Loading…
Reference in a new issue