2021-03-08 11:49:10 -05:00
|
|
|
""" base model with default fields """
|
2021-08-06 17:42:18 -04:00
|
|
|
import base64
|
|
|
|
from Crypto import Random
|
2021-09-27 17:02:34 -04:00
|
|
|
|
|
|
|
from django.core.exceptions import PermissionDenied
|
2020-02-17 19:50:44 -05:00
|
|
|
from django.db import models
|
2021-10-06 12:48:11 -04:00
|
|
|
from django.db.models import Q
|
2020-05-12 21:56:28 -04:00
|
|
|
from django.dispatch import receiver
|
2021-09-27 18:54:58 -04:00
|
|
|
from django.http import Http404
|
2021-09-11 17:16:52 -04:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-02-17 19:50:44 -05:00
|
|
|
|
2021-02-04 13:47:03 -05:00
|
|
|
from bookwyrm.settings import DOMAIN
|
|
|
|
from .fields import RemoteIdField
|
2020-02-17 19:50:44 -05:00
|
|
|
|
2020-10-30 14:21:02 -04:00
|
|
|
|
2021-09-11 17:16:52 -04:00
|
|
|
DeactivationReason = [
|
|
|
|
("pending", _("Pending")),
|
|
|
|
("self_deletion", _("Self deletion")),
|
|
|
|
("moderator_suspension", _("Moderator suspension")),
|
|
|
|
("moderator_deletion", _("Moderator deletion")),
|
|
|
|
("domain_block", _("Domain block")),
|
|
|
|
]
|
2021-05-11 14:31:02 -04:00
|
|
|
|
|
|
|
|
2021-08-06 17:42:18 -04:00
|
|
|
def new_access_code():
|
|
|
|
"""the identifier for a user invite"""
|
|
|
|
return base64.b32encode(Random.get_random_bytes(5)).decode("ascii")
|
|
|
|
|
|
|
|
|
2020-09-21 11:16:34 -04:00
|
|
|
class BookWyrmModel(models.Model):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""shared fields"""
|
2021-03-08 11:49:10 -05:00
|
|
|
|
2020-02-17 19:50:44 -05:00
|
|
|
created_date = models.DateTimeField(auto_now_add=True)
|
2020-02-20 20:33:50 -05:00
|
|
|
updated_date = models.DateTimeField(auto_now=True)
|
2021-03-08 11:49:10 -05:00
|
|
|
remote_id = RemoteIdField(null=True, activitypub_field="id")
|
2020-02-17 19:50:44 -05:00
|
|
|
|
2020-05-12 21:56:28 -04:00
|
|
|
def get_remote_id(self):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""generate a url that resolves to the local object"""
|
2021-09-18 14:32:00 -04:00
|
|
|
base_path = f"https://{DOMAIN}"
|
2021-03-08 11:49:10 -05:00
|
|
|
if hasattr(self, "user"):
|
2021-09-18 14:32:00 -04:00
|
|
|
base_path = f"{base_path}{self.user.local_path}"
|
2020-02-17 20:53:40 -05:00
|
|
|
model_name = type(self).__name__.lower()
|
2021-09-18 14:32:00 -04:00
|
|
|
return f"{base_path}/{model_name}/{self.id}"
|
2020-02-17 19:50:44 -05:00
|
|
|
|
|
|
|
class Meta:
|
2021-04-26 12:15:42 -04:00
|
|
|
"""this is just here to provide default fields for other models"""
|
2021-03-08 11:49:10 -05:00
|
|
|
|
2020-02-17 19:50:44 -05:00
|
|
|
abstract = True
|
2020-05-12 21:56:28 -04:00
|
|
|
|
2020-12-30 20:36:35 -05:00
|
|
|
@property
|
|
|
|
def local_path(self):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""how to link to this object in the local app"""
|
2021-09-18 14:32:00 -04:00
|
|
|
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
|
2020-12-30 20:36:35 -05:00
|
|
|
|
2021-09-27 18:54:58 -04:00
|
|
|
def raise_visible_to_user(self, viewer):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""is a user authorized to view an object?"""
|
2021-04-11 13:45:08 -04:00
|
|
|
# make sure this is an object with privacy owned by a user
|
|
|
|
if not hasattr(self, "user") or not hasattr(self, "privacy"):
|
2021-09-27 18:54:58 -04:00
|
|
|
return
|
2021-04-11 13:45:08 -04:00
|
|
|
|
|
|
|
# viewer can't see it if the object's owner blocked them
|
|
|
|
if viewer in self.user.blocks.all():
|
2021-09-27 18:54:58 -04:00
|
|
|
raise Http404()
|
2021-04-11 13:45:08 -04:00
|
|
|
|
|
|
|
# you can see your own posts and any public or unlisted posts
|
|
|
|
if viewer == self.user or self.privacy in ["public", "unlisted"]:
|
2021-09-27 18:54:58 -04:00
|
|
|
return
|
2021-04-11 13:45:08 -04:00
|
|
|
|
|
|
|
# you can see the followers only posts of people you follow
|
2021-10-14 19:26:28 -04:00
|
|
|
if self.privacy == "followers" and (
|
|
|
|
self.user.followers.filter(id=viewer.id).first()
|
2021-04-11 13:45:08 -04:00
|
|
|
):
|
2021-09-27 18:54:58 -04:00
|
|
|
return
|
2021-04-11 13:45:08 -04:00
|
|
|
|
|
|
|
# you can see dms you are tagged in
|
|
|
|
if hasattr(self, "mention_users"):
|
|
|
|
if (
|
2021-10-15 16:26:02 -04:00
|
|
|
self.privacy in ["direct", "followers"]
|
2021-04-11 13:45:08 -04:00
|
|
|
and self.mention_users.filter(id=viewer.id).first()
|
|
|
|
):
|
2021-10-02 19:49:38 -04:00
|
|
|
|
2021-10-04 17:09:24 -04:00
|
|
|
return
|
2021-09-27 01:36:41 -04:00
|
|
|
|
2021-09-27 06:24:25 -04:00
|
|
|
# you can see groups of which you are a member
|
2021-10-04 06:31:28 -04:00
|
|
|
if (
|
|
|
|
hasattr(self, "memberships")
|
2021-12-28 17:33:30 -05:00
|
|
|
and viewer.is_authenticated
|
2021-10-04 06:31:28 -04:00
|
|
|
and self.memberships.filter(user=viewer).exists()
|
|
|
|
):
|
2021-10-04 17:09:24 -04:00
|
|
|
return
|
2021-09-27 06:24:25 -04:00
|
|
|
|
|
|
|
# you can see objects which have a group of which you are a member
|
|
|
|
if hasattr(self, "group"):
|
|
|
|
if (
|
2021-10-02 04:31:56 -04:00
|
|
|
hasattr(self.group, "memberships")
|
|
|
|
and self.group.memberships.filter(user=viewer).exists()
|
2021-09-27 06:24:25 -04:00
|
|
|
):
|
2021-10-04 17:09:24 -04:00
|
|
|
return
|
2021-09-27 01:36:41 -04:00
|
|
|
|
2021-09-27 18:54:58 -04:00
|
|
|
raise Http404()
|
2021-04-11 13:45:08 -04:00
|
|
|
|
2021-09-27 17:02:34 -04:00
|
|
|
def raise_not_editable(self, viewer):
|
|
|
|
"""does this user have permission to edit this object? liable to be overwritten
|
|
|
|
by models that inherit this base model class"""
|
|
|
|
if not hasattr(self, "user"):
|
|
|
|
return
|
|
|
|
|
|
|
|
# generally moderators shouldn't be able to edit other people's stuff
|
|
|
|
if self.user == viewer:
|
|
|
|
return
|
|
|
|
|
2021-09-27 18:54:58 -04:00
|
|
|
raise PermissionDenied()
|
2021-09-27 17:02:34 -04:00
|
|
|
|
|
|
|
def raise_not_deletable(self, viewer):
|
|
|
|
"""does this user have permission to delete this object? liable to be
|
|
|
|
overwritten by models that inherit this base model class"""
|
|
|
|
if not hasattr(self, "user"):
|
|
|
|
return
|
|
|
|
|
|
|
|
# but generally moderators can delete other people's stuff
|
|
|
|
if self.user == viewer or viewer.has_perm("moderate_post"):
|
|
|
|
return
|
|
|
|
|
2021-09-27 18:54:58 -04:00
|
|
|
raise PermissionDenied()
|
2021-04-11 13:45:08 -04:00
|
|
|
|
2021-10-06 12:48:11 -04:00
|
|
|
@classmethod
|
2021-10-06 14:12:03 -04:00
|
|
|
def privacy_filter(cls, viewer, privacy_levels=None):
|
2021-10-06 12:48:11 -04:00
|
|
|
"""filter objects that have "user" and "privacy" fields"""
|
|
|
|
queryset = cls.objects
|
|
|
|
if hasattr(queryset, "select_subclasses"):
|
|
|
|
queryset = queryset.select_subclasses()
|
|
|
|
|
|
|
|
privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"]
|
2021-10-06 14:15:17 -04:00
|
|
|
# you can't see followers only or direct messages if you're not logged in
|
|
|
|
if viewer.is_anonymous:
|
|
|
|
privacy_levels = [
|
|
|
|
p for p in privacy_levels if not p in ["followers", "direct"]
|
|
|
|
]
|
2021-10-06 14:23:36 -04:00
|
|
|
else:
|
|
|
|
# exclude blocks from both directions
|
2021-10-06 12:48:11 -04:00
|
|
|
queryset = queryset.exclude(
|
|
|
|
Q(user__blocked_by=viewer) | Q(user__blocks=viewer)
|
|
|
|
)
|
|
|
|
|
2021-10-06 14:23:36 -04:00
|
|
|
# filter to only provided privacy levels
|
2021-10-06 12:48:11 -04:00
|
|
|
queryset = queryset.filter(privacy__in=privacy_levels)
|
|
|
|
|
2021-10-06 14:12:03 -04:00
|
|
|
if "followers" in privacy_levels:
|
2021-10-06 12:48:11 -04:00
|
|
|
queryset = cls.followers_filter(queryset, viewer)
|
|
|
|
|
|
|
|
# exclude direct messages not intended for the user
|
|
|
|
if "direct" in privacy_levels:
|
|
|
|
queryset = cls.direct_filter(queryset, viewer)
|
|
|
|
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def followers_filter(cls, queryset, viewer):
|
|
|
|
"""Override-able filter for "followers" privacy level"""
|
|
|
|
return queryset.exclude(
|
|
|
|
~Q( # user isn't following and it isn't their own status
|
|
|
|
Q(user__followers=viewer) | Q(user=viewer)
|
|
|
|
),
|
|
|
|
privacy="followers", # and the status is followers only
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def direct_filter(cls, queryset, viewer):
|
|
|
|
"""Override-able filter for "direct" privacy level"""
|
2021-10-06 14:08:54 -04:00
|
|
|
return queryset.exclude(~Q(user=viewer), privacy="direct")
|
2021-10-06 12:48:11 -04:00
|
|
|
|
2020-05-12 21:56:28 -04:00
|
|
|
|
2020-05-13 21:23:54 -04:00
|
|
|
@receiver(models.signals.post_save)
|
2021-03-08 11:49:10 -05:00
|
|
|
# pylint: disable=unused-argument
|
2021-03-22 17:11:23 -04:00
|
|
|
def set_remote_id(sender, instance, created, *args, **kwargs):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""set the remote_id after save (when the id is available)"""
|
2021-03-08 11:49:10 -05:00
|
|
|
if not created or not hasattr(instance, "get_remote_id"):
|
2020-05-12 21:56:28 -04:00
|
|
|
return
|
2020-05-14 14:28:45 -04:00
|
|
|
if not instance.remote_id:
|
|
|
|
instance.remote_id = instance.get_remote_id()
|
2021-02-06 19:13:59 -05:00
|
|
|
try:
|
|
|
|
instance.save(broadcast=False)
|
|
|
|
except TypeError:
|
|
|
|
instance.save()
|