diff --git a/bookwyrm/forms/admin.py b/bookwyrm/forms/admin.py index 4141327d3..ae15e011b 100644 --- a/bookwyrm/forms/admin.py +++ b/bookwyrm/forms/admin.py @@ -2,13 +2,14 @@ import datetime from django import forms +from django.core.exceptions import PermissionDenied from django.forms import widgets from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django_celery_beat.models import IntervalSchedule from bookwyrm import models -from .custom_form import CustomForm +from .custom_form import CustomForm, StyledForm # pylint: disable=missing-class-docstring @@ -130,7 +131,7 @@ class AutoModRuleForm(CustomForm): fields = ["string_match", "flag_users", "flag_statuses", "created_by"] -class IntervalScheduleForm(CustomForm): +class IntervalScheduleForm(StyledForm): class Meta: model = IntervalSchedule fields = ["every", "period"] @@ -139,3 +140,10 @@ class IntervalScheduleForm(CustomForm): "every": forms.NumberInput(attrs={"aria-describedby": "desc_every"}), "period": forms.Select(attrs={"aria-describedby": "desc_period"}), } + + # pylint: disable=arguments-differ + def save(self, request, *args, **kwargs): + """This is an outside model so the perms check works differently""" + if not request.user.has_perm("bookwyrm.moderate_user"): + raise PermissionDenied() + return super().save(*args, **kwargs) diff --git a/bookwyrm/forms/custom_form.py b/bookwyrm/forms/custom_form.py index 74a3417a2..c604deea4 100644 --- a/bookwyrm/forms/custom_form.py +++ b/bookwyrm/forms/custom_form.py @@ -4,7 +4,7 @@ from django.forms import ModelForm from django.forms.widgets import Textarea -class CustomForm(ModelForm): +class StyledForm(ModelForm): """add css classes to the forms""" def __init__(self, *args, **kwargs): @@ -16,7 +16,7 @@ class CustomForm(ModelForm): css_classes["checkbox"] = "checkbox" css_classes["textarea"] = "textarea" # pylint: disable=super-with-arguments - super(CustomForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for visible in self.visible_fields(): if hasattr(visible.field.widget, "input_type"): input_type = visible.field.widget.input_type @@ -24,3 +24,13 @@ class CustomForm(ModelForm): input_type = "textarea" visible.field.widget.attrs["rows"] = 5 visible.field.widget.attrs["class"] = css_classes[input_type] + + +class CustomForm(StyledForm): + """Check permissions on save""" + + # pylint: disable=arguments-differ + def save(self, request, *args, **kwargs): + """Save and check perms""" + self.instance.raise_not_editable(request.user) + return super().save(*args, **kwargs) diff --git a/bookwyrm/migrations/0158_auto_20220919_1634.py b/bookwyrm/migrations/0158_auto_20220919_1634.py new file mode 100644 index 000000000..c7cce19fd --- /dev/null +++ b/bookwyrm/migrations/0158_auto_20220919_1634.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.15 on 2022-09-19 16:34 + +import bookwyrm.models.fields +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0157_auto_20220909_2338"), + ] + + operations = [ + migrations.AddField( + model_name="automod", + name="created_date", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="automod", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + migrations.AddField( + model_name="automod", + name="updated_date", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="emailblocklist", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + migrations.AddField( + model_name="emailblocklist", + name="updated_date", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="ipblocklist", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + migrations.AddField( + model_name="ipblocklist", + name="updated_date", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index dbc4aa54e..c40f9dfb9 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -3,18 +3,33 @@ from functools import reduce import operator from django.apps import apps +from django.core.exceptions import PermissionDenied from django.db import models, transaction from django.db.models import Q from django.utils.translation import gettext_lazy as _ from bookwyrm.tasks import app +from .base_model import BookWyrmModel from .user import User -class EmailBlocklist(models.Model): +class AdminModel(BookWyrmModel): + """Overrides the permissions methods""" + + class Meta: + """this is just here to provide default fields for other models""" + + abstract = True + + def raise_not_editable(self, viewer): + if viewer.has_perm("bookwyrm.moderate_user"): + return + raise PermissionDenied() + + +class EmailBlocklist(AdminModel): """blocked email addresses""" - created_date = models.DateTimeField(auto_now_add=True) domain = models.CharField(max_length=255, unique=True) is_active = models.BooleanField(default=True) @@ -29,10 +44,9 @@ class EmailBlocklist(models.Model): return User.objects.filter(email__endswith=f"@{self.domain}") -class IPBlocklist(models.Model): +class IPBlocklist(AdminModel): """blocked ip addresses""" - created_date = models.DateTimeField(auto_now_add=True) address = models.CharField(max_length=255, unique=True) is_active = models.BooleanField(default=True) @@ -42,7 +56,7 @@ class IPBlocklist(models.Model): ordering = ("-created_date",) -class AutoMod(models.Model): +class AutoMod(AdminModel): """rules to automatically flag suspicious activity""" string_match = models.CharField(max_length=200, unique=True) diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index d161c0349..f6e665053 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -1,5 +1,7 @@ """ flagged for moderation """ +from django.core.exceptions import PermissionDenied from django.db import models + from bookwyrm.settings import DOMAIN from .base_model import BookWyrmModel @@ -21,6 +23,12 @@ class Report(BookWyrmModel): links = models.ManyToManyField("Link", blank=True) resolved = models.BooleanField(default=False) + def raise_not_editable(self, viewer): + """instead of user being the owner field, it's reporter""" + if self.reporter == viewer or viewer.has_perm("bookwyrm.moderate_user"): + return + raise PermissionDenied() + def get_remote_id(self): return f"https://{DOMAIN}/settings/reports/{self.id}" diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 7730391f1..0f56162b1 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -3,6 +3,7 @@ import datetime from urllib.parse import urljoin import uuid +from django.core.exceptions import PermissionDenied from django.db import models, IntegrityError from django.dispatch import receiver from django.utils import timezone @@ -15,7 +16,23 @@ from .user import User from .fields import get_absolute_url -class SiteSettings(models.Model): +class SiteModel(models.Model): + """we just need edit perms""" + + class Meta: + """this is just here to provide default fields for other models""" + + abstract = True + + # pylint: disable=no-self-use + def raise_not_editable(self, viewer): + """Check if the user has the right permissions""" + if viewer.has_perm("bookwyrm.edit_instance_settings"): + return + raise PermissionDenied() + + +class SiteSettings(SiteModel): """customized settings for this instance""" name = models.CharField(default="BookWyrm", max_length=100) @@ -115,7 +132,7 @@ class SiteSettings(models.Model): super().save(*args, **kwargs) -class Theme(models.Model): +class Theme(SiteModel): """Theme files""" created_date = models.DateTimeField(auto_now_add=True) @@ -138,6 +155,13 @@ class SiteInvite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) invitees = models.ManyToManyField(User, related_name="invitees") + # pylint: disable=no-self-use + def raise_not_editable(self, viewer): + """Admins only""" + if viewer.has_perm("bookwyrm.create_invites"): + return + raise PermissionDenied() + def valid(self): """make sure it hasn't expired or been used""" return (self.expiry is None or self.expiry > timezone.now()) and ( @@ -161,6 +185,12 @@ class InviteRequest(BookWyrmModel): invite_sent = models.BooleanField(default=False) ignored = models.BooleanField(default=False) + def raise_not_editable(self, viewer): + """Only check perms on edit, not create""" + if not self.id or viewer.has_perm("bookwyrm.create_invites"): + return + raise PermissionDenied() + def save(self, *args, **kwargs): """don't create a request for a registered email""" if not self.id and User.objects.filter(email=self.email).exists(): diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 8a2b8082f..055941d8c 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -5,6 +5,7 @@ from urllib.parse import urlparse from django.apps import apps from django.contrib.auth.models import AbstractUser, Group from django.contrib.postgres.fields import ArrayField, CICharField +from django.core.exceptions import PermissionDenied from django.dispatch import receiver from django.db import models, transaction from django.utils import timezone @@ -401,6 +402,12 @@ class User(OrderedCollectionPageMixin, AbstractUser): editable=False, ).save(broadcast=False) + def raise_not_editable(self, viewer): + """Who can edit the user object?""" + if self == viewer or viewer.has_perm("bookwyrm.moderate_user"): + return + raise PermissionDenied() + class KeyPair(ActivitypubMixin, BookWyrmModel): """public and private keys for a user""" diff --git a/bookwyrm/templates/settings/themes.html b/bookwyrm/templates/settings/themes.html index afa3f0be4..628b04d77 100644 --- a/bookwyrm/templates/settings/themes.html +++ b/bookwyrm/templates/settings/themes.html @@ -54,7 +54,6 @@ method="POST" action="{% url 'settings-themes' %}" class="box" - enctype="multipart/form-data" >
{% csrf_token %} diff --git a/bookwyrm/tests/views/admin/test_automod.py b/bookwyrm/tests/views/admin/test_automod.py index 95db4d52f..a1c03d436 100644 --- a/bookwyrm/tests/views/admin/test_automod.py +++ b/bookwyrm/tests/views/admin/test_automod.py @@ -15,6 +15,7 @@ from bookwyrm.tests.validate_html import validate_html class AutomodViews(TestCase): """every response to a get request, html or json""" + # pylint: disable=invalid-name def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() diff --git a/bookwyrm/tests/views/admin/test_federation.py b/bookwyrm/tests/views/admin/test_federation.py index 33d7990b3..95b3225d5 100644 --- a/bookwyrm/tests/views/admin/test_federation.py +++ b/bookwyrm/tests/views/admin/test_federation.py @@ -17,6 +17,7 @@ from bookwyrm.tests.validate_html import validate_html class FederationViews(TestCase): """every response to a get request, html or json""" + # pylint: disable=invalid-name def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() diff --git a/bookwyrm/tests/views/admin/test_themes.py b/bookwyrm/tests/views/admin/test_themes.py new file mode 100644 index 000000000..bc6377681 --- /dev/null +++ b/bookwyrm/tests/views/admin/test_themes.py @@ -0,0 +1,88 @@ +""" test for app action functionality """ +from unittest.mock import patch + +from django.contrib.auth.models import Group +from django.core.exceptions import PermissionDenied +from django.template.response import TemplateResponse +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import forms, models, views +from bookwyrm.management.commands import initdb +from bookwyrm.tests.validate_html import validate_html + + +class AdminThemesViews(TestCase): + """Edit site settings""" + + # pylint: disable=invalid-name + def setUp(self): + """we need basic test data and mocks""" + self.factory = RequestFactory() + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ), patch("bookwyrm.lists_stream.populate_lists_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.another_user = models.User.objects.create_user( + "rat@local.com", + "rat@rat.rat", + "password", + local=True, + localname="rat", + ) + initdb.init_groups() + initdb.init_permissions() + group = Group.objects.get(name="admin") + self.local_user.groups.set([group]) + + self.site = models.SiteSettings.objects.create() + + def test_themes_get(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Themes.as_view() + request = self.factory.get("") + request.user = self.local_user + + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_themes_post(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Themes.as_view() + + form = forms.ThemeForm() + form.data["name"] = "test theme" + form.data["path"] = "not/a/path.scss" + request = self.factory.post("", form.data) + request.user = self.local_user + + result = view(request) + + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + theme = models.Theme.objects.last() + self.assertEqual(theme.name, "test theme") + self.assertEqual(theme.path, "not/a/path.scss") + + def test_themes_post_forbidden(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Themes.as_view() + + form = forms.ThemeForm() + form.data["name"] = "test theme" + form.data["path"] = "not/a/path.scss" + request = self.factory.post("", form.data) + request.user = self.another_user + + with self.assertRaises(PermissionDenied): + view(request) diff --git a/bookwyrm/tests/views/landing/test_invite.py b/bookwyrm/tests/views/landing/test_invite.py index a58771873..a96ecb9f2 100644 --- a/bookwyrm/tests/views/landing/test_invite.py +++ b/bookwyrm/tests/views/landing/test_invite.py @@ -14,6 +14,7 @@ from bookwyrm.tests.validate_html import validate_html class InviteViews(TestCase): """every response to a get request, html or json""" + # pylint: disable=invalid-name def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() @@ -82,6 +83,7 @@ class InviteViews(TestCase): view = views.InviteRequest.as_view() request = self.factory.post("", form.data) + request.user = self.local_user result = view(request) validate_html(result.render()) @@ -96,6 +98,7 @@ class InviteViews(TestCase): view = views.InviteRequest.as_view() request = self.factory.post("", form.data) + request.user = self.local_user result = view(request) validate_html(result.render()) @@ -109,6 +112,7 @@ class InviteViews(TestCase): request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True + result = view(request) self.assertIsInstance(result, TemplateResponse) validate_html(result.render()) diff --git a/bookwyrm/tests/views/lists/test_lists.py b/bookwyrm/tests/views/lists/test_lists.py index e55baae25..38a97c412 100644 --- a/bookwyrm/tests/views/lists/test_lists.py +++ b/bookwyrm/tests/views/lists/test_lists.py @@ -15,6 +15,7 @@ from bookwyrm.tests.validate_html import validate_html class ListViews(TestCase): """lists of lists""" + # pylint: disable=invalid-name def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() diff --git a/bookwyrm/tests/views/test_report.py b/bookwyrm/tests/views/test_report.py index 85dca8aea..487b02929 100644 --- a/bookwyrm/tests/views/test_report.py +++ b/bookwyrm/tests/views/test_report.py @@ -11,6 +11,7 @@ from bookwyrm.tests.validate_html import validate_html class ReportViews(TestCase): """every response to a get request, html or json""" + # pylint: disable=invalid-name def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() diff --git a/bookwyrm/views/admin/announcements.py b/bookwyrm/views/admin/announcements.py index 47bb4ab94..0b5ce9fa4 100644 --- a/bookwyrm/views/admin/announcements.py +++ b/bookwyrm/views/admin/announcements.py @@ -102,7 +102,7 @@ class EditAnnouncement(View): return TemplateResponse( request, "settings/announcements/edit_announcement.html", data ) - announcement = form.save() + announcement = form.save(request) return redirect("settings-announcements", announcement.id) diff --git a/bookwyrm/views/admin/automod.py b/bookwyrm/views/admin/automod.py index 65eae71a9..9a32dd9ee 100644 --- a/bookwyrm/views/admin/automod.py +++ b/bookwyrm/views/admin/automod.py @@ -34,7 +34,7 @@ class AutoMod(View): """add rule""" form = forms.AutoModRuleForm(request.POST) if form.is_valid(): - form.save() + form.save(request) form = forms.AutoModRuleForm() data = automod_view_data() @@ -54,7 +54,7 @@ def schedule_automod_task(request): return TemplateResponse(request, "settings/automod/rules.html", data) with transaction.atomic(): - schedule = form.save() + schedule = form.save(request) PeriodicTask.objects.get_or_create( interval=schedule, name="automod-task", diff --git a/bookwyrm/views/admin/email_blocklist.py b/bookwyrm/views/admin/email_blocklist.py index eecad4ffb..c31fa7366 100644 --- a/bookwyrm/views/admin/email_blocklist.py +++ b/bookwyrm/views/admin/email_blocklist.py @@ -40,7 +40,7 @@ class EmailBlocklist(View): return TemplateResponse( request, "settings/email_blocklist/email_blocklist.html", data ) - form.save() + form.save(request) data["form"] = forms.EmailBlocklistForm() return TemplateResponse( diff --git a/bookwyrm/views/admin/federation.py b/bookwyrm/views/admin/federation.py index 88bd43aac..e1cb80949 100644 --- a/bookwyrm/views/admin/federation.py +++ b/bookwyrm/views/admin/federation.py @@ -86,7 +86,7 @@ class AddFederatedServer(View): return TemplateResponse( request, "settings/federation/edit_instance.html", data ) - server = form.save() + server = form.save(request) return redirect("settings-federated-server", server.id) @@ -156,7 +156,7 @@ class FederatedServer(View): """update note""" server = get_object_or_404(models.FederatedServer, id=server) server.notes = request.POST.get("notes") - server.save() + server.save(request) return redirect("settings-federated-server", server.id) diff --git a/bookwyrm/views/admin/invite.py b/bookwyrm/views/admin/invite.py index 7da84c96c..5c9b61fde 100644 --- a/bookwyrm/views/admin/invite.py +++ b/bookwyrm/views/admin/invite.py @@ -52,9 +52,9 @@ class ManageInvites(View): if not form.is_valid(): return HttpResponseBadRequest(f"ERRORS: {form.errors}") - invite = form.save(commit=False) + invite = form.save(request, commit=False) invite.user = request.user - invite.save() + invite.save(request) paginated = Paginator( models.SiteInvite.objects.filter(user=request.user).order_by( @@ -170,7 +170,7 @@ class InviteRequest(View): received = False if form.is_valid(): received = True - form.save() + form.save(request) data = {"request_form": form, "request_received": received} return TemplateResponse(request, "landing/landing.html", data) diff --git a/bookwyrm/views/admin/ip_blocklist.py b/bookwyrm/views/admin/ip_blocklist.py index d57a2422c..a81bc738a 100644 --- a/bookwyrm/views/admin/ip_blocklist.py +++ b/bookwyrm/views/admin/ip_blocklist.py @@ -40,7 +40,7 @@ class IPBlocklist(View): return TemplateResponse( request, "settings/ip_blocklist/ip_blocklist.html", data ) - form.save() + form.save(request) data["form"] = forms.IPBlocklistForm() return TemplateResponse( diff --git a/bookwyrm/views/admin/link_domains.py b/bookwyrm/views/admin/link_domains.py index 0b8674170..6501834a1 100644 --- a/bookwyrm/views/admin/link_domains.py +++ b/bookwyrm/views/admin/link_domains.py @@ -39,7 +39,7 @@ class LinkDomain(View): """Set display name""" domain = get_object_or_404(models.LinkDomain, id=domain_id) form = forms.LinkDomainForm(request.POST, instance=domain) - form.save() + form.save(request) return redirect("settings-link-domain", status=status) diff --git a/bookwyrm/views/admin/site.py b/bookwyrm/views/admin/site.py index f345d9970..df3b12aa0 100644 --- a/bookwyrm/views/admin/site.py +++ b/bookwyrm/views/admin/site.py @@ -29,7 +29,7 @@ class Site(View): if not form.is_valid(): data = {"site_form": form} return TemplateResponse(request, "settings/site.html", data) - site = form.save() + site = form.save(request) data = {"site_form": forms.SiteForm(instance=site), "success": True} return TemplateResponse(request, "settings/site.html", data) diff --git a/bookwyrm/views/admin/themes.py b/bookwyrm/views/admin/themes.py index ae9a748d3..4d795fbe0 100644 --- a/bookwyrm/views/admin/themes.py +++ b/bookwyrm/views/admin/themes.py @@ -24,9 +24,9 @@ class Themes(View): def post(self, request): """edit the site settings""" - form = forms.ThemeForm(request.POST, request.FILES) + form = forms.ThemeForm(request.POST) if form.is_valid(): - form.save() + form.save(request) data = get_view_data() diff --git a/bookwyrm/views/admin/user_admin.py b/bookwyrm/views/admin/user_admin.py index 6ec6f93d2..c6827dcb7 100644 --- a/bookwyrm/views/admin/user_admin.py +++ b/bookwyrm/views/admin/user_admin.py @@ -88,6 +88,6 @@ class UserAdmin(View): else: form = forms.UserGroupForm(request.POST, instance=user) if form.is_valid(): - form.save() + form.save(request) data = {"user": user, "group_form": form} return TemplateResponse(request, "settings/users/user.html", data) diff --git a/bookwyrm/views/author.py b/bookwyrm/views/author.py index c7b17310c..4dcf4c447 100644 --- a/bookwyrm/views/author.py +++ b/bookwyrm/views/author.py @@ -68,7 +68,7 @@ class EditAuthor(View): if not form.is_valid(): data = {"author": author, "form": form} return TemplateResponse(request, "author/edit_author.html", data) - author = form.save() + author = form.save(request) return redirect(f"/author/{author.id}") diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 09fc4d0c8..47b03b0cb 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -56,7 +56,7 @@ class EditBook(View): for author_id in remove_authors: book.authors.remove(author_id) - book = form.save(commit=False) + book = form.save(request, commit=False) url = request.POST.get("cover-url") if url: @@ -119,7 +119,7 @@ class CreateBook(View): return TemplateResponse(request, "book/edit/edit_book.html", data) with transaction.atomic(): - book = form.save() + book = form.save(request) parent_work = get_object_or_404(models.Work, id=parent_work_id) book.parent_work = parent_work @@ -229,7 +229,7 @@ class ConfirmEditBook(View): with transaction.atomic(): # save book - book = form.save() + book = form.save(request) # add known authors authors = None diff --git a/bookwyrm/views/books/links.py b/bookwyrm/views/books/links.py index 516210230..60153f8c3 100644 --- a/bookwyrm/views/books/links.py +++ b/bookwyrm/views/books/links.py @@ -34,7 +34,7 @@ class BookFileLinks(View): """Edit a link""" link = get_object_or_404(models.FileLink, id=link_id, book=book_id) form = forms.FileLinkForm(request.POST, instance=link) - form.save() + form.save(request) return self.get(request, book_id) @@ -76,7 +76,7 @@ class AddFileLink(View): request, "book/file_links/file_link_page.html", data ) - link = form.save() + link = form.save(request) book.file_links.add(link) book.last_edited_by = request.user book.save() diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index eb4678f75..6d7aeb239 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -30,7 +30,7 @@ class Feed(View): form = forms.FeedStatusTypesForm(request.POST, instance=request.user) if form.is_valid(): # workaround to avoid broadcasting this change - user = form.save(commit=False) + user = form.save(request, commit=False) user.save(broadcast=False, update_fields=["feed_status_types"]) filters_applied = True diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index 3285c076f..fdb8824fa 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -38,7 +38,7 @@ class GetStartedProfile(View): if not form.is_valid(): data = {"form": form, "next": "get-started-books"} return TemplateResponse(request, "get_started/profile.html", data) - save_user_form(form) + save_user_form(request, form) return redirect(self.next_view) @@ -82,7 +82,6 @@ class GetStartedBooks(View): for (book_id, shelf_id) in shelve_actions: book = get_object_or_404(models.Edition, id=book_id) shelf = get_object_or_404(models.Shelf, id=shelf_id) - shelf.raise_not_editable(request.user) models.ShelfBook.objects.create(book=book, shelf=shelf, user=request.user) return redirect(self.next_view) diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 57ff4bd75..b5fd5bdc2 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -48,8 +48,6 @@ class Goal(View): year = int(year) user = get_user_from_username(request.user, username) goal = models.AnnualGoal.objects.filter(year=year, user=user).first() - if goal: - goal.raise_not_editable(request.user) form = forms.GoalForm(request.POST, instance=goal) if not form.is_valid(): @@ -59,7 +57,7 @@ class Goal(View): "year": year, } return TemplateResponse(request, "user/goal.html", data) - goal = form.save() + goal = form.save(request) if request.POST.get("post-status"): # create status, if appropriate diff --git a/bookwyrm/views/group.py b/bookwyrm/views/group.py index b2271e78d..1ccfd6849 100644 --- a/bookwyrm/views/group.py +++ b/bookwyrm/views/group.py @@ -52,7 +52,7 @@ class Group(View): form = forms.GroupForm(request.POST, instance=user_group) if not form.is_valid(): return redirect("group", user_group.id) - user_group = form.save() + user_group = form.save(request) # let the other members know something about the group changed memberships = models.GroupMember.objects.filter(group=user_group) @@ -113,10 +113,8 @@ class UserGroups(View): if not form.is_valid(): return redirect(request.user.local_path + "/groups") - group = form.save(commit=False) - group.raise_not_editable(request.user) with transaction.atomic(): - group.save() + group = form.save(request) # add the creator as a group member models.GroupMember.objects.create(group=group, user=request.user) return redirect("group", group.id) @@ -129,10 +127,13 @@ class FindUsers(View): # this is mostly borrowed from the Get Started friend finder def get(self, request, group_id): - """basic profile info""" + """Search for a user to add the a group, or load suggested users cache""" user_query = request.GET.get("user_query") group = get_object_or_404(models.Group, id=group_id) + + # only users who can edit can add users group.raise_not_editable(request.user) + lists = ( models.List.privacy_filter(request.user) .filter(group=group) diff --git a/bookwyrm/views/list/curate.py b/bookwyrm/views/list/curate.py index 924f37709..7155ffc43 100644 --- a/bookwyrm/views/list/curate.py +++ b/bookwyrm/views/list/curate.py @@ -31,7 +31,6 @@ class Curate(View): def post(self, request, list_id): """edit a book_list""" book_list = get_object_or_404(models.List, id=list_id) - book_list.raise_not_editable(request.user) suggestion = get_object_or_404(models.ListItem, id=request.POST.get("item")) approved = request.POST.get("approved") == "true" diff --git a/bookwyrm/views/list/list.py b/bookwyrm/views/list/list.py index d0b5e08f4..3797997a9 100644 --- a/bookwyrm/views/list/list.py +++ b/bookwyrm/views/list/list.py @@ -81,13 +81,12 @@ class List(View): def post(self, request, list_id): """edit a list""" book_list = get_object_or_404(models.List, id=list_id) - book_list.raise_not_editable(request.user) form = forms.ListForm(request.POST, instance=book_list) if not form.is_valid(): # this shouldn't happen raise Exception(form.errors) - book_list = form.save() + book_list = form.save(request) if not book_list.curation == "group": book_list.group = None book_list.save(broadcast=False) @@ -196,7 +195,7 @@ def add_book(request): if not form.is_valid(): return List().get(request, book_list.id, add_failed=True) - item = form.save(commit=False) + item = form.save(request, commit=False) if book_list.curation == "curated": # make a pending entry at the end of the list @@ -242,7 +241,6 @@ def set_book_position(request, list_item_id): special care with the unique ordering per list. """ list_item = get_object_or_404(models.ListItem, id=list_item_id) - list_item.book_list.raise_not_editable(request.user) try: int_position = int(request.POST.get("position")) except ValueError: diff --git a/bookwyrm/views/list/list_item.py b/bookwyrm/views/list/list_item.py index 6dca908fb..691df4da3 100644 --- a/bookwyrm/views/list/list_item.py +++ b/bookwyrm/views/list/list_item.py @@ -16,10 +16,9 @@ class ListItem(View): def post(self, request, list_id, list_item): """Edit a list item's notes""" list_item = get_object_or_404(models.ListItem, id=list_item, book_list=list_id) - list_item.raise_not_editable(request.user) form = forms.ListItemForm(request.POST, instance=list_item) if form.is_valid(): - item = form.save(commit=False) + item = form.save(request, commit=False) item.notes = to_markdown(item.notes) item.save() else: diff --git a/bookwyrm/views/list/lists.py b/bookwyrm/views/list/lists.py index ee6ff0867..2514fad58 100644 --- a/bookwyrm/views/list/lists.py +++ b/bookwyrm/views/list/lists.py @@ -36,8 +36,7 @@ class Lists(View): form = forms.ListForm(request.POST) if not form.is_valid(): return redirect("lists") - book_list = form.save(commit=False) - book_list.raise_not_editable(request.user) + book_list = form.save(request, commit=False) # list should not have a group if it is not group curated if not book_list.curation == "group": diff --git a/bookwyrm/views/preferences/edit_user.py b/bookwyrm/views/preferences/edit_user.py index a1b5b3638..00bcd70aa 100644 --- a/bookwyrm/views/preferences/edit_user.py +++ b/bookwyrm/views/preferences/edit_user.py @@ -34,14 +34,14 @@ class EditUser(View): data = {"form": form, "user": request.user} return TemplateResponse(request, "preferences/edit_user.html", data) - user = save_user_form(form) + user = save_user_form(request, form) return set_language(user, redirect("user-feed", request.user.localname)) -def save_user_form(form): +def save_user_form(request, form): """special handling for the user form""" - user = form.save(commit=False) + user = form.save(request, commit=False) if "avatar" in form.files: # crop and resize avatar upload diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 482da3cd0..328dfd7fa 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -159,7 +159,7 @@ class ReadThrough(View): models.ReadThrough, id=request.POST.get("id") ) return TemplateResponse(request, "readthrough/readthrough.html", data) - form.save() + form.save(request) return redirect("book", book_id) diff --git a/bookwyrm/views/report.py b/bookwyrm/views/report.py index 118f67988..121b7a232 100644 --- a/bookwyrm/views/report.py +++ b/bookwyrm/views/report.py @@ -33,7 +33,7 @@ class Report(View): if not form.is_valid(): raise ValueError(form.errors) - report = form.save() + report = form.save(request) if report.links.exists(): # revert the domain to pending domain = report.links.first().domain diff --git a/bookwyrm/views/shelf/shelf.py b/bookwyrm/views/shelf/shelf.py index 378b346b3..0c3074902 100644 --- a/bookwyrm/views/shelf/shelf.py +++ b/bookwyrm/views/shelf/shelf.py @@ -113,7 +113,6 @@ class Shelf(View): """edit a shelf""" user = get_user_from_username(request.user, username) shelf = get_object_or_404(user.shelf_set, identifier=shelf_identifier) - shelf.raise_not_editable(request.user) # you can't change the name of the default shelves if not shelf.editable and request.POST.get("name") != shelf.name: @@ -122,7 +121,7 @@ class Shelf(View): form = forms.ShelfForm(request.POST, instance=shelf) if not form.is_valid(): return redirect(shelf.local_path) - shelf = form.save() + shelf = form.save(request) return redirect(shelf.local_path) diff --git a/bookwyrm/views/shelf/shelf_actions.py b/bookwyrm/views/shelf/shelf_actions.py index 7dbb83dea..d2aa7d566 100644 --- a/bookwyrm/views/shelf/shelf_actions.py +++ b/bookwyrm/views/shelf/shelf_actions.py @@ -15,9 +15,7 @@ def create_shelf(request): if not form.is_valid(): return redirect("user-shelves", request.user.localname) - shelf = form.save(commit=False) - shelf.raise_not_editable(request.user) - shelf.save() + shelf = form.save(request) return redirect(shelf.local_path) diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index e4eb5ada5..dc6bbc579 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -34,7 +34,6 @@ class EditStatus(View): status = get_object_or_404( models.Status.objects.select_subclasses(), id=status_id ) - status.raise_not_editable(request.user) status_type = "reply" if status.reply_parent else status.status_type.lower() data = { @@ -65,7 +64,6 @@ class CreateStatus(View): existing_status = get_object_or_404( models.Status.objects.select_subclasses(), id=existing_status_id ) - existing_status.raise_not_editable(request.user) existing_status.edited_date = timezone.now() status_type = status_type[0].upper() + status_type[1:] @@ -84,8 +82,7 @@ class CreateStatus(View): return HttpResponseBadRequest() return redirect("/") - status = form.save(commit=False) - status.raise_not_editable(request.user) + status = form.save(request) # save the plain, unformatted version of the status for future editing status.raw_content = status.content if hasattr(status, "quote"): @@ -167,7 +164,6 @@ def edit_readthrough(request): """can't use the form because the dates are too finnicky""" # TODO: remove this, it duplicates the code in the ReadThrough view readthrough = get_object_or_404(models.ReadThrough, id=request.POST.get("id")) - readthrough.raise_not_editable(request.user) readthrough.start_date = load_date_in_user_tz_as_utc( request.POST.get("start_date"), request.user