Merge branch 'main' into validate-username
This commit is contained in:
@ -9,8 +9,11 @@ from .connector import Connector
|
||||
from .shelf import Shelf, ShelfBook
|
||||
|
||||
from .status import Status, GeneratedNote, Review, Comment, Quotation
|
||||
from .status import Favorite, Boost, Notification, ReadThrough
|
||||
from .status import Boost
|
||||
from .attachment import Image
|
||||
from .favorite import Favorite
|
||||
from .notification import Notification
|
||||
from .readthrough import ReadThrough
|
||||
|
||||
from .tag import Tag, UserTag
|
||||
|
||||
@ -25,3 +28,6 @@ from .site import SiteSettings, SiteInvite, PasswordReset
|
||||
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
||||
activity_models = {c[1].activity_serializer.__name__: c[1] \
|
||||
for c in cls_members if hasattr(c[1], 'activity_serializer')}
|
||||
|
||||
status_models = [
|
||||
c.__name__ for (_, c) in activity_models.items() if issubclass(c, Status)]
|
||||
|
@ -35,6 +35,11 @@ class BookWyrmModel(models.Model):
|
||||
''' this is just here to provide default fields for other models '''
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def local_path(self):
|
||||
''' how to link to this object in the local app '''
|
||||
return self.get_remote_id().replace('https://%s' % DOMAIN, '')
|
||||
|
||||
|
||||
@receiver(models.signals.post_save)
|
||||
#pylint: disable=unused-argument
|
||||
@ -104,7 +109,7 @@ class ActivitypubMixin:
|
||||
not field.deduplication_field:
|
||||
continue
|
||||
|
||||
value = data.get(field.activitypub_field)
|
||||
value = data.get(field.get_activitypub_field())
|
||||
if not value:
|
||||
continue
|
||||
filters.append({field.name: value})
|
||||
@ -237,7 +242,9 @@ class OrderedCollectionPageMixin(ActivitypubMixin):
|
||||
).serialize()
|
||||
|
||||
|
||||
def to_ordered_collection_page(queryset, remote_id, id_only=False, page=1):
|
||||
# pylint: disable=unused-argument
|
||||
def to_ordered_collection_page(
|
||||
queryset, remote_id, id_only=False, page=1, **kwargs):
|
||||
''' serialize and pagiante a queryset '''
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
|
||||
|
@ -126,6 +126,14 @@ class Work(OrderedCollectionPageMixin, Book):
|
||||
''' in case the default edition is not set '''
|
||||
return self.default_edition or self.editions.first()
|
||||
|
||||
def to_edition_list(self, **kwargs):
|
||||
''' an ordered collection of editions '''
|
||||
return self.to_ordered_collection(
|
||||
self.editions.order_by('-updated_date').all(),
|
||||
remote_id='%s/editions' % self.remote_id,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
activity_serializer = activitypub.Work
|
||||
serialize_reverse_fields = [('editions', 'editions')]
|
||||
deserialize_reverse_fields = [('editions', 'editions')]
|
||||
|
26
bookwyrm/models/favorite.py
Normal file
26
bookwyrm/models/favorite.py
Normal file
@ -0,0 +1,26 @@
|
||||
''' like/fav/star a status '''
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
||||
from . import fields
|
||||
|
||||
class Favorite(ActivitypubMixin, BookWyrmModel):
|
||||
''' fav'ing a post '''
|
||||
user = fields.ForeignKey(
|
||||
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||
status = fields.ForeignKey(
|
||||
'Status', on_delete=models.PROTECT, activitypub_field='object')
|
||||
|
||||
activity_serializer = activitypub.Like
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' update user active time '''
|
||||
self.user.last_active_date = timezone.now()
|
||||
self.user.save()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
''' can't fav things twice '''
|
||||
unique_together = ('user', 'status')
|
@ -6,7 +6,7 @@ from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from bookwyrm import books_manager
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.models import ReadThrough, User, Book
|
||||
from .fields import PrivacyLevels
|
||||
|
||||
@ -71,12 +71,12 @@ class ImportItem(models.Model):
|
||||
|
||||
def get_book_from_isbn(self):
|
||||
''' search by isbn '''
|
||||
search_result = books_manager.first_search_result(
|
||||
search_result = connector_manager.first_search_result(
|
||||
self.isbn, min_confidence=0.999
|
||||
)
|
||||
if search_result:
|
||||
# raises ConnectorException
|
||||
return books_manager.get_or_create_book(search_result.key)
|
||||
return search_result.connector.get_or_create_book(search_result.key)
|
||||
return None
|
||||
|
||||
|
||||
@ -86,12 +86,12 @@ class ImportItem(models.Model):
|
||||
self.data['Title'],
|
||||
self.data['Author']
|
||||
)
|
||||
search_result = books_manager.first_search_result(
|
||||
search_result = connector_manager.first_search_result(
|
||||
search_term, min_confidence=0.999
|
||||
)
|
||||
if search_result:
|
||||
# raises ConnectorException
|
||||
return books_manager.get_or_create_book(search_result.key)
|
||||
return search_result.connector.get_or_create_book(search_result.key)
|
||||
return None
|
||||
|
||||
|
||||
|
33
bookwyrm/models/notification.py
Normal file
33
bookwyrm/models/notification.py
Normal file
@ -0,0 +1,33 @@
|
||||
''' alert a user to activity '''
|
||||
from django.db import models
|
||||
from .base_model import BookWyrmModel
|
||||
|
||||
|
||||
NotificationType = models.TextChoices(
|
||||
'NotificationType',
|
||||
'FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT')
|
||||
|
||||
class Notification(BookWyrmModel):
|
||||
''' you've been tagged, liked, followed, etc '''
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
related_book = models.ForeignKey(
|
||||
'Edition', on_delete=models.PROTECT, null=True)
|
||||
related_user = models.ForeignKey(
|
||||
'User',
|
||||
on_delete=models.PROTECT, null=True, related_name='related_user')
|
||||
related_status = models.ForeignKey(
|
||||
'Status', on_delete=models.PROTECT, null=True)
|
||||
related_import = models.ForeignKey(
|
||||
'ImportJob', on_delete=models.PROTECT, null=True)
|
||||
read = models.BooleanField(default=False)
|
||||
notification_type = models.CharField(
|
||||
max_length=255, choices=NotificationType.choices)
|
||||
|
||||
class Meta:
|
||||
''' checks if notifcation is in enum list for valid types '''
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
check=models.Q(notification_type__in=NotificationType.values),
|
||||
name="notification_type_valid",
|
||||
)
|
||||
]
|
26
bookwyrm/models/readthrough.py
Normal file
26
bookwyrm/models/readthrough.py
Normal file
@ -0,0 +1,26 @@
|
||||
''' progress in a book '''
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from .base_model import BookWyrmModel
|
||||
|
||||
|
||||
class ReadThrough(BookWyrmModel):
|
||||
''' Store progress through a book in the database. '''
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||
pages_read = models.IntegerField(
|
||||
null=True,
|
||||
blank=True)
|
||||
start_date = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True)
|
||||
finish_date = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' update user active time '''
|
||||
self.user.last_active_date = timezone.now()
|
||||
self.user.save()
|
||||
super().save(*args, **kwargs)
|
@ -54,7 +54,7 @@ class UserRelationship(ActivitypubMixin, BookWyrmModel):
|
||||
|
||||
|
||||
def to_reject_activity(self):
|
||||
''' generate an Accept for this follow request '''
|
||||
''' generate a Reject for this follow request '''
|
||||
return activitypub.Reject(
|
||||
id=self.get_remote_id(status='rejects'),
|
||||
actor=self.user_object.remote_id,
|
||||
|
@ -118,7 +118,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||
activity['attachment'] = [
|
||||
image_serializer(b.cover, b.alt_text) \
|
||||
for b in self.mention_books.all()[:4] if b.cover]
|
||||
if hasattr(self, 'book'):
|
||||
if hasattr(self, 'book') and self.book.cover:
|
||||
activity['attachment'].append(
|
||||
image_serializer(self.book.cover, self.book.alt_text)
|
||||
)
|
||||
@ -222,26 +222,6 @@ class Review(Status):
|
||||
pure_type = 'Article'
|
||||
|
||||
|
||||
class Favorite(ActivitypubMixin, BookWyrmModel):
|
||||
''' fav'ing a post '''
|
||||
user = fields.ForeignKey(
|
||||
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||
status = fields.ForeignKey(
|
||||
'Status', on_delete=models.PROTECT, activitypub_field='object')
|
||||
|
||||
activity_serializer = activitypub.Like
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' update user active time '''
|
||||
self.user.last_active_date = timezone.now()
|
||||
self.user.save()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
''' can't fav things twice '''
|
||||
unique_together = ('user', 'status')
|
||||
|
||||
|
||||
class Boost(Status):
|
||||
''' boost'ing a post '''
|
||||
boosted_status = fields.ForeignKey(
|
||||
@ -268,54 +248,3 @@ class Boost(Status):
|
||||
# This constraint can't work as it would cross tables.
|
||||
# class Meta:
|
||||
# unique_together = ('user', 'boosted_status')
|
||||
|
||||
|
||||
class ReadThrough(BookWyrmModel):
|
||||
''' Store progress through a book in the database. '''
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||
pages_read = models.IntegerField(
|
||||
null=True,
|
||||
blank=True)
|
||||
start_date = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True)
|
||||
finish_date = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' update user active time '''
|
||||
self.user.last_active_date = timezone.now()
|
||||
self.user.save()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
NotificationType = models.TextChoices(
|
||||
'NotificationType',
|
||||
'FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT')
|
||||
|
||||
class Notification(BookWyrmModel):
|
||||
''' you've been tagged, liked, followed, etc '''
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
related_book = models.ForeignKey(
|
||||
'Edition', on_delete=models.PROTECT, null=True)
|
||||
related_user = models.ForeignKey(
|
||||
'User',
|
||||
on_delete=models.PROTECT, null=True, related_name='related_user')
|
||||
related_status = models.ForeignKey(
|
||||
'Status', on_delete=models.PROTECT, null=True)
|
||||
related_import = models.ForeignKey(
|
||||
'ImportJob', on_delete=models.PROTECT, null=True)
|
||||
read = models.BooleanField(default=False)
|
||||
notification_type = models.CharField(
|
||||
max_length=255, choices=NotificationType.choices)
|
||||
|
||||
class Meta:
|
||||
''' checks if notifcation is in enum list for valid types '''
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
check=models.Q(notification_type__in=NotificationType.values),
|
||||
name="notification_type_valid",
|
||||
)
|
||||
]
|
||||
|
@ -17,7 +17,9 @@ class Tag(OrderedCollectionMixin, BookWyrmModel):
|
||||
@classmethod
|
||||
def book_queryset(cls, identifier):
|
||||
''' county of books associated with this tag '''
|
||||
return cls.objects.filter(identifier=identifier)
|
||||
return cls.objects.filter(
|
||||
identifier=identifier
|
||||
).order_by('-updated_date')
|
||||
|
||||
@property
|
||||
def collection_queryset(self):
|
||||
@ -64,7 +66,7 @@ class UserTag(BookWyrmModel):
|
||||
id='%s#remove' % self.remote_id,
|
||||
actor=user.remote_id,
|
||||
object=self.book.to_activity(),
|
||||
target=self.to_activity(),
|
||||
target=self.remote_id,
|
||||
).serialize()
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
''' database schema for user data '''
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
@ -107,11 +108,22 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
|
||||
activity_serializer = activitypub.Person
|
||||
|
||||
def to_outbox(self, **kwargs):
|
||||
def to_outbox(self, filter_type=None, **kwargs):
|
||||
''' an ordered collection of statuses '''
|
||||
queryset = Status.objects.filter(
|
||||
if filter_type:
|
||||
filter_class = apps.get_model(
|
||||
'bookwyrm.%s' % filter_type, require_ready=True)
|
||||
if not issubclass(filter_class, Status):
|
||||
raise TypeError(
|
||||
'filter_status_class must be a subclass of models.Status')
|
||||
queryset = filter_class.objects
|
||||
else:
|
||||
queryset = Status.objects
|
||||
|
||||
queryset = queryset.filter(
|
||||
user=self,
|
||||
deleted=False,
|
||||
privacy__in=['public', 'unlisted'],
|
||||
).select_subclasses().order_by('-published_date')
|
||||
return self.to_ordered_collection(queryset, \
|
||||
remote_id=self.outbox, **kwargs)
|
||||
@ -119,14 +131,22 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
def to_following_activity(self, **kwargs):
|
||||
''' activitypub following list '''
|
||||
remote_id = '%s/following' % self.remote_id
|
||||
return self.to_ordered_collection(self.following.all(), \
|
||||
remote_id=remote_id, id_only=True, **kwargs)
|
||||
return self.to_ordered_collection(
|
||||
self.following.order_by('-updated_date').all(),
|
||||
remote_id=remote_id,
|
||||
id_only=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def to_followers_activity(self, **kwargs):
|
||||
''' activitypub followers list '''
|
||||
remote_id = '%s/followers' % self.remote_id
|
||||
return self.to_ordered_collection(self.followers.all(), \
|
||||
remote_id=remote_id, id_only=True, **kwargs)
|
||||
return self.to_ordered_collection(
|
||||
self.followers.order_by('-updated_date').all(),
|
||||
remote_id=remote_id,
|
||||
id_only=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def to_activity(self):
|
||||
''' override default AP serializer to add context object
|
||||
@ -165,6 +185,11 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def local_path(self):
|
||||
''' this model doesn't inherit bookwyrm model, so here we are '''
|
||||
return '/user/%s' % (self.localname or self.username)
|
||||
|
||||
|
||||
class KeyPair(ActivitypubMixin, BookWyrmModel):
|
||||
''' public and private keys for a user '''
|
||||
@ -270,7 +295,7 @@ def get_or_create_remote_server(domain):
|
||||
@app.task
|
||||
def get_remote_reviews(outbox):
|
||||
''' ingest reviews by a new remote bookwyrm user '''
|
||||
outbox_page = outbox + '?page=true'
|
||||
outbox_page = outbox + '?page=true&type=Review'
|
||||
data = get_data(outbox_page)
|
||||
|
||||
# TODO: pagination?
|
||||
|
Reference in New Issue
Block a user