Skip to content

Commit 6fd6e7f

Browse files
authored
Merge branch 'main' into refactor-follow-activities-and-test-data-previously
2 parents 91aa1bd + e0d6dc8 commit 6fd6e7f

35 files changed

+738
-394
lines changed

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.11.2 # Use the latest version from https://github.com/astral-sh/ruff-pre-commit/releases
4+
hooks:
5+
# Run the linter.
6+
- id: ruff
7+
# Run the formatter.
8+
- id: ruff-format

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,31 @@ To run the tests, use the following command:
6464
pytest
6565
```
6666

67+
## 🧹 pre-commit Hooks
68+
69+
We use [pre-commit](https://pre-commit.com/) to automatically lint and format code before each commit, ensuring code quality and consistency.
70+
71+
### 🚀 Setting it up
72+
73+
1. **Install the pre-commit hook**
74+
This sets up Git hooks in your local repo:
75+
76+
```sh
77+
pre-commit install
78+
```
79+
80+
2. **You're all set!**
81+
Now, every time you commit, `pre-commit` will automatically run checks (like `ruff`) on staged files.
82+
83+
### 💡 Run checks manually (optional)
84+
85+
To run all hooks on all files manually:
86+
87+
```bash
88+
pre-commit run --all-files
89+
```
90+
91+
This is useful for checking your work before pushing large changes.
92+
6793
## License
6894
This project is licensed under the Apache License 2.0.

conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

3+
34
def pytest_collection_modifyitems(items):
45
# Automatically adds django_db marker to all test functions
56
for item in items:
6-
item.add_marker(pytest.mark.django_db)
7+
item.add_marker(pytest.mark.django_db)

main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
def print_hi(name):
88
# Use a breakpoint in the code line below to debug your script.
9-
print(f'Welcome to {name}') # Press ⌘F8 to toggle the breakpoint.
9+
print(f"Welcome to {name}") # Press ⌘F8 to toggle the breakpoint.
1010

1111

1212
# Press the green button in the gutter to run the script.
13-
if __name__ == '__main__':
14-
print_hi('Activity Pub Testbed Project')
13+
if __name__ == "__main__":
14+
print_hi("Activity Pub Testbed Project")
1515

1616
# See PyCharm help at https://www.jetbrains.com/help/pycharm/

manage.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#!/usr/bin/env python
22
"""Django's command-line utility for administrative tasks."""
3+
34
import os
45
import sys
56

67

78
def main():
89
"""Run administrative tasks."""
9-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testbed.settings.development')
10+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testbed.settings.development")
1011
try:
1112
from django.core.management import execute_from_command_line
1213
except ImportError as exc:
@@ -18,5 +19,5 @@ def main():
1819
execute_from_command_line(sys.argv)
1920

2021

21-
if __name__ == '__main__':
22+
if __name__ == "__main__":
2223
main()

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ factory_boy==3.3.1
88
html-sanitizer==2.4.4
99
Markdown==3.7
1010
pip-tools==7.4.1
11+
pre_commit==4.2.0
1112
psycopg2==2.9.10
1213
pytest==8.3.3
1314
pytest-cov==6.0.0
1415
pytest-django==4.9.0
15-
requests==2.32.3
16+
requests==2.32.3
17+
ruff==0.11.2

testbed/asgi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111

1212
from django.core.asgi import get_asgi_application
1313

14-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testbed.settings.production')
14+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testbed.settings.production")
1515

1616
application = get_asgi_application()

testbed/core/admin.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,117 @@
11
from django.contrib import admin
22
from .models import (
3-
Actor,
4-
Note,
5-
CreateActivity,
6-
LikeActivity,
7-
FollowActivity,
8-
PortabilityOutbox
9-
)
3+
Actor,
4+
Note,
5+
CreateActivity,
6+
LikeActivity,
7+
FollowActivity,
8+
PortabilityOutbox,
9+
)
1010

1111

1212
@admin.register(Actor)
1313
class ActorAdmin(admin.ModelAdmin):
14-
list_display = ('username', 'full_name', 'created_at', 'updated_at')
15-
search_fields = ('username', 'full_name')
16-
readonly_fields = ('created_at', 'updated_at')
14+
list_display = ("username", "full_name", "created_at", "updated_at")
15+
search_fields = ("username", "full_name")
16+
readonly_fields = ("created_at", "updated_at")
17+
1718

1819
@admin.register(Note)
1920
class NoteAdmin(admin.ModelAdmin):
20-
list_display = ('actor', 'content', 'published', 'visibility')
21-
list_filter = ('visibility', 'published')
22-
search_fields = ('content', 'actor__username')
21+
list_display = ("actor", "content", "published", "visibility")
22+
list_filter = ("visibility", "published")
23+
search_fields = ("content", "actor__username")
24+
2325

2426
@admin.register(CreateActivity)
2527
class CreateActivityAdmin(admin.ModelAdmin):
26-
list_display = ('actor', 'timestamp', 'visibility')
27-
list_filter = ('visibility', 'timestamp')
28+
list_display = ("actor", "timestamp", "visibility")
29+
list_filter = ("visibility", "timestamp")
30+
2831

2932
@admin.register(LikeActivity)
3033
class LikeActivityAdmin(admin.ModelAdmin):
31-
list_display = ('actor', 'get_liked_object', 'timestamp', 'visibility')
32-
list_filter = ('visibility', 'timestamp')
33-
search_fields = ('actor__username', 'object_url', 'object_data__content')
34+
list_display = ("actor", "get_liked_object", "timestamp", "visibility")
35+
list_filter = ("visibility", "timestamp")
36+
search_fields = ("actor__username", "object_url", "object_data__content")
3437

3538
def get_liked_object(self, obj):
3639
if obj.note:
3740
return f"Local: {obj.note}"
38-
return f"Remote: {obj.object_data.get('content', '')[:50]}... ({obj.object_url})"
41+
return (
42+
f"Remote: {obj.object_data.get('content', '')[:50]}... ({obj.object_url})"
43+
)
44+
3945
get_liked_object.short_description = "Liked Object"
4046

47+
4148
@admin.register(FollowActivity)
4249
class FollowActivityAdmin(admin.ModelAdmin):
43-
list_display = ('actor', 'target_actor', 'timestamp', 'visibility')
44-
list_filter = ('visibility', 'timestamp')
50+
list_display = ("actor", "target_actor", "timestamp", "visibility")
51+
list_filter = ("visibility", "timestamp")
52+
4553

4654
@admin.register(PortabilityOutbox)
4755
class PortabilityOutboxAdmin(admin.ModelAdmin):
48-
list_display = ('actor', 'created_at')
49-
readonly_fields = ('created_at', 'get_create_activities', 'get_like_activities', 'get_follow_activities')
50-
fields = ('actor', 'created_at', 'get_create_activities', 'get_like_activities', 'get_follow_activities')
56+
list_display = ("actor", "created_at")
57+
readonly_fields = (
58+
"created_at",
59+
"get_create_activities",
60+
"get_like_activities",
61+
"get_follow_activities",
62+
)
63+
fields = (
64+
"actor",
65+
"created_at",
66+
"get_create_activities",
67+
"get_like_activities",
68+
"get_follow_activities",
69+
)
5170

5271
def get_create_activities(self, obj):
5372
if not obj:
5473
return "No create activities"
55-
74+
5675
create_activities = obj.activities_create.filter(actor=obj.actor)
5776
output = []
5877
for activity in create_activities:
5978
if activity.note:
6079
output.append(f"Created note: {activity.note.content[:50]}...")
6180
else:
6281
output.append("Created actor profile")
63-
82+
6483
return "\n".join(output) if output else "No create activities"
65-
84+
6685
get_create_activities.short_description = "Create Activities"
6786

6887
def get_like_activities(self, obj):
6988
if not obj:
7089
return "No like activities"
71-
90+
7291
like_activities = obj.activities_like.filter(actor=obj.actor)
7392
output = []
7493
for activity in like_activities:
7594
if activity.note:
7695
output.append(f"Liked local: {activity.note.content[:50]}...")
7796
else:
78-
content = activity.object_data.get('content', '')[:50]
97+
content = activity.object_data.get("content", "")[:50]
7998
output.append(f"Liked remote: {content}... ({activity.object_url})")
80-
99+
81100
return "\n".join(output) if output else "No like activities"
82-
101+
83102
get_like_activities.short_description = "Like Activities"
84103

85104
def get_follow_activities(self, obj):
86105
if not obj:
87106
return "No follow activities"
88-
107+
89108
follow_activities = obj.activities_follow.filter(actor=obj.actor)
90109
output = []
91110
for activity in follow_activities:
92111
output.append(f"Followed: {activity.target_actor.username}")
93-
112+
94113
return "\n".join(output) if output else "No follow activities"
95-
114+
96115
get_follow_activities.short_description = "Follow Activities"
97116

98117
def has_add_permission(self, request):

testbed/core/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33

44
class CoreConfig(AppConfig):
5-
default_auto_field = 'django.db.models.BigAutoField'
6-
name = 'testbed.core'
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "testbed.core"

testbed/core/factories.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22
from factory.django import DjangoModelFactory
33
from django.contrib.auth.models import User
44
from testbed.core.models import (
5-
Actor, Note,
5+
Actor,
6+
Note,
67
CreateActivity,
78
LikeActivity,
89
FollowActivity,
9-
PortabilityOutbox
10+
PortabilityOutbox,
1011
)
1112

1213

1314
class UserFactory(DjangoModelFactory):
1415
class Meta:
1516
model = User
1617

17-
username = factory.Sequence(lambda n: f'user_{n}')
18-
email = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
19-
password = factory.PostGenerationMethodCall('set_password', 'testpass123')
18+
username = factory.Sequence(lambda n: f"user_{n}")
19+
email = factory.LazyAttribute(lambda o: f"{o.username}@example.com")
20+
password = factory.PostGenerationMethodCall("set_password", "testpass123")
2021
is_staff = False
2122
is_active = True
2223

@@ -41,8 +42,8 @@ class Meta:
4142
model = Note
4243

4344
actor = factory.SubFactory(ActorFactory)
44-
content = factory.Faker('text', max_nb_chars=200)
45-
visibility = factory.Iterator(['public', 'private', 'followers-only'])
45+
content = factory.Faker("text", max_nb_chars=200)
46+
visibility = factory.Iterator(["public", "private", "followers-only"])
4647

4748

4849
class CreateActivityFactory(DjangoModelFactory):
@@ -51,34 +52,34 @@ class Meta:
5152

5253
actor = factory.SubFactory(ActorFactory)
5354
note = factory.SubFactory(NoteFactory)
54-
visibility = factory.Iterator(['public', 'private', 'followers-only'])
55+
visibility = factory.Iterator(["public", "private", "followers-only"])
5556

5657
# Helper method to create a CreateActivity for Actor creation
5758
@classmethod
5859
def create_for_actor(cls, actor):
5960
return cls.create(
6061
actor=actor,
61-
note=None, # No note means this is an Actor creation activity
62-
visibility='public'
62+
note=None, # No note means this is an Actor creation activity
63+
visibility="public",
6364
)
64-
65+
6566

6667
class LikeActivityFactory(DjangoModelFactory):
6768
class Meta:
6869
model = LikeActivity
6970

7071
actor = factory.SubFactory(ActorFactory)
71-
note = factory.SubFactory(NoteFactory) # Required for Like activity
72-
visibility = factory.Iterator(['public', 'private', 'followers-only'])
72+
note = factory.SubFactory(NoteFactory) # Required for Like activity
73+
visibility = factory.Iterator(["public", "private", "followers-only"])
7374

7475

7576
class FollowActivityFactory(DjangoModelFactory):
7677
class Meta:
7778
model = FollowActivity
7879

7980
actor = factory.SubFactory(ActorFactory)
80-
target_actor = factory.SubFactory(ActorFactory) # Required for Follow activity
81-
visibility = factory.Iterator(['public', 'private', 'followers-only'])
81+
target_actor = factory.SubFactory(ActorFactory) # Required for Follow activity
82+
visibility = factory.Iterator(["public", "private", "followers-only"])
8283

8384

8485
class PortabilityOutboxFactory(DjangoModelFactory):

0 commit comments

Comments
 (0)