Skip to content

Commit 2116989

Browse files
committed
[feature] Allowed ready only access of shared objects to non-superusers #238
Closes #238
1 parent 069a9c9 commit 2116989

File tree

3 files changed

+69
-3
lines changed

3 files changed

+69
-3
lines changed

openwisp_users/multitenancy.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ def get_queryset(self, request):
4949
if user.is_superuser:
5050
return qs
5151
if hasattr(self.model, 'organization'):
52-
return qs.filter(organization__in=user.organizations_managed)
52+
return qs.filter(
53+
Q(organization__in=user.organizations_managed) | Q(organization=None)
54+
)
5355
if self.model.__name__ == 'Organization':
5456
return qs.filter(pk__in=user.organizations_managed)
5557
elif not self.multitenant_parent:
@@ -58,6 +60,33 @@ def get_queryset(self, request):
5860
qsarg = '{0}__organization__in'.format(self.multitenant_parent)
5961
return qs.filter(**{qsarg: user.organizations_managed})
6062

63+
def _has_org_permission(self, request, obj, perm_func):
64+
"""
65+
Helper method to check object-level permissions for users
66+
associated with specific organizations.
67+
"""
68+
perm = perm_func(request, obj)
69+
if not request.user.is_superuser and obj and hasattr(obj, 'organization_id'):
70+
perm = perm and (
71+
obj.organization_id
72+
and str(obj.organization_id) in request.user.organizations_managed
73+
)
74+
return perm
75+
76+
def has_change_permission(self, request, obj=None):
77+
"""
78+
Returns True if the user has permission to change the object.
79+
Non-superusers cannot change shared objects.
80+
"""
81+
return self._has_org_permission(request, obj, super().has_change_permission)
82+
83+
def has_delete_permission(self, request, obj=None):
84+
"""
85+
Returns True if the user has permission to delete the object.
86+
Non-superusers cannot change shared objects.
87+
"""
88+
return self._has_org_permission(request, obj, super().has_delete_permission)
89+
6190
def _edit_form(self, request, form):
6291
"""
6392
Modifies the form querysets as follows;
@@ -149,7 +178,9 @@ class MultitenantOrgFilter(AutocompleteFilter):
149178
org_lookup = 'id__in'
150179
title = _('organization')
151180
widget_attrs = AutocompleteFilter.widget_attrs.copy()
152-
widget_attrs.update({'data-empty-label': SHARED_SYSTEMWIDE_LABEL})
181+
widget_attrs.update(
182+
{'data-empty-label': SHARED_SYSTEMWIDE_LABEL, 'data-is-filter': 'true'}
183+
)
153184

154185

155186
class MultitenantRelatedOrgFilter(MultitenantOrgFilter):
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use strict";
2+
{
3+
const $ = django.jQuery;
4+
5+
$.fn.djangoAdminSelect2 = function () {
6+
$.each(this, function (i, element) {
7+
$(element).select2({
8+
ajax: {
9+
data: (params) => {
10+
console.log("params", element.dataset);
11+
return {
12+
term: params.term,
13+
page: params.page,
14+
app_label: element.dataset.appLabel,
15+
model_name: element.dataset.modelName,
16+
field_name: element.dataset.fieldName,
17+
is_filter: element.dataset.isFilter,
18+
};
19+
},
20+
},
21+
});
22+
});
23+
return this;
24+
};
25+
26+
$(function () {
27+
// Initialize all autocomplete widgets except the one in the template
28+
// form used when a new formset is added.
29+
$(".admin-autocomplete").not("[name*=__prefix__]").djangoAdminSelect2();
30+
});
31+
32+
document.addEventListener("formset:added", (event) => {
33+
$(event.target).find(".admin-autocomplete").djangoAdminSelect2();
34+
});
35+
}

openwisp_users/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ def get_empty_label(self):
4343

4444
def get_allow_null(self):
4545
if self.object_list.model == Organization:
46-
return self.request.user.is_superuser
46+
return self.request.user.is_superuser or self.request.GET.get('is_filter')
4747
return super().get_allow_null()

0 commit comments

Comments
 (0)