diff options
Diffstat (limited to 'users')
-rw-r--r-- | users/admin.py | 2 | ||||
-rw-r--r-- | users/migrations/0002_follow_state_follow_state_attempted_and_more.py | 44 | ||||
-rw-r--r-- | users/migrations/0003_remove_follow_accepted_remove_follow_requested_and_more.py | 31 | ||||
-rw-r--r-- | users/migrations/0004_remove_follow_state_locked_and_more.py | 21 | ||||
-rw-r--r-- | users/models/follow.py | 28 | ||||
-rw-r--r-- | users/tasks/follow.py | 33 | ||||
-rw-r--r-- | users/views/identity.py | 1 |
7 files changed, 149 insertions, 11 deletions
diff --git a/users/admin.py b/users/admin.py index 0b0cc80..e517b0a 100644 --- a/users/admin.py +++ b/users/admin.py @@ -25,4 +25,4 @@ class IdentityAdmin(admin.ModelAdmin): @admin.register(Follow) class FollowAdmin(admin.ModelAdmin): - list_display = ["id", "source", "target", "requested", "accepted"] + list_display = ["id", "source", "target", "state"] diff --git a/users/migrations/0002_follow_state_follow_state_attempted_and_more.py b/users/migrations/0002_follow_state_follow_state_attempted_and_more.py new file mode 100644 index 0000000..b33236a --- /dev/null +++ b/users/migrations/0002_follow_state_follow_state_attempted_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.3 on 2022-11-07 19:22 + +import django.utils.timezone +from django.db import migrations, models + +import stator.models +import users.models.follow + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="follow", + name="state", + field=stator.models.StateField( + choices=[ + ("pending", "pending"), + ("requested", "requested"), + ("accepted", "accepted"), + ], + default="pending", + graph=users.models.follow.FollowStates, + max_length=100, + ), + ), + migrations.AddField( + model_name="follow", + name="state_attempted", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="follow", + name="state_changed", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + ] diff --git a/users/migrations/0003_remove_follow_accepted_remove_follow_requested_and_more.py b/users/migrations/0003_remove_follow_accepted_remove_follow_requested_and_more.py new file mode 100644 index 0000000..180bfdd --- /dev/null +++ b/users/migrations/0003_remove_follow_accepted_remove_follow_requested_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.3 on 2022-11-08 03:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0002_follow_state_follow_state_attempted_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="follow", + name="accepted", + ), + migrations.RemoveField( + model_name="follow", + name="requested", + ), + migrations.AddField( + model_name="follow", + name="state_locked", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="follow", + name="state_runner", + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/users/migrations/0004_remove_follow_state_locked_and_more.py b/users/migrations/0004_remove_follow_state_locked_and_more.py new file mode 100644 index 0000000..bf98080 --- /dev/null +++ b/users/migrations/0004_remove_follow_state_locked_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.3 on 2022-11-09 05:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0003_remove_follow_accepted_remove_follow_requested_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="follow", + name="state_locked", + ), + migrations.RemoveField( + model_name="follow", + name="state_runner", + ), + ] diff --git a/users/models/follow.py b/users/models/follow.py index 29d036e..04f90ee 100644 --- a/users/models/follow.py +++ b/users/models/follow.py @@ -2,10 +2,23 @@ from typing import Optional from django.db import models -from miniq.models import Task +from stator.models import State, StateField, StateGraph, StatorModel -class Follow(models.Model): +class FollowStates(StateGraph): + pending = State(try_interval=3600) + requested = State() + accepted = State() + + @pending.add_transition(requested) + async def try_request(cls, instance): + print("Would have tried to follow") + return False + + requested.add_manual_transition(accepted) + + +class Follow(StatorModel): """ When one user (the source) follows other (the target) """ @@ -24,8 +37,7 @@ class Follow(models.Model): uri = models.CharField(blank=True, null=True, max_length=500) note = models.TextField(blank=True, null=True) - requested = models.BooleanField(default=False) - accepted = models.BooleanField(default=False) + state = StateField(FollowStates) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) @@ -50,17 +62,15 @@ class Follow(models.Model): (which can be local or remote). """ if not source.local: - raise ValueError("You cannot initiate follows on a remote Identity") + raise ValueError("You cannot initiate follows from a remote Identity") try: follow = Follow.objects.get(source=source, target=target) except Follow.DoesNotExist: follow = Follow.objects.create(source=source, target=target, uri="") follow.uri = source.actor_uri + f"follow/{follow.pk}/" + # TODO: Local follow approvals if target.local: - follow.requested = True - follow.accepted = True - else: - Task.submit("follow_request", str(follow.pk)) + follow.state = FollowStates.accepted follow.save() return follow diff --git a/users/tasks/follow.py b/users/tasks/follow.py index 872b35f..0f802cf 100644 --- a/users/tasks/follow.py +++ b/users/tasks/follow.py @@ -27,3 +27,36 @@ async def handle_follow_request(task_handler): if response.status_code >= 400: raise ValueError(f"Request error: {response.status_code} {response.content}") await Follow.objects.filter(pk=follow.pk).aupdate(requested=True) + + +def send_follow_undo(id): + """ + Request a follow from a remote server + """ + follow = Follow.objects.select_related("source", "source__domain", "target").get( + pk=id + ) + # Construct the request + request = canonicalise( + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": follow.uri + "#undo", + "type": "Undo", + "actor": follow.source.actor_uri, + "object": { + "id": follow.uri, + "type": "Follow", + "actor": follow.source.actor_uri, + "object": follow.target.actor_uri, + }, + } + ) + # Sign it and send it + from asgiref.sync import async_to_sync + + response = async_to_sync(HttpSignature.signed_request)( + follow.target.inbox_uri, request, follow.source + ) + if response.status_code >= 400: + raise ValueError(f"Request error: {response.status_code} {response.content}") + print(response) diff --git a/users/views/identity.py b/users/views/identity.py index 98fcdd6..41c7880 100644 --- a/users/views/identity.py +++ b/users/views/identity.py @@ -16,7 +16,6 @@ from django.views.generic import FormView, TemplateView, View from core.forms import FormHelper from core.ld import canonicalise from core.signatures import HttpSignature -from miniq.models import Task from users.decorators import identity_required from users.models import Domain, Follow, Identity from users.shortcuts import by_handle_or_404 |