Skip to content

Commit

Permalink
Uses a separate model for suggestion lists
Browse files Browse the repository at this point in the history
  • Loading branch information
mouse-reeve committed Aug 27, 2024
1 parent 6b615d4 commit 0034653
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 168 deletions.
14 changes: 10 additions & 4 deletions bookwyrm/forms/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ class Meta:
fields = ["user", "name", "description", "curation", "privacy", "group"]


class ListItemForm(CustomForm):
class Meta:
model = models.ListItem
fields = ["user", "book", "book_list", "notes"]


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


class ListItemForm(CustomForm):
class SuggestionListItemForm(CustomForm):
class Meta:
model = models.ListItem
model = models.SuggestionListItem
fields = ["user", "book", "book_list", "notes"]


Expand Down
1 change: 1 addition & 0 deletions bookwyrm/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .shelf import Shelf, ShelfBook
from .list import List, ListItem
from .list import SuggestionList, SuggestionListItem

from .status import Status, GeneratedNote, Comment, Quotation
from .status import Review, ReviewRating
Expand Down
185 changes: 117 additions & 68 deletions bookwyrm/models/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,76 +23,119 @@
)


class List(OrderedCollectionMixin, BookWyrmModel):
"""a list of books"""
class AbstractList(OrderedCollectionMixin, BookWyrmModel):
"""Abstract model for regular lists and suggestion lists"""

name = fields.CharField(max_length=100)
embed_key = models.UUIDField(unique=True, null=True, editable=False)
activity_serializer = activitypub.BookList
privacy = fields.PrivacyField()
user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="owner"
)
description = fields.TextField(blank=True, null=True, activitypub_field="summary")
privacy = fields.PrivacyField()
curation = fields.CharField(
max_length=255, default="closed", choices=CurationType.choices
)
group = models.ForeignKey(
"Group",
on_delete=models.SET_NULL,
default=None,
blank=True,
null=True,
)

def save(self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs):
"""on save, update embed_key and avoid clash with existing code"""
if not self.embed_key:
self.embed_key = uuid.uuid4()
update_fields = add_update_fields(update_fields, "embed_key")

super().save(*args, update_fields=update_fields, **kwargs)

@property
def collection_queryset(self):
raise NotImplementedError

class Meta:
"""default sorting"""

ordering = ("-updated_date",)
abstract = True


class SuggestionList(AbstractList):
"""a list of user-provided suggested things to read next"""

books = models.ManyToManyField(
"Edition",
symmetrical=False,
through="ListItem",
through="SuggestionListItem",
through_fields=("book_list", "book"),
)
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,
unique=True,
)

def get_remote_id(self):
"""don't want the user to be in there in this case"""
return f"{BASE_URL}/list/{self.id}"

@property
def collection_queryset(self):
"""list of books for this shelf, overrides OrderedCollectionMixin"""
return self.books.filter(listitem__approved=True).order_by("listitem")
return self.books.order_by("suggestionlistitem")

def save(self, *args, **kwargs):
"""on save, update embed_key and avoid clash with existing code"""
self.user = activitypub.get_representative()
self.privacy = "public"

super().save(*args, **kwargs)

def raise_not_editable(self, viewer):
"""anyone can create a suggestion list, no one can edit"""
return

def get_remote_id(self):
"""don't want the user to be in there in this case"""
return f"{BASE_URL}/book/{self.suggests_for.id}/suggestions"

@property
def get_name(self):
def 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
return _("Suggestions for %(title)s") % {"title": self.suggests_for.title}

@property
def get_description(self):
def 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 _(
"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"""
class List(AbstractList):
"""a list of books"""

ordering = ("-updated_date",)
books = models.ManyToManyField(
"Edition",
symmetrical=False,
through="ListItem",
through_fields=("book_list", "book"),
)
name = fields.CharField(max_length=100)
description = fields.TextField(blank=True, null=True, activitypub_field="summary")
curation = fields.CharField(
max_length=255, default="closed", choices=CurationType.choices
)
group = models.ForeignKey(
"Group",
on_delete=models.SET_NULL,
default=None,
blank=True,
null=True,
)

@property
def collection_queryset(self):
"""list of books for this shelf, overrides OrderedCollectionMixin"""
return self.books.filter(listitem__approved=True).order_by("listitem")

def get_remote_id(self):
"""don't want the user to be in there in this case"""
return f"{BASE_URL}/list/{self.id}"

def raise_not_editable(self, viewer):
"""the associated user OR the list owner can edit"""
Expand Down Expand Up @@ -156,45 +199,22 @@ def remove_from_group(cls, owner, user):
group=None, curation="closed"
)

def save(self, *args, update_fields: Optional[Iterable[str]] = None, **kwargs):
"""on save, update embed_key and avoid clash with existing code"""
if not self.embed_key:
self.embed_key = uuid.uuid4()
update_fields = add_update_fields(update_fields, "embed_key")

# ensure that suggestion lists have the right properties
if self.suggests_for:
self.privacy = "public"
self.curation = "open"

super().save(*args, update_fields=update_fields, **kwargs)


