This commit is contained in:
gitea 2025-02-19 13:56:19 +00:00
parent 7d93bfde9f
commit fe0f0d836d
14 changed files with 536 additions and 20 deletions

View File

@ -1,3 +1,5 @@
from django.contrib.auth.models import Permission
from django.contrib.auth.models import Group
from appointment.models import Appointment, Service, StaffMember
from django.urls import reverse
from django_countries.widgets import CountrySelectWidget
@ -896,3 +898,27 @@ class OpportunityStatusForm(forms.Form):
required=True,
)
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ["name"]
class PermissionForm(forms.ModelForm):
name = forms.ModelMultipleChoiceField(
queryset=Permission.objects.filter(content_type__app_label='inventory'),
widget=forms.CheckboxSelectMultiple(),
required=True
)
class Meta:
model = Permission
fields = ["name"]
class UserGroupForm(forms.ModelForm):
name = forms.ModelMultipleChoiceField(
queryset= Group.objects.all(),
widget=forms.CheckboxSelectMultiple(),
required=True
)
class Meta:
model = Group
fields = ["name"]

View File

@ -54,7 +54,6 @@ class InjectDealerMiddleware:
try:
dealer = get_user_type(request)
request.user.dealer = dealer
except Exception as e:
pass
response = self.get_response(request)

View File

@ -938,6 +938,13 @@ class Staff(models.Model, LocalizedNameMixin):
objects = StaffUserManager()
@property
def user(self):
return self.staff_member.user
@property
def groups(self):
return self.staff_member.user.groups
class Meta:
verbose_name = _("Staff")
verbose_name_plural = _("Staff")

View File

@ -336,6 +336,14 @@ urlpatterns = [
path("user/<int:pk>/", views.UserDetailView.as_view(), name="user_detail"),
path("user/", views.UserListView.as_view(), name="user_list"),
path("user/<int:pk>/confirm/", views.UserDeleteview, name="user_delete"),
path("user/<int:pk>/groups/", views.UserGroupView, name="user_groups"),
# Group URLs
path("group/create/", views.GroupCreateView.as_view(), name="group_create"),
path("group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"),
path("group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
path("group/", views.GroupListView.as_view(), name="group_list"),
path("group/<int:pk>/confirm/", views.GroupDeleteview, name="group_delete"),
path("group/<int:pk>/permission/", views.GroupPermissionView, name="group_permission"),
# Organization URLs
path(
"organizations/", views.OrganizationListView.as_view(), name="organization_list"

View File

@ -1,4 +1,5 @@
from django.db.models import Func
from django.contrib.auth.models import Permission
from appointment.models import Appointment,AppointmentRequest,Service,StaffMember
from datetime import timedelta
from calendar import month_name
@ -1845,8 +1846,142 @@ def delete_vendor(request, pk):
# slug_field = "order_id"
# slug_url_kwarg = "order_id"
#group
class GroupListView(LoginRequiredMixin, ListView):
model = Group
context_object_name = "groups"
paginate_by = 10
template_name = "groups/group_list.html"
# def get_queryset(self):
# query = self.request.GET.get("q")
# dealer = get_user_type(self.request)
# staff = models.Staff.objects.filter(dealer=dealer).all()
# return apply_search_filters(staff, query)
class GroupDetailView(LoginRequiredMixin, DetailView):
model = Group
template_name = "groups/group_detail.html"
context_object_name = "group"
class GroupCreateView(
LoginRequiredMixin,
SuccessMessageMixin,
CreateView,
):
model = Group
form_class = forms.GroupForm
template_name = "groups/group_form.html"
success_url = reverse_lazy("group_list")
success_message = _("Group created successfully.")
# def form_valid(self, form):
# dealer = get_user_type(self.request)
# email = form.cleaned_data["email"]
# password = "Tenhal@123"
# user = User.objects.create_user(username=form.cleaned_data["name"], email=email, password=password)
# user.is_staff = True
# user.save()
# staff_member = StaffMember.objects.create(user=user)
# services = form.cleaned_data["service_offered"]
# if services:
# for service in services:
# staff_member.services_offered.add(service)
# staff = form.save(commit=False)
# staff.staff_member = staff_member
# staff.dealer = dealer
# staff.save()
# return super().form_valid(form)
class GroupUpdateView(
LoginRequiredMixin,
SuccessMessageMixin,
UpdateView,
):
model = Group
form_class = forms.GroupForm
template_name = "groups/group_form.html"
success_url = reverse_lazy("group_list")
success_message = _("Group updated successfully.")
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs["instance"] = self.get_object() # Pass the Staff instance to the form
# return kwargs
# def get_form(self, form_class = None):
# form = super().get_form(form_class)
# form.fields['email'].disabled = True
# return form
# def get_initial(self):
# initial = super().get_initial()
# initial['service_offered'] = self.object.staff_member.services_offered.all()
# initial['email'] = self.object.staff_member.user.email
# return initial
# def form_valid(self, form):
# services = form.cleaned_data["service_offered"]
# if not services:
# self.object.staff_member.services_offered.clear()
# else:
# for service in services:
# self.object.staff_member.services_offered.add(service)
# staff = form.save(commit=False)
# staff.name = form.cleaned_data["name"]
# staff.arabic_name = form.cleaned_data["arabic_name"]
# staff.phone_number = form.cleaned_data["phone_number"]
# staff.staff_type = form.cleaned_data["staff_type"]
# staff.save()
# return super().form_valid(form)
def GroupDeleteview(request, pk):
group = get_object_or_404(Group, pk=pk)
group.delete()
messages.success(request, _("Group deleted successfully."))
return redirect("group_list")
def GroupPermissionView(request, pk):
group = get_object_or_404(Group, pk=pk)
if request.method == "POST":
form = forms.PermissionForm(request.POST)
group.permissions.clear()
permissions = request.POST.getlist("name")
for i in permissions:
try:
group.permissions.add(Permission.objects.get(id=int(i)))
except Permission.DoesNotExist:
continue
messages.success(request, _("Permission added successfully."))
return redirect("group_detail", pk=group.pk)
form = forms.PermissionForm(initial={"name": group.permissions.all()})
return render(request,"groups/group_permission_form.html",{"group": group, "form": form})
# Users
def UserGroupView(request, pk):
staff = get_object_or_404(models.Staff, pk=pk)
if request.method == "POST":
form = forms.UserGroupForm(request.POST)
groups = request.POST.getlist("name")
staff.groups.clear()
for i in groups:
try:
staff.groups.add(Group.objects.get(id=int(i)))
except Group.DoesNotExist:
continue
messages.success(request, _("Group added successfully."))
return redirect("user_detail", pk=staff.pk)
form = forms.UserGroupForm(initial={"name": staff.user.groups.all()})
return render(request,"users/user_group_form.html",{"staff": staff, "form": form})
class UserListView(LoginRequiredMixin, ListView):
model = models.Staff
context_object_name = "users"
@ -1885,14 +2020,15 @@ class UserCreateView(
user = User.objects.create_user(username=form.cleaned_data["name"], email=email, password=password)
user.is_staff = True
user.save()
staff_member = StaffMember.objects.create(user=user)
services = form.cleaned_data["service_offered"]
if services:
for service in services:
staff_member.services_offered.add(service)
staff_member = StaffMember.objects.create(user=user)
for service in form.cleaned_data["service_offered"]:
staff_member.services_offered.add(service)
staff = form.save(commit=False)
staff.staff_member = staff_member
staff.dealer = dealer
group = Group.objects.filter(name__iexact=staff.staff_type).first()
if group:
staff.groups.add(group)
staff.save()
return super().form_valid(form)
@ -1919,8 +2055,8 @@ class UserUpdateView(
return form
def get_initial(self):
initial = super().get_initial()
initial['service_offered'] = self.object.staff_member.services_offered.all()
initial['email'] = self.object.staff_member.user.email
initial['service_offered'] = self.object.staff_member.services_offered.all()
return initial
def form_valid(self, form):
services = form.cleaned_data["service_offered"]
@ -1934,12 +2070,13 @@ class UserUpdateView(
staff.name = form.cleaned_data["name"]
staff.arabic_name = form.cleaned_data["arabic_name"]
staff.phone_number = form.cleaned_data["phone_number"]
staff.staff_type = form.cleaned_data["staff_type"]
staff.staff_type = form.cleaned_data["staff_type"]
staff.save()
return super().form_valid(form)
def UserDeleteview(request, pk):
user = get_object_or_404(models.Staff, pk=pk)
user.staff_member.delete()
user.delete()
messages.success(request, _("User deleted successfully."))
return redirect("user_list")

View File

@ -0,0 +1,119 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{{ _("View Group") }}{% endblock title %}
{% block content %}
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabindex="-1"
aria-labelledby="deleteModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm ">
<div class="modal-content rounded">
<div class="modal-body d-flex justify-content-center">
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1>
<span class="text-danger">
{% trans "Are you sure you want to delete this group?" %}
</span>
</div>
<div class="btn-group">
<button type="button"
class="btn btn-sm btn-secondary"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<a type="button"
class="btn btn-sm btn-danger"
href="{% url 'group_delete' group.id %}">
{% trans 'Yes' %}
</a>
</div>
</div>
</div>
</div>
<div class="row my-5">
<div class="card rounded ">
<div class="card-header ">
<p class="mb-0">{{ _("Group Details") }}</p>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>{{ _("Name") }}:</strong> {{ group.name }}</p>
</div>
<div class="col-md-6">
<table class="table table-hover table-responsive-sm fs-9 mb-0">
<thead>
<tr>
<th><h4>{% trans 'Users'|capfirst %}</h4></th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in group.user_set.all %}
<tr>
<td><p><strong>{{ _("Name") }}:</strong> {{ user.staffmember.staff }}</p></td>
<td><p><strong>{{ _("Email") }}:</strong> {{ user }}</p></td>
</tr>
{% empty %}
<tr>
<td>{% trans "No Permissions" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="table-responsive scrollbar mx-n1 px-1">
<div class="card-header ">
</div>
<h4 class="my-4">Permissions</h4>
<a class="btn btn-sm btn-primary mt-2 mb-4" href="{% url 'group_permission' group.id %}"><i class="fa-solid fa-unlock"></i> Manage Permissions</a>
<table class="table table-hover table-responsive-sm fs-9 mb-0">
<thead>
<tr>
<th>{% trans 'name'|capfirst %}</th>
<th>{% trans 'Action'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for permission in group.permissions.all %}
<tr>
<td>{{ permission.codename }}</td>
<td>{{ permission.name }}</td>
</tr>
{% empty %}
<tr>
<td>{% trans "No Permissions" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer d-flex ">
<a class="btn btn-sm btn-primary me-1" href="{% url 'group_update' group.id %}">
<i class="fa-solid fa-pen-to-square"></i>
{{ _("Edit") }}
</a>
<a class="btn btn-sm btn-danger me-1"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<i class="fa-solid fa-trash"></i>
{{ _("Delete") }}
</a>
<a class="btn btn-sm btn-secondary"
href="{% url 'group_list' %}">
<i class="fa-solid fa-arrow-left"></i>
{% trans "Back to List" %}
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "Group" %}{% 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 staff.created %}
{{ _("Edit Group") }}
{% else %}
{{ _("Add Group") }}
{% 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="{{request.META.HTTP_REFERER}}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "cancel"|capfirst %}</a>
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title %}{% trans "Group" %}{% endblock title %}
{% block content %}
<section class="">
<div class="row">
<div class="col-auto">
<div class="d-md-flex justify-content-between">
<div>
<a href="{% url 'group_create' %}" class="btn btn-sm btn-phoenix-primary"><span class="fas fa-plus me-2"></span>{% trans "Add Group" %}</a>
</div>
</div>
</div>
<div class="table-responsive scrollbar mx-n1 px-1">
<table class="table table-hover table-responsive-sm fs-9 mb-0">
<thead>
<tr>
<th>{% trans 'name'|capfirst %}</th>
<th>{% trans 'total Users'|capfirst %}</th>
<th>{% trans 'total permission'|capfirst %}</th>
<th>{% trans 'actions'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
<td><i class="fa-solid fa-users"></i> {{ group.user_set.count }}</td>
<td><i class="fa-solid fa-unlock"></i> {{ group.permissions.count }}</td>
<td>
<a class="btn btn-phoenix-success"
href="{% url 'group_detail' group.id %}">
<i class="fa-solid fa-eye"></i>
{% trans 'view'|capfirst %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
</section>
{% endblock %}

View 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' group.pk %}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "cancel"|capfirst %}</a>
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -411,7 +411,7 @@
<div class="card position-relative border-0">
<div class="card-body p-0">
<div class="text-center pt-4 pb-3">
<div class="avatar avatar-xl">
{% if user.dealer.logo %}
<img class="rounded-circle" src="{{ user.dealer.logo.url }}" alt="" />
@ -441,7 +441,7 @@
{% endif %}
{% if user.dealer %}
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'user_list' %}"><span class="me-2 text-body align-bottom" data-feather="users"></span>{{ _("Staff") }}</a>
<a class="nav-link px-3 d-block" href="{% url 'user_list' %}"><span class="me-2 text-body align-bottom" data-feather="users"></span>{{ _("Staff & Group") }}</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'dealer_activity' %}"> <span class="me-2 text-body align-bottom" data-feather="lock"></span>{{ _("Activity") }}</a>

View File

@ -19,7 +19,7 @@
<span class="text-danger">
{% trans "Are you sure you want to delete this user?" %}
</span>
</div>
</div>
<div class="btn-group">
<button type="button"
class="btn btn-sm btn-secondary"
@ -39,7 +39,7 @@
<div class="row my-5">
<div class="card rounded ">
<div class="card-header ">
<p class="mb-0">{{ _("User Details") }}</p>
<p class="mb-0"><i class="fa-solid fa-user"></i> {{ _("User Details") }}</p>
</div>
<div class="card-body">
<div class="row">
@ -53,20 +53,45 @@
</div>
</div>
</div>
<div class="table-responsive scrollbar mx-n1 px-1">
<div class="card-header ">
</div>
<h4 class="my-4">Groups</h4>
<a class="btn btn-sm btn-primary mt-2 mb-4" href="{% url 'user_groups' user_.pk %}"><i class="fa-solid fa-users"></i> Manage Groups</a>
<table class="table table-hover table-responsive-sm fs-9 mb-0">
<thead>
<tr>
<th>{% trans 'name'|capfirst %}</th>
</tr>
</thead>
<tbody>
{% for group in user_.groups.all %}
<tr>
<td>{{ group }}</td>
</tr>
{% empty %}
<tr>
<td>{% trans "No Group" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer d-flex ">
<a class="btn btn-sm btn-primary me-1" href="{% url 'user_update' user_.id %}">
<!--<i class="bi bi-pencil-square"></i> -->
<i class="fa-solid fa-pen-to-square"></i>
{{ _("Edit") }}
</a>
<a class="btn btn-sm btn-danger me-1"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<!--<i class="bi bi-trash-fill"></i>-->
<i class="fa-solid fa-trash"></i>
{{ _("Delete") }}
</a>
<a class="btn btn-sm btn-secondary"
href="{% url 'user_list' %}">
<!--<i class="bi bi-arrow-left-square-fill"></i>-->
<i class="fa-regular fa-circle-left"></i>
{% trans "Back to List" %}
</a>
</div>

View File

@ -12,6 +12,7 @@
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3">
<i class="fa-solid fa-user-tie"></i>
{% if staff.created %}
{{ _("Edit Staff") }}
{% else %}
@ -32,9 +33,9 @@
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex mb-3">
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-phoenix-primary me-2 px-6">{% trans "cancel"|capfirst %}</a>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "cancel"|capfirst %}</a>
<button class="btn btn-primary" type="submit">
<!--<i class="bi bi-save"></i> -->
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>

View File

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "Group" %}{% 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"><i class="fa-solid fa-users"></i> {{ _("Manage Groups") }}</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="{{request.META.HTTP_REFERER}}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "cancel"|capfirst %}</a>
<button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -11,8 +11,8 @@
<div class="col-auto">
<div class="d-md-flex justify-content-between">
<div>
<a href="{% url 'user_create' %}" class="btn btn-sm btn-phoenix-primary"><span class="fas fa-plus me-2"></span>{% trans "Add Staff" %}</a>
<a href="{% url 'user_create' %}" class="btn btn-sm btn-phoenix-primary"><i class="fa-solid fa-user-tie"></i> {% trans "Add New Staff" %}</a>
<a href="{% url 'group_list' %}" class="btn btn-sm btn-phoenix-success"><i class="fa-solid fa-user-group"></i> {% trans "Manage Groups & Permissions" %}</a>
</div>
</div>
</div>
@ -25,6 +25,7 @@
<th>{% trans 'arabic name'|capfirst %}</th>
<th>{% trans 'phone number'|capfirst %}</th>
<th>{% trans 'role'|capfirst %}</th>
<th>{% trans 'groups'|capfirst %}</th>
<th>{% trans 'actions'|capfirst %}</th>
</tr>
</thead>
@ -34,10 +35,18 @@
<td>{{ user.name }}</td>
<td>{{ user.arabic_name }}</td>
<td>{{ user.phone_number }}</td>
<td>{% trans user.staff_type %}</td>
<td>
<span class="badge badge-sm bg-primary"><i class="fa-solid fa-scroll"></i> {% trans user.staff_type|title %}</span>
</td>
<td>
{% for group in user.groups.all %}
<span class="badge badge-sm bg-info"><i class="fa-solid fa-user-group"></i> {{ group.name }}</span>
{% endfor %}
</td>
<td>
<a class="btn btn-phoenix-success"
href="{% url 'user_detail' user.id %}">
<i class="fa-solid fa-eye"></i>
{% trans 'view'|capfirst %}
</a>
</td>