1
0
Fork 0

Merge branch 'main' into misc/add_signatures_to_requests_for_masto_compat

This commit is contained in:
Mouse Reeve 2022-01-13 11:37:54 -08:00 committed by GitHub
commit 8cc4427e60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
272 changed files with 17785 additions and 5282 deletions

View file

@ -4,6 +4,7 @@ import sys
from .book import Book, Work, Edition, BookDataModel
from .author import Author
from .link import Link, FileLink, LinkDomain
from .connector import Connector
from .shelf import Shelf, ShelfBook

View file

@ -1,6 +1,8 @@
""" database schema for info about authors """
import re
from django.contrib.postgres.indexes import GinIndex
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.db import models
from bookwyrm import activitypub
@ -34,6 +36,17 @@ class Author(BookDataModel):
)
bio = fields.HtmlField(null=True, blank=True)
def save(self, *args, **kwargs):
"""clear related template caches"""
# clear template caches
if self.id:
cache_keys = [
make_template_fragment_key("titleby", [book])
for book in self.book_set.values_list("id", flat=True)
]
cache.delete_many(cache_keys)
return super().save(*args, **kwargs)
@property
def isni_link(self):
"""generate the url from the isni id"""

View file

@ -3,6 +3,8 @@ import re
from django.contrib.postgres.search import SearchVectorField
from django.contrib.postgres.indexes import GinIndex
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.db import models, transaction
from django.db.models import Prefetch
from django.dispatch import receiver
@ -185,6 +187,11 @@ class Book(BookDataModel):
"""can't be abstract for query reasons, but you shouldn't USE it"""
if not isinstance(self, Edition) and not isinstance(self, Work):
raise ValueError("Books should be added as Editions or Works")
# clear template caches
cache_key = make_template_fragment_key("titleby", [self.id])
cache.delete(cache_key)
return super().save(*args, **kwargs)
def get_remote_id(self):
@ -234,8 +241,11 @@ class Work(OrderedCollectionPageMixin, Book):
)
activity_serializer = activitypub.Work
serialize_reverse_fields = [("editions", "editions", "-edition_rank")]
deserialize_reverse_fields = [("editions", "editions")]
serialize_reverse_fields = [
("editions", "editions", "-edition_rank"),
("file_links", "fileLinks", "-created_date"),
]
deserialize_reverse_fields = [("editions", "editions"), ("file_links", "fileLinks")]
# https://schema.org/BookFormatType
@ -289,6 +299,8 @@ class Edition(Book):
activity_serializer = activitypub.Edition
name_field = "title"
serialize_reverse_fields = [("file_links", "fileLinks", "-created_date")]
deserialize_reverse_fields = [("file_links", "fileLinks")]
def get_rank(self):
"""calculate how complete the data is on this edition"""

View file

@ -203,9 +203,12 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
return value.split("@")[0]
PrivacyLevels = models.TextChoices(
"Privacy", ["public", "unlisted", "followers", "direct"]
)
PrivacyLevels = [
("public", _("Public")),
("unlisted", _("Unlisted")),
("followers", _("Followers")),
("direct", _("Private")),
]
class PrivacyField(ActivitypubFieldMixin, models.CharField):
@ -214,9 +217,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
public = "https://www.w3.org/ns/activitystreams#Public"
def __init__(self, *args, **kwargs):
super().__init__(
*args, max_length=255, choices=PrivacyLevels.choices, default="public"
)
super().__init__(*args, max_length=255, choices=PrivacyLevels, default="public")
# pylint: disable=invalid-name
def set_field_from_activity(self, instance, data, overwrite=True):
@ -516,6 +517,10 @@ class CharField(ActivitypubFieldMixin, models.CharField):
"""activitypub-aware char field"""
class URLField(ActivitypubFieldMixin, models.URLField):
"""activitypub-aware url field"""
class TextField(ActivitypubFieldMixin, models.TextField):
"""activitypub-aware text field"""

View file

@ -40,9 +40,7 @@ class ImportJob(models.Model):
mappings = models.JSONField()
complete = models.BooleanField(default=False)
source = models.CharField(max_length=100)
privacy = models.CharField(
max_length=255, default="public", choices=PrivacyLevels.choices
)
privacy = models.CharField(max_length=255, default="public", choices=PrivacyLevels)
retry = models.BooleanField(default=False)
@property

85
bookwyrm/models/link.py Normal file
View file