class ListItem(CollectionItemMixin, BookWyrmModel):
"""ok"""
class AbstractListItem(CollectionItemMixin, BookWyrmModel):
"""Abstracy class for list items for all types of lists"""

book = fields.ForeignKey(
"Edition", on_delete=models.PROTECT, activitypub_field="book"
)
book_list = models.ForeignKey("List", on_delete=models.CASCADE)
user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor"
)
notes = fields.HtmlField(blank=True, null=True, max_length=300)
approved = models.BooleanField(default=True)
order = fields.IntegerField()
endorsement = models.ManyToManyField("User", related_name="endorsers")

activity_serializer = activitypub.ListItem
collection_field = "book_list"

def save(self, *args, **kwargs):
"""Update the list's date"""
super().save(*args, **kwargs)
# tick the updated date on the parent list
self.book_list.updated_date = timezone.now()
self.book_list.save(broadcast=False, update_fields=["updated_date"])

def raise_not_deletable(self, viewer):
"""the associated user OR the list owner can delete"""
if self.book_list.user == viewer:
Expand All @@ -211,5 +231,34 @@ class Meta:
"""A book may only be placed into a list once,
and each order in the list may be used only once"""

unique_together = (("book", "book_list"), ("order", "book_list"))
unique_together = ("book", "book_list")
ordering = ("-created_date",)
abstract = True


class ListItem(AbstractListItem):
"""ok"""

book_list = models.ForeignKey("List", on_delete=models.CASCADE)
approved = models.BooleanField(default=True)
order = fields.IntegerField()

def save(self, *args, **kwargs):
"""Update the list's date"""
super().save(*args, **kwargs)
# tick the updated date on the parent list
self.book_list.updated_date = timezone.now()
self.book_list.save(broadcast=False, update_fields=["updated_date"])

class Meta:
"""A book may only be placed into a list once,
and each order in the list may be used only once"""

unique_together = (("book", "book_list"), ("order", "book_list"))


class SuggestionListItem(AbstractListItem):
"""items on a suggestion list"""

book_list = models.ForeignKey("SuggestionList", on_delete=models.CASCADE)
endorsement = models.ManyToManyField("User", related_name="suggestion_endorsers")
14 changes: 7 additions & 7 deletions bookwyrm/templates/book/suggestion_list/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

<h2 class="title is-3" id="suggestions-section">
{% trans "Suggestions" %}
<a class="help has-text-weight-normal" href="{% url 'suggestion-list' book_id=book.id %}">
{% blocktrans trimmed with count=book.suggestion_list.suggestionlistitem_set.count|intcomma %}
View all {{ count }} suggestions
{% endblocktrans %}
</a>
</h2>

{% if book.suggestion_list %}
Expand All @@ -13,7 +18,7 @@ <h2 class="title is-3" id="suggestions-section">
{% endblocktrans %}
</p>

{% with book.suggestion_list.listitem_set.all|slice:3 as items %}
{% 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">
Expand All @@ -22,11 +27,6 @@ <h2 class="title is-3" id="suggestions-section">
</div>
</section>
{% else %}
<a class="help has-text-weight-normal" href="{% url 'list' book.suggestion_list.id %}">
{% blocktrans trimmed with count=book.suggestion_list.listitem_set.count|intcomma %}
View all {{ count }} suggestions
{% endblocktrans %}
</a>
<ol class="columns is-multiline">
{% for item in items %}
<li class="mb-5 column is-one-third">
Expand All @@ -42,7 +42,7 @@ <h2 class="title is-3" id="suggestions-section">
{% 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">
<form name="create-list" method="post" action="{% url '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 }}">
Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/templates/lists/embed-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h1 class="title is-4">
</p>

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

<section>
Expand Down
6 changes: 3 additions & 3 deletions bookwyrm/templates/lists/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% load i18n %}
{% load list_page_tags %}

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

{% block opengraph %}
{% include 'snippets/opengraph.html' with title=list|opengraph_title description=list|opengraph_description %}
Expand All @@ -11,7 +11,7 @@
{% block content %}
<header class="columns content is-mobile">
<div class="column">
<h1 class="title">{{ list.get_name }} <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span></h1>
<h1 class="title">{{ list.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 %}
Expand All @@ -35,7 +35,7 @@ <h1 class="title">{{ list.get_name }} <span class="subtitle">{% include 'snippet
{% block breadcrumbs %}{% endblock %}

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

<div class="block">
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/templates/lists/list.html
Original file line number Diff line number Diff line change
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.get_name|truncatechars:30 }}
{{ list.name|truncatechars:30 }}
</a>
</li>
</ul>
Expand Down Expand Up @@ -182,7 +182,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.get_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.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
Original file line number Diff line number Diff line change
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.get_name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
<a href="{{ list.local_path }}" class="is-clipped">{{ list.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.get_description }}"{% endif %}>
{% if list.get_description %}
{{ list.get_description|to_markdown|safe|truncatechars_html:30 }}
<div class="is-clipped" {% if list.description %}title="{{ list.description }}"{% endif %}>
{% if list.description %}
{{ list.description|to_markdown|safe|truncatechars_html:30 }}
{% else %}
&nbsp;
{% endif %}
Expand Down
Loading

0 comments on commit 0034653

Please sign in to comment.