Skip to content

Commit 7704c83

Browse files
committed
Split up priviledges between site_admin and staff. Only site_admin can delete users in frontend, or change team memberships.
1 parent fc4d12b commit 7704c83

File tree

6 files changed

+87
-5
lines changed

6 files changed

+87
-5
lines changed

exact/exact/administration/templates/administration/user_management.html

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ <h4 class="mb-1" id="detail-title"></h4>
6969
<div class="nav nav-pills mb-3" id="detail-tabs" role="tablist" style="gap:.5rem;">
7070
<a class="nav-link active" data-toggle="pill" href="#tab-personal" role="tab">Personal</a>
7171
<a class="nav-link" data-toggle="pill" href="#tab-access" role="tab">Password / Access</a>
72+
{% if user.prefs.is_site_admin %}
7273
<a class="nav-link" data-toggle="pill" href="#tab-teams" role="tab">Teams</a>
74+
{% endif %}
75+
<a class="nav-link" data-toggle="pill" href="#tab-gui" role="tab">GUI</a>
7376
</div>
7477

7578
<div class="tab-content">
@@ -148,13 +151,13 @@ <h5 class="mb-1">Edit user</h5>
148151
<button class="btn btn-outline-danger" id="btn-toggle-active" type="button">Toggle active</button>
149152
<button class="btn btn-outline-warning" id="btn-toggle-staff" type="button">Toggle staff</button>
150153

151-
{% if user.prefs.site_admin %}
154+
{% if user.prefs.is_site_admin %}
152155
<button class="btn btn-danger" id="btn-delete-user" type="button">Delete user…</button>
153156
{% endif %}
154157
</div>
155158
</div>
156159

157-
{% if user.prefs.site_admin %}
160+
{% if user.prefs.is_site_admin %}
158161
<small class="text-muted d-block mt-2">
159162
Deleting a user is irreversible and may remove memberships and tokens.
160163
</small>
@@ -199,6 +202,7 @@ <h5>Access tokens</h5>
199202
</div>
200203

201204
<!-- Teams -->
205+
{% if user.prefs.is_site_admin %}
202206
<div class="tab-pane fade" id="tab-teams" role="tabpanel">
203207
<div class="card mb-3">
204208
<div class="card-body">
@@ -221,7 +225,16 @@ <h5 class="mb-3">Manage team memberships</h5>
221225
</div>
222226

223227
</div>
228+
{% endif %}
229+
<div class="tab-pane fade" id="tab-gui" role="tabpanel">
230+
<div class="card mb-3">
231+
<div class="card-body">
232+
<h5 class="mb-3">GUI Mode:
233+
<select type=select id="f-ui-frontend"><option value=1>Default (bright)</option><option value=2>Lightroom (dark)</option></select></h5>
234+
</div>
235+
</div>
224236

237+
</div>
225238
</div>
226239
</div>
227240

@@ -294,6 +307,7 @@ <h5 class="modal-title" id="deleteUserModalLabel">Delete user</h5>
294307
.replace('/1/teams/1/remove/', '/' + userId + '/teams/' + teamId + '/remove/');
295308
if (name === "teamToggleAdmin") return "{% url 'administration:user_team_toggle_admin_api' user_id=1 team_id=1 %}"
296309
.replace('/1/teams/1/toggle-admin/', '/' + userId + '/teams/' + teamId + '/toggle-admin/');
310+
297311
return null;
298312
}
299313

@@ -343,10 +357,11 @@ <h5 class="modal-title" id="deleteUserModalLabel">Delete user</h5>
343357
results.forEach(function(u) {
344358
var badge = u.is_active ? '' : ' <span class="badge badge-secondary">inactive</span>';
345359
var staffBadge = u.is_staff ? ' <span class="badge badge-info">staff</span>' : '';
360+
var siteadminBadge = u.is_site_admin ? ' <span class="badge badge-success">site admin</span>' : '';
346361
var item =
347362
'<button type="button" class="list-group-item list-group-item-action user-row" data-id="' + u.id + '">' +
348363
'<div class="d-flex justify-content-between">' +
349-
'<div><strong>' + (u.display_name || u.username) + '</strong>' + badge + staffBadge + '</div>' +
364+
'<div><strong>' + (u.display_name || u.username) + '</strong>' + badge + staffBadge + siteadminBadge + '</div>' +
350365
'</div>' +
351366
'<div class="text-muted" style="font-size: .9em;">' + (u.email || '') + '</div>' +
352367
'</button>';
@@ -435,6 +450,7 @@ <h5 class="modal-title" id="deleteUserModalLabel">Delete user</h5>
435450

436451
$('#f-date-joined').text(fmt(u.date_joined));
437452
$('#f-last-login').text(fmt(u.last_login));
453+
$('#f-ui-frontend').val(u.prefs.frontend);
438454

439455
$('#random-password-box').hide();
440456

@@ -633,6 +649,35 @@ <h5 class="modal-title" id="deleteUserModalLabel">Delete user</h5>
633649
});
634650
});
635651

