Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic post reply/edit reply actions #1814

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5a2634f
Add tests for base posting state, hooks for save thread states
rafalp Sep 15, 2024
314c3aa
Add tests for start thread states, add memory leak fix to filter hook
rafalp Sep 15, 2024
9d2c07c
Add start thread state hooks to posting
rafalp Sep 19, 2024
375fcc0
Add hooks to start thread page
rafalp Sep 19, 2024
db15338
Add check_post_in_closed_thread_permission thread permission
rafalp Sep 24, 2024
1b31ddd
Commit WIP post reply code and new permissions
rafalp Sep 24, 2024
4941555
Rename start_thread_in_category to start_thread
rafalp Sep 24, 2024
3ac4486
Add reply private thread permission check
rafalp Sep 24, 2024
79b2a20
WIP add new permissions to groups
rafalp Sep 24, 2024
a0abf69
Add edit thread/post permissions
rafalp Sep 24, 2024
ab51b9a
WIP edit thread permission check
rafalp Sep 24, 2024
fff5d09
Add plugin hook to check edit thread permission
rafalp Sep 24, 2024
ea4e89d
Add tests for edit thread permission check
rafalp Sep 25, 2024
27e5ec1
Check edit post permission
rafalp Sep 25, 2024
d585671
Add some tests for check edit post permission
rafalp Sep 25, 2024
03f4a70
Complete tests for edit thread/reply permission checks
rafalp Sep 25, 2024
b2d39a5
Add missing start/reply permission checks for edit thread/post
rafalp Sep 26, 2024
5046b20
Add private thread edit post permission check
rafalp Sep 26, 2024
d4c150b
Edit private thread permission check
rafalp Sep 26, 2024
d25e4c2
Test is_private_threads_moderator
rafalp Sep 26, 2024
54bc2e8
Add tests for is_category_moderator
rafalp Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions misago/admin/groups/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,50 @@ class EditGroupForm(forms.ModelForm):

# Permissions

can_edit_own_threads = YesNoSwitch(
label=pgettext_lazy("admin group permissions form", "Can edit own threads"),
)
own_threads_edit_time_limit = forms.IntegerField(
label=pgettext_lazy(
"admin group permissions form", "Time limit for editing own threads"
),
help_text=pgettext_lazy(
"admin group permissions form",
"Enter the number of minutes after a user starts a thread during which they can still edit it. Enter zero to remove this time limit.",
),
min_value=0,
)

can_edit_own_posts = YesNoSwitch(
label=pgettext_lazy("admin group permissions form", "Can edit own posts"),
)
own_posts_edit_time_limit = forms.IntegerField(
label=pgettext_lazy(
"admin group permissions form", "Time limit for editing own posts"
),
help_text=pgettext_lazy(
"admin group permissions form",
"Enter the number of minutes after a user posts a message during which they can still edit it. Enter zero to remove this time limit.",
),
min_value=0,
)

can_use_private_threads = YesNoSwitch(
label=pgettext_lazy("admin group permissions form", "Can use private threads"),
)
can_start_private_threads = YesNoSwitch(
label=pgettext_lazy(
"admin group permissions form", "Can start new private threads"
),
)
private_thread_users_limit = forms.IntegerField(
label=pgettext_lazy("admin group permissions form", "Invited users limit"),
help_text=pgettext_lazy(
"admin group permissions form",
"Enter the maximum number of users that can be invited to private threads started by members of this group.",
),
min_value=1,
)

