Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion list #2560

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions bookwyrm/activitypub/ordered_collection.py
Expand Up @@ -40,6 +40,7 @@ class BookList(OrderedCollectionPrivate):

summary: str = None
curation: str = "closed"
book: str = None
type: str = "BookList"


Expand Down
6 changes: 6 additions & 0 deletions bookwyrm/forms/lists.py
Expand Up @@ -14,6 +14,12 @@ class Meta:
fields = ["user", "name", "description", "curation", "privacy", "group"]


class SuggestionListForm(CustomForm):
class Meta:
model = models.List
fields = ["user", "suggests_for"]


class ListItemForm(CustomForm):
class Meta:
model = models.ListItem
Expand Down
26 changes: 26 additions & 0 deletions bookwyrm/migrations/0180_list_suggests_for.py
@@ -0,0 +1,26 @@
# Generated by Django 3.2.20 on 2023-08-01 13:12

import bookwyrm.models.fields
from django.db import migrations
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("bookwyrm", "0179_populate_sort_title"),
]

operations = [
migrations.AddField(
model_name="list",
name="suggests_for",
field=bookwyrm.models.fields.OneToOneField(
default=None,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="suggestion_list",
to="bookwyrm.edition",
),
),
]
6 changes: 3 additions & 3 deletions bookwyrm/models/fields.py
Expand Up @@ -296,7 +296,7 @@ def field_to_activity(self, value):


class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField):
"""activitypub-aware foreign key field"""
"""activitypub-aware one to one field"""

def field_to_activity(self, value):
if not value:
Expand Down Expand Up @@ -584,11 +584,11 @@ class BooleanField(ActivitypubFieldMixin, models.BooleanField):


class IntegerField(ActivitypubFieldMixin, models.IntegerField):
"""activitypub-aware boolean field"""
"""activitypub-aware integer field"""


class DecimalField(ActivitypubFieldMixin, models.DecimalField):
"""activitypub-aware boolean field"""
"""activitypub-aware decimal field"""

def field_to_activity(self, value):
if not value:
Expand Down
30 changes: 30 additions & 0 deletions bookwyrm/models/list.py
Expand Up @@ -5,6 +5,7 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN
Expand Down Expand Up @@ -47,6 +48,14 @@ class List(OrderedCollectionMixin, BookWyrmModel):
)
embed_key = models.UUIDField(unique=True, null=True, editable=False)
activity_serializer = activitypub.BookList
suggests_for = fields.OneToOneField(
"Edition",
on_delete=models.PROTECT,
activitypub_field="book",
related_name="suggestion_list",
default=None,
null=True,
)

def get_remote_id(self):
"""don't want the user to be in there in this case"""
Expand All @@ -57,6 +66,27 @@ def collection_queryset(self):
"""list of books for this shelf, overrides OrderedCollectionMixin"""
return self.books.filter(listitem__approved=True).order_by("listitem")

@property
def get_name(self):
"""The name comes from the book title if it's a suggestion list"""
if self.suggests_for:
return _("Suggestions for %(title)s") % {"title": self.suggests_for.title}

return self.name

@property
def get_description(self):
"""The description comes from the book title if it's a suggestion list"""
if self.suggests_for:
return _(
"This is the list of suggestions for <a href='%(url)s'>%(title)s</a>"
) % {
"title": self.suggests_for.title,
"url": self.suggests_for.local_path,
}

return self.description

class Meta:
"""default sorting"""

Expand Down
7 changes: 5 additions & 2 deletions bookwyrm/templates/book/book.html
Expand Up @@ -378,7 +378,7 @@ <h2 class="title is-5">{% trans "Places" %}</h2>
</section>
{% endif %}

{% if lists.exists or request.user.list_set.exists %}
{% if lists.exists or list_options.exists %}
<section class="content block is-clipped">
<h2 class="title is-5">{% trans "Lists" %}</h2>
<ul>
Expand Down Expand Up @@ -415,8 +415,11 @@ <h2 class="title is-5">{% trans "Lists" %}</h2>
</section>
</div>
</div>

</div>

<section class="block">
{% include "book/suggestion_list/list.html" %}
</section>
{% endwith %}
{% endblock %}

Expand Down
66 changes: 66 additions & 0 deletions bookwyrm/templates/book/suggestion_list/list.html
@@ -0,0 +1,66 @@
{% load i18n %}

<h2 class="title is-3" id="suggestions-section">
{% trans "Suggestions" %}
</h2>

