Merge pull request #2258 from bookwyrm-social/form-perms

Check permissions automatically on form save
This commit is contained in:
Mouse Reeve 2022-09-19 13:32:41 -07:00 committed by GitHub
commit fdc477afdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 291 additions and 68 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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),
),
]

View file

@ -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)

View file

@ -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}"

View file

@ -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():

View file

@ -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"""

View file

@ -54,7 +54,6 @@
method="POST"
action="{% url 'settings-themes' %}"
class="box"
enctype="multipart/form-data"
>
<fieldset>
{% csrf_token %}

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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())

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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",

View file

@ -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(

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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}")

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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:

View file

@ -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:

View file

@ -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":

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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