Browse Source

feat(admin): implement tiered permissions tabs and modern dual-listbox with live badges

main
sina_sajjadi 4 days ago
parent
commit
fe697b29df
  1. 6
      apps/account/admin/user.py
  2. 54
      templates/admin/base_site.html

6
apps/account/admin/user.py

@ -117,6 +117,12 @@ class UserAdmin(ModelAdmin, BaseUserAdmin):
("role", "user_type"), ("role", "user_type"),
("is_active", "is_staff", "is_superuser"), ("is_active", "is_staff", "is_superuser"),
"groups", "groups",
),
}),
(_("Granular Permissions"), {
"classes": ["tab"],
"description": _("Low-level Django database permissions assigned directly to this user. Recommended to manage permissions via Roles or Groups instead."),
"fields": (
"user_permissions", "user_permissions",
), ),
}), }),

54
templates/admin/base_site.html

@ -205,6 +205,60 @@
} }
}); });
}); });
// Modern Dual-Listbox with Real-time Counters and Badges
(function() {
function setupListboxEnhancements() {
const selectors = document.querySelectorAll('.selector');
if (!selectors.length) return;
selectors.forEach(selector => {
const availableSelect = selector.querySelector('.selector-available select');
const chosenSelect = selector.querySelector('.selector-chosen select');
const availableH2 = selector.querySelector('.selector-available h2');
const chosenH2 = selector.querySelector('.selector-chosen h2');
function updateCounters() {
if (availableSelect && availableH2) {
const count = availableSelect.options.length;
let badge = availableH2.querySelector('.counter-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'counter-badge text-xs px-2.5 py-0.5 rounded-full font-medium ml-2.5 bg-slate-200 text-slate-700 dark:bg-slate-700 dark:text-slate-300';
availableH2.appendChild(badge);
}
badge.textContent = count + ' available';
}
if (chosenSelect && chosenH2) {
const count = chosenSelect.options.length;
let badge = chosenH2.querySelector('.counter-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'counter-badge text-xs px-2.5 py-0.5 rounded-full font-semibold ml-2.5 bg-indigo-100 text-indigo-700 dark:bg-indigo-900/60 dark:text-indigo-300';
chosenH2.appendChild(badge);
}
badge.textContent = count + ' assigned';
}
}
updateCounters();
selector.addEventListener('click', function () {
setTimeout(updateCounters, 100);
});
if (availableSelect) availableSelect.addEventListener('change', updateCounters);
if (chosenSelect) chosenSelect.addEventListener('change', updateCounters);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupListboxEnhancements);
} else {
setupListboxEnhancements();
}
window.addEventListener('load', function() {
setTimeout(setupListboxEnhancements, 100);
});
})();
</script> </script>
{% if plausible_domain %} {% if plausible_domain %}
<script defer data-domain="{{ plausible_domain }}" src="https://plausible.io/js/script.js"></script> <script defer data-domain="{{ plausible_domain }}" src="https://plausible.io/js/script.js"></script>

Loading…
Cancel
Save