652+
$('#f-ui-frontend').on('change', function() {
653+
if (!currentUserId) return;
654+
655+
var payload = {
656+
frontend: $('#f-ui-frontend').val().trim(),
657+
};
658+
659+
$.ajax({
660+
url: urlFor("update", currentUserId),
661+
method: "POST",
662+
contentType: "application/json",
663+
data: JSON.stringify(payload),
664+
headers: { "X-CSRFToken": csrftoken },
665+
success: function() {
666+
$.notify("User updated.", "success");
667+
loadUserDetail(currentUserId);
668+
loadUserList();
669+
},
670+
error: function(xhr) {
671+
var msg = "Update failed.";
672+
try {
673+
var data = xhr.responseJSON;
674+
if (data && data.error) msg = (typeof data.error === "string") ? data.error : JSON.stringify(data.error);
675+
} catch(e) {}
676+
$.notify(msg, "error");
677+
}
678+
});
679+
});
680+
636681
$(document).on('click', '.team-toggle-admin', function() {
637682
if (!currentUserId) return;
638683
var teamId = $(this).data('team-id');
@@ -679,7 +724,9 @@ <h5 class="modal-title" id="deleteUserModalLabel">Delete user</h5>
679724
// Initial load
680725
$(document).ready(function() {
681726
loadUserList();
727+
{% if user.prefs.is_site_admin %}
682728
loadTeamsCatalog();
729+
{% endif %}
683730
});
684731
</script>
685732

exact/exact/administration/views.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from django.shortcuts import render, get_object_or_404
3636
from exact.users.models import Team, TeamMembership
3737
from .permissions import site_admin_required
38+
from exact.users.models import UserPreferences
3839
from django.core.exceptions import ValidationError
3940
import json
4041
import secrets
@@ -84,7 +85,7 @@ def product(request, product_id):
8485
})
8586

8687

87-
@site_admin_required
88+
@staff_member_required
8889
@require_POST
8990
def user_update_api(request, user_id: int):
9091
u = get_object_or_404(User, pk=user_id)
@@ -103,6 +104,12 @@ def user_update_api(request, user_id: int):
103104
if field in payload:
104105
setattr(u, field, (payload[field] or "").strip())
105106

107+
allowed_prefs = ["frontend"]
108+
for field in allowed_prefs:
109+
if field in payload:
110+
setattr(u.prefs, field, (payload[field] or 1))
111+
u.prefs.save()
112+
106113
# Basic validation – extend as needed
107114
if "email" in payload and u.email and "@" not in u.email:
108115
return JsonResponse({"error": "Invalid email address."}, status=400)
@@ -257,6 +264,11 @@ def user_list_api(request):
257264

258265
data = []
259266
for u in qs:
267+
268+
if getattr(u, "prefs", None) is None:
269+
# Create preferences object for user, if nonexistant
270+
u.prefs, _ = UserPreferences.objects.get_or_create(user=u)
271+
260272
display_name = (f"{u.first_name} {u.last_name}".strip() or u.username)
261273
data.append({
262274
"id": u.id,
@@ -265,6 +277,7 @@ def user_list_api(request):
265277
"email": u.email,
266278
"is_active": u.is_active,
267279
"is_staff": u.is_staff,
280+
'is_site_admin' : u.prefs.is_site_admin,
268281
})
269282

270283
return JsonResponse({"results": data})
@@ -328,6 +341,7 @@ def user_detail_api(request, user_id: int):
328341
"is_active": u.is_active,
329342
"is_staff": u.is_staff,
330343
"is_superuser": getattr(u, "is_superuser", False),
344+
"prefs": {'frontend': u.prefs.frontend if getattr(u, "prefs", 0) else 0,},
331345
"date_joined": u.date_joined.isoformat() if getattr(u, "date_joined", None) else None,
332346
"last_login": u.last_login.isoformat() if getattr(u, "last_login", None) else None,
333347
},
@@ -350,6 +364,9 @@ def user_toggle_active_api(request, user_id: int):
350364
if u.id == request.user.id:
351365
return JsonResponse({"error": "You cannot deactivate your own account."}, status=400)
352366

367+
if u.prefs.is_site_admin:
368+
return JsonResponse({"error": "You cannot deactivate site admin accounts."}, status=400)
369+
353370
u.is_active = not u.is_active
354371
u.save(update_fields=["is_active"])
355372
return JsonResponse({"ok": True, "is_active": u.is_active})

exact/exact/base/templates/base/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
<a class="dropdown-item" href="{% url 'administration:plugins' %}">Plugins</a>
9999
{% endif %}
100100
<a class="dropdown-item" href="{% url 'administration:storage' %}">Storage</a>
101-
{% if request.user.is_superuser and request.user.prefs.is_site_admin %}
101+
{% if request.user.is_staff or request.user.prefs.is_site_admin %}
102102
<a class="dropdown-item" href="{% url 'administration:user_management' %}">User Management</a>
103103
{% endif %}
104104
</div>

exact/exact/users/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
default_app_config = "exact.users.apps.UsersConfig"

exact/exact/users/apps.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
class UsersConfig(AppConfig):
55
default_auto_field = 'django.db.models.BigAutoField'
66
name = 'exact.users'
7+
8+
def ready(self):
9+
from . import signals
10+

exact/exact/users/signals.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.contrib.auth import get_user_model
2+
from django.db.models.signals import post_save
3+
from django.dispatch import receiver
4+
5+
from .models import UserPreferences # your prefs model
6+
7+
User = get_user_model()
8+
9+
@receiver(post_save, sender=User)
10+
def ensure_user_preferences(sender, instance, created, **kwargs):
11+
if created:
12+
UserPreferences.objects.get_or_create(user=instance)
13+

0 commit comments

Comments
 (0)