From 8f79b362f8c43d37d8c6e82f32f882831416d11b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 5 Aug 2022 09:51:55 -0700 Subject: [PATCH 01/15] Check permissions automatically on form save --- bookwyrm/forms/custom_form.py | 5 +++++ bookwyrm/views/goal.py | 4 +--- bookwyrm/views/group.py | 11 ++++++----- bookwyrm/views/list/list.py | 3 +-- bookwyrm/views/list/list_item.py | 3 +-- bookwyrm/views/list/lists.py | 3 +-- bookwyrm/views/reading.py | 2 +- bookwyrm/views/shelf/shelf.py | 3 +-- bookwyrm/views/shelf/shelf_actions.py | 4 +--- bookwyrm/views/status.py | 4 +--- 10 files changed, 19 insertions(+), 23 deletions(-) diff --git a/bookwyrm/forms/custom_form.py b/bookwyrm/forms/custom_form.py index 74a3417a2..3c2b4685f 100644 --- a/bookwyrm/forms/custom_form.py +++ b/bookwyrm/forms/custom_form.py @@ -24,3 +24,8 @@ class CustomForm(ModelForm): input_type = "textarea" visible.field.widget.attrs["rows"] = 5 visible.field.widget.attrs["class"] = css_classes[input_type] + + 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/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/list.py b/bookwyrm/views/list/list.py index d0b5e08f4..35e18d244 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) 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..1b2250794 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) # list should not have a group if it is not group curated if not book_list.curation == "group": 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/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 c0a045f8a..2f957f087 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -65,7 +65,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 +83,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"): From 351292fcdacb03f19e3ce081db8ff65c78730296 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Sep 2022 10:50:05 -0700 Subject: [PATCH 02/15] Catches places where form.save() needs the request Thank you, tests. --- bookwyrm/views/admin/announcements.py | 2 +- bookwyrm/views/admin/link_domains.py | 2 +- bookwyrm/views/author.py | 2 +- bookwyrm/views/feed.py | 2 +- bookwyrm/views/get_started.py | 2 +- bookwyrm/views/list/list.py | 2 +- bookwyrm/views/preferences/edit_user.py | 6 +++--- bookwyrm/views/report.py | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) 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/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/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/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..e31b64cb9 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) diff --git a/bookwyrm/views/list/list.py b/bookwyrm/views/list/list.py index 35e18d244..5c0dd2e4e 100644 --- a/bookwyrm/views/list/list.py +++ b/bookwyrm/views/list/list.py @@ -195,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 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/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 From 543d13af6ec43965da22d1be79c6df4bf57cf8ed Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 8 Sep 2022 11:03:06 -0700 Subject: [PATCH 03/15] Removes explicit calls to raise_not_editable from views These raises are handled implicitly in the form, so they don't have to be called outright. --- bookwyrm/views/get_started.py | 1 - bookwyrm/views/list/curate.py | 1 - bookwyrm/views/list/list.py | 1 - bookwyrm/views/status.py | 2 -- 4 files changed, 5 deletions(-) diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index e31b64cb9..fdb8824fa 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -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/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 5c0dd2e4e..3797997a9 100644 --- a/bookwyrm/views/list/list.py +++ b/bookwyrm/views/list/list.py @@ -241,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/status.py b/bookwyrm/views/status.py index 2f957f087..ee91b0fb8 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 = { @@ -163,7 +162,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 From aa67f23b032f6fbb02065a63ab04ee128fadafb0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 09:20:07 -0700 Subject: [PATCH 04/15] Override raise_not_editable for report model This model uses "reporter" as the field that represents the onwer of the object, and "user" and the subject. In retrospect, maybe not the ideal way to do it. --- bookwyrm/models/report.py | 8 ++++++++ bookwyrm/tests/views/test_report.py | 1 + 2 files changed, 9 insertions(+) diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index d161c0349..19d3b62f1 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: + return + raise PermissionDenied() + def get_remote_id(self): return f"https://{DOMAIN}/settings/reports/{self.id}" 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() From 3f5eb6c682867eca69639eaa9eb00605b21d230b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 09:23:55 -0700 Subject: [PATCH 05/15] Adds raise_not_editable to User model This model doens't inherit from BookwyrmModel for various reasons, but it still needs to editability check. --- bookwyrm/models/user.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index f0c3cf04d..df443951a 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 @@ -393,6 +394,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""" From e51980bc123ee7aea843395a980ad63e02deeb01 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 09:35:38 -0700 Subject: [PATCH 06/15] Use BookWyrmModel as parent for admin models This will make them more internally consistent and give them built-in permissions checking --- .../migrations/0158_auto_20220919_1634.py | 65 +++++++++++++++++++ bookwyrm/models/antispam.py | 9 ++- bookwyrm/views/admin/automod.py | 4 +- bookwyrm/views/admin/email_blocklist.py | 2 +- bookwyrm/views/admin/federation.py | 6 +- bookwyrm/views/admin/ip_blocklist.py | 2 +- bookwyrm/views/admin/user_admin.py | 2 +- 7 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 bookwyrm/migrations/0158_auto_20220919_1634.py 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 dd2a6df26..628f4525c 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -8,13 +8,13 @@ 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 EmailBlocklist(BookWyrmModel): """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 +29,9 @@ class EmailBlocklist(models.Model): return User.objects.filter(email__endswith=f"@{self.domain}") -class IPBlocklist(models.Model): +class IPBlocklist(BookWyrmModel): """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 +41,7 @@ class IPBlocklist(models.Model): ordering = ("-created_date",) -class AutoMod(models.Model): +class AutoMod(BookWyrmModel): """rules to automatically flag suspicious activity""" string_match = models.CharField(max_length=200, unique=True) 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..2c5368f52 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) @@ -119,7 +119,7 @@ class ImportServerBlocklist(View): server_name=server_name, ) server.notes = info_link - server.save() + server.save(request) server.block() success_count += 1 data = {"failed": failed, "succeeded": success_count} @@ -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/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/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) From 330be16516a813b557e896c9a3d2d421515d2535 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 09:51:41 -0700 Subject: [PATCH 07/15] Adds permissions checking for admin models --- bookwyrm/forms/admin.py | 11 +++++++++-- bookwyrm/forms/custom_form.py | 8 ++++++-- bookwyrm/models/antispam.py | 21 ++++++++++++++++++--- bookwyrm/models/report.py | 2 +- bookwyrm/tests/views/admin/test_automod.py | 1 + 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/bookwyrm/forms/admin.py b/bookwyrm/forms/admin.py index 4141327d3..ea0f8ccc7 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,9 @@ class IntervalScheduleForm(CustomForm): "every": forms.NumberInput(attrs={"aria-describedby": "desc_every"}), "period": forms.Select(attrs={"aria-describedby": "desc_period"}), } + + 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 3c2b4685f..10f72f967 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 @@ -25,6 +25,10 @@ class CustomForm(ModelForm): visible.field.widget.attrs["rows"] = 5 visible.field.widget.attrs["class"] = css_classes[input_type] + +class CustomForm(StyledForm): + """Check permissions on save""" + def save(self, request, *args, **kwargs): """Save and check perms""" self.instance.raise_not_editable(request.user) diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index 628f4525c..40d663a22 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -3,6 +3,7 @@ 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 _ @@ -12,7 +13,21 @@ from .base_model import BookWyrmModel from .user import User -class EmailBlocklist(BookWyrmModel): +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""" domain = models.CharField(max_length=255, unique=True) @@ -29,7 +44,7 @@ class EmailBlocklist(BookWyrmModel): return User.objects.filter(email__endswith=f"@{self.domain}") -class IPBlocklist(BookWyrmModel): +class IPBlocklist(AdminModel): """blocked ip addresses""" address = models.CharField(max_length=255, unique=True) @@ -41,7 +56,7 @@ class IPBlocklist(BookWyrmModel): ordering = ("-created_date",) -class AutoMod(BookWyrmModel): +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 19d3b62f1..f6e665053 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -25,7 +25,7 @@ class Report(BookWyrmModel): def raise_not_editable(self, viewer): """instead of user being the owner field, it's reporter""" - if self.reporter == viewer: + if self.reporter == viewer or viewer.has_perm("bookwyrm.moderate_user"): return raise PermissionDenied() 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() From b0d869700672f7a7b483983a5fb9fd804503a98b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 10:16:29 -0700 Subject: [PATCH 08/15] Adds missing save in edit book view --- bookwyrm/views/books/edit_book.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 09fc4d0c8..97efaa27b 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: From 3fd573c0da5a9eaadd5b5198f159e5474b6c408f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 10:16:38 -0700 Subject: [PATCH 09/15] Check perms on site model form --- bookwyrm/models/site.py | 8 ++++++++ bookwyrm/views/admin/site.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 7730391f1..4d891e721 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 @@ -114,6 +115,13 @@ class SiteSettings(models.Model): self.invite_question_text = "What is your favourite book?" super().save(*args, **kwargs) + # 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 Theme(models.Model): """Theme files""" 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) From be480e40f0c27f7d96e769d81079cdc560515522 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 10:18:30 -0700 Subject: [PATCH 10/15] Updates links form saves --- bookwyrm/views/books/links.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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() From 1e988cae6c23781d39eb3a7404f8b45ff4a8f2e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 10:26:29 -0700 Subject: [PATCH 11/15] More edit book lines --- bookwyrm/views/books/edit_book.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 97efaa27b..47b03b0cb 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -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 From 9d8d85ebc111f08d1d7f12306fc493e7e8c7c6fa Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 10:26:40 -0700 Subject: [PATCH 12/15] Invite perms checks --- bookwyrm/models/site.py | 13 +++++++++++++ bookwyrm/tests/views/landing/test_invite.py | 1 + bookwyrm/views/admin/invite.py | 6 +++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 4d891e721..6c2381f8a 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -146,6 +146,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 ( @@ -169,6 +176,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/tests/views/landing/test_invite.py b/bookwyrm/tests/views/landing/test_invite.py index a58771873..707851e8f 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() 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) From bf092ec44e74b6f6c4ee8c21686ca7167f642fac Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 12:10:27 -0700 Subject: [PATCH 13/15] Small fixes --- bookwyrm/tests/views/admin/test_federation.py | 1 + bookwyrm/tests/views/landing/test_invite.py | 3 +++ bookwyrm/tests/views/lists/test_lists.py | 1 + bookwyrm/views/admin/federation.py | 2 +- bookwyrm/views/list/lists.py | 2 +- 5 files changed, 7 insertions(+), 2 deletions(-) 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/landing/test_invite.py b/bookwyrm/tests/views/landing/test_invite.py index 707851e8f..a96ecb9f2 100644 --- a/bookwyrm/tests/views/landing/test_invite.py +++ b/bookwyrm/tests/views/landing/test_invite.py @@ -83,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()) @@ -97,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()) @@ -110,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/views/admin/federation.py b/bookwyrm/views/admin/federation.py index 2c5368f52..e1cb80949 100644 --- a/bookwyrm/views/admin/federation.py +++ b/bookwyrm/views/admin/federation.py @@ -119,7 +119,7 @@ class ImportServerBlocklist(View): server_name=server_name, ) server.notes = info_link - server.save(request) + server.save() server.block() success_count += 1 data = {"failed": failed, "succeeded": success_count} diff --git a/bookwyrm/views/list/lists.py b/bookwyrm/views/list/lists.py index 1b2250794..2514fad58 100644 --- a/bookwyrm/views/list/lists.py +++ b/bookwyrm/views/list/lists.py @@ -36,7 +36,7 @@ class Lists(View): form = forms.ListForm(request.POST) if not form.is_valid(): return redirect("lists") - book_list = form.save(request) + 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": From 35aebacf70fd51c93a0db87a76f6c5b2c04c740f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 13:07:57 -0700 Subject: [PATCH 14/15] Fixes theme form and adds view tests --- bookwyrm/models/site.py | 27 ++++--- bookwyrm/templates/settings/themes.html | 1 - bookwyrm/tests/views/admin/test_themes.py | 88 +++++++++++++++++++++++ bookwyrm/views/admin/themes.py | 4 +- 4 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 bookwyrm/tests/views/admin/test_themes.py diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 6c2381f8a..0f56162b1 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -16,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,15 +131,8 @@ class SiteSettings(models.Model): self.invite_question_text = "What is your favourite book?" super().save(*args, **kwargs) - # 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 Theme(models.Model): +class Theme(SiteModel): """Theme files""" created_date = models.DateTimeField(auto_now_add=True) 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_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/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() From 7d61cb55bc5fb1193290c9c42b080333074867cc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 19 Sep 2022 13:23:18 -0700 Subject: [PATCH 15/15] Ignore pylint complaints about argument counts to form saves --- bookwyrm/forms/admin.py | 1 + bookwyrm/forms/custom_form.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bookwyrm/forms/admin.py b/bookwyrm/forms/admin.py index ea0f8ccc7..ae15e011b 100644 --- a/bookwyrm/forms/admin.py +++ b/bookwyrm/forms/admin.py @@ -141,6 +141,7 @@ class IntervalScheduleForm(StyledForm): "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"): diff --git a/bookwyrm/forms/custom_form.py b/bookwyrm/forms/custom_form.py index 10f72f967..c604deea4 100644 --- a/bookwyrm/forms/custom_form.py +++ b/bookwyrm/forms/custom_form.py @@ -29,6 +29,7 @@ class StyledForm(ModelForm): 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)