{% if book.suggestion_list %}
{% with book.suggestion_list.listitem_set.all 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-one-third is-centered">
{% include "book/suggestion_list/search.html" %}
</div>
</section>
{% else %}
<ol class="columns">
{% for item in items %}
<li class="mb-5 column is-one-quarter">
<div class="card">
<div class="card-content pb-3">
{% with book=item.book %}
<div class="is-cover">
<a href="{{ item.book.local_path }}" aria-hidden="true">
{% include 'snippets/book_cover.html' with cover_class='is-h-m-mobile is-h-m-tablet is-align-items-flex-start' size='medium' %}
</a>
</div>

<p class="mt-3">
{% include 'snippets/book_titleby.html' %}
</p>
{% endwith %}
</div>
<div class="card-footer is-stacked-mobile has-background-tertiary is-align-items-stretch">
<div class="card-footer-item">
<p>
{% blocktrans trimmed with username=item.user.display_name user_path=item.user.local_path %}
Added by <a href="{{ user_path }}">{{ username }}</a>
{% endblocktrans %}
</p>
</div>
<div class="card-footer-item">
<p>
TODO :: endorsements
</p>
</div>
</div>
</div>
</li>
{% endfor %}
<li class="mb-5 column is-one-quarter">
{% include "book/suggestion_list/search.html" %}
</li>
</ol>
{% endif %}
{% endwith %}
{% else %}
<section class="section is-medium has-background-light">
<form name="create-list" method="post" action="{% url 'book-create-suggestion-list' book_id=book.id %}#suggestions-section" class="has-text-centered">
{% csrf_token %}
<input type="hidden" name="user" value="{{ request.user.id }}">
<input type="hidden" name="suggests_for" value="{{ book.id }}">
<button type="submit" class="button is-medium">{% trans "Create suggestion list" %}</button>
</form>
</section>
{% endif %}
54 changes: 54 additions & 0 deletions bookwyrm/templates/book/suggestion_list/search.html
@@ -0,0 +1,54 @@
{% load i18n %}
{% load utilities %}

{% if request.user.is_authenticated %}
{% with book.suggestion_list as list %}
<h2 class="title is-5">
{% trans "Add suggestions" %}
</h2>
<form name="search" action="{% url 'book' book_id=book.id %}#suggestions-section" method="GET" class="block">
<div class="field has-addons">
<div class="control">
<input aria-label="{% trans 'Search for a book' %}" class="input" type="text" name="suggestion_query" placeholder="{% trans 'Search for a book' %}" value="{{ query }}">
</div>
<div class="control">
<button class="button" type="submit">
<span class="icon icon-search" title="{% trans 'Search' %}">
<span class="is-sr-only">{% trans "search" %}</span>
</span>
</button>
</div>
</div>
{% if query %}
<p class="help"><a href="{% url 'book' book_id=book.id %}#suggestions-section">{% trans "Clear search" %}</a></p>
{% endif %}
</form>
{% if not suggested_books %}
{% if query %}
<p>{% blocktrans %}No books found matching the query "{{ query }}"{% endblocktrans %}</p>{% else %}
<p>{% trans "No books found" %}</p>
{% endif %}
{% endif %}

{% if suggested_books|length > 0 %}
{% for book in suggested_books %}
<div class="columns is-mobile is-gapless">
<div class="column ml-3">
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>

{% join "add_item" list.id book.id as modal_id %}
<button
type="button"
class="button is-small is-link"
data-modal-open="{{ modal_id }}"
>
{% trans "Add" %}
</button>
{% include "lists/add_item_modal.html" with id=modal_id is_suggestion=True %}
</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% endif %}

6 changes: 5 additions & 1 deletion bookwyrm/templates/lists/add_item_modal.html
Expand Up @@ -19,7 +19,11 @@
<form
name="add-book-{{ book.id }}"
method="POST"
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
{% if is_suggestion %}
action="{% url 'book-add-suggestion' book_id=list.suggests_for.id %}{% if query %}?suggestion_query={{ query }}#suggestions-section{% endif %}"
{% else %}
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
{% endif %}
>
{% endblock %}

Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/templates/lists/embed-list.html
Expand Up @@ -21,7 +21,7 @@ <h1 class="title is-4">
</p>

<div class="block content">
{% include 'snippets/trimmed_text.html' with full=list.description %}
{% include 'snippets/trimmed_text.html' with full=list.get_description %}
</div>

<section>
Expand Down
10 changes: 6 additions & 4 deletions bookwyrm/templates/lists/layout.html
Expand Up @@ -2,7 +2,7 @@
{% load i18n %}
{% load list_page_tags %}

{% block title %}{{ list.name }}{% endblock %}
{% block title %}{{ list.get_name }}{% endblock %}