@ -0,0 +1,85 @@
""" outlink data """
from urllib.parse import urlparse
from django.core.exceptions import PermissionDenied
from django.db import models
from django.utils.translation import gettext_lazy as _
from bookwyrm import activitypub
from .activitypub_mixin import ActivitypubMixin
from .base_model import BookWyrmModel
from . import fields
class Link(ActivitypubMixin, BookWyrmModel):
"""a link to a website"""
url = fields.URLField(max_length=255, activitypub_field="href")
added_by = fields.ForeignKey(
"User", on_delete=models.SET_NULL, null=True, activitypub_field="attributedTo"
)
domain = models.ForeignKey(
"LinkDomain",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="links",
)
activity_serializer = activitypub.Link
reverse_unfurl = True
@property
def name(self):
"""link name via the assocaited domain"""
return self.domain.name
def save(self, *args, **kwargs):
"""create a link"""
# get or create the associated domain
if not self.domain:
domain = urlparse(self.url).netloc
self.domain, _ = LinkDomain.objects.get_or_create(domain=domain)
# this is never broadcast, the owning model broadcasts an update
if "broadcast" in kwargs:
del kwargs["broadcast"]
return super().save(*args, **kwargs)
class FileLink(Link):
"""a link to a file"""
book = models.ForeignKey(
"Book", on_delete=models.CASCADE, related_name="file_links", null=True
)
filetype = fields.CharField(max_length=5, activitypub_field="mediaType")
StatusChoices = [
("approved", _("Approved")),
("blocked", _("Blocked")),
("pending", _("Pending")),
]
class LinkDomain(BookWyrmModel):
"""List of domains used in links"""
domain = models.CharField(max_length=255, unique=True)
status = models.CharField(max_length=50, choices=StatusChoices, default="pending")
name = models.CharField(max_length=100)
reported_by = models.ForeignKey(
"User", blank=True, null=True, on_delete=models.SET_NULL
)
def raise_not_editable(self, viewer):
if viewer.has_perm("moderate_post"):
return
raise PermissionDenied()
def save(self, *args, **kwargs):
"""set a default name"""
if not self.name:
self.name = self.domain
super().save(*args, **kwargs)

View file

@ -1,5 +1,6 @@
""" progress in a book """
from django.core import validators
from django.core.cache import cache
from django.db import models
from django.db.models import F, Q
@ -30,6 +31,7 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs):
"""update user active time"""
cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}")
self.user.update_active_date()
# an active readthrough must have an unset finish date
if self.finish_date:

View file

@ -1,5 +1,6 @@
""" defines relationships between users """
from django.apps import apps
from django.core.cache import cache
from django.db import models, transaction, IntegrityError
from django.db.models import Q
@ -36,6 +37,17 @@ class UserRelationship(BookWyrmModel):
"""the remote user needs to recieve direct broadcasts"""
return [u for u in [self.user_subject, self.user_object] if not u.local]
def save(self, *args, **kwargs):
"""clear the template cache"""
# invalidate the template cache
cache.delete_many(
[
f"relationship-{self.user_subject.id}-{self.user_object.id}",
f"relationship-{self.user_object.id}-{self.user_subject.id}",
]
)
super().save(*args, **kwargs)
class Meta:
"""relationships should be unique"""

View file

@ -1,6 +1,5 @@
""" flagged for moderation """
from django.db import models
from django.db.models import F, Q
from .base_model import BookWyrmModel
@ -13,14 +12,12 @@ class Report(BookWyrmModel):
note = models.TextField(null=True, blank=True)
user = models.ForeignKey("User", on_delete=models.PROTECT)
statuses = models.ManyToManyField("Status", blank=True)
links = models.ManyToManyField("Link", blank=True)
resolved = models.BooleanField(default=False)
class Meta:
"""don't let users report themselves"""
"""set order by default"""
constraints = [
models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report")
]
ordering = ("-created_date",)

View file

@ -82,6 +82,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
if not self.reply_parent:
self.thread_id = self.id
super().save(broadcast=False, update_fields=["thread_id"])
def delete(self, *args, **kwargs): # pylint: disable=unused-argument

View file

@ -129,7 +129,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
related_name="favorite_statuses",
)
default_post_privacy = models.CharField(
max_length=255, default="public", choices=fields.PrivacyLevels.choices
max_length=255, default="public", choices=fields.PrivacyLevels
)
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
created_date = models.DateTimeField(auto_now_add=True)
@ -346,6 +346,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def delete(self, *args, **kwargs):
"""deactivate rather than delete a user"""
# pylint: disable=attribute-defined-outside-init
self.is_active = False
# skip the logic in this class's save()
super().save(*args, **kwargs)
@ -406,14 +407,6 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
self.private_key, self.public_key = create_key_pair()
return super().save(*args, **kwargs)
def to_activity(self, **kwargs):
"""override default AP serializer to add context object
idk if this is the best way to go about this"""
activity_object = super().to_activity(**kwargs)
del activity_object["@context"]
del activity_object["type"]
return activity_object
def get_current_year():
"""sets default year for annual goal to this year"""
@ -427,7 +420,7 @@ class AnnualGoal(BookWyrmModel):
goal = models.IntegerField(validators=[MinValueValidator(1)])
year = models.IntegerField(default=get_current_year)
privacy = models.CharField(
max_length=255, default="public", choices=fields.PrivacyLevels.choices
max_length=255, default="public", choices=fields.PrivacyLevels
)
class Meta: