Skip to content

Commit

Permalink
Following now has a 'through' model to capture acceptance-id and date…
Browse files Browse the repository at this point in the history
…, following will get accepted, and remote actors can be followed.
  • Loading branch information
andreasofthings committed Aug 11, 2024
1 parent 5f35ff6 commit 4c9d875
Show file tree
Hide file tree
Showing 30 changed files with 494 additions and 113 deletions.
36 changes: 25 additions & 11 deletions webapp/activities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ def wrapper(target, activity: ActivityObject, *args, **kwargs):
action_object = Actor.objects.get(id=activity.object)
except Actor.DoesNotExist:
logger.error(f"Object not found: '{activity.object}'")
return JsonResponse({"error": "Actor f'{activity.object}' not found"}, status=404)
action_object = None

action.send(
sender=localactor, verb=activity.type, action_object=action_object, target=target
) # noqa: E501

return f(*args, **kwargs)
return f(target, activity, *args, **kwargs)

return wrapper


@action_decorator
def create(self, target, message: dict) -> JsonResponse:
def create(target: Actor, activity: ActivityObject) -> JsonResponse:
"""
Create a new `:model:Note`.
Expand Down Expand Up @@ -72,9 +72,12 @@ def create(self, target, message: dict) -> JsonResponse:
} # noqa: E501
"""

logger.error(f"Create Object: {message}")
logger.error(f"Create Object: {activity.object}")

note = activity.object
assert note is not None
assert isinstance(note, dict)

note = message.get("object")
if note.type == "Note":
from webapp.models import Note

Expand All @@ -86,22 +89,33 @@ def create(self, target, message: dict) -> JsonResponse:

return JsonResponse(
{
"status": f"success: {message['actor']} {message['type']} {message['object']}" # noqa: E501
"status": f"success: {activity.actor} {activity.type} {activity.object}" # noqa: E501
} # noqa: E501
) # noqa: E501


@action_decorator
def accept(self, message: dict) -> JsonResponse:
def accept(target: Actor, activity: ActivityObject) -> JsonResponse:
"""
Accept
Received an Accept.
Remember the accept-id in the database.
So we can later delete the follow request.
:param target: The target of the activity
:param activity: The :py:class:webapp.activity.Activityobject`
"""
from webapp.models.activitypub.actor import Fllwng

fllwng = Fllwng.objects.get(actor=activity.actor)
fllwng.accepted = activity.id # remember the accept-id
fllwng.save()

return JsonResponse({"status": "accepted."})


@action_decorator
def delete(self, message: dict) -> JsonResponse:
def delete(target: Actor, activity: ActivityObject) -> JsonResponse:
"""
Delete an activity.
"""
Expand Down Expand Up @@ -189,9 +203,9 @@ def follow(target: Actor, activity: ActivityObject):
) # noqa: E501, BLK100

if settings.DEBUG:
acceptFollow(remoteActor.inbox, activity, action_id[0][1].id)
acceptFollow(remoteActor.get('inbox'), activity, action_id[0][1].id)
else:
acceptFollow.delay(remoteActor.inbox, activity, action_id[0][1].id)
acceptFollow.delay(remoteActor.get('inbox'), activity, action_id[0][1].id)

