Merge pull request #605 from mouse-reeve/user-creation

User save() override instead of signal to set user fields
This commit is contained in:
Mouse Reeve 2021-02-22 08:27:52 -08:00 committed by GitHub
commit 2db1409cfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 76 deletions

View file

@ -188,7 +188,7 @@ def handle_block(activity):
''' blocking a user ''' ''' blocking a user '''
# create "block" databse entry # create "block" databse entry
activitypub.Block(**activity).to_model(models.UserBlocks) activitypub.Block(**activity).to_model(models.UserBlocks)
# the removing relationships is handled in post-save hook in model # the removing relationships is handled in model save
@app.task @app.task

View file

@ -1,8 +1,7 @@
''' defines relationships between users ''' ''' defines relationships between users '''
from django.apps import apps 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.db.models import Q
from django.dispatch import receiver
from bookwyrm import activitypub from bookwyrm import activitypub
from .activitypub_mixin import ActivitypubMixin, ActivityMixin from .activitypub_mixin import ActivitypubMixin, ActivityMixin
@ -61,6 +60,20 @@ class UserFollows(ActivitypubMixin, UserRelationship):
status = 'follows' status = 'follows'
activity_serializer = activitypub.Follow activity_serializer = activitypub.Follow
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()
super().save(*args, **kwargs)
@classmethod @classmethod
def from_request(cls, follow_request): def from_request(cls, follow_request):
@ -79,23 +92,25 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
def save(self, *args, broadcast=True, **kwargs): def save(self, *args, broadcast=True, **kwargs):
''' make sure the follow or block relationship doesn't already exist ''' ''' make sure the follow or block relationship doesn't already exist '''
try: # don't create a request if a follow already exists
UserFollows.objects.get( if UserFollows.objects.filter(
user_subject=self.user_subject, user_subject=self.user_subject,
user_object=self.user_object, user_object=self.user_object,
) ).exists():
# blocking in either direction is a no-go raise IntegrityError()
UserBlocks.objects.get( # blocking in either direction is a no-go
user_subject=self.user_subject, if UserBlocks.objects.filter(
user_object=self.user_object, Q(
) user_subject=self.user_subject,
UserBlocks.objects.get( user_object=self.user_object,
user_subject=self.user_object, ) | Q(
user_object=self.user_subject, user_subject=self.user_object,
) user_object=self.user_subject,
return None )
except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist): ).exists():
super().save(*args, **kwargs) raise IntegrityError()
super().save(*args, **kwargs)
if broadcast and self.user_subject.local and not self.user_object.local: if broadcast and self.user_subject.local and not self.user_object.local:
self.broadcast(self.to_activity(), self.user_subject) self.broadcast(self.to_activity(), self.user_subject)
@ -143,20 +158,15 @@ class UserBlocks(ActivityMixin, UserRelationship):
status = 'blocks' status = 'blocks'
activity_serializer = activitypub.Block 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) UserFollows.objects.filter(
#pylint: disable=unused-argument Q(user_subject=self.user_subject, user_object=self.user_object) | \
def execute_after_save(sender, instance, created, *args, **kwargs): Q(user_subject=self.user_object, user_object=self.user_subject)
''' remove follow or follow request rels after a block is created ''' ).delete()
UserFollows.objects.filter( UserFollowRequest.objects.filter(
Q(user_subject=instance.user_subject, Q(user_subject=self.user_subject, user_object=self.user_object) | \
user_object=instance.user_object) | \ Q(user_subject=self.user_object, user_object=self.user_subject)
Q(user_subject=instance.user_object, ).delete()
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()

View file

@ -6,7 +6,6 @@ from django.apps import apps
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
from bookwyrm import activitypub from bookwyrm import activitypub
@ -172,15 +171,23 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
''' populate fields for new local users ''' ''' populate fields for new local users '''
# this user already exists, no need to populate fields
if not self.local and not re.match(regex.full_username, self.username): if not self.local and not re.match(regex.full_username, self.username):
# generate a username that uses the domain (webfinger format) # generate a username that uses the domain (webfinger format)
actor_parts = urlparse(self.remote_id) actor_parts = urlparse(self.remote_id)
self.username = '%s@%s' % (self.username, actor_parts.netloc) self.username = '%s@%s' % (self.username, actor_parts.netloc)
return super().save(*args, **kwargs) super().save(*args, **kwargs)
return
if self.id or not self.local: # this user already exists, no need to populate fields
return super().save(*args, **kwargs) if self.id:
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 # populate fields for local users
self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname) self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname)
@ -188,7 +195,32 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.shared_inbox = 'https://%s/inbox' % DOMAIN self.shared_inbox = 'https://%s/inbox' % DOMAIN
self.outbox = '%s/outbox' % self.remote_id 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 @property
def local_path(self): def local_path(self):
@ -280,42 +312,6 @@ class AnnualGoal(BookWyrmModel):
finish_date__year__gte=self.year).count() 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 @app.task
def set_remote_server(user_id): def set_remote_server(user_id):
''' figure out the user's remote server in the background ''' ''' figure out the user's remote server in the background '''