bookwyrm/bookwyrm/models/base_model.py

185 lines
6.3 KiB
Python
Raw Normal View History

2021-03-08 16:49:10 +00:00
""" base model with default fields """
2021-08-06 21:42:18 +00:00
import base64
from Crypto import Random
2021-09-27 21:02:34 +00:00
2021-10-06 16:48:11 +00:00
from django.core.exceptions import PermissionDenied, FieldError
from django.db import models
2021-10-06 16:48:11 +00:00
from django.db.models import Q
from django.dispatch import receiver
2021-09-27 22:54:58 +00:00
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from bookwyrm.settings import DOMAIN
from .fields import RemoteIdField
DeactivationReason = [
("pending", _("Pending")),
("self_deletion", _("Self deletion")),
("moderator_suspension", _("Moderator suspension")),
("moderator_deletion", _("Moderator deletion")),
("domain_block", _("Domain block")),
]
2021-08-06 21:42:18 +00:00
def new_access_code():
"""the identifier for a user invite"""
return base64.b32encode(Random.get_random_bytes(5)).decode("ascii")
2020-09-21 15:16:34 +00:00
class BookWyrmModel(models.Model):
2021-04-26 16:15:42 +00:00
"""shared fields"""
2021-03-08 16:49:10 +00:00
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
2021-03-08 16:49:10 +00:00
remote_id = RemoteIdField(null=True, activitypub_field="id")
def get_remote_id(self):
2021-04-26 16:15:42 +00:00
"""generate a url that resolves to the local object"""
2021-09-18 18:32:00 +00:00
base_path = f"https://{DOMAIN}"
2021-03-08 16:49:10 +00:00
if hasattr(self, "user"):
2021-09-18 18:32:00 +00:00
base_path = f"{base_path}{self.user.local_path}"
2020-02-18 01:53:40 +00:00
model_name = type(self).__name__.lower()
2021-09-18 18:32:00 +00:00
return f"{base_path}/{model_name}/{self.id}"
class Meta:
2021-04-26 16:15:42 +00:00
"""this is just here to provide default fields for other models"""
2021-03-08 16:49:10 +00:00
abstract = True
2020-12-31 01:36:35 +00:00
@property
def local_path(self):
2021-04-26 16:15:42 +00:00
"""how to link to this object in the local app"""
2021-09-18 18:32:00 +00:00
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
2020-12-31 01:36:35 +00:00
2021-09-27 22:54:58 +00:00
def raise_visible_to_user(self, viewer):
2021-04-26 16:15:42 +00:00
"""is a user authorized to view an object?"""
# 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 22:54:58 +00:00
return
# viewer can't see it if the object's owner blocked them
if viewer in self.user.blocks.all():
2021-09-27 22:54:58 +00:00
raise Http404()
# 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 22:54:58 +00:00
return
# you can see the followers only posts of people you follow
if (
self.privacy == "followers"
and self.user.followers.filter(id=viewer.id).first()
):
2021-09-27 22:54:58 +00:00
return
# you can see dms you are tagged in
if hasattr(self, "mention_users"):
if (
self.privacy == "direct"
and self.mention_users.filter(id=viewer.id).first()
):
2021-09-27 22:54:58 +00:00
return
raise Http404()
2021-09-27 21:02:34 +00: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 22:54:58 +00:00
raise PermissionDenied()
2021-09-27 21:02:34 +00: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 22:54:58 +00:00
raise PermissionDenied()
2021-09-27 21:02:34 +00:00
2021-10-06 16:48:11 +00:00
@classmethod
2021-10-06 17:37:09 +00:00
def privacy_filter(cls, viewer, privacy_levels=None, following_only=False):
2021-10-06 16:48:11 +00: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"]
# if there'd a deleted field, exclude deleted items
try:
queryset = queryset.filter(deleted=False)
except FieldError:
pass
# exclude blocks from both directions
if not viewer.is_anonymous:
queryset = queryset.exclude(
Q(user__blocked_by=viewer) | Q(user__blocks=viewer)
)
# 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"]
]
# filter to only privided privacy levels
queryset = queryset.filter(privacy__in=privacy_levels)
# only include statuses the user follows
if following_only:
queryset = queryset.exclude(
~Q( # remove everythign except
Q(user__followers=viewer)
| Q(user=viewer) # user following
| Q(mention_users=viewer) # is self # mentions user
),
)
# exclude followers-only statuses the user doesn't follow
elif "followers" in privacy_levels:
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"""
return queryset.exclude(~Q(user=viewer), privacy="direct")
2021-10-06 16:48:11 +00:00
2021-09-27 21:02:34 +00:00
2020-05-14 01:23:54 +00:00
@receiver(models.signals.post_save)
2021-03-08 16:49:10 +00:00
# pylint: disable=unused-argument
2021-03-22 21:11:23 +00:00
def set_remote_id(sender, instance, created, *args, **kwargs):
2021-04-26 16:15:42 +00:00
"""set the remote_id after save (when the id is available)"""
2021-03-08 16:49:10 +00:00
if not created or not hasattr(instance, "get_remote_id"):
return
2020-05-14 18:28:45 +00:00
if not instance.remote_id:
instance.remote_id = instance.get_remote_id()
2021-02-07 00:13:59 +00:00
try:
instance.save(broadcast=False)
except TypeError:
instance.save()