mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-23 10:01:04 +00:00
Merge pull request #2258 from bookwyrm-social/form-perms
Check permissions automatically on form save
This commit is contained in:
commit
fdc477afdf
41 changed files with 291 additions and 68 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
65
bookwyrm/migrations/0158_auto_20220919_1634.py
Normal file
65
bookwyrm/migrations/0158_auto_20220919_1634.py
Normal 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),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
method="POST"
|
||||
action="{% url 'settings-themes' %}"
|
||||
class="box"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
88
bookwyrm/tests/views/admin/test_themes.py
Normal file
88
bookwyrm/tests/views/admin/test_themes.py
Normal 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)
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue