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:
sankalp committed 2026-06-29 07:19:53 +05:45
1 parent 1bdb254e94
commit e3551a822c
6 files changed
+57 -47

No files matched your search

+2 -1
View File
@@ -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(
+11 -11
View File
@@ -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(
+3 -2
View File
@@ -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()
+5 -5
View File
@@ -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")
)
+35 -28
View File
@@ -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})
+1
View File
@@ -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),