diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 80b28310..1f1f1a3b 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -150,6 +150,12 @@ class LimitedEditUserForm(CustomForm): help_texts = {f: None for f in fields} +class UserGroupForm(CustomForm): + class Meta: + model = models.User + fields = ["groups"] + + class TagForm(CustomForm): class Meta: model = models.Tag diff --git a/bookwyrm/templates/book/publisher_info.html b/bookwyrm/templates/book/publisher_info.html index a16332c5..b7975a62 100644 --- a/bookwyrm/templates/book/publisher_info.html +++ b/bookwyrm/templates/book/publisher_info.html @@ -1,6 +1,7 @@ {% spaceless %} {% load i18n %} +{% load humanize %}

{% with format=book.physical_format pages=book.pages %} @@ -39,7 +40,7 @@ {% endif %}

- {% with date=book.published_date|date:'M jS Y' publisher=book.publishers|join:', ' %} + {% with date=book.published_date|naturalday publisher=book.publishers|join:', ' %} {% if date or book.first_published_date %} -

-
-

{% trans "User details" %}

-
- {% include 'user/user_preview.html' with user=report.user %} - {% if report.user.summary %} -
- {{ report.user.summary | to_markdown | safe }} -
- {% endif %} +{% include 'user_admin/user_info.html' with user=report.user %} -

{% trans "View user profile" %}

-
-
- {% if not report.user.local %} - {% with server=report.user.federated_server %} -
-

{% trans "Instance details" %}

-
- {% if server %} -
{{ server.server_name }}
-
-
-
{% trans "Software:" %}
-
{{ server.application_type }}
-
-
-
{% trans "Version:" %}
-
{{ server.application_version }}
-
-
-
{% trans "Status:" %}
-
{{ server.status }}
-
-
- {% if server.notes %} -
{% trans "Notes" %}
-
- {{ server.notes }} -
- {% endif %} - -

- {% trans "View instance" %} -

- {% else %} - {% trans "Not set" %} - {% endif %} -
-
- {% endwith %} - {% endif %} -
- -
-

{% trans "Actions" %}

-
-

- {% trans "Send direct message" %} -

-
- {% csrf_token %} - {% if report.user.is_active %} - - {% else %} - - {% endif %} -
-
-
+{% include 'user_admin/user_moderation_actions.html' with user=report.user %}

{% trans "Moderator Comments" %}

diff --git a/bookwyrm/templates/moderation/reports.html b/bookwyrm/templates/moderation/reports.html index 72cadae5..f9d9d99b 100644 --- a/bookwyrm/templates/moderation/reports.html +++ b/bookwyrm/templates/moderation/reports.html @@ -30,7 +30,7 @@
-{% include 'settings/user_admin_filters.html' %} +{% include 'user_admin/user_admin_filters.html' %}
{% if not reports %} diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index 7c694d78..ba0a25cd 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -123,7 +123,7 @@ {% include 'snippets/status_preview.html' with status=related_status %}
- {{ related_status.published_date | post_date }} + {{ related_status.published_date|timesince }} {% include 'snippets/privacy-icons.html' with item=related_status %}
diff --git a/bookwyrm/templates/snippets/status/status_body.html b/bookwyrm/templates/snippets/status/status_body.html index 58d3fbf4..18f7876e 100644 --- a/bookwyrm/templates/snippets/status/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -10,4 +10,4 @@ {% endif %} {% endwith %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/bookwyrm/templates/settings/server_filter.html b/bookwyrm/templates/user_admin/server_filter.html similarity index 100% rename from bookwyrm/templates/settings/server_filter.html rename to bookwyrm/templates/user_admin/server_filter.html diff --git a/bookwyrm/templates/user_admin/user.html b/bookwyrm/templates/user_admin/user.html new file mode 100644 index 00000000..46390650 --- /dev/null +++ b/bookwyrm/templates/user_admin/user.html @@ -0,0 +1,19 @@ +{% extends 'settings/admin_layout.html' %} +{% load i18n %} +{% load bookwyrm_tags %} +{% load humanize %} + +{% block title %}{{ user.username }}{% endblock %} +{% block header %}{{ user.username }}{% endblock %} + +{% block panel %} +
+ {% trans "Back to users" %} +
+ +{% include 'user_admin/user_info.html' with user=user %} + +{% include 'user_admin/user_moderation_actions.html' with user=user %} + +{% endblock %} + diff --git a/bookwyrm/templates/settings/user_admin.html b/bookwyrm/templates/user_admin/user_admin.html similarity index 93% rename from bookwyrm/templates/settings/user_admin.html rename to bookwyrm/templates/user_admin/user_admin.html index a96d37f5..2ab526a9 100644 --- a/bookwyrm/templates/settings/user_admin.html +++ b/bookwyrm/templates/user_admin/user_admin.html @@ -13,7 +13,7 @@ {% block panel %} -{% include 'settings/user_admin_filters.html' %} +{% include 'user_admin/user_admin_filters.html' %} @@ -41,7 +41,7 @@ {% for user in users %} - + diff --git a/bookwyrm/templates/settings/user_admin_filters.html b/bookwyrm/templates/user_admin/user_admin_filters.html similarity index 51% rename from bookwyrm/templates/settings/user_admin_filters.html rename to bookwyrm/templates/user_admin/user_admin_filters.html index a7b5c8aa..57e017e5 100644 --- a/bookwyrm/templates/settings/user_admin_filters.html +++ b/bookwyrm/templates/user_admin/user_admin_filters.html @@ -1,6 +1,6 @@ {% extends 'snippets/filters_panel/filters_panel.html' %} {% block filter_fields %} -{% include 'settings/server_filter.html' %} -{% include 'settings/username_filter.html' %} +{% include 'user_admin/server_filter.html' %} +{% include 'user_admin/username_filter.html' %} {% endblock %} diff --git a/bookwyrm/templates/user_admin/user_info.html b/bookwyrm/templates/user_admin/user_info.html new file mode 100644 index 00000000..e5f5d580 --- /dev/null +++ b/bookwyrm/templates/user_admin/user_info.html @@ -0,0 +1,56 @@ +{% load i18n %} +{% load bookwyrm_tags %} +
+
+

{% trans "User details" %}

+
+ {% include 'user/user_preview.html' with user=user %} + {% if user.summary %} +
+ {{ user.summary | to_markdown | safe }} +
+ {% endif %} + +

{% trans "View user profile" %}

+
+
+ {% if not user.local %} + {% with server=user.federated_server %} +
+

{% trans "Instance details" %}

+
+ {% if server %} +
{{ server.server_name }}
+
+
+
{% trans "Software:" %}
+
{{ server.application_type }}
+
+
+
{% trans "Version:" %}
+
{{ server.application_version }}
+
+
+
{% trans "Status:" %}
+
{{ server.status }}
+
+
+ {% if server.notes %} +
{% trans "Notes" %}
+
+ {{ server.notes }} +
+ {% endif %} + +

+ {% trans "View instance" %} +

+ {% else %} + {% trans "Not set" %} + {% endif %} +
+
+ {% endwith %} + {% endif %} +
+ diff --git a/bookwyrm/templates/user_admin/user_moderation_actions.html b/bookwyrm/templates/user_admin/user_moderation_actions.html new file mode 100644 index 00000000..816e787a --- /dev/null +++ b/bookwyrm/templates/user_admin/user_moderation_actions.html @@ -0,0 +1,42 @@ +{% load i18n %} +
+

{% trans "Actions" %}

+
+

+ {% trans "Send direct message" %} +

+
+ {% csrf_token %} + {% if user.is_active %} + + {% else %} + + {% endif %} + +
+ {% if user.local %} +
+
+ {% csrf_token %} + + {% if group_form.non_field_errors %} + {{ group_form.non_field_errors }} + {% endif %} + {% with group=user.groups.first %} +
+ +
+ {% for error in group_form.groups.errors %} +

{{ error | escape }}

+ {% endfor %} + {% endwith %} + + +
+ {% endif %} +
diff --git a/bookwyrm/templates/settings/username_filter.html b/bookwyrm/templates/user_admin/username_filter.html similarity index 100% rename from bookwyrm/templates/settings/username_filter.html rename to bookwyrm/templates/user_admin/username_filter.html diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index 52054ada..5d981909 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -129,28 +129,6 @@ def get_uuid(identifier): return "%s%s" % (identifier, uuid4()) -@register.filter(name="post_date") -def time_since(date): - """ concise time ago function """ - if not isinstance(date, datetime): - return "" - now = timezone.now() - - if date < (now - relativedelta(weeks=1)): - formatter = "%b %-d" - if date.year != now.year: - formatter += " %Y" - return date.strftime(formatter) - delta = relativedelta(now, date) - if delta.days: - return "%dd" % delta.days - if delta.hours: - return "%dh" % delta.hours - if delta.minutes: - return "%dm" % delta.minutes - return "%ds" % delta.seconds - - @register.filter(name="to_markdown") def get_markdown(content): """ convert markdown to html """ diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index b4dc517f..2fadb978 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -181,36 +181,6 @@ class TemplateTags(TestCase): uuid = bookwyrm_tags.get_uuid("hi") self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) - def test_time_since(self, _): - """ ultraconcise timestamps """ - self.assertEqual(bookwyrm_tags.time_since("bleh"), "") - - now = timezone.now() - self.assertEqual(bookwyrm_tags.time_since(now), "0s") - - seconds_ago = now - relativedelta(seconds=4) - self.assertEqual(bookwyrm_tags.time_since(seconds_ago), "4s") - - minutes_ago = now - relativedelta(minutes=8) - self.assertEqual(bookwyrm_tags.time_since(minutes_ago), "8m") - - hours_ago = now - relativedelta(hours=9) - self.assertEqual(bookwyrm_tags.time_since(hours_ago), "9h") - - days_ago = now - relativedelta(days=3) - self.assertEqual(bookwyrm_tags.time_since(days_ago), "3d") - - # I am not going to figure out how to mock dates tonight. - months_ago = now - relativedelta(months=5) - self.assertTrue( - re.match(r"[A-Z][a-z]{2} \d?\d", bookwyrm_tags.time_since(months_ago)) - ) - - years_ago = now - relativedelta(years=10) - self.assertTrue( - re.match(r"[A-Z][a-z]{2} \d?\d \d{4}", bookwyrm_tags.time_since(years_ago)) - ) - def test_get_markdown(self, _): """ mardown format data """ result = bookwyrm_tags.get_markdown("_hi_") diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 1c56067a..bce19993 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -1,5 +1,4 @@ """ test for app action functionality """ -from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -115,22 +114,19 @@ class ReportViews(TestCase): report.refresh_from_db() self.assertFalse(report.resolved) - def test_deactivate_user(self): + def test_suspend_user(self): """ toggle whether a user is able to log in """ self.assertTrue(self.rat.is_active) - report = models.Report.objects.create(reporter=self.local_user, user=self.rat) request = self.factory.post("") request.user = self.local_user request.user.is_superuser = True # de-activate - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.deactivate_user(request, report.id) + views.suspend_user(request, self.rat.id) self.rat.refresh_from_db() self.assertFalse(self.rat.is_active) # re-activate - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.deactivate_user(request, report.id) + views.suspend_user(request, self.rat.id) self.rat.refresh_from_db() self.assertTrue(self.rat.is_active) diff --git a/bookwyrm/tests/views/test_user_admin.py b/bookwyrm/tests/views/test_user_admin.py index dd20c1b6..b1e9d639 100644 --- a/bookwyrm/tests/views/test_user_admin.py +++ b/bookwyrm/tests/views/test_user_admin.py @@ -1,4 +1,6 @@ """ test for app action functionality """ +from unittest.mock import patch +from django.contrib.auth.models import Group from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -21,9 +23,9 @@ class UserAdminViews(TestCase): ) models.SiteSettings.objects.create() - def test_user_admin_page(self): + def test_user_admin_list_page(self): """ there are so many views, this just makes sure it LOADS """ - view = views.UserAdmin.as_view() + view = views.UserAdminList.as_view() request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True @@ -31,3 +33,38 @@ class UserAdminViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_user_admin_page(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.UserAdmin.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + + result = view(request, self.local_user.id) + + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_user_admin_page_post(self): + """ set the user's group """ + group = Group.objects.create(name="editor") + self.assertEqual( + list(self.local_user.groups.values_list("name", flat=True)), [] + ) + + view = views.UserAdmin.as_view() + request = self.factory.post("", {"groups": [group.id]}) + request.user = self.local_user + request.user.is_superuser = True + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + result = view(request, self.local_user.id) + + self.assertIsInstance(result, TemplateResponse) + result.render() + + self.assertEqual( + list(self.local_user.groups.values_list("name", flat=True)), ["editor"] + ) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 9e280f3e..8c5266a6 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -51,13 +51,20 @@ urlpatterns = [ r"^password-reset/(?P[A-Za-z0-9]+)/?$", views.PasswordReset.as_view() ), # admin - re_path(r"^settings/site-settings", views.Site.as_view(), name="settings-site"), + re_path(r"^settings/site-settings/?$", views.Site.as_view(), name="settings-site"), re_path( - r"^settings/email-preview", + r"^settings/email-preview/?$", views.site.email_preview, name="settings-email-preview", ), - re_path(r"^settings/users", views.UserAdmin.as_view(), name="settings-users"), + re_path( + r"^settings/users/?$", views.UserAdminList.as_view(), name="settings-users" + ), + re_path( + r"^settings/users/(?P\d+)/?$", + views.UserAdmin.as_view(), + name="settings-user", + ), re_path( r"^settings/federation/?$", views.Federation.as_view(), @@ -113,9 +120,9 @@ urlpatterns = [ name="settings-report", ), re_path( - r"^settings/reports/(?P\d+)/deactivate/?$", - views.deactivate_user, - name="settings-report-deactivate", + r"^settings/reports/(?P\d+)/suspend/?$", + views.suspend_user, + name="settings-report-suspend", ), re_path( r"^settings/reports/(?P\d+)/resolve/?$", diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 9f8463b4..c0f35ba8 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -25,7 +25,7 @@ from .notifications import Notifications from .outbox import Outbox from .reading import edit_readthrough, create_readthrough, delete_readthrough from .reading import start_reading, finish_reading, delete_progressupdate -from .reports import Report, Reports, make_report, resolve_report, deactivate_user +from .reports import Report, Reports, make_report, resolve_report, suspend_user from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword from .search import Search @@ -37,5 +37,5 @@ from .status import CreateStatus, DeleteStatus, DeleteAndRedraft from .tag import Tag, AddTag, RemoveTag from .updates import get_notification_count, get_unread_status_count from .user import User, EditUser, Followers, Following -from .user_admin import UserAdmin +from .user_admin import UserAdmin, UserAdminList from .wellknown import * diff --git a/bookwyrm/views/reports.py b/bookwyrm/views/reports.py index 3dd53cb9..07eb9b97 100644 --- a/bookwyrm/views/reports.py +++ b/bookwyrm/views/reports.py @@ -74,12 +74,13 @@ class Report(View): @login_required @permission_required("bookwyrm_moderate_user") -def deactivate_user(_, report_id): +def suspend_user(_, user_id): """ mark an account as inactive """ - report = get_object_or_404(models.Report, id=report_id) - report.user.is_active = not report.user.is_active - report.user.save() - return redirect("settings-report", report.id) + user = get_object_or_404(models.User, id=user_id) + user.is_active = not user.is_active + # this isn't a full deletion, so we don't want to tell the world + user.save(broadcast=False) + return redirect("settings-user", user.id) @login_required diff --git a/bookwyrm/views/user_admin.py b/bookwyrm/views/user_admin.py index c0d097d7..4537abce 100644 --- a/bookwyrm/views/user_admin.py +++ b/bookwyrm/views/user_admin.py @@ -1,11 +1,12 @@ """ manage user """ from django.contrib.auth.decorators import login_required, permission_required from django.core.paginator import Paginator +from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import models +from bookwyrm import forms, models from bookwyrm.settings import PAGE_LENGTH @@ -15,7 +16,7 @@ from bookwyrm.settings import PAGE_LENGTH permission_required("bookwyrm.moderate_users", raise_exception=True), name="dispatch", ) -class UserAdmin(View): +class UserAdminList(View): """ admin view of users on this server """ def get(self, request): @@ -49,4 +50,28 @@ class UserAdmin(View): "sort": sort, "server": server, } - return TemplateResponse(request, "settings/user_admin.html", data) + return TemplateResponse(request, "user_admin/user_admin.html", data) + + +@method_decorator(login_required, name="dispatch") +@method_decorator( + permission_required("bookwyrm.moderate_users", raise_exception=True), + name="dispatch", +) +class UserAdmin(View): + """ moderate an individual user """ + + def get(self, request, user): + """ user view """ + user = get_object_or_404(models.User, id=user) + data = {"user": user, "group_form": forms.UserGroupForm()} + return TemplateResponse(request, "user_admin/user.html", data) + + def post(self, request, user): + """ update user group """ + user = get_object_or_404(models.User, id=user) + form = forms.UserGroupForm(request.POST, instance=user) + if form.is_valid(): + form.save() + data = {"user": user, "group_form": form} + return TemplateResponse(request, "user_admin/user.html", data)
{{ user.username }}{{ user.username }} {{ user.created_date }} {{ user.last_active_date }} {% if user.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}