import json
from django import forms
from django.utils.html import escape
from django.utils.safestring import mark_safe
from apps.account.models.role import Role, PERMISSION_CATEGORIES, ALL_PERMISSION_FIELDS
from apps.account.admin.access_modal_widget import UserAccessManagementWidget
CATEGORY_ICONS = {
"Identity & Profiles": "badge",
"Chat & Communications": "forum",
"CMS & Articles": "article",
"LMS & Academics": "school",
"Meetings": "video_camera_front",
"Events": "event",
"Community Projects": "task_alt",
"Donations & Charity": "volunteer_activism",
"Support & Tickets": "confirmation_number",
"Dynamic Forms": "dynamic_form",
"Diplomacy & Institutional": "public",
"Administration & Security": "admin_panel_settings",
}
class GroupPillSelectMultipleWidget(forms.CheckboxSelectMultiple):
"""
Renders Django auth Groups as modern, interactive clickable pill cards.
Replaces the cumbersome dual-listbox with instant one-click toggle chips.
"""
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = []
elif isinstance(value, str):
value = [value]
else:
value = [str(v) for v in value]
attrs = attrs or {}
output = [
'
',
'
'
]
has_choices = False
for option_value, option_label in self.choices:
if not option_value:
continue
has_choices = True
is_checked = str(option_value) in value
checked_attr = 'checked' if is_checked else ''
card_class = (
'group-pill-card cursor-pointer select-none inline-flex items-center gap-2 px-3.5 py-2 rounded-lg border text-sm transition-all duration-150 '
+ ('bg-indigo-50 border-indigo-400 text-indigo-700 shadow-xs dark:bg-indigo-950/60 dark:border-indigo-600 dark:text-indigo-300 font-semibold'
if is_checked else 'bg-white border-slate-200 text-slate-700 hover:bg-slate-50 dark:bg-slate-900 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800')
)
icon_name = 'check_circle' if is_checked else 'add_circle'
icon_class = 'material-symbols-outlined text-base pill-icon ' + ('text-indigo-600 dark:text-indigo-400' if is_checked else 'text-slate-400')
output.append(
f''
)
if not has_choices:
output.append('No groups available in system.')
output.append('''
Click any group chip to instantly assign or revoke membership for this user.
''')
return mark_safe('\n'.join(output))
class RegionPillSelectMultipleWidget(forms.CheckboxSelectMultiple):
"""
Renders Regions as modern, interactive clickable pill cards with map/public icons.
Allows easy point-and-click assignment of managed regions to administrators.
"""
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = []
elif isinstance(value, str):
value = [value]
else:
value = [str(v.pk if hasattr(v, 'pk') else v) for v in value]
attrs = attrs or {}
output = [
'
',
'
'
]
has_choices = False
for option_value, option_label in self.choices:
if not option_value:
continue
has_choices = True
is_checked = str(option_value) in value
checked_attr = 'checked' if is_checked else ''
card_class = (
'region-pill-card cursor-pointer select-none inline-flex items-center gap-2 px-3.5 py-2 rounded-lg border text-sm transition-all duration-150 '
+ ('bg-emerald-50 border-emerald-400 text-emerald-700 shadow-xs dark:bg-emerald-950/60 dark:border-emerald-600 dark:text-emerald-300 font-semibold'
if is_checked else 'bg-white border-slate-200 text-slate-700 hover:bg-slate-50 dark:bg-slate-900 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800')
)
icon_name = 'check_circle' if is_checked else 'public'
icon_class = 'material-symbols-outlined text-base pill-icon ' + ('text-emerald-600 dark:text-emerald-400' if is_checked else 'text-slate-400')
output.append(
f''
)
if not has_choices:
output.append('No regions available in system.')
output.append('''
Click any region chip to assign or unassign geographic jurisdiction for this regional admin.
''')
return mark_safe('\n'.join(output))
class RolePermissionsMatrixWidget(forms.Widget):
"""
Renders an interactive, categorized visual permissions matrix.
Allows point-and-click permission overriding (Inherit / Allow / Deny)
with real-time inheritance tracking from the user's Assigned Role.
"""
def render(self, name, value, attrs=None, renderer=None):
if isinstance(value, str):
try:
overrides = json.loads(value) if value.strip() else {}
except Exception:
overrides = {}
elif isinstance(value, dict):
overrides = value
else:
overrides = {}
# Fetch all roles and their permissions dictionary
roles_data = {}
for r in Role.objects.all():
roles_data[str(r.id)] = r.get_permissions_dict()
categories_data = []
for cat_label, field_names in PERMISSION_CATEGORIES.items():
cat_name_str = str(cat_label)
icon = CATEGORY_ICONS.get(cat_name_str, "admin_panel_settings")
perms_list = []
for field_name in field_names:
try:
f = Role._meta.get_field(field_name)
vname = str(f.verbose_name)
except Exception:
vname = field_name.replace("_", " ").title()
perms_list.append({
"name": field_name,
"label": vname,
})
categories_data.append({
"title": cat_name_str,
"icon": icon,
"perms": perms_list,
})
roles_json_str = json.dumps(roles_data)
overrides_json_str = json.dumps(overrides)
html_parts = [
'
',
f'',
f'',
f'',
'''
tuneApplication Permissions Matrix
Permissions are automatically inherited from the user's Assigned Role. You can selectively override any permission below.
search
'''
]
for cat in categories_data:
cat_title = cat["title"]
cat_icon = cat["icon"]
cat_perms = cat["perms"]
html_parts.append(f'''