summaryrefslogtreecommitdiffstats
path: root/users
diff options
context:
space:
mode:
Diffstat (limited to 'users')
-rw-r--r--users/admin.py2
-rw-r--r--users/migrations/0002_follow_state_follow_state_attempted_and_more.py44
-rw-r--r--users/migrations/0003_remove_follow_accepted_remove_follow_requested_and_more.py31
-rw-r--r--users/migrations/0004_remove_follow_state_locked_and_more.py21
-rw-r--r--users/models/follow.py28
-rw-r--r--users/tasks/follow.py33
-rw-r--r--users/views/identity.py1
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