2021-03-08 16:49:10 +00:00
|
|
|
""" make a list of books!! """
|
2021-12-04 15:06:07 +00:00
|
|
|
import uuid
|
|
|
|
|
2021-02-10 21:46:56 +00:00
|
|
|
from django.apps import apps
|
2022-01-25 20:10:58 +00:00
|
|
|
from django.core.exceptions import PermissionDenied
|
2021-01-31 05:00:36 +00:00
|
|
|
from django.db import models
|
2021-10-09 05:11:11 +00:00
|
|
|
from django.db.models import Q
|
2021-03-30 17:28:50 +00:00
|
|
|
from django.utils import timezone
|
2021-01-31 05:00:36 +00:00
|
|
|
|
|
|
|
from bookwyrm import activitypub
|
2021-01-31 16:08:52 +00:00
|
|
|
from bookwyrm.settings import DOMAIN
|
2021-10-04 22:05:20 +00:00
|
|
|
|
2021-02-04 20:25:07 +00:00
|
|
|
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
2021-02-04 18:47:03 +00:00
|
|
|
from .base_model import BookWyrmModel
|
2021-10-03 01:22:03 +00:00
|
|
|
from .group import GroupMember
|
2021-10-04 22:05:20 +00:00
|
|
|
from . import fields
|
2021-01-31 05:00:36 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
CurationType = models.TextChoices(
|
|
|
|
"Curation",
|
2021-10-04 10:31:28 +00:00
|
|
|
["closed", "open", "curated", "group"],
|
2021-03-08 16:49:10 +00:00
|
|
|
)
|
|
|
|
|
2021-01-31 05:00:36 +00:00
|
|
|
|
|
|
|
class List(OrderedCollectionMixin, BookWyrmModel):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""a list of books"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2021-01-31 05:00:36 +00:00
|
|
|
name = fields.CharField(max_length=100)
|
|
|
|
user = fields.ForeignKey(
|
2021-03-08 16:49:10 +00:00
|
|
|
"User", on_delete=models.PROTECT, activitypub_field="owner"
|
|
|
|
)
|
|
|
|
description = fields.TextField(blank=True, null=True, activitypub_field="summary")
|
2021-02-02 17:37:46 +00:00
|
|
|
privacy = fields.PrivacyField()
|
2021-01-31 05:00:36 +00:00
|
|
|
curation = fields.CharField(
|
2021-03-08 16:49:10 +00:00
|
|
|
max_length=255, default="closed", choices=CurationType.choices
|
2021-01-31 05:00:36 +00:00
|
|
|
)
|
2021-09-26 05:50:15 +00:00
|
|
|
group = models.ForeignKey(
|
2021-10-02 06:52:34 +00:00
|
|
|
"Group",
|
2021-10-16 06:35:36 +00:00
|
|
|
on_delete=models.SET_NULL,
|
2021-09-26 08:28:16 +00:00
|
|
|
default=None,
|
2021-09-27 05:36:41 +00:00
|
|
|
blank=True,
|
|
|
|
null=True,
|
2021-09-26 05:50:15 +00:00
|
|
|
)
|
2021-01-31 05:00:36 +00:00
|
|
|
books = models.ManyToManyField(
|
2021-03-08 16:49:10 +00:00
|
|
|
"Edition",
|
2021-01-31 05:00:36 +00:00
|
|
|
symmetrical=False,
|
2021-03-08 16:49:10 +00:00
|
|
|
through="ListItem",
|
|
|
|
through_fields=("book_list", "book"),
|
2021-01-31 05:00:36 +00:00
|
|
|
)
|
2021-12-04 15:06:07 +00:00
|
|
|
embed_key = models.UUIDField(unique=True, null=True, editable=False)
|
2021-02-02 19:05:47 +00:00
|
|
|
activity_serializer = activitypub.BookList
|
2021-01-31 16:08:52 +00:00
|
|
|
|
|
|
|
def get_remote_id(self):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""don't want the user to be in there in this case"""
|
2021-09-18 18:32:00 +00:00
|
|
|
return f"https://{DOMAIN}/list/{self.id}"
|
2021-01-31 16:08:52 +00:00
|
|
|
|
2021-01-31 05:00:36 +00:00
|
|
|
@property
|
|
|
|
def collection_queryset(self):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""list of books for this shelf, overrides OrderedCollectionMixin"""
|
2021-04-19 21:47:59 +00:00
|
|
|
return self.books.filter(listitem__approved=True).order_by("listitem")
|
2021-01-31 05:00:36 +00:00
|
|
|
|
2021-02-01 01:34:06 +00:00
|
|
|
class Meta:
|
2021-04-26 16:15:42 +00:00
|
|
|
"""default sorting"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
|
|
|
ordering = ("-updated_date",)
|
2021-02-01 01:34:06 +00:00
|
|
|
|
2021-10-03 02:45:19 +00:00
|
|
|
def raise_not_editable(self, viewer):
|
|
|
|
"""the associated user OR the list owner can edit"""
|
|
|
|
if self.user == viewer:
|
|
|
|
return
|
|
|
|
# group members can edit items in group lists
|
2021-10-04 10:31:28 +00:00
|
|
|
is_group_member = GroupMember.objects.filter(
|
|
|
|
group=self.group, user=viewer
|
|
|
|
).exists()
|
2021-10-03 02:45:19 +00:00
|
|
|
if is_group_member:
|
|
|
|
return
|
|
|
|
super().raise_not_editable(viewer)
|
2021-01-31 05:00:36 +00:00
|
|
|
|
2022-01-25 20:10:58 +00:00
|
|
|
def raise_not_submittable(self, viewer):
|
|
|
|
"""can the user submit a book to the list?"""
|
|
|
|
# if you can't view the list you can't submit to it
|
|
|
|
self.raise_visible_to_user(viewer)
|
|
|
|
|
|
|
|
# all good if you're the owner or the list is open
|
|
|
|
if self.user == viewer or self.curation in ["open", "curated"]:
|
|
|
|
return
|
|
|
|
if self.curation == "group":
|
|
|
|
is_group_member = GroupMember.objects.filter(
|
|
|
|
group=self.group, user=viewer
|
|
|
|
).exists()
|
|
|
|
if is_group_member:
|
|
|
|
return
|
|
|
|
raise PermissionDenied()
|
|
|
|
|
2021-10-09 05:11:11 +00:00
|
|
|
@classmethod
|
|
|
|
def followers_filter(cls, queryset, viewer):
|
2021-10-10 02:18:16 +00:00
|
|
|
"""Override filter for "followers" privacy level to allow non-following
|
|
|
|
group members to see the existence of group lists"""
|
2021-10-09 05:11:11 +00:00
|
|
|
|
|
|
|
return queryset.exclude(
|
2021-10-10 02:18:16 +00:00
|
|
|
~Q( # user isn't following or group member
|
2021-10-09 11:15:24 +00:00
|
|
|
Q(user__followers=viewer)
|
|
|
|
| Q(user=viewer)
|
|
|
|
| Q(group__memberships__user=viewer)
|
2021-10-09 05:11:11 +00:00
|
|
|
),
|
|
|
|
privacy="followers", # and the status (of the list) is followers only
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def direct_filter(cls, queryset, viewer):
|
2021-10-10 02:18:16 +00:00
|
|
|
"""Override filter for "direct" privacy level to allow
|
|
|
|
group members to see the existence of group lists"""
|
2021-10-09 05:11:11 +00:00
|
|
|
|
2021-10-09 11:11:46 +00:00
|
|
|
return queryset.exclude(
|
2021-10-09 11:15:24 +00:00
|
|
|
~Q( # user not self and not in the group if this is a group list
|
|
|
|
Q(user=viewer) | Q(group__memberships__user=viewer)
|
|
|
|
),
|
|
|
|
privacy="direct",
|
2021-10-09 11:11:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def remove_from_group(cls, owner, user):
|
|
|
|
"""remove a list from a group"""
|
|
|
|
|
2021-10-15 21:17:42 +00:00
|
|
|
cls.objects.filter(group__user=owner, user=user).all().update(
|
2021-10-16 05:09:03 +00:00
|
|
|
group=None, curation="closed"
|
|
|
|
)
|
2021-10-09 11:11:46 +00:00
|
|
|
|
2021-12-04 15:06:07 +00:00
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
"""on save, update embed_key and avoid clash with existing code"""
|
|
|
|
if not self.embed_key:
|
|
|
|
self.embed_key = uuid.uuid4()
|
2021-12-04 16:33:28 +00:00
|
|
|
return super().save(*args, **kwargs)
|
2021-12-04 15:06:07 +00:00
|
|
|
|
2021-10-15 19:59:07 +00:00
|
|
|
|
2021-02-04 20:25:07 +00:00
|
|
|
class ListItem(CollectionItemMixin, BookWyrmModel):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""ok"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2021-01-31 05:00:36 +00:00
|
|
|
book = fields.ForeignKey(
|
2021-04-08 21:47:38 +00:00
|
|
|
"Edition", on_delete=models.PROTECT, activitypub_field="book"
|
2021-03-08 16:49:10 +00:00
|
|
|
)
|
2021-04-08 21:53:45 +00:00
|
|
|
book_list = models.ForeignKey("List", on_delete=models.CASCADE)
|
2021-02-04 20:25:07 +00:00
|
|
|
user = fields.ForeignKey(
|
2021-03-08 16:49:10 +00:00
|
|
|
"User", on_delete=models.PROTECT, activitypub_field="actor"
|
2021-01-31 05:00:36 +00:00
|
|
|
)
|
2022-02-04 20:06:47 +00:00
|
|
|
notes = fields.HtmlField(blank=True, null=True, max_length=300)
|
2021-01-31 05:00:36 +00:00
|
|
|
approved = models.BooleanField(default=True)
|
2021-04-08 16:05:21 +00:00
|
|
|
order = fields.IntegerField()
|
2021-03-08 16:49:10 +00:00
|
|
|
endorsement = models.ManyToManyField("User", related_name="endorsers")
|
2021-01-31 05:00:36 +00:00
|
|
|
|
2021-04-08 21:17:29 +00:00
|
|
|
activity_serializer = activitypub.ListItem
|
2021-03-08 16:49:10 +00:00
|
|
|
collection_field = "book_list"
|
2021-01-31 05:00:36 +00:00
|
|
|
|
2021-02-10 21:46:56 +00:00
|
|
|
def save(self, *args, **kwargs):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""create a notification too"""
|
2021-02-10 21:46:56 +00:00
|
|
|
created = not bool(self.id)
|
|
|
|
super().save(*args, **kwargs)
|
2021-03-30 17:28:50 +00:00
|
|
|
# tick the updated date on the parent list
|
|
|
|
self.book_list.updated_date = timezone.now()
|
|
|
|
self.book_list.save(broadcast=False)
|
|
|
|
|
2021-02-10 21:46:56 +00:00
|
|
|
list_owner = self.book_list.user
|
2021-10-02 10:14:53 +00:00
|
|
|
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
2021-02-10 21:46:56 +00:00
|
|
|
# create a notification if somoene ELSE added to a local user's list
|
|
|
|
if created and list_owner.local and list_owner != self.user:
|
|
|
|
model.objects.create(
|
|
|
|
user=list_owner,
|
|
|
|
related_user=self.user,
|
|
|
|
related_list_item=self,
|
2021-03-08 16:49:10 +00:00
|
|
|
notification_type="ADD",
|
2021-02-10 21:46:56 +00:00
|
|
|
)
|
|
|
|
|
2021-10-02 10:14:53 +00:00
|
|
|
if self.book_list.group:
|
|
|
|
for membership in self.book_list.group.memberships.all():
|
|
|
|
if membership.user != self.user:
|
|
|
|
model.objects.create(
|
|
|
|
user=membership.user,
|
|
|
|
related_user=self.user,
|
|
|
|
related_list_item=self,
|
2021-10-04 10:31:28 +00:00
|
|
|
notification_type="ADD",
|
|
|
|
)
|
|
|
|
|
2021-09-27 21:03:06 +00:00
|
|
|
def raise_not_deletable(self, viewer):
|
|
|
|
"""the associated user OR the list owner can delete"""
|
|
|
|
if self.book_list.user == viewer:
|
|
|
|
return
|
2021-10-03 00:45:53 +00:00
|
|
|
# group members can delete items in group lists
|
2021-10-04 10:31:28 +00:00
|
|
|
is_group_member = GroupMember.objects.filter(
|
|
|
|
group=self.book_list.group, user=viewer
|
|
|
|
).exists()
|
2021-10-03 00:45:53 +00:00
|
|
|
if is_group_member:
|
|
|
|
return
|
2021-09-27 21:03:06 +00:00
|
|
|
super().raise_not_deletable(viewer)
|
|
|
|
|
2021-01-31 05:00:36 +00:00
|
|
|
class Meta:
|
2021-06-18 21:12:56 +00:00
|
|
|
"""A book may only be placed into a list once,
|
|
|
|
and each order in the list may be used only once"""
|
|
|
|
|
2021-04-08 16:05:21 +00:00
|
|
|
unique_together = (("book", "book_list"), ("order", "book_list"))
|
2021-03-08 16:49:10 +00:00
|
|
|
ordering = ("-created_date",)
|