moviewyrm/bookwyrm/models/list.py

200 lines
6.7 KiB
Python
Raw Normal View History

2021-03-08 16:49:10 +00:00
""" make a list of books!! """
2021-12-04 15:06:07 +00:00
import uuid
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
from django.db.models import Q
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
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
from .base_model import BookWyrmModel
from .group import GroupMember
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
)
group = models.ForeignKey(
"Group",
on_delete=models.SET_NULL,
default=None,
blank=True,
null=True,
)
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)
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()
@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"""
return queryset.exclude(
2021-10-10 02:18:16 +00:00
~Q( # user isn't following or group member
Q(user__followers=viewer)
| Q(user=viewer)
| Q(group__memberships__user=viewer)
),
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"""
return queryset.exclude(
~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",
)
@classmethod
def remove_from_group(cls, owner, user):
"""remove a list from a group"""
cls.objects.filter(group__user=owner, user=user).all().update(
2021-10-16 05:09:03 +00:00
group=None, curation="closed"
)
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
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(
"Edition", on_delete=models.PROTECT, activitypub_field="book"
2021-03-08 16:49:10 +00:00
)
book_list = models.ForeignKey("List", on_delete=models.CASCADE)
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-01-24 20:01:17 +00:00
notes = fields.TextField(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
activity_serializer = activitypub.ListItem
2021-03-08 16:49:10 +00:00
collection_field = "book_list"
2021-01-31 05:00:36 +00:00
def save(self, *args, **kwargs):
2021-04-26 16:15:42 +00:00
"""create a notification too"""
created = not bool(self.id)
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)
list_owner = self.book_list.user
model = apps.get_model("bookwyrm.Notification", require_ready=True)
# 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",
)
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
# 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()
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",)