Merge branch 'main' into inbox-refactor
This commit is contained in:
@ -286,7 +286,7 @@ class OrderedCollectionPageMixin(ObjectMixin):
|
||||
|
||||
def to_ordered_collection(self, queryset, \
|
||||
remote_id=None, page=False, collection_only=False, **kwargs):
|
||||
''' an ordered collection of whatevers '''
|
||||
'pure=pure, '' an ordered collection of whatevers '''
|
||||
if not queryset.ordered:
|
||||
raise RuntimeError('queryset must be ordered')
|
||||
|
||||
@ -480,7 +480,7 @@ def sign_and_send(sender, data, destination):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def to_ordered_collection_page(
|
||||
queryset, remote_id, id_only=False, page=1, **kwargs):
|
||||
queryset, remote_id, id_only=False, page=1, pure=False, **kwargs):
|
||||
''' serialize and pagiante a queryset '''
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
|
||||
@ -488,7 +488,7 @@ def to_ordered_collection_page(
|
||||
if id_only:
|
||||
items = [s.remote_id for s in activity_page.object_list]
|
||||
else:
|
||||
items = [s.to_activity() for s in activity_page.object_list]
|
||||
items = [s.to_activity(pure=pure) for s in activity_page.object_list]
|
||||
|
||||
prev_page = next_page = None
|
||||
if activity_page.has_next():
|
||||
|
@ -1,8 +1,7 @@
|
||||
''' defines relationships between users '''
|
||||
from django.apps import apps
|
||||
from django.db import models, transaction
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
|
||||
@ -61,15 +60,26 @@ class UserFollows(ActivityMixin, UserRelationship):
|
||||
''' Following a user '''
|
||||
status = 'follows'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' never broadcast a creation (that's handled by "accept"), only a
|
||||
deletion (an unfollow, as opposed to "reject" and undo pending) '''
|
||||
super().save(*args, broadcast=False, **kwargs)
|
||||
|
||||
def to_activity(self):
|
||||
''' overrides default to manually set serializer '''
|
||||
return activitypub.Follow(**generate_activity(self))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' really really don't let a user follow someone who blocked them '''
|
||||
# blocking in either direction is a no-go
|
||||
if UserBlocks.objects.filter(
|
||||
Q(
|
||||
user_subject=self.user_subject,
|
||||
user_object=self.user_object,
|
||||
) | Q(
|
||||
user_subject=self.user_object,
|
||||
user_object=self.user_subject,
|
||||
)
|
||||
).exists():
|
||||
raise IntegrityError()
|
||||
# don't broadcast this type of relationship -- accepts and requests
|
||||
# are handled by the UserFollowRequest model
|
||||
super().save(*args, broadcast=False, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, follow_request):
|
||||
@ -88,24 +98,23 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
|
||||
|
||||
def save(self, *args, broadcast=True, **kwargs):
|
||||
''' make sure the follow or block relationship doesn't already exist '''
|
||||
try:
|
||||
UserFollows.objects.get(
|
||||
# don't create a request if a follow already exists
|
||||
if UserFollows.objects.filter(
|
||||
user_subject=self.user_subject,
|
||||
user_object=self.user_object,
|
||||
)
|
||||
# blocking in either direction is a no-go
|
||||
UserBlocks.objects.get(
|
||||
user_subject=self.user_subject,
|
||||
user_object=self.user_object,
|
||||
)
|
||||
UserBlocks.objects.get(
|
||||
user_subject=self.user_object,
|
||||
user_object=self.user_subject,
|
||||
)
|
||||
return
|
||||
except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist):
|
||||
pass
|
||||
|
||||
).exists():
|
||||
raise IntegrityError()
|
||||
# blocking in either direction is a no-go
|
||||
if UserBlocks.objects.filter(
|
||||
Q(
|
||||
user_subject=self.user_subject,
|
||||
user_object=self.user_object,
|
||||
) | Q(
|
||||
user_subject=self.user_object,
|
||||
user_object=self.user_subject,
|
||||
)
|
||||
).exists():
|
||||
raise IntegrityError()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if broadcast and self.user_subject.local and not self.user_object.local:
|
||||
@ -160,20 +169,15 @@ class UserBlocks(ActivityMixin, UserRelationship):
|
||||
status = 'blocks'
|
||||
activity_serializer = activitypub.Block
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' remove follow or follow request rels after a block is created '''
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@receiver(models.signals.post_save, sender=UserBlocks)
|
||||
#pylint: disable=unused-argument
|
||||
def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||
''' remove follow or follow request rels after a block is created '''
|
||||
UserFollows.objects.filter(
|
||||
Q(user_subject=instance.user_subject,
|
||||
user_object=instance.user_object) | \
|
||||
Q(user_subject=instance.user_object,
|
||||
user_object=instance.user_subject)
|
||||
).delete()
|
||||
UserFollowRequest.objects.filter(
|
||||
Q(user_subject=instance.user_subject,
|
||||
user_object=instance.user_object) | \
|
||||
Q(user_subject=instance.user_object,
|
||||
user_object=instance.user_subject)
|
||||
).delete()
|
||||
UserFollows.objects.filter(
|
||||
Q(user_subject=self.user_subject, user_object=self.user_object) | \
|
||||
Q(user_subject=self.user_object, user_object=self.user_subject)
|
||||
).delete()
|
||||
UserFollowRequest.objects.filter(
|
||||
Q(user_subject=self.user_subject, user_object=self.user_object) | \
|
||||
Q(user_subject=self.user_object, user_object=self.user_subject)
|
||||
).delete()
|
||||
|
@ -6,11 +6,10 @@ from django.apps import apps
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.connectors import get_data
|
||||
from bookwyrm.connectors import get_data, ConnectorException
|
||||
from bookwyrm.models.shelf import Shelf
|
||||
from bookwyrm.models.status import Status, Review
|
||||
from bookwyrm.settings import DOMAIN
|
||||
@ -113,6 +112,16 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
|
||||
activity_serializer = activitypub.Person
|
||||
|
||||
@classmethod
|
||||
def viewer_aware_objects(cls, viewer):
|
||||
''' the user queryset filtered for the context of the logged in user '''
|
||||
queryset = cls.objects.filter(is_active=True)
|
||||
if viewer.is_authenticated:
|
||||
queryset = queryset.exclude(
|
||||
blocks=viewer
|
||||
)
|
||||
return queryset
|
||||
|
||||
def to_outbox(self, filter_type=None, **kwargs):
|
||||
''' an ordered collection of statuses '''
|
||||
if filter_type:
|
||||
@ -172,15 +181,23 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
''' populate fields for new local users '''
|
||||
# this user already exists, no need to populate fields
|
||||
created = not bool(self.id)
|
||||
if not self.local and not re.match(regex.full_username, self.username):
|
||||
# generate a username that uses the domain (webfinger format)
|
||||
actor_parts = urlparse(self.remote_id)
|
||||
self.username = '%s@%s' % (self.username, actor_parts.netloc)
|
||||
return super().save(*args, **kwargs)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if self.id or not self.local:
|
||||
return super().save(*args, **kwargs)
|
||||
# this user already exists, no need to populate fields
|
||||
if not created:
|
||||
super().save(*args, **kwargs)
|
||||
return
|
||||
|
||||
# this is a new remote user, we need to set their remote server field
|
||||
if not self.local:
|
||||
super().save(*args, **kwargs)
|
||||
set_remote_server.delay(self.id)
|
||||
return
|
||||
|
||||
# populate fields for local users
|
||||
self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname)
|
||||
@ -188,7 +205,32 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||
self.shared_inbox = 'https://%s/inbox' % DOMAIN
|
||||
self.outbox = '%s/outbox' % self.remote_id
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
# an id needs to be set before we can proceed with related models
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# create keys and shelves for new local users
|
||||
self.key_pair = KeyPair.objects.create(
|
||||
remote_id='%s/#main-key' % self.remote_id)
|
||||
self.save(broadcast=False)
|
||||
|
||||
shelves = [{
|
||||
'name': 'To Read',
|
||||
'identifier': 'to-read',
|
||||
}, {
|
||||
'name': 'Currently Reading',
|
||||
'identifier': 'reading',
|
||||
}, {
|
||||
'name': 'Read',
|
||||
'identifier': 'read',
|
||||
}]
|
||||
|
||||
for shelf in shelves:
|
||||
Shelf(
|
||||
name=shelf['name'],
|
||||
identifier=shelf['identifier'],
|
||||
user=self,
|
||||
editable=False
|
||||
).save(broadcast=False)
|
||||
|
||||
@property
|
||||
def local_path(self):
|
||||
@ -280,42 +322,6 @@ class AnnualGoal(BookWyrmModel):
|
||||
finish_date__year__gte=self.year).count()
|
||||
|
||||
|
||||
|
||||
@receiver(models.signals.post_save, sender=User)
|
||||
#pylint: disable=unused-argument
|
||||
def execute_after_save(sender, instance, created, *args, **kwargs):
|
||||
''' create shelves for new users '''
|
||||
if not created:
|
||||
return
|
||||
|
||||
if not instance.local:
|
||||
set_remote_server.delay(instance.id)
|
||||
return
|
||||
|
||||
instance.key_pair = KeyPair.objects.create(
|
||||
remote_id='%s/#main-key' % instance.remote_id)
|
||||
instance.save(broadcast=False)
|
||||
|
||||
shelves = [{
|
||||
'name': 'To Read',
|
||||
'identifier': 'to-read',
|
||||
}, {
|
||||
'name': 'Currently Reading',
|
||||
'identifier': 'reading',
|
||||
}, {
|
||||
'name': 'Read',
|
||||
'identifier': 'read',
|
||||
}]
|
||||
|
||||
for shelf in shelves:
|
||||
Shelf(
|
||||
name=shelf['name'],
|
||||
identifier=shelf['identifier'],
|
||||
user=instance,
|
||||
editable=False
|
||||
).save(broadcast=False)
|
||||
|
||||
|
||||
@app.task
|
||||
def set_remote_server(user_id):
|
||||
''' figure out the user's remote server in the background '''
|
||||
@ -323,7 +329,7 @@ def set_remote_server(user_id):
|
||||
actor_parts = urlparse(user.remote_id)
|
||||
user.federated_server = \
|
||||
get_or_create_remote_server(actor_parts.netloc)
|
||||
user.save()
|
||||
user.save(broadcast=False)
|
||||
if user.bookwyrm_user:
|
||||
get_remote_reviews.delay(user.outbox)
|
||||
|
||||
@ -337,19 +343,24 @@ def get_or_create_remote_server(domain):
|
||||
except FederatedServer.DoesNotExist:
|
||||
pass
|
||||
|
||||
data = get_data('https://%s/.well-known/nodeinfo' % domain)
|
||||
|
||||
try:
|
||||
nodeinfo_url = data.get('links')[0].get('href')
|
||||
except (TypeError, KeyError):
|
||||
return None
|
||||
data = get_data('https://%s/.well-known/nodeinfo' % domain)
|
||||
try:
|
||||
nodeinfo_url = data.get('links')[0].get('href')
|
||||
except (TypeError, KeyError):
|
||||
raise ConnectorException()
|
||||
|
||||
data = get_data(nodeinfo_url)
|
||||
application_type = data.get('software', {}).get('name')
|
||||
application_version = data.get('software', {}).get('version')
|
||||
except ConnectorException:
|
||||
application_type = application_version = None
|
||||
|
||||
data = get_data(nodeinfo_url)
|
||||
|
||||
server = FederatedServer.objects.create(
|
||||
server_name=domain,
|
||||
application_type=data['software']['name'],
|
||||
application_version=data['software']['version'],
|
||||
application_type=application_type,
|
||||
application_version=application_version,
|
||||
)
|
||||
return server
|
||||
|
||||
|
Reference in New Issue
Block a user