diff --git a/takahe/urls.py b/takahe/urls.py index 8ed647e..cb833ae 100644 --- a/takahe/urls.py +++ b/takahe/urls.py @@ -65,6 +65,16 @@ urlpatterns = [ settings.CsvFollowers.as_view(), name="settings_export_followers_csv", ), + path( + "@/settings/import_export/blocks.csv", + settings.CsvBlocks.as_view(), + name="settings_export_blocks_csv", + ), + path( + "@/settings/import_export/mutes.csv", + settings.CsvMutes.as_view(), + name="settings_export_mutes_csv", + ), path( "@/settings/migrate_in/", settings.MigrateInPage.as_view(), diff --git a/templates/settings/import_export.html b/templates/settings/import_export.html index 768dace..92fcbf1 100644 --- a/templates/settings/import_export.html +++ b/templates/settings/import_export.html @@ -50,7 +50,7 @@ {{ numbers.blocks }} {{ numbers.blocks|pluralize:"people,people" }} - + Download CSV @@ -59,7 +59,7 @@ {{ numbers.mutes }} {{ numbers.mutes|pluralize:"people,people" }} - + Download CSV diff --git a/tests/users/views/test_import_export.py b/tests/users/views/test_import_export.py index 1273eb0..cfe2cb5 100644 --- a/tests/users/views/test_import_export.py +++ b/tests/users/views/test_import_export.py @@ -88,3 +88,92 @@ def test_export_followers( ) assert response.status_code == 200 assert response.content.strip() == b"Account address\r\ntest@example2.com" + + +@pytest.mark.django_db +def test_export_blocks( + client_with_user: Client, + identity: Identity, + identity2: Identity, + stator: StatorRunner, + httpx_mock: HTTPXMock, +): + """ + Validates the "export a CSV of blocked users" functionality works + """ + # Block remote_identity + IdentityService(identity).block(identity2) + + # Run stator to process it + stator.run_single_cycle() + + # Download the CSV + response = client_with_user.get( + f"/@{identity.handle}/settings/import_export/blocks.csv" + ) + assert response.status_code == 200 + assert response.content.strip() == b"Account address\r\ntest@example2.com" + + # Unblock should clear the CSV content + IdentityService(identity).unblock(identity2) + + # Run stator to process it + stator.run_single_cycle() + + response = client_with_user.get( + f"/@{identity.handle}/settings/import_export/blocks.csv" + ) + assert response.status_code == 200 + assert response.content.strip() == b"Account address" + + +@pytest.mark.django_db +def test_export_mutes( + client_with_user: Client, + identity: Identity, + identity2: Identity, + stator: StatorRunner, + httpx_mock: HTTPXMock, +): + """ + Validates the "export a CSV of muted users" functionality works + """ + # Mute remote_identity + IdentityService(identity).mute(identity2) + + # Run stator to process it + stator.run_single_cycle() + + # Download the CSV + response = client_with_user.get( + f"/@{identity.handle}/settings/import_export/mutes.csv" + ) + assert response.status_code == 200 + assert ( + response.content.strip() + == b"Account address,Hide notifications\r\ntest@example2.com,false" + ) + + # Mute remote_identity + IdentityService(identity).mute(identity2, include_notifications=True) + + # Run stator to process it + stator.run_single_cycle() + + # Download the CSV + response = client_with_user.get( + f"/@{identity.handle}/settings/import_export/mutes.csv" + ) + assert response.status_code == 200 + assert ( + response.content.strip() + == b"Account address,Hide notifications\r\ntest@example2.com,true" + ) + + # Unmute should clear the CSV content + IdentityService(identity).unmute(identity2) + response = client_with_user.get( + f"/@{identity.handle}/settings/import_export/mutes.csv" + ) + assert response.status_code == 200 + assert response.content.strip() == b"Account address,Hide notifications" diff --git a/users/views/settings/__init__.py b/users/views/settings/__init__.py index 0f824db..dcbbf84 100644 --- a/users/views/settings/__init__.py +++ b/users/views/settings/__init__.py @@ -6,8 +6,10 @@ from django.views.generic import View from users.views.settings.delete import DeleteIdentity # noqa from users.views.settings.follows import FollowsPage # noqa from users.views.settings.import_export import ( # noqa + CsvBlocks, CsvFollowers, CsvFollowing, + CsvMutes, ImportExportPage, ) from users.views.settings.interface import InterfacePage # noqa diff --git a/users/views/settings/import_export.py b/users/views/settings/import_export.py index 27f27d1..1de487d 100644 --- a/users/views/settings/import_export.py +++ b/users/views/settings/import_export.py @@ -7,7 +7,7 @@ from django.shortcuts import redirect from django.utils.decorators import method_decorator from django.views.generic import FormView, View -from users.models import Follow, InboxMessage +from users.models import Block, Follow, InboxMessage from users.views.base import IdentityViewMixin @@ -147,3 +147,35 @@ class CsvFollowers(CsvView): def get_handle(self, follow: Follow): return follow.source.handle + + +class CsvBlocks(CsvView): + columns = { + "Account address": "get_handle", + } + + filename = "blocked_accounts.csv" + + def get_queryset(self, request): + return self.identity.outbound_blocks.active().filter(mute=False) + + def get_handle(self, block: Block): + return block.target.handle + + +class CsvMutes(CsvView): + columns = { + "Account address": "get_handle", + "Hide notifications": "get_notification", + } + + filename = "muted_accounts.csv" + + def get_queryset(self, request): + return self.identity.outbound_blocks.active().filter(mute=True) + + def get_handle(self, mute: Block): + return mute.target.handle + + def get_notification(self, mute: Block): + return mute.include_notifications