-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat: Allow page permission change from advanced settings #8387
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
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR adds a unified page permissions menu by extending the advanced settings view with a permission tab and enforcing consistent permission checks across admin view, page tree, and toolbar, refactors page admin URLs to use named object_id parameters, enhances modal popup JS to detect successful Django admin popup saves via script tags, and updates frontend and backend tests to expect the new &_popup=1 parameter. Sequence diagram for modal popup save detection enhancementsequenceDiagram
participant User
participant Modal
participant DjangoAdmin
User->>Modal: Submit form in modal
Modal->>DjangoAdmin: Send form data
DjangoAdmin-->>Modal: Return response (may include script#django-admin-popup-response-constants)
Modal->>Modal: Check for script tag in response
Modal->>User: Indicate successful save and close modal
Class diagram for updated PageAdmin permission checksclassDiagram
class PageAdmin {
+get_form(request, obj=None, **kwargs)
+advanced(request, object_id)
+get_tree_rows(request, pages, language, depth=1, follow_descendants=True)
-has_change_advanced_settings_permission(request, obj)
-has_change_permissions_permission(request, obj)
}
PageAdmin <|-- ModelAdmin
class PagePermissions {
+user_can_add_subpage(user, target)
+user_can_change_page(user, page, site)
+user_can_change_page_advanced_settings(user, page, site)
+user_can_change_page_permissions(user, page, site)
}
PageAdmin --> PagePermissions
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `cms/admin/pageadmin.py:274` </location>
<code_context>
+ form._request = request
+ else:
+ # In case it's not the advanced view, return an empty form
+ class Meta:
+ model = Page
+ fields = []
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring by moving the EmptyPageForm class definition outside the get_form method and consolidating repeated permission checks into a helper function.
```python
# Move this out of get_form and only define it once per module
class EmptyPageForm(ModelForm):
class Meta:
model = Page
fields = []
# -----------------------------
def get_form(self, request, obj=None, **kwargs):
"""Get PageForm or a no-op form if not in advanced settings."""
if not getattr(request, '_advanced_settings', False):
return EmptyPageForm
FormClass = super().get_form(request, obj, **kwargs)
FormClass._site = get_site(request)
FormClass._request = request
return FormClass
# -----------------------------
# Helper to collapse repeated permission checks
def _ensure_permission(self, request, obj, check_fn, message):
if not check_fn(request, obj):
raise PermissionDenied(message)
def change_permissions(self, request, page_id):
page = self.get_object(request, object_id=page_id)
if page is None:
raise self._get_404_exception(page_id)
# combine your two permission checks into one call
self._ensure_permission(
request,
page,
self.has_change_advanced_settings_permission,
"No permission for editing advanced settings"
)
request._change_page_permissions = True
show_permissions = (
get_cms_setting('PERMISSION') and
self.has_change_permissions_permission(request, obj=page)
)
context = {
'title': _("Change Page Permissions"),
'show_permissions': show_permissions,
}
return self.change_view(request, page_id, extra_context=context)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
0bcc9a1 to
23f90f5
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8387 +/- ##
==========================================
+ Coverage 89.72% 89.77% +0.05%
==========================================
Files 129 129
Lines 12749 12755 +6
==========================================
+ Hits 11439 11451 +12
+ Misses 1310 1304 -6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The logic that combines advanced‐settings and permissions checks is duplicated across the admin view, toolbar, and tree rendering—consider extracting it into a single helper to keep behaviors consistent.
- The dynamic EmptyForm creation in get_form is clever but a bit opaque—extract it into a well-named private method or inner class to make the fallback intent clearer.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The logic that combines advanced‐settings and permissions checks is duplicated across the admin view, toolbar, and tree rendering—consider extracting it into a single helper to keep behaviors consistent.
- The dynamic EmptyForm creation in get_form is clever but a bit opaque—extract it into a well-named private method or inner class to make the fallback intent clearer.
## Individual Comments
### Comment 1
<location> `cms/admin/pageadmin.py:307-315` </location>
<code_context>
show_permissions = False
- context = {"title": _("Advanced Settings"), "show_permissions": show_permissions}
+
+ show_advanced_settings = self.has_change_advanced_settings_permission(request, obj=page)
+
+ if not show_permissions and not show_advanced_settings:
+ raise PermissionDenied("No permission for editing advanced settings")
+
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Permission check order may leak information about permissions.
Check if the page exists before performing permission checks to avoid revealing permission information for non-existent pages.
```suggestion
if not page:
from django.http import Http404
raise Http404("Page does not exist")
if get_cms_setting("PERMISSION"):
show_permissions = self.has_change_permissions_permission(request, obj=page)
else:
show_permissions = False
show_advanced_settings = self.has_change_advanced_settings_permission(request, obj=page)
if not show_permissions and not show_advanced_settings:
raise PermissionDenied("No permission for editing advanced settings")
```
</issue_to_address>
### Comment 2
<location> `cms/admin/pageadmin.py:1389-1390` </location>
<code_context>
"has_add_page_permission": user_can_add(user, target=page),
"has_change_permission": user_can_change(request.user, page, site),
- "has_change_advanced_settings_permission": user_can_change_advanced(request.user, page, site),
+ "has_change_advanced_settings_permission": (
+ user_can_change_advanced(request.user, page, site) or user_can_change_permissions(request.user, page, site)
+ ),
"has_move_page_permission": has_move_page_permission,
</code_context>
<issue_to_address>
**🚨 question (security):** Combining advanced settings and permissions checks may blur permission boundaries.
Please verify that expanding access via either advanced settings or permissions checks does not unintentionally allow users elevated privileges beyond your intended model.
</issue_to_address>
### Comment 3
<location> `cms/static/cms/js/modules/cms.modal.js:909-910` </location>
<code_context>
// If the response contains the close-frame (and potentially the data bridge),
// the form was saved successfully
- that.saved = that.saved || body.hasClass('cms-close-frame');
+ that.saved = that.saved || body.hasClass('cms-close-frame')
+ || body.find('script#django-admin-popup-response-constants').length > 0;
// tabindex is required for keyboard navigation
</code_context>
<issue_to_address>
**suggestion:** Checking for script tag may be fragile if admin markup changes.
Using a specific script tag as a success indicator may break if Django admin markup changes. Please evaluate if a more stable method exists.
Suggested implementation:
```javascript
that.saved = that.saved || body.hasClass('cms-close-frame')
|| body.find('.messagelist :not(.error)').length > 0;
```
```javascript
var saveSuccess = Boolean(contents.find('.messagelist :not(.error)').length) ||
body.find('.messagelist :not(.error)').length > 0;
```
</issue_to_address>
### Comment 4
<location> `cms/static/cms/js/modules/cms.modal.js:944-945` </location>
<code_context>
}
- var saveSuccess = Boolean(contents.find('.messagelist :not(".error")').length);
+ var saveSuccess = Boolean(contents.find('.messagelist :not(".error")').length) ||
+ body.find('script#django-admin-popup-response-constants').length > 0;
// in case message didn't appear, assume that admin page is actually a success
</code_context>
<issue_to_address>
**suggestion:** Duplicated logic for save success could be refactored.
Refactor the save success logic into a shared helper to avoid duplication and improve maintainability.
Suggested implementation:
```javascript
function isSaveSuccess(contents, body) {
return Boolean(contents.find('.messagelist :not(".error")').length) ||
body.find('script#django-admin-popup-response-constants').length > 0;
}
that.saved = that.saved || body.hasClass('cms-close-frame')
|| isSaveSuccess(contents, body);
```
```javascript
var saveSuccess = isSaveSuccess(contents, body);
```
</issue_to_address>
### Comment 5
<location> `cms/tests/test_admin.py:873-882` </location>
<code_context>
+ @override_settings(CMS_PERMISSION=True)
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for users with only permission to change permissions (not advanced settings).
Please add a test for users with only change permissions to verify they can access the advanced settings endpoint as intended.
</issue_to_address>
### Comment 6
<location> `cms/static/cms/js/modules/cms.modal.js:944-945` </location>
<code_context>
</code_context>
<issue_to_address>
**issue (code-quality):** Use `const` or `let` instead of `var`. ([`avoid-using-var`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/avoid-using-var))
<details><summary>Explanation</summary>`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code).
`let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than
function-scoped.
From the [Airbnb JavaScript Style Guide](https://airbnb.io/javascript/#references--prefer-const)
</details>
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@jrief This approach is a bit different from your local solution. It also allows users with no permission for advanced settings to change page permissions from both the page tree and the toolbar in the frontend editor. The advanced settings dialog only contains the settings if the user has the permissions for it, otherwise it only contains the permission settings. On the plus side:
On the minus side:
What do you think? |
Description
Allow opening advances settings to change permissions, if a user has permissions to change permissions, even if advanced settings permissions are not given.
In this case the advanced settings form does not show any of the advanced settings but only the page permissions.
Closes a gap where users with the right to change page permissions but no rights to the advanced settings could not edit permissions.
Bug Fixes:
Enhancements:
Tests:
fixes #7825
needs test for ui
Related resources
Checklist
mainSummary by Sourcery
Add a unified page permissions menu by extending the advanced settings modal, enforce consistent permission checks across admin views, the toolbar, and page tree, and enhance modal popup handling to recognize successful saves and include the required popup flag.
New Features:
Bug Fixes:
Enhancements:
Tests: