forked from mirrors/bookwyrm
Merge pull request #85 from cthulahoops/follow_request_acceptance
Follow request acceptance
This commit is contained in:
commit
ba44566d6e
13 changed files with 161 additions and 41 deletions
|
@ -3,6 +3,6 @@ from .actor import get_actor
|
||||||
from .collection import get_outbox, get_outbox_page, get_add, get_remove, \
|
from .collection import get_outbox, get_outbox_page, get_add, get_remove, \
|
||||||
get_following, get_followers
|
get_following, get_followers
|
||||||
from .create import get_create
|
from .create import get_create
|
||||||
from .follow import get_follow_request, get_unfollow, get_accept
|
from .follow import get_follow_request, get_unfollow, get_accept, get_reject
|
||||||
from .status import get_review, get_review_article, get_status, get_replies, \
|
from .status import get_review, get_review_article, get_status, get_replies, \
|
||||||
get_favorite, get_add_tag, get_remove_tag, get_replies_page
|
get_favorite, get_add_tag, get_remove_tag, get_replies_page
|
||||||
|
|
|
@ -32,13 +32,33 @@ def get_unfollow(relationship):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_accept(user, request_activity):
|
def get_accept(user, relationship):
|
||||||
''' accept a follow request '''
|
''' accept a follow request '''
|
||||||
return {
|
return {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'id': '%s#accepts/follows/' % user.absolute_id,
|
'id': '%s#accepts/follows/' % user.absolute_id,
|
||||||
'type': 'Accept',
|
'type': 'Accept',
|
||||||
'actor': user.actor,
|
'actor': user.actor,
|
||||||
'object': request_activity,
|
'object': {
|
||||||
|
'id': relationship.relationship_id,
|
||||||
|
'type': 'Follow',
|
||||||
|
'actor': relationship.user_subject.actor,
|
||||||
|
'object': relationship.user_object.actor,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_reject(user, relationship):
|
||||||
|
''' reject a follow request '''
|
||||||
|
return {
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': '%s#rejects/follows/' % user.absolute_id,
|
||||||
|
'type': 'Reject',
|
||||||
|
'actor': user.actor,
|
||||||
|
'object': {
|
||||||
|
'id': relationship.relationship_id,
|
||||||
|
'type': 'Follow',
|
||||||
|
'actor': relationship.user_subject.actor,
|
||||||
|
'object': relationship.user_object.actor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ def shared_inbox(request):
|
||||||
|
|
||||||
elif activity['type'] == 'Add':
|
elif activity['type'] == 'Add':
|
||||||
response = handle_incoming_add(activity)
|
response = handle_incoming_add(activity)
|
||||||
|
elif activity['type'] == 'Reject':
|
||||||
|
response = handle_incoming_follow_reject(activity)
|
||||||
|
|
||||||
# TODO: Add, Undo, Remove, etc
|
# TODO: Add, Undo, Remove, etc
|
||||||
|
|
||||||
|
@ -218,7 +220,7 @@ def handle_incoming_follow(activity):
|
||||||
user = get_or_create_remote_user(activity['actor'])
|
user = get_or_create_remote_user(activity['actor'])
|
||||||
# TODO: allow users to manually approve requests
|
# TODO: allow users to manually approve requests
|
||||||
try:
|
try:
|
||||||
models.UserFollowRequest.objects.create(
|
request = models.UserFollowRequest.objects.create(
|
||||||
user_subject=user,
|
user_subject=user,
|
||||||
user_object=to_follow,
|
user_object=to_follow,
|
||||||
relationship_id=activity['id']
|
relationship_id=activity['id']
|
||||||
|
@ -229,9 +231,11 @@ def handle_incoming_follow(activity):
|
||||||
# Accept, but then do we need to match the activity id?
|
# Accept, but then do we need to match the activity id?
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
create_notification(to_follow, 'FOLLOW', related_user=user)
|
|
||||||
if not to_follow.manually_approves_followers:
|
if not to_follow.manually_approves_followers:
|
||||||
outgoing.handle_outgoing_accept(user, to_follow, activity)
|
create_notification(to_follow, 'FOLLOW', related_user=user)
|
||||||
|
outgoing.handle_outgoing_accept(user, to_follow, request)
|
||||||
|
else:
|
||||||
|
create_notification(to_follow, 'FOLLOW_REQUEST', related_user=user)
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@ -258,10 +262,29 @@ def handle_incoming_follow_accept(activity):
|
||||||
# figure out who they are
|
# figure out who they are
|
||||||
accepter = get_or_create_remote_user(activity['actor'])
|
accepter = get_or_create_remote_user(activity['actor'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=accepter)
|
||||||
|
request.delete()
|
||||||
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
accepter.followers.add(requester)
|
accepter.followers.add(requester)
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_incoming_follow_reject(activity):
|
||||||
|
''' someone is rejecting a follow request '''
|
||||||
|
requester = models.User.objects.get(actor=activity['object']['actor'])
|
||||||
|
rejecter = get_or_create_remote_user(activity['actor'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=rejecter)
|
||||||
|
request.delete()
|
||||||
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
def handle_incoming_create(activity):
|
def handle_incoming_create(activity):
|
||||||
''' someone did something, good on them '''
|
''' someone did something, good on them '''
|
||||||
user = get_or_create_remote_user(activity['actor'])
|
user = get_or_create_remote_user(activity['actor'])
|
||||||
|
@ -329,17 +352,3 @@ def handle_incoming_add(activity):
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
|
||||||
def handle_incoming_accept(activity):
|
|
||||||
''' someone is accepting a follow request '''
|
|
||||||
# our local user
|
|
||||||
user = models.User.objects.get(actor=activity['actor'])
|
|
||||||
# the person our local user wants to follow, who said yes
|
|
||||||
followed = get_or_create_remote_user(activity['object']['actor'])
|
|
||||||
|
|
||||||
# save this relationship in the db
|
|
||||||
followed.followers.add(user)
|
|
||||||
|
|
||||||
return HttpResponse()
|
|
||||||
|
|
||||||
|
|
22
fedireads/migrations/0016_auto_20200313_1337.py
Normal file
22
fedireads/migrations/0016_auto_20200313_1337.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-13 13:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fedireads', '0015_auto_20200311_1212'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='notification_type',
|
||||||
|
field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request')], max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='notification',
|
||||||
|
constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST']), name='notification_type_valid'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -88,6 +88,9 @@ class Tag(FedireadsModel):
|
||||||
unique_together = ('user', 'book', 'name')
|
unique_together = ('user', 'book', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
NotificationType = models.TextChoices(
|
||||||
|
'NotificationType', 'FAVORITE REPLY TAG FOLLOW FOLLOW_REQUEST')
|
||||||
|
|
||||||
class Notification(FedireadsModel):
|
class Notification(FedireadsModel):
|
||||||
''' you've been tagged, liked, followed, etc '''
|
''' you've been tagged, liked, followed, etc '''
|
||||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||||
|
@ -99,17 +102,12 @@ class Notification(FedireadsModel):
|
||||||
related_status = models.ForeignKey(
|
related_status = models.ForeignKey(
|
||||||
'Status', on_delete=models.PROTECT, null=True)
|
'Status', on_delete=models.PROTECT, null=True)
|
||||||
read = models.BooleanField(default=False)
|
read = models.BooleanField(default=False)
|
||||||
notification_type = models.CharField(max_length=255)
|
notification_type = models.CharField(
|
||||||
|
max_length=255, choices=NotificationType.choices)
|
||||||
def save(self, *args, **kwargs):
|
class Meta:
|
||||||
# TODO: there's probably a real way to do enums
|
constraints = [
|
||||||
types = [
|
models.CheckConstraint(
|
||||||
'FAVORITE',
|
check=models.Q(notification_type__in=NotificationType.values),
|
||||||
'REPLY',
|
name="notification_type_valid",
|
||||||
'TAG',
|
)
|
||||||
'FOLLOW'
|
|
||||||
]
|
]
|
||||||
if not self.notification_type in types:
|
|
||||||
raise ValueError('Invalid notitication type')
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
|
@ -99,20 +99,23 @@ def handle_outgoing_unfollow(user, to_unfollow):
|
||||||
raise(error['error'])
|
raise(error['error'])
|
||||||
|
|
||||||
|
|
||||||
def handle_outgoing_accept(user, to_follow, request_activity):
|
def handle_outgoing_accept(user, to_follow, follow_request):
|
||||||
''' send an acceptance message to a follow request '''
|
''' send an acceptance message to a follow request '''
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
follow_request = models.UserFollowRequest.objects.get(
|
|
||||||
relationship_id=request_activity['id']
|
|
||||||
)
|
|
||||||
relationship = models.UserFollows.from_request(follow_request)
|
relationship = models.UserFollows.from_request(follow_request)
|
||||||
follow_request.delete()
|
follow_request.delete()
|
||||||
relationship.save()
|
relationship.save()
|
||||||
|
|
||||||
activity = activitypub.get_accept(to_follow, request_activity)
|
activity = activitypub.get_accept(to_follow, follow_request)
|
||||||
recipient = get_recipients(to_follow, 'direct', direct_recipients=[user])
|
recipient = get_recipients(to_follow, 'direct', direct_recipients=[user])
|
||||||
broadcast(to_follow, activity, recipient)
|
broadcast(to_follow, activity, recipient)
|
||||||
|
|
||||||
|
def handle_outgoing_reject(user, to_follow, relationship):
|
||||||
|
relationship.delete()
|
||||||
|
|
||||||
|
activity = activitypub.get_reject(to_follow, relationship)
|
||||||
|
recipient = get_recipients(to_follow, 'direct', direct_recipients=[user])
|
||||||
|
broadcast(to_follow, activity, recipient)
|
||||||
|
|
||||||
def handle_shelve(user, book, shelf):
|
def handle_shelve(user, book, shelf):
|
||||||
''' a local user is getting a book put on their shelf '''
|
''' a local user is getting a book put on their shelf '''
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
{% elif notification.notification_type == 'FOLLOW' %}
|
{% elif notification.notification_type == 'FOLLOW' %}
|
||||||
{% include 'snippets/username.html' with user=notification.related_user %}
|
{% include 'snippets/username.html' with user=notification.related_user %}
|
||||||
followed you
|
followed you
|
||||||
|
|
||||||
|
{% elif notification.notification_type == 'FOLLOW_REQUEST' %}
|
||||||
|
{% include 'snippets/username.html' with user=notification.related_user %}
|
||||||
|
sent you a follow request
|
||||||
|
{% include 'snippets/follow_request_buttons.html' with user=notification.related_user %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small>{{ notification.created_date | naturaltime }}</small>
|
<small>{{ notification.created_date | naturaltime }}</small>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{% if not request.user in user.followers.all %}
|
{% if request.user in user.follower_requests.all %}
|
||||||
|
<div>
|
||||||
|
Follow request already sent.
|
||||||
|
</div>
|
||||||
|
{% elif not request.user in user.followers.all %}
|
||||||
<form action="/follow/" method="post">
|
<form action="/follow/" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.username }}"></input>
|
<input type="hidden" name="user" value="{{ user.username }}"></input>
|
||||||
|
|
10
fedireads/templates/snippets/follow_request_buttons.html
Normal file
10
fedireads/templates/snippets/follow_request_buttons.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<form action="/accept_follow_request/" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type=hidden name="user" value="{{ user.username }}">
|
||||||
|
<input type=submit value="Accept">
|
||||||
|
</form>
|
||||||
|
<form action="/delete_follow_request/" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type=hidden name="user" value="{{ user.username }}">
|
||||||
|
<input type=submit value="Delete">
|
||||||
|
</form>
|
|
@ -20,6 +20,18 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if is_self and user.follower_requests.all %}
|
||||||
|
<div>
|
||||||
|
<h2>Follow Requests</h2>
|
||||||
|
{% for requester in user.follower_requests.all %}
|
||||||
|
<div>
|
||||||
|
{% include 'snippets/username.html' with user=requester show_full=True %}
|
||||||
|
{% include 'snippets/follow_request_buttons.html' with user=requester %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<h2>Followers</h2>
|
<h2>Followers</h2>
|
||||||
{% for follower in user.followers.all %}
|
{% for follower in user.followers.all %}
|
||||||
|
|
|
@ -62,4 +62,8 @@ urlpatterns = [
|
||||||
re_path(r'^edit_profile/?$', actions.edit_profile),
|
re_path(r'^edit_profile/?$', actions.edit_profile),
|
||||||
re_path(r'^clear-notifications/?$', actions.clear_notifications),
|
re_path(r'^clear-notifications/?$', actions.clear_notifications),
|
||||||
|
|
||||||
|
re_path(r'^accept_follow_request/?$', actions.accept_follow_request),
|
||||||
|
re_path(r'^delete_follow_request/?$', actions.delete_follow_request),
|
||||||
|
|
||||||
|
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
@ -163,3 +163,36 @@ def clear_notifications(request):
|
||||||
request.user.notification_set.filter(read=True).delete()
|
request.user.notification_set.filter(read=True).delete()
|
||||||
return redirect('/notifications')
|
return redirect('/notifications')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def accept_follow_request(request):
|
||||||
|
username = request.POST['user']
|
||||||
|
try:
|
||||||
|
requester = get_user_from_username(username)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
follow_request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=request.user)
|
||||||
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
|
# Request already dealt with.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
outgoing.handle_outgoing_accept(requester, request.user, follow_request)
|
||||||
|
|
||||||
|
return redirect('/user/%s' % request.user.localname)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def delete_follow_request(request):
|
||||||
|
username = request.POST['user']
|
||||||
|
try:
|
||||||
|
requester = get_user_from_username(username)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
follow_request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=request.user)
|
||||||
|
except models.UserFollowRequest.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
outgoing.handle_outgoing_reject(requester, request.user, follow_request)
|
||||||
|
return redirect('/user/%s' % request.user.localname)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from fedireads.models import User
|
||||||
from fedireads.books_manager import get_or_create_book
|
from fedireads.books_manager import get_or_create_book
|
||||||
|
|
||||||
User.objects.create_user('mouse', 'mouse.reeve@gmail.com', 'password123')
|
User.objects.create_user('mouse', 'mouse.reeve@gmail.com', 'password123')
|
||||||
User.objects.create_user('rat', 'rat@rat.com', 'ratword')
|
User.objects.create_user('rat', 'rat@rat.com', 'ratword', manually_approves_followers=True)
|
||||||
|
|
||||||
User.objects.get(id=1).followers.add(User.objects.get(id=2))
|
User.objects.get(id=1).followers.add(User.objects.get(id=2))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue