update
This commit is contained in:
commit
a0087d1188
@ -1528,44 +1528,139 @@ class GroupForm(forms.ModelForm):
|
||||
fields = ["name"]
|
||||
|
||||
|
||||
# class PermissionForm(forms.ModelForm):
|
||||
# """
|
||||
# Represents a form for managing permissions using a multiple-choice field.
|
||||
|
||||
# This class is a Django ModelForm that is used to handle permission
|
||||
# assignments. It provides a multiple selection widget pre-populated with
|
||||
# permissions based on specific app labels. The form offers a way to submit
|
||||
# and validate permission data for further processing.
|
||||
|
||||
# :ivar name: A multiple-choice field that allows users to select permissions
|
||||
# related to specific app labels (`inventory` and `django_ledger`).
|
||||
# :type name: ModelMultipleChoiceField
|
||||
# """
|
||||
|
||||
# name = forms.ModelMultipleChoiceField(
|
||||
# queryset=cache.get(
|
||||
# "permissions_queryset",
|
||||
# Permission.objects.filter(
|
||||
# content_type__app_label__in=["inventory", "django_ledger"]
|
||||
# ),
|
||||
# ),
|
||||
# widget=forms.CheckboxSelectMultiple(),
|
||||
# required=True,
|
||||
# )
|
||||
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
# cache.set(
|
||||
# "permissions_queryset",
|
||||
# Permission.objects.filter(
|
||||
# content_type__app_label__in=["inventory", "django_ledger"]
|
||||
# ),
|
||||
# 60 * 60,
|
||||
# )
|
||||
|
||||
# class Meta:
|
||||
# model = Permission
|
||||
# fields = ["name"]
|
||||
class PermissionForm(forms.ModelForm):
|
||||
"""
|
||||
Represents a form for managing permissions using a multiple-choice field.
|
||||
|
||||
This class is a Django ModelForm that is used to handle permission
|
||||
assignments. It provides a multiple selection widget pre-populated with
|
||||
permissions based on specific app labels. The form offers a way to submit
|
||||
and validate permission data for further processing.
|
||||
|
||||
:ivar name: A multiple-choice field that allows users to select permissions
|
||||
related to specific app labels (`inventory` and `django_ledger`).
|
||||
:type name: ModelMultipleChoiceField
|
||||
Form for managing permissions with grouped checkboxes by app and model.
|
||||
"""
|
||||
|
||||
name = forms.ModelMultipleChoiceField(
|
||||
queryset=cache.get(
|
||||
"permissions_queryset",
|
||||
Permission.objects.filter(
|
||||
content_type__app_label__in=["inventory", "django_ledger"]
|
||||
),
|
||||
),
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
required=True,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
cache.set(
|
||||
EXCLUDED_MODELS = [
|
||||
"inventory.car",
|
||||
"inventory.carfinance",
|
||||
"inventory.carlocation",
|
||||
"inventory.customcard",
|
||||
"inventory.cartransfer",
|
||||
"inventory.carcolors",
|
||||
"inventory.carequipment",
|
||||
"inventory.interiorcolors",
|
||||
"inventory.exteriorcolors",
|
||||
"inventory.carreservation",
|
||||
"inventory.lead",
|
||||
"inventory.customgroup",
|
||||
"inventory.saleorder",
|
||||
"inventory.payment",
|
||||
"inventory.staff",
|
||||
"inventory.schedule",
|
||||
"inventory.activity",
|
||||
"inventory.opportunity",
|
||||
"django_ledger.estimatemodel",
|
||||
"django_ledger.invoicemodel",
|
||||
"django_ledger.accountmodel",
|
||||
"django_ledger.chartofaccountmodel",
|
||||
"django_ledger.customermodel",
|
||||
"django_ledger.billmodel"
|
||||
"inventory.car",
|
||||
"inventory.carequipment",
|
||||
"inventory.interiorcolors",
|
||||
"inventory.exteriorcolors",
|
||||
"inventory.carcolors",
|
||||
"inventory.carlocation",
|
||||
"inventory.customcard",
|
||||
"inventory.carreservation"
|
||||
"django_ledger.estimatemodel",
|
||||
"django_ledger.invoicemodel",
|
||||
"django_ledger.customermodel",
|
||||
"inventory.saleorder",
|
||||
"inventory.payment",
|
||||
"inventory.staff",
|
||||
"inventory.schedule",
|
||||
"inventory.activity",
|
||||
"inventory.opportunity",
|
||||
"inventory.customer",
|
||||
"inventory.organization",
|
||||
"inventory.lead",
|
||||
"inventory.salequotation",
|
||||
"inventory.salequotationcar"
|
||||
"inventory.carfinance",
|
||||
"django_ledger.bankaccountmodel",
|
||||
"django_ledger.accountmodel",
|
||||
"django_ledger.chartofaccountmodel",
|
||||
"django_ledger.customcard",
|
||||
"django_ledger.billmodel",
|
||||
"django_ledger.itemmodel",
|
||||
"django_ledger.invoicemodel",
|
||||
"django_ledger.vendormodel",
|
||||
"django_ledger.journalentrymodel"
|
||||
]
|
||||
|
||||
permissions = cache.get(
|
||||
"permissions_queryset",
|
||||
Permission.objects.filter(
|
||||
content_type__app_label__in=["inventory", "django_ledger"]
|
||||
),
|
||||
60 * 60,
|
||||
content_type__app_label__in=[m.split('.')[0] for m in EXCLUDED_MODELS],
|
||||
content_type__model__in=[m.split('.')[1] for m in EXCLUDED_MODELS]
|
||||
).select_related('content_type')
|
||||
)
|
||||
|
||||
# Group permissions by app_label and model
|
||||
self.grouped_permissions = {}
|
||||
for perm in permissions:
|
||||
app_label = perm.content_type.app_label
|
||||
model = perm.content_type.model
|
||||
if app_label not in self.grouped_permissions:
|
||||
self.grouped_permissions[app_label] = {}
|
||||
if model not in self.grouped_permissions[app_label]:
|
||||
self.grouped_permissions[app_label][model] = []
|
||||
self.grouped_permissions[app_label][model].append(perm)
|
||||
|
||||
# Create a multiple choice field (hidden, will use custom rendering)
|
||||
self.fields['permissions'] = forms.ModelMultipleChoiceField(
|
||||
queryset=permissions,
|
||||
widget=forms.MultipleHiddenInput(),
|
||||
required=False,
|
||||
initial=self.instance.permissions.all() if self.instance.pk else []
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Permission
|
||||
fields = ["name"]
|
||||
fields = []
|
||||
|
||||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
|
||||
@ -644,4 +644,11 @@ def inventory_table(context, queryset):
|
||||
"inventory_list": queryset,
|
||||
}
|
||||
ctx.update(queryset.aggregate(inventory_total_value=Sum("total_value")))
|
||||
return ctx
|
||||
return ctx
|
||||
|
||||
|
||||
@register.filter
|
||||
def count_checked(permissions):
|
||||
"""Count how many permissions are marked as checked"""
|
||||
print(permissions)
|
||||
return sum(1 for perm in permissions if getattr(perm, 'is_checked', False))
|
||||
@ -2756,42 +2756,78 @@ def GroupDeleteview(request, dealer_slug,pk):
|
||||
|
||||
|
||||
@login_required
|
||||
def GroupPermissionView(request, dealer_slug,pk):
|
||||
"""
|
||||
Handles the view for adding or modifying permissions of a specific group. This view
|
||||
fetches the group based on the primary key passed as a parameter, and either displays
|
||||
a form for editing permissions or processes the submitted permissions.
|
||||
def GroupPermissionView(request, dealer_slug, pk):
|
||||
# Verify dealer and group exist
|
||||
get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||
customgroup = get_object_or_404(models.CustomGroup, pk=pk)
|
||||
|
||||
If the request method is POST, the permissions of the group are cleared and updated
|
||||
based on the submitted data. A success message is displayed upon completion, and
|
||||
the user is redirected to the group's detail page.
|
||||
|
||||
In case of a GET request, the view renders the form pre-filled with the group's
|
||||
current permissions.
|
||||
|
||||
:param request: The HTTP request object.
|
||||
:type request: HttpRequest
|
||||
:param pk: The primary key of the group whose permissions are being modified.
|
||||
:type pk: int
|
||||
:return: The HTTP response depending on the request type. For GET requests, renders
|
||||
the permission form for the specified group. For POST requests, clears and updates
|
||||
the group's permissions and redirects to the group's detail page.
|
||||
:rtype: HttpResponse
|
||||
"""
|
||||
get_object_or_404(models.Dealer,slug=dealer_slug)
|
||||
group = get_object_or_404(models.CustomGroup, pk=pk)
|
||||
if request.method == "POST":
|
||||
form = forms.PermissionForm(request.POST)
|
||||
group.clear_permissions()
|
||||
permissions = request.POST.getlist("name")
|
||||
for i in permissions:
|
||||
group.add_permission(Permission.objects.get(id=int(i)))
|
||||
messages.success(request, _("Permission added successfully"))
|
||||
return redirect("group_detail", dealer_slug=dealer_slug,pk=group.pk)
|
||||
form = forms.PermissionForm(initial={"name": group.permissions})
|
||||
return render(
|
||||
request, "groups/group_permission_form.html", {"group": group, "form": form}
|
||||
)
|
||||
form = forms.PermissionForm(request.POST, instance=customgroup)
|
||||
if form.is_valid():
|
||||
# Clear existing permissions
|
||||
customgroup.clear_permissions()
|
||||
|
||||
# Add new permissions from form
|
||||
permissions = form.cleaned_data.get('permissions', [])
|
||||
for permission in permissions:
|
||||
customgroup.add_permission(permission)
|
||||
|
||||
messages.success(request, _("Permissions updated successfully"))
|
||||
return redirect("group_detail", dealer_slug=dealer_slug, pk=customgroup.pk)
|
||||
else:
|
||||
# Initial form with current permissions
|
||||
form = forms.PermissionForm(instance=customgroup)
|
||||
|
||||
group_permission_ids = set(customgroup.permissions.values_list('id', flat=True))
|
||||
|
||||
# Mark permissions as checked in the form data
|
||||
for app_label, model in form.grouped_permissions.items():
|
||||
for mo, perms in model.items():
|
||||
for perm in perms:
|
||||
perm.is_checked = perm.id in group_permission_ids
|
||||
|
||||
return render(request,"groups/group_permission_form.html", {
|
||||
"group": customgroup,
|
||||
"form": form,
|
||||
"group_permission_apps": set(customgroup.group.permissions.values_list('content_type__app_label', flat=True)),
|
||||
"group_permission_models": set(customgroup.group.permissions.values_list('content_type__model', flat=True))
|
||||
})
|
||||
# def GroupPermissionView(request, dealer_slug,pk):
|
||||
# """
|
||||
# Handles the view for adding or modifying permissions of a specific group. This view
|
||||
# fetches the group based on the primary key passed as a parameter, and either displays
|
||||
# a form for editing permissions or processes the submitted permissions.
|
||||
|
||||
# If the request method is POST, the permissions of the group are cleared and updated
|
||||
# based on the submitted data. A success message is displayed upon completion, and
|
||||
# the user is redirected to the group's detail page.
|
||||
|
||||
# In case of a GET request, the view renders the form pre-filled with the group's
|
||||
# current permissions.
|
||||
|
||||
# :param request: The HTTP request object.
|
||||
# :type request: HttpRequest
|
||||
# :param pk: The primary key of the group whose permissions are being modified.
|
||||
# :type pk: int
|
||||
# :return: The HTTP response depending on the request type. For GET requests, renders
|
||||
# the permission form for the specified group. For POST requests, clears and updates
|
||||
# the group's permissions and redirects to the group's detail page.
|
||||
# :rtype: HttpResponse
|
||||
# """
|
||||
# get_object_or_404(models.Dealer,slug=dealer_slug)
|
||||
# group = get_object_or_404(models.CustomGroup, pk=pk)
|
||||
# if request.method == "POST":
|
||||
# form = forms.PermissionForm(request.POST)
|
||||
# group.clear_permissions()
|
||||
# permissions = request.POST.getlist("name")
|
||||
# for i in permissions:
|
||||
# group.add_permission(Permission.objects.get(id=int(i)))
|
||||
# messages.success(request, _("Permission added successfully"))
|
||||
# return redirect("group_detail", dealer_slug=dealer_slug,pk=group.pk)
|
||||
# form = forms.PermissionForm(initial={"name": group.permissions})
|
||||
# return render(
|
||||
# request, "groups/group_permission_form.html", {"group": group, "form": form}
|
||||
# )
|
||||
|
||||
|
||||
# Users
|
||||
|
||||
45
templates/groups/group_permission_form-copy.html
Normal file
45
templates/groups/group_permission_form-copy.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}{% trans "Permission" %}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="d-sm-flex justify-content-between">
|
||||
|
||||
<h3 class="mb-3">
|
||||
{% if group.created %}
|
||||
{{ _("Edit Permission") }}
|
||||
{% else %}
|
||||
{{ _("Add Permission") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex mb-3">
|
||||
<a href="{% url 'group_detail' request.dealer.slug group.pk %}" class="btn btn-phoenix-primary me-2 "><i class="fa-solid fa-ban"></i> {% trans "Cancel"|capfirst %}</a>
|
||||
<button class="btn btn-phoenix-primary" type="submit">
|
||||
<i class="fa-solid fa-floppy-disk"></i>
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,46 +1,157 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}{% trans "Permission" %}{% endblock title %}
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block title %}{% trans "Permission Management" %}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="d-sm-flex justify-content-between align-items-center">
|
||||
<h3 class="mb-0">
|
||||
{% if group %}
|
||||
{{ _("Edit Permissions for") }}: <strong>{{ group.name }}</strong>
|
||||
{% else %}
|
||||
{{ _("Add Permissions") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<div class="d-sm-flex justify-content-between">
|
||||
|
||||
<h3 class="mb-3">
|
||||
{% if group.created %}
|
||||
{{ _("Edit Permission") }}
|
||||
{% else %}
|
||||
{{ _("Add Permission") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
</div>
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex mb-3">
|
||||
<button class="btn btn-phoenix-primary me-2" type="submit">
|
||||
<i class="fa-solid fa-floppy-disk"></i>
|
||||
{{ _("Save") }}
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Permissions Grid -->
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4" id="permissionsGrid">
|
||||
{% for app_label, models in form.grouped_permissions.items %}
|
||||
<div class="col">
|
||||
<div class="card h-100 border-{% if app_label in group_permission_apps %}primary{% else %}light{% endif %}">
|
||||
<div class="card-header bg-{% if app_label in group_permission_apps %}primary text-white{% else %}light{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-{% if app_label in group_permission_apps %}check-circle{% else %}cube{% endif %} me-2"></i>
|
||||
{{ app_label|capfirst }}
|
||||
</h5>
|
||||
<span class="badge bg-{% if app_label in group_permission_apps %}light text-primary{% else %}secondary{% endif %}">
|
||||
{{ models|length }} {% trans "models" %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="accordion">
|
||||
{% for model, perms in models.items %}
|
||||
<div class="accordion-item border-0">
|
||||
<h6 class="accordion-header" id="heading-{{ app_label|slugify }}-{{ model|slugify }}">
|
||||
<button class="accordion-button collapsed bg-white shadow-none py-2"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-{{ app_label|slugify }}-{{ model|slugify }}"
|
||||
aria-expanded="false">
|
||||
<i class="fas fa-table me-2"></i>{{ model|capfirst }}
|
||||
<span class="badge bg-{% if model in group_permission_models %}primary{% else %}secondary{% endif %} rounded-pill ms-2">
|
||||
{{ perms|length }} / {{ perms|count_checked }}
|
||||
</span>
|
||||
</button>
|
||||
</h6>
|
||||
<div id="collapse-{{ app_label|slugify }}-{{ model|slugify }}"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="heading-{{ app_label|slugify }}-{{ model|slugify }}">
|
||||
<div class="accordion-body pt-0">
|
||||
<div class="list-group list-group-flush">
|
||||
{% for perm in perms %}
|
||||
<label class="list-group-item d-flex gap-2 {% if perm.is_checked %}bg-light-primary{% endif %}">
|
||||
<input class="form-check-input flex-shrink-0 mt-0"
|
||||
type="checkbox"
|
||||
name="permissions"
|
||||
value="{{ perm.id }}"
|
||||
id="perm_{{ perm.id }}"
|
||||
{% if perm.is_checked %}checked{% endif %}>
|
||||
<span>
|
||||
<span class="d-block fw-bold">{{ perm.name|capfirst }}</span>
|
||||
<small class="d-block text-muted">{{ perm.codename }}</small>
|
||||
{% if perm.is_checked %}
|
||||
<span class="badge bg-success mt-1">
|
||||
<i class="fas fa-check me-1"></i>{% trans "Assigned" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="badge bg-primary rounded-pill me-2">
|
||||
{{ group.permissions.count }} {% trans "selected" %}
|
||||
</span>
|
||||
<span class="text-muted">
|
||||
{% trans "Permissions will be updated immediately" %}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'group_detail' request.dealer.slug group.pk %}"
|
||||
class="btn btn-outline-secondary me-2">
|
||||
<i class="fas fa-ban me-2"></i>{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>{% trans "Save Changes" %}
|
||||
</button>
|
||||
<a href="{% url 'group_detail' request.dealer.slug group.pk %}" class="btn btn-phoenix-secondary "><i class="fa-solid fa-ban"></i> {% trans "Cancel"|capfirst %}</a>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg-light-primary {
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
.list-group-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Search functionality
|
||||
document.getElementById('permissionSearch').addEventListener('input', function(e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
document.querySelectorAll('.accordion-body .list-group-item').forEach(item => {
|
||||
const text = item.textContent.toLowerCase();
|
||||
item.style.display = text.includes(searchTerm) ? '' : 'none';
|
||||
});
|
||||
|
||||
// Open relevant accordions
|
||||
document.querySelectorAll('.accordion-collapse').forEach(collapse => {
|
||||
const visibleItems = collapse.querySelectorAll('.list-group-item[style=""]');
|
||||
if (visibleItems.length > 0) {
|
||||
const button = document.querySelector(
|
||||
`button[data-bs-target="#${collapse.id}"]`
|
||||
);
|
||||
if (button && !button.classList.contains('collapsed')) {
|
||||
new bootstrap.Collapse(collapse, {toggle: true});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user