return JsonResponse(
{
Expand Down
1 change: 0 additions & 1 deletion webapp/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from decimal import Decimal
from dataclasses import dataclass
from dataclasses import field

# from dataclasses import asdict
# from dataclasses import is_dataclass

Expand Down
11 changes: 10 additions & 1 deletion webapp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
from django.urls import path, reverse
from django.utils.decorators import method_decorator
from django.utils.html import escape

# from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters

from .forms import UserChangeForm, UserCreationForm
from .models import Action, Actor, Note, Profile, User, Like
from webapp.models.activitypub.actor import Follow
from .tasks import generateProfileKeyPair

try:
Expand Down Expand Up @@ -262,13 +264,20 @@ class LikeAdmin(admin.ModelAdmin):
admin.site.register(Like, LikeAdmin)


class FollowInline(admin.TabularInline):
model = Follow
fk_name = "actor"
list_display = ("object",)


class ActorAdmin(admin.ModelAdmin):
model = Actor
list_display = (
"id",
"type",
"profile",
)
inlines = [FollowInline]


admin.site.register(Actor, ActorAdmin)
Expand All @@ -286,7 +295,7 @@ class ActionAdmin(GenericAdminModelAdmin):
"public",
)
# list_editable = ("activity_type",)
list_filter = ("timestamp", ) # "activity_type")
list_filter = ("timestamp",) # "activity_type")
# raw_id_fields = (
# "actor_content_type",
# "target_content_type",
Expand Down
112 changes: 102 additions & 10 deletions webapp/fields.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,108 @@
"""
..todo::
[ ] Add a description for the module.
[ ] Add a description for the class.
[ ] Add a description for the method.
[ ] Add a description for the attribute.
[ ] Add a description for the parameter.
[ ] Add a description for the return value.
[ ] Add a description for the exception.
[ ] Basically everything. Do under no circumstances use this yet.
The idea of this is to be able to add a "FedID" to any model, that will
automatically be generated and stored, so objects creation and serialization
for ActivityPub can be done without having to worry about the ID.
"""
from django.db import models
from django.forms import CharField, URLInput, ValidationError, validators
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from urllib.parse import urlsplit, urlunsplit
import warnings
from django.utils.deprecation import RemovedInDjango60Warning

from webapp.validators import validate_iri


class URLField(CharField):
widget = URLInput
default_error_messages = {
"invalid": _("Enter a valid URL."),
}
default_validators = [validators.URLValidator()]

def __init__(self, *, assume_scheme=None, **kwargs):
if assume_scheme is None:
if settings.FORMS_URLFIELD_ASSUME_HTTPS:
assume_scheme = "https"
else:
warnings.warn(
"The default scheme will be changed from 'http' to 'https' in "
"Django 6.0. Pass the forms.URLField.assume_scheme argument to "
"silence this warning, or set the FORMS_URLFIELD_ASSUME_HTTPS "
"transitional setting to True to opt into using 'https' as the new "
"default scheme.",
RemovedInDjango60Warning,
stacklevel=2,
)
assume_scheme = "http"
# RemovedInDjango60Warning: When the deprecation ends, replace with:
# self.assume_scheme = assume_scheme or "https"
self.assume_scheme = assume_scheme
super().__init__(strip=True, **kwargs)

def to_python(self, value):
def split_url(url):
"""
Return a list of url parts via urlsplit(), or raise
ValidationError for some malformed URLs.
"""
try:
return list(urlsplit(url))
except ValueError:
# urlsplit can raise a ValueError with some
# misformatted URLs.
raise ValidationError(self.error_messages["invalid"], code="invalid")

value = super().to_python(value)
if value:
url_fields = split_url(value)
if not url_fields[0]:
# If no URL scheme given, add a scheme.
url_fields[0] = self.assume_scheme
if not url_fields[1]:
# Assume that if no domain is provided, that the path segment
# contains the domain.
url_fields[1] = url_fields[2]
url_fields[2] = ""
# Rebuild the url_fields list, since the domain segment may now
# contain the path too.
url_fields = split_url(urlunsplit(url_fields))
value = urlunsplit(url_fields)
return value

class FedIDField(models.URLField):
"""
A Field that represents a FedID.
class IRIField(models.CharField):
default_validators = [validate_iri]
description = _("IRI")

.. seealso::
Activity Pub <Object Identifiers https://www.w3.org/TR/activitypub/#obj-id>_`
def __init__(self, verbose_name=None, name=None, **kwargs):
kwargs.setdefault("max_length", 200)
super().__init__(verbose_name, name, **kwargs)

**id**:
The object's unique global identifier (unless the object is transient, in which case the id MAY be omitted).
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if kwargs.get("max_length") == 200:
del kwargs["max_length"]
return name, path, args, kwargs

"""
def __init__(self, *args, **kwargs):
super(ActorField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
# As with CharField, this will cause URL validation to be performed
# twice.
return super().formfield(
**{
"form_class": IRIField,
**kwargs,
}
)
20 changes: 0 additions & 20 deletions webapp/migrations/0042_actor_flw.py

This file was deleted.

64 changes: 64 additions & 0 deletions webapp/migrations/0042_fllwng_actor_flw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Generated by Django 5.0.7 on 2024-08-10 15:36

import django.db.models.deletion
import webapp.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("webapp", "0041_alter_actor_id"),
]

operations = [
migrations.CreateModel(
name="Fllwng",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
unique=True,
validators=[webapp.validators.validate_iri],
),
),
(
"accepted",
models.URLField(
blank=True,
null=True,
validators=[webapp.validators.validate_iri],
),
),
(
"actor",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="actor",
to="webapp.actor",
),
),
(
"object",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="object",
to="webapp.actor",
),
),
],
),
migrations.AddField(
model_name="actor",
name="flw",
field=models.ManyToManyField(
blank=True,
related_name="flwng",
through="webapp.Fllwng",
to="webapp.actor",
),
),
]
17 changes: 17 additions & 0 deletions webapp/migrations/0043_remove_actor_follows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-08-11 15:01

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("webapp", "0042_fllwng_actor_flw"),
]

operations = [
migrations.RemoveField(
model_name="actor",
name="follows",
),
]
Loading

0 comments on commit 4c9d875

Please sign in to comment.