can_change_username = YesNoSwitch(
label=pgettext_lazy("admin group permissions form", "Can change username"),
Expand Down Expand Up @@ -167,7 +208,13 @@ class Meta:
"css_suffix",
"is_page",
"is_hidden",
"can_edit_own_threads",
"own_threads_edit_time_limit",
"can_edit_own_posts",
"own_posts_edit_time_limit",
"can_use_private_threads",
"can_start_private_threads",
"private_thread_users_limit",
"can_change_username",
"username_changes_limit",
"username_changes_expire",
Expand Down
20 changes: 20 additions & 0 deletions misago/admin/templates/misago/admin/groups/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,31 @@

</fieldset>
</div>
<div class="form-fieldset">
<fieldset>
<legend>{% trans "Threads" context "admin group permissions form" %}</legend>

{% form_row form.group.can_edit_own_threads %}
{% form_row form.group.own_threads_edit_time_limit %}

</fieldset>
</div>
<div class="form-fieldset">
<fieldset>
<legend>{% trans "Posts" context "admin group permissions form" %}</legend>

{% form_row form.group.can_edit_own_posts %}
{% form_row form.group.own_posts_edit_time_limit %}

</fieldset>
</div>
<div class="form-fieldset">
<fieldset>
<legend>{% trans "Private threads" context "admin group permissions form" %}</legend>

{% form_row form.group.can_use_private_threads %}
{% form_row form.group.can_start_private_threads %}
{% form_row form.group.private_thread_users_limit %}

</fieldset>
</div>
Expand Down
8 changes: 8 additions & 0 deletions misago/admin/tests/test_group_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ def get_form_data(group: Group) -> dict:
"group-user_title": group.user_title or "",
"group-is_page": "1" if group.is_page else "",
"group-is_hidden": "1" if group.is_hidden else "",
"group-can_edit_own_threads": "1" if group.can_edit_own_threads else "",
"group-own_threads_edit_time_limit": str(group.own_threads_edit_time_limit),
"group-can_edit_own_posts": "1" if group.can_edit_own_posts else "",
"group-own_posts_edit_time_limit": str(group.own_posts_edit_time_limit),
"group-can_use_private_threads": "1" if group.can_use_private_threads else "",
"group-can_start_private_threads": (
"1" if group.can_start_private_threads else ""
),
"group-private_thread_users_limit": str(group.private_thread_users_limit),
"group-can_change_username": "1" if group.can_change_username else "",
"group-username_changes_limit": str(group.username_changes_limit),
"group-username_changes_expire": str(group.username_changes_expire),
Expand Down
2 changes: 1 addition & 1 deletion misago/categories/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def can_see_last_thread(

if (
category.show_started_only
and category.id not in permissions.categories_moderator
and not permissions.is_category_moderator(category.id)
and (user.is_anonymous or category.last_poster_id != user.id)
):
return False
Expand Down
4 changes: 2 additions & 2 deletions misago/moderation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def get_disabled_category_choices(

categories_browse = user_permissions.categories[CategoryPermission.BROWSE]
categories_start = user_permissions.categories[CategoryPermission.START]
categories_moderator = user_permissions.categories_moderator
moderated_categories = user_permissions.moderated_categories

for category in categories.categories_list:
if (
category["is_vanilla"]
or category["id"] not in categories_browse
or category["id"] not in categories_start
or (category["is_closed"] and category["id"] not in categories_moderator)
or (category["is_closed"] and category["id"] not in moderated_categories)
):
choices.add(category["id"])

Expand Down
4 changes: 4 additions & 0 deletions misago/permissions/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def _copy_category_permissions_action(
"can_use_private_threads",
"can_start_private_threads",
"private_thread_users_limit",
"can_edit_own_threads",
"own_threads_edit_time_limit",
"can_edit_own_posts",
"own_posts_edit_time_limit",
"can_change_username",
"username_changes_limit",
"username_changes_expire",
Expand Down
28 changes: 25 additions & 3 deletions misago/permissions/hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
from .build_user_category_permissions import build_user_category_permissions_hook
from .build_user_permissions import build_user_permissions_hook
from .check_browse_category_permission import check_browse_category_permission_hook
from .check_edit_post_permission import check_edit_post_permission_hook
from .check_edit_private_thread_permission import (
check_edit_private_thread_permission_hook,
)
from .check_edit_private_thread_post_permission import (
check_edit_private_thread_post_permission_hook,
)
from .check_edit_thread_permission import check_edit_thread_permission_hook
from .check_post_in_closed_category_permission import (
check_post_in_closed_category_permission_hook,
)
from .check_post_in_closed_thread_permission import (
check_post_in_closed_thread_permission_hook,
)
from .check_private_threads_permission import check_private_threads_permission_hook
from .check_reply_private_thread_permission import (
check_reply_private_thread_permission_hook,
)
from .check_reply_thread_permission import check_reply_thread_permission_hook
from .check_see_category_permission import check_see_category_permission_hook
from .check_see_private_thread_permission import (
check_see_private_thread_permission_hook,
Expand All @@ -13,8 +28,8 @@
from .check_start_private_threads_permission import (
check_start_private_threads_permission_hook,
)
from .check_start_thread_in_category_permission import (
check_start_thread_in_category_permission_hook,
from .check_start_thread_permission import (
check_start_thread_permission_hook,
)
from .copy_category_permissions import copy_category_permissions_hook
from .copy_group_permissions import copy_group_permissions_hook
Expand All @@ -40,13 +55,20 @@
"build_user_category_permissions_hook",
"build_user_permissions_hook",
"check_browse_category_permission_hook",
"check_edit_post_permission_hook",
"check_edit_private_thread_permission_hook",
"check_edit_private_thread_post_permission_hook",
"check_edit_thread_permission_hook",
"check_post_in_closed_category_permission_hook",
"check_post_in_closed_thread_permission_hook",
"check_private_threads_permission_hook",
"check_reply_private_thread_permission_hook",
"check_reply_thread_permission_hook",
"check_see_category_permission_hook",
"check_see_private_thread_permission_hook",
"check_see_thread_permission_hook",
"check_start_private_threads_permission_hook",
"check_start_thread_in_category_permission_hook",
"check_start_thread_permission_hook",
"copy_category_permissions_hook",
"copy_group_permissions_hook",
"filter_private_thread_posts_queryset_hook",
Expand Down
143 changes: 143 additions & 0 deletions misago/permissions/hooks/check_edit_post_permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from typing import TYPE_CHECKING, Protocol

from ...categories.models import Category
from ...plugins.hooks import FilterHook
from ...threads.models import Post, Thread

if TYPE_CHECKING:
from ..proxy import UserPermissionsProxy


class CheckEditPostPermissionHookAction(Protocol):
"""
A standard Misago function used to check if the user has permission to
edit a post. It raises Django's `PermissionDenied` with an
error message if they can't.

# Arguments

## `user_permissions: UserPermissionsProxy`

A proxy object with the current user's permissions.

## `category: Category`

A category to check permissions for.

## `thread: Thread`

A thread to check permissions for.

## `post: Post`

A post to check permissions for.
"""

def __call__(
self,
permissions: "UserPermissionsProxy",
category: Category,
thread: Thread,
post: Post,
) -> None: ...


class CheckEditPostPermissionHookFilter(Protocol):
"""
A function implemented by a plugin that can be registered in this hook.

# Arguments

## `action: CheckEditPostPermissionHookAction`

A standard Misago function used to check if the user has permission to
edit a post. It raises Django's `PermissionDenied` with an
error message if they can't.

See the [action](#action) section for details.

## `user_permissions: UserPermissionsProxy`

A proxy object with the current user's permissions.

## `category: Category`

A category to check permissions for.

## `thread: Thread`

A thread to check permissions for.

## `post: Post`

A post to check permissions for.
"""

def __call__(
self,
action: CheckEditPostPermissionHookAction,
permissions: "UserPermissionsProxy",
category: Category,
thread: Thread,
post: Post,
) -> None: ...


class CheckEditPostPermissionHook(
FilterHook[
CheckEditPostPermissionHookAction,
CheckEditPostPermissionHookFilter,
]
):
"""
This hook wraps the standard function that Misago uses to check if the user
has permission to edit a post. It raises Django's `PermissionDenied`
with an error message if they can't.

# Example

The code below implements a custom filter function that prevents a user from
editing a post if it contains a special string.

```python
from django.core.exceptions import PermissionDenied
from misago.categories.models import Category
from misago.permissions.hooks import check_edit_post_permission_hook
from misago.permissions.proxy import UserPermissionsProxy
from misago.threads.models import Post, Thread

@check_edit_post_permission_hook.append_filter
def check_user_can_edit_thread(
action,
permissions: UserPermissionsProxy,
category: Category,
thread: Thread,
post: Post,
) -> None:
action(permissions, category, post, thread)

if (
"[PROTECT]" in post.original
and not (
permissions.is_global_moderator
or permissions.is_category_moderator(thread.category_id)
)
):
raise PermissionError("Only a moderator can edit this post.")
```
"""

__slots__ = FilterHook.__slots__

def __call__(
self,
action: CheckEditPostPermissionHookAction,
permissions: "UserPermissionsProxy",
category: Category,
thread: Thread,
post: Post,
) -> None:
return super().__call__(action, permissions, category, thread, post)


check_edit_post_permission_hook = CheckEditPostPermissionHook()
Loading
Loading