{% block opengraph %}
{% include 'snippets/opengraph.html' with title=list|opengraph_title description=list|opengraph_description %}
Expand All @@ -11,14 +11,16 @@
{% block content %}
<header class="columns content is-mobile">
<div class="column">
<h1 class="title">{{ list.name }} <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span></h1>
<h1 class="title">{{ list.get_name }} <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span></h1>
{% if list.suggests_for == None %}
<p class="subtitle help">
{% include 'lists/created_text.html' with list=list %}
</p>
{% endif %}
</div>

<div class="column is-narrow is-flex field is-grouped">
{% if request.user == list.user %}
{% if request.user == list.user and list.suggests_for == None %}
<div class="control">
{% trans "Edit List" as button_text %}
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_list" focus="edit_list_header" %}
Expand All @@ -33,7 +35,7 @@ <h1 class="title">{{ list.name }} <span class="subtitle">{% include 'snippets/pr
{% block breadcrumbs %}{% endblock %}

<div class="block content">
{% include 'snippets/trimmed_text.html' with full=list.description %}
{% include 'snippets/trimmed_text.html' with full=list.get_description %}
</div>

<div class="block">
Expand Down
6 changes: 4 additions & 2 deletions bookwyrm/templates/lists/list.html
Expand Up @@ -12,7 +12,7 @@
<li><a href="{% url 'lists' %}">{% trans "Lists" %}</a></li>
<li class="is-active">
<a href="#" aria-current="page">
{{ list.name|truncatechars:30 }}
{{ list.get_name|truncatechars:30 }}
</a>
</li>
</ul>
Expand Down Expand Up @@ -177,6 +177,7 @@
</section>

<section class="column is-one-quarter">
{% if list.suggests_for == None %}
<h2 class="title is-5">
{% trans "Sort List" %}
</h2>
Expand All @@ -199,6 +200,7 @@ <h2 class="title is-5">
</button>
</div>
</form>
{% endif %}
{% if request.user.is_authenticated and not list.curation == 'closed' or request.user == list.user %}
<h2 class="title is-5 mt-6">
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
Expand Down Expand Up @@ -275,7 +277,7 @@ <h2 class="title is-5 mt-6" id="embed-label">
data-copytext
data-copytext-label="{% trans 'Copy embed code' %}"
data-copytext-success="{% trans 'Copied!' %}"
>&lt;iframe style="border-width:0;" id="bookwyrm_list_embed" width="400" height="600" title="{% blocktrans trimmed with list_name=list.name site_name=site.name owner=list.user.display_name %}
>&lt;iframe style="border-width:0;" id="bookwyrm_list_embed" width="400" height="600" title="{% blocktrans trimmed with list_name=list.get_name site_name=site.name owner=list.user.display_name %}
{{ list_name }}, a list by {{owner}} on {{ site_name }}
{% endblocktrans %}" src="{{ embed_url }}"&gt;&lt;/iframe&gt;</textarea>
</div>
Expand Down
8 changes: 4 additions & 4 deletions bookwyrm/templates/lists/list_items.html
Expand Up @@ -8,7 +8,7 @@
<div class="card is-stretchable">
<header class="card-header">
<h4 class="card-header-title is-clipped">
<a href="{{ list.local_path }}" class="is-clipped">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
<a href="{{ list.local_path }}" class="is-clipped">{{ list.get_name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
</h4>
{% if request.user.is_authenticated and request.user|saved:list %}
<div class="card-header-icon">
Expand All @@ -33,9 +33,9 @@ <h4 class="card-header-title is-clipped">
{% endwith %}

<div class="card-content is-flex-grow-0">
<div class="is-clipped" {% if list.description %}title="{{ list.description }}"{% endif %}>
{% if list.description %}
{{ list.description|to_markdown|safe|truncatechars_html:30 }}
<div class="is-clipped" {% if list.description %}title="{{ list.get_description }}"{% endif %}>
{% if list.get_description %}
{{ list.get_description|to_markdown|safe|truncatechars_html:30 }}
{% else %}
&nbsp;
{% endif %}
Expand Down
10 changes: 10 additions & 0 deletions bookwyrm/urls.py
Expand Up @@ -706,6 +706,16 @@
views.update_book_from_remote,
name="book-update-remote",
),
re_path(
rf"{BOOK_PATH}/create-suggestion-list/?$",
views.create_suggestion_list,
name="book-create-suggestion-list",
),
re_path(
rf"{BOOK_PATH}/book-add-suggestion/?$",
views.book_add_suggestion,
name="book-add-suggestion",
),
re_path(
r"^author/(?P<author_id>\d+)/update/(?P<connector_identifier>[\w\.]+)/?$",
views.update_author_from_remote,
Expand Down