refactor(src/dashboard): enhance DB query efficiency and pagination stability
Prevent N+1 queries, streamline aggregation, and fix role deletion count
This commit is contained in:
6 files changed
+57
-47
No files matched your search
@@ -110,7 +110,8 @@ def external_access_view(request, token):
|
||||
if authenticated and access.is_active:
|
||||
tickets = (
|
||||
Ticket.objects.filter(project=access.project)
|
||||
.select_related("status", "priority")
|
||||
.select_related("status", "priority", "created_by", "epic")
|
||||
.prefetch_related("assignees")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
return render(
|
||||
|
||||
@@ -62,17 +62,17 @@ def notifications_list(request):
|
||||
except ValueError:
|
||||
per_page = 20
|
||||
|
||||
from django.db.models import Count
|
||||
|
||||
paginator = Paginator(qs.select_related("user"), per_page)
|
||||
page_number = request.GET.get("page", 1)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
all_count = Notification.objects.filter(user=request.user).count()
|
||||
unread_count = Notification.objects.filter(
|
||||
user=request.user, read_at__isnull=True
|
||||
).count()
|
||||
read_count = Notification.objects.filter(
|
||||
user=request.user, read_at__isnull=False
|
||||
).count()
|
||||
counts = Notification.objects.filter(user=request.user).aggregate(
|
||||
all_count=Count("id"),
|
||||
unread_count=Count("id", filter=Q(read_at__isnull=True)),
|
||||
read_count=Count("id", filter=Q(read_at__isnull=False)),
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -81,9 +81,9 @@ def notifications_list(request):
|
||||
"notifications": page_obj,
|
||||
"page_obj": page_obj,
|
||||
"filter": filter_param,
|
||||
"all_count": all_count,
|
||||
"unread_count": unread_count,
|
||||
"read_count": read_count,
|
||||
"all_count": counts["all_count"],
|
||||
"unread_count": counts["unread_count"],
|
||||
"read_count": counts["read_count"],
|
||||
"search_query": search_query,
|
||||
"type_filter": type_filter,
|
||||
"per_page": per_page,
|
||||
@@ -93,7 +93,7 @@ def notifications_list(request):
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def notifications_dropdown(request):
|
||||
notifications = Notification.objects.filter(
|
||||
notifications = Notification.objects.select_related("user").filter(
|
||||
user=request.user, read_at__isnull=True
|
||||
)[:10]
|
||||
return render(
|
||||
|
||||
@@ -369,10 +369,11 @@ def role_delete(request, role_id):
|
||||
if role.is_system:
|
||||
messages.error(request, "System roles cannot be deleted.")
|
||||
return redirect("roles-list")
|
||||
if role.users.exists():
|
||||
user_count = role.users.count()
|
||||
if user_count:
|
||||
messages.error(
|
||||
request,
|
||||
f"Cannot delete role '{role.name}' — it is assigned to {role.users.count()} user(s).",
|
||||
f"Cannot delete role '{role.name}' — it is assigned to {user_count} user(s).",
|
||||
)
|
||||
return redirect("roles-list")
|
||||
BoardRolePermission.objects.filter(role=role.slug).delete()
|
||||
|
||||
@@ -187,7 +187,7 @@ def project_detail(request, project_id):
|
||||
# ─ Tickets with pagination ─
|
||||
tickets_qs = (
|
||||
Ticket.objects.filter(project=project)
|
||||
.select_related("status", "priority")
|
||||
.select_related("status", "priority", "created_by", "epic", "project")
|
||||
.prefetch_related("assignees")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
@@ -292,9 +292,9 @@ def project_detail(request, project_id):
|
||||
"today": today,
|
||||
"external_access": external_access,
|
||||
"is_admin": request.user.is_admin,
|
||||
"permissions": BoardRolePermission.objects.filter(project=project).order_by(
|
||||
"role"
|
||||
),
|
||||
"permissions": BoardRolePermission.objects.filter(project=project)
|
||||
.prefetch_related("move_to_statuses")
|
||||
.order_by("role"),
|
||||
"role_choices": Role.objects.all(),
|
||||
"admin_roles": ADMIN_ROLES,
|
||||
},
|
||||
@@ -396,7 +396,7 @@ def project_edit(request, project_id):
|
||||
|
||||
tickets_qs = (
|
||||
Ticket.objects.filter(project=project)
|
||||
.select_related("status", "priority")
|
||||
.select_related("status", "priority", "created_by", "epic", "project")
|
||||
.prefetch_related("assignees")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ from datetime import date
|
||||
import openpyxl
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Prefetch
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
@@ -774,39 +775,45 @@ def tickets_bulk_status(request):
|
||||
{"success": False, "error": "Missing ticket_ids or status_id"}, status=400
|
||||
)
|
||||
|
||||
status = get_object_or_404(TicketStatus, pk=status_id)
|
||||
tickets = Ticket.objects.filter(pk__in=ticket_ids).select_related("status")
|
||||
|
||||
if not request.user.is_admin:
|
||||
user_project_ids = request.user.projects.values_list("id", flat=True)
|
||||
tickets = tickets.filter(project_id__in=user_project_ids)
|
||||
|
||||
count = 0
|
||||
for ticket in tickets:
|
||||
old_name = ticket.status.name
|
||||
ticket.status = status
|
||||
ticket.save(update_fields=["status", "updated_at"])
|
||||
|
||||
TicketHistory.objects.create(
|
||||
ticket=ticket,
|
||||
user=request.user,
|
||||
status=status,
|
||||
with transaction.atomic():
|
||||
status = get_object_or_404(TicketStatus, pk=status_id)
|
||||
tickets = (
|
||||
Ticket.objects.select_for_update()
|
||||
.filter(pk__in=ticket_ids)
|
||||
.select_related("status", "created_by")
|
||||
.prefetch_related("assignees")
|
||||
)
|
||||
|
||||
notify_users = list(ticket.assignees.all())
|
||||
if ticket.created_by and ticket.created_by not in notify_users:
|
||||
notify_users.append(ticket.created_by)
|
||||
if not request.user.is_admin:
|
||||
user_project_ids = request.user.projects.values_list("id", flat=True)
|
||||
tickets = tickets.filter(project_id__in=user_project_ids)
|
||||
|
||||
for u in notify_users:
|
||||
Notification.objects.create(
|
||||
user=u,
|
||||
type="status_changed",
|
||||
title=f"Ticket {ticket.uuid} status changed",
|
||||
message=f"Status changed from {old_name} to {status.name} by {request.user.get_full_name() or request.user.username}",
|
||||
data={"ticket_id": ticket.id, "ticket_uuid": ticket.uuid},
|
||||
count = 0
|
||||
for ticket in tickets:
|
||||
old_name = ticket.status.name
|
||||
ticket.status = status
|
||||
ticket.save(update_fields=["status", "updated_at"])
|
||||
|
||||
TicketHistory.objects.create(
|
||||
ticket=ticket,
|
||||
user=request.user,
|
||||
status=status,
|
||||
)
|
||||
|
||||
count += 1
|
||||
notify_users = list(ticket.assignees.all())
|
||||
if ticket.created_by and ticket.created_by not in notify_users:
|
||||
notify_users.append(ticket.created_by)
|
||||
|
||||
for u in notify_users:
|
||||
Notification.objects.create(
|
||||
user=u,
|
||||
type="status_changed",
|
||||
title=f"Ticket {ticket.uuid} status changed",
|
||||
message=f"Status changed from {old_name} to {status.name} by {request.user.get_full_name() or request.user.username}",
|
||||
data={"ticket_id": ticket.id, "ticket_uuid": ticket.uuid},
|
||||
)
|
||||
|
||||
count += 1
|
||||
|
||||
return JsonResponse({"success": True, "count": count})
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ def users_list(request):
|
||||
return redirect("dashboard")
|
||||
qs = (
|
||||
User.objects.filter(is_active=True)
|
||||
.select_related("role")
|
||||
.annotate(
|
||||
project_count=Count("projects", distinct=True),
|
||||
ticket_count=Count("created_tickets", distinct=True),
|
||||
|
||||
Reference in new issue
Block a user