Adds endorsement functionality

This commit is contained in:
Mouse Reeve 2024-08-27 07:27:44 -07:00
parent 5f906e3597
commit 12291c9e50
9 changed files with 86 additions and 18 deletions

View file

@ -215,16 +215,23 @@ class AbstractListItem(CollectionItemMixin, BookWyrmModel):
activity_serializer = activitypub.ListItem
collection_field = "book_list"
def endorse(self, user):
"""another user supports this suggestion"""
# you can't endorse your own contribution, silly
if user == self.user:
return
self.endorsement.add(user)
def unendorse(self, user):
"""the user rescinds support this suggestion"""
if user == self.user:
return
self.endorsement.remove(user)
def raise_not_deletable(self, viewer):
"""the associated user OR the list owner can delete"""
if self.book_list.user == viewer:
return
# group members can delete items in group lists
is_group_member = GroupMember.objects.filter(
group=self.book_list.group, user=viewer
).exists()
if is_group_member:
return
super().raise_not_deletable(viewer)
class Meta:
@ -243,6 +250,16 @@ class ListItem(AbstractListItem):
approved = models.BooleanField(default=True)
order = fields.IntegerField()
def raise_not_deletable(self, viewer):
"""the associated user OR the list owner can delete"""
# group members can delete items in group lists
is_group_member = GroupMember.objects.filter(
group=self.book_list.group, user=viewer
).exists()
if is_group_member:
return
super().raise_not_deletable(viewer)
def save(self, *args, **kwargs):
"""Update the list's date"""
super().save(*args, **kwargs)

View file

@ -36,9 +36,7 @@
</p>
</div>
<div class="card-footer-item">
<p>
TODO :: endorsements
</p>
{% include "book/suggestion_list/endorsement_button.html" %}
</div>
</div>
</div>

View file

@ -0,0 +1,10 @@
{% load i18n %}
<form class="" action="{% url "suggestion-endorse" book.id item.id %}" method="POST">
{% csrf_token %}
<button class="tag is-info {% if request.user not in item.endorsement.all and request.user != item.user %}is-light{% endif %}" type="submit" {% if request.user == item.user %}disabled{% endif %}>
<span class="icon icon-star-empty" aria-label="{% trans 'Endorsements' %}"></span>
<span>
{{ item.endorsement.count|add:1 }}
</span>
</button>
</form>

View file

@ -18,8 +18,6 @@
{% endblocktrans %}
</p>
{% with book.suggestion_list.suggestionlistitem_set.all|slice:3 as items %}
{% if items|length == 0 %}
<section class="section has-background-light is-flex is-flex-direction-column is-align-items-center">
<div class="column is-full is-centered">
@ -39,7 +37,6 @@
{% include "book/suggestion_list/search.html" %}
</div>
{% endif %}
{% endwith %}
{% else %}
<section class="section is-medium has-background-light">
<form name="create-list" method="post" action="{% url 'suggestion-list' book_id=book.id %}#suggestions-section" class="has-text-centered">

View file

@ -91,6 +91,11 @@
{% endblocktrans %}
</p>
</div>
{% if list.suggests_for %}
<div class="card-footer-item">
{% include "book/suggestion_list/endorsement_button.html" with book=list.suggests_for %}
</div>
{% endif %}
{% if list.user == request.user or list.group|is_member:request.user %}
<form
name="set-position-{{ item.id }}"

View file

@ -775,6 +775,11 @@ urlpatterns = [
views.book_remove_suggestion,
name="book-remove-suggestion",
),
re_path(
rf"{BOOK_PATH}/suggestions/endorse/(?P<item_id>\d+)/?$",
views.endorse_suggestion,
name="suggestion-endorse",
),
re_path(
r"^author/(?P<author_id>\d+)/update/(?P<connector_identifier>[\w\.]+)/?$",
views.update_author_from_remote,

View file

@ -107,7 +107,11 @@ from .list.list import (
# suggestion lists
from .suggestion_list import SuggestionList
from .suggestion_list import book_add_suggestion, book_remove_suggestion
from .suggestion_list import (
book_add_suggestion,
book_remove_suggestion,
endorse_suggestion,
)
# misc views
from .author import Author, EditAuthor, update_author_from_remote

View file

@ -4,7 +4,7 @@ from uuid import uuid4
from django.contrib.auth.decorators import login_required, permission_required
from django.core.paginator import Paginator
from django.db.models import Avg, Q
from django.db.models import Avg, Q, Count
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
@ -136,6 +136,15 @@ class Book(View):
"quotation_count": book.quotation_set.filter(**filters).count(),
}
if hasattr(book, "suggestion_list"):
data["items"] = (
book.suggestion_list.suggestionlistitem_set.prefetch_related(
"user", "book", "book__authors"
)
.annotate(endorsement_count=Count("endorsement"))
.order_by("-endorsement_count")[:3]
)
data["suggested_books"] = get_list_suggestions(
book.suggestion_list,
request.user,

View file

@ -2,6 +2,7 @@
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls import reverse
@ -30,8 +31,12 @@ class SuggestionList(View):
if is_api_request(request):
return ActivitypubResponse(book_list.to_activity(**request.GET))
items = book_list.suggestionlistitem_set.prefetch_related(
"user", "book", "book__authors"
items = (
book_list.suggestionlistitem_set.prefetch_related(
"user", "book", "book__authors"
)
.annotate(endorsement_count=Count("endorsement"))
.order_by("-endorsement_count")
)
paginated = Paginator(items, PAGE_LENGTH)
@ -103,12 +108,30 @@ def book_add_suggestion(request, book_id):
@require_POST
@login_required
def book_remove_suggestion(request, _):
def book_remove_suggestion(request, book_id):
"""remove a book from a suggestion list"""
item = get_object_or_404(models.SuggestionListItem, id=request.POST.get("item"))
item = get_object_or_404(
models.SuggestionListItem,
id=request.POST.get("item"),
book_list__suggests_for=book_id,
)
item.raise_not_deletable(request.user)
with transaction.atomic():
item.delete()
return redirect_to_referer(request)
@require_POST
@login_required
def endorse_suggestion(request, book_id, item_id):
"""endorse a suggestion"""
item = get_object_or_404(
models.SuggestionListItem, id=item_id, book_list__suggests_for=book_id
)
if request.user not in item.endorsement.all():
item.endorse(request.user)
else:
item.unendorse(request.user)
return redirect_to_referer(request)