diff --git a/bookwyrm/forms/admin.py b/bookwyrm/forms/admin.py index 2d69ef702..acff6cfaa 100644 --- a/bookwyrm/forms/admin.py +++ b/bookwyrm/forms/admin.py @@ -55,11 +55,45 @@ class CreateInviteForm(CustomForm): class SiteForm(CustomForm): class Meta: model = models.SiteSettings - exclude = ["admin_code", "install_mode", "imports_enabled"] + fields = [ + "name", + "instance_tagline", + "instance_description", + "instance_short_description", + "default_theme", + "code_of_conduct", + "privacy_policy", + "impressum", + "show_impressum", + "logo", + "logo_small", + "favicon", + "support_link", + "support_title", + "admin_email", + "footer_item", + ] widgets = { "instance_short_description": forms.TextInput( attrs={"aria-describedby": "desc_instance_short_description"} ), + } + + +class RegistrationForm(CustomForm): + class Meta: + model = models.SiteSettings + fields = [ + "allow_registration", + "allow_invite_requests", + "registration_closed_text", + "invite_request_text", + "invite_request_question", + "invite_question_text", + "require_confirm_email", + ] + + widgets = { "require_confirm_email": forms.CheckboxInput( attrs={"aria-describedby": "desc_require_confirm_email"} ), @@ -69,6 +103,23 @@ class SiteForm(CustomForm): } +class RegistrationLimitedForm(CustomForm): + class Meta: + model = models.SiteSettings + fields = [ + "registration_closed_text", + "invite_request_text", + "invite_request_question", + "invite_question_text", + ] + + widgets = { + "invite_request_text": forms.Textarea( + attrs={"aria-describedby": "desc_invite_request_text"} + ), + } + + class ThemeForm(CustomForm): class Meta: model = models.Theme diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 23020a0a6..fda40bd07 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -8,54 +8,64 @@ from bookwyrm import models def init_groups(): """permission levels""" - groups = ["admin", "moderator", "editor"] + groups = ["admin", "owner", "moderator", "editor"] for group in groups: - Group.objects.create(name=group) + Group.objects.get_or_create(name=group) def init_permissions(): """permission types""" permissions = [ + { + "codename": "manage_registration", + "name": "allow or prevent user registration", + "groups": ["admin"], + }, + { + "codename": "system_administration", + "name": "technical controls", + "groups": ["admin"], + }, { "codename": "edit_instance_settings", "name": "change the instance info", - "groups": ["admin"], + "groups": ["admin", "owner"], }, { "codename": "set_user_group", "name": "change what group a user is in", - "groups": ["admin", "moderator"], + "groups": ["admin", "owner", "moderator"], }, { "codename": "control_federation", "name": "control who to federate with", - "groups": ["admin", "moderator"], + "groups": ["admin", "owner", "moderator"], }, { "codename": "create_invites", "name": "issue invitations to join", - "groups": ["admin", "moderator"], + "groups": ["admin", "owner", "moderator"], }, { "codename": "moderate_user", "name": "deactivate or silence a user", - "groups": ["admin", "moderator"], + "groups": ["admin", "owner", "moderator"], }, { "codename": "moderate_post", "name": "delete other users' posts", - "groups": ["admin", "moderator"], + "groups": ["admin", "owner", "moderator"], }, { "codename": "edit_book", "name": "edit book info", - "groups": ["admin", "moderator", "editor"], + "groups": ["admin", "owner", "moderator", "editor"], }, ] content_type = ContentType.objects.get_for_model(models.User) for permission in permissions: - permission_obj = Permission.objects.create( + permission_obj, _ = Permission.objects.get_or_create( codename=permission["codename"], name=permission["name"], content_type=content_type, diff --git a/bookwyrm/migrations/0168_auto_20221205_2331.py b/bookwyrm/migrations/0168_auto_20221205_2331.py new file mode 100644 index 000000000..901ca56f0 --- /dev/null +++ b/bookwyrm/migrations/0168_auto_20221205_2331.py @@ -0,0 +1,63 @@ +""" I added two new permission types and a new group to the management command that +creates the database on install, this creates them for existing instances """ +# Generated by Django 3.2.16 on 2022-12-05 23:31 + +from django.db import migrations + + +def create_groups_and_perms(apps, schema_editor): + """create the new "owner" group and "system admin" permission""" + db_alias = schema_editor.connection.alias + group_model = apps.get_model("auth", "Group") + # Add the "owner" group, if needed + owner_group, group_created = group_model.objects.using(db_alias).get_or_create( + name="owner" + ) + + # Create perms, if needed + user_model = apps.get_model("bookwyrm", "User") + content_type_model = apps.get_model("contenttypes", "ContentType") + content_type = content_type_model.objects.get_for_model(user_model) + perms_model = apps.get_model("auth", "Permission") + reg_perm, perm_created = perms_model.objects.using(db_alias).get_or_create( + codename="manage_registration", + name="allow or prevent user registration", + content_type=content_type, + ) + admin_perm, admin_perm_created = perms_model.objects.using(db_alias).get_or_create( + codename="system_administration", + name="technical controls", + content_type=content_type, + ) + + # Add perms to the group if anything was created + if group_created or perm_created or admin_perm_created: + perms = [ + "edit_instance_settings", + "set_user_group", + "control_federation", + "create_invites", + "moderate_user", + "moderate_post", + "edit_book", + ] + owner_group.permissions.set( + perms_model.objects.using(db_alias).filter(codename__in=perms).all() + ) + + # also extend these perms to admins + # This is get or create so the tests don't fail -- it should already exist + admin_group, _ = group_model.objects.using(db_alias).get_or_create(name="admin") + admin_group.permissions.add(reg_perm) + admin_group.permissions.add(admin_perm) + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0167_auto_20221125_1900"), + ] + + operations = [ + migrations.RunPython(create_groups_and_perms, migrations.RunPython.noop) + ] diff --git a/bookwyrm/migrations/0170_merge_0168_auto_20221205_2331_0169_auto_20221206_0902.py b/bookwyrm/migrations/0170_merge_0168_auto_20221205_2331_0169_auto_20221206_0902.py new file mode 100644 index 000000000..3e199b014 --- /dev/null +++ b/bookwyrm/migrations/0170_merge_0168_auto_20221205_2331_0169_auto_20221206_0902.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.16 on 2022-12-11 20:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0168_auto_20221205_2331"), + ("bookwyrm", "0169_auto_20221206_0902"), + ] + + operations = [] diff --git a/bookwyrm/templates/settings/layout.html b/bookwyrm/templates/settings/layout.html index 729bc1efd..b87fdf974 100644 --- a/bookwyrm/templates/settings/layout.html +++ b/bookwyrm/templates/settings/layout.html @@ -103,10 +103,21 @@ {% trans "Site Settings" %} {% block site-subtabs %}{% endblock %} +
  • + {% if perms.bookwyrm.manage_registration %} + {% url 'settings-registration' as url %} + {% trans "Registration" %} + {% else %} + {% url 'settings-registration-limited' as url %} + {% trans "Registration" %} + {% endif %} +
  • + {% if perms.bookwyrm.system_administration %}
  • {% url 'settings-themes' as url %} {% trans "Themes" %}
  • + {% endif %} {% endif %} diff --git a/bookwyrm/templates/settings/registration.html b/bookwyrm/templates/settings/registration.html new file mode 100644 index 000000000..6126f6b92 --- /dev/null +++ b/bookwyrm/templates/settings/registration.html @@ -0,0 +1,83 @@ +{% extends 'settings/layout.html' %} +{% load i18n %} + +{% block title %}{% trans "Registration" %}{% endblock %} + +{% block header %}{% trans "Registration" %}{% endblock %} + +{% block panel %} +{% if success %} +
    + + + {% trans "Settings saved" %} + +
    +{% endif %} + +{% if form.errors %} +
    + + + {% trans "Unable to save settings" %} + +
    +{% endif %} + +
    + {% csrf_token %} +
    +
    + +
    +
    + +

    {% trans "(Recommended if registration is open)" %}

    +
    +
    + +
    +
    + + {{ form.invite_request_text }} + + {% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %} +
    +
    + +
    +
    + +
    +
    + + {{ form.registration_closed_text }} +
    +
    + + +
    +{% endblock %} + diff --git a/bookwyrm/templates/settings/registration_limited.html b/bookwyrm/templates/settings/registration_limited.html new file mode 100644 index 000000000..343afefbb --- /dev/null +++ b/bookwyrm/templates/settings/registration_limited.html @@ -0,0 +1,81 @@ +{% extends 'settings/layout.html' %} +{% load i18n %} + +{% block title %}{% trans "Registration" %}{% endblock %} + +{% block header %}{% trans "Registration" %}{% endblock %} + +{% block panel %} +{% if success %} +
    + + + {% trans "Settings saved" %} + +
    +{% endif %} + +{% if form.errors %} +
    + + + {% trans "Unable to save settings" %} + +
    +{% endif %} + +{% if site.allow_registration %} +
    + {% trans "Registration is enabled on this instance" %} +
    +{% else %} +
    + {% csrf_token %} +
    + {% if site.allow_invite_requests %} +
    + + {{ form.invite_request_text }} + + {% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %} +
    +
    + +
    +
    + +
    + {% else %} + + + + {% endif %} + + {% if not site.allow_invite_requests and not site.allow_registration %} +
    + + {{ form.registration_closed_text }} +
    + {% else %} + + {% endif %} +
    + + +
    +{% endif %} +{% endblock %} + diff --git a/bookwyrm/templates/settings/site.html b/bookwyrm/templates/settings/site.html index 4cfa531e5..b6aef774a 100644 --- a/bookwyrm/templates/settings/site.html +++ b/bookwyrm/templates/settings/site.html @@ -10,7 +10,6 @@
  • {% trans "Instance Info" %}
  • {% trans "Display" %}
  • {% trans "Footer Content" %}
  • -
  • {% trans "Registration" %}
  • {% endblock %} @@ -141,55 +140,6 @@ - - -
    -

    {% trans "Registration" %}

    -
    -
    - -
    -
    - -

    {% trans "(Recommended if registration is open)" %}

    -
    -
    - -
    -
    - -
    -
    - -
    -
    - - {{ site_form.registration_closed_text }} -
    -
    - - {{ site_form.invite_request_text }} - - {% include 'snippets/form_errors.html' with errors_list=site_form.invite_request_text.errors id="desc_invite_request_text" %} -
    -
    -
    - diff --git a/bookwyrm/tests/management/test_initdb.py b/bookwyrm/tests/management/test_initdb.py index a00c6d674..229009a55 100644 --- a/bookwyrm/tests/management/test_initdb.py +++ b/bookwyrm/tests/management/test_initdb.py @@ -12,7 +12,7 @@ class InitDB(TestCase): def test_init_groups(self): """Create groups""" initdb.init_groups() - self.assertEqual(Group.objects.count(), 3) + self.assertEqual(Group.objects.count(), 4) self.assertTrue(Group.objects.filter(name="admin").exists()) self.assertTrue(Group.objects.filter(name="moderator").exists()) self.assertTrue(Group.objects.filter(name="editor").exists()) @@ -87,7 +87,7 @@ class InitDB(TestCase): command.handle() # everything should have been called - self.assertEqual(Group.objects.count(), 3) + self.assertEqual(Group.objects.count(), 4) self.assertTrue(Permission.objects.exists()) self.assertEqual(models.Connector.objects.count(), 3) self.assertEqual(models.SiteSettings.objects.count(), 1) @@ -99,7 +99,7 @@ class InitDB(TestCase): command.handle(limit="group") # everything should have been called - self.assertEqual(Group.objects.count(), 3) + self.assertEqual(Group.objects.count(), 4) self.assertEqual(models.Connector.objects.count(), 0) self.assertEqual(models.SiteSettings.objects.count(), 0) self.assertEqual(models.LinkDomain.objects.count(), 0) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index ee260a6e6..ac3a80580 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -86,6 +86,16 @@ urlpatterns = [ r"^settings/dashboard/?$", views.Dashboard.as_view(), name="settings-dashboard" ), re_path(r"^settings/site-settings/?$", views.Site.as_view(), name="settings-site"), + re_path( + r"^settings/site-registration/?$", + views.RegistrationLimited.as_view(), + name="settings-registration-limited", + ), + re_path( + r"^settings/site-registration-admin/?$", + views.Registration.as_view(), + name="settings-registration", + ), re_path(r"^settings/themes/?$", views.Themes.as_view(), name="settings-themes"), re_path( r"^settings/themes/(?P\d+)/delete/?$", diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 4fcaed3a9..db88f1ae2 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -24,7 +24,7 @@ from .admin.reports import ( unsuspend_user, moderator_delete_user, ) -from .admin.site import Site +from .admin.site import Site, Registration, RegistrationLimited from .admin.themes import Themes, delete_theme from .admin.user_admin import UserAdmin, UserAdminList diff --git a/bookwyrm/views/admin/site.py b/bookwyrm/views/admin/site.py index e2574f1dd..4b0a9c7d8 100644 --- a/bookwyrm/views/admin/site.py +++ b/bookwyrm/views/admin/site.py @@ -33,3 +33,57 @@ class Site(View): data = {"site_form": forms.SiteForm(instance=site), "success": True} return TemplateResponse(request, "settings/site.html", data) + + +@method_decorator(login_required, name="dispatch") +@method_decorator( + permission_required("bookwyrm.edit_instance_settings", raise_exception=True), + name="dispatch", +) +class RegistrationLimited(View): + """Things related to registering that non-admins owners can change""" + + def get(self, request): + """edit form""" + site = models.SiteSettings.objects.get() + data = {"form": forms.RegistrationLimitedForm(instance=site)} + return TemplateResponse(request, "settings/registration_limited.html", data) + + def post(self, request): + """edit the site settings""" + site = models.SiteSettings.objects.get() + form = forms.RegistrationLimitedForm(request.POST, request.FILES, instance=site) + if not form.is_valid(): + data = {"form": form} + return TemplateResponse(request, "settings/registration_limited.html", data) + site = form.save(request) + + data = {"form": forms.RegistrationLimitedForm(instance=site), "success": True} + return TemplateResponse(request, "settings/registration_limited.html", data) + + +@method_decorator(login_required, name="dispatch") +@method_decorator( + permission_required("bookwyrm.manage_registration", raise_exception=True), + name="dispatch", +) +class Registration(View): + """Control everything about registration""" + + def get(self, request): + """edit form""" + site = models.SiteSettings.objects.get() + data = {"form": forms.RegistrationForm(instance=site)} + return TemplateResponse(request, "settings/registration.html", data) + + def post(self, request): + """edit the site settings""" + site = models.SiteSettings.objects.get() + form = forms.RegistrationForm(request.POST, request.FILES, instance=site) + if not form.is_valid(): + data = {"form": form} + return TemplateResponse(request, "settings/registration.html", data) + site = form.save(request) + + data = {"form": forms.RegistrationForm(instance=site), "success": True} + return TemplateResponse(request, "settings/registration.html", data) diff --git a/bookwyrm/views/admin/themes.py b/bookwyrm/views/admin/themes.py index 4d795fbe0..5658d243a 100644 --- a/bookwyrm/views/admin/themes.py +++ b/bookwyrm/views/admin/themes.py @@ -12,7 +12,7 @@ from bookwyrm import forms, models # pylint: disable= no-self-use @method_decorator(login_required, name="dispatch") @method_decorator( - permission_required("bookwyrm.edit_instance_settings", raise_exception=True), + permission_required("bookwyrm.system_administration", raise_exception=True), name="dispatch", ) class Themes(View): @@ -46,7 +46,7 @@ def get_view_data(): @require_POST -@permission_required("bookwyrm.edit_instance_settings", raise_exception=True) +@permission_required("bookwyrm.system_administration", raise_exception=True) # pylint: disable=unused-argument def delete_theme(request, theme_id): """Remove a theme"""