feat(src/dashboard/board_views): add atomic transaction and select_for_update to move_ticket view

Ensure consistent status updates and prevent race conditions when moving tickets
This commit is contained in:
sankalp committed 2026-06-29 07:19:35 +05:45
1 parent 7431c8f050
commit 1bdb254e94
3 files changed
+21 -18

No files matched your search

+14 -2
View File
@@ -2,6 +2,7 @@ import json
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_http_methods
@@ -48,6 +49,11 @@ def board_create_ticket(request, project_id):
due_date=due_date,
created_by=request.user,
)
ticket = (
Ticket.objects.select_related("project", "created_by")
.prefetch_related("assignees")
.get(pk=ticket.pk)
)
for uid in assignee_ids:
TicketUser.objects.get_or_create(ticket=ticket, user_id=uid)
@@ -89,7 +95,14 @@ def board_create_ticket(request, project_id):
@require_http_methods(["POST"])
def move_ticket(request, project_id, ticket_id):
ticket = get_object_or_404(Ticket, pk=ticket_id, project_id=project_id)
with transaction.atomic():
ticket = get_object_or_404(
Ticket.objects.select_for_update()
.select_related("project", "created_by")
.prefetch_related("assignees"),
pk=ticket_id,
project_id=project_id,
)
data = json.loads(request.body)
status_id = data.get("status_id")
if not status_id:
@@ -128,7 +141,6 @@ def move_ticket(request, project_id, ticket_id):
message=f"Ticket moved to {status.name} by {request.user.get_full_name() or request.user.username}",
data={"ticket_id": ticket.id, "ticket_uuid": ticket.uuid},
)
# Send email notification
send_notification_email(
user=u,
notification_type="status_changed",
+6 -15
View File
@@ -47,9 +47,7 @@ def project_board(request, project_id=None):
pk=project_id,
)
project_members = selected_project.project_members.select_related("user").all()
statuses = TicketStatus.objects.filter(project=selected_project).order_by(
"sort_order"
)
statuses = list(selected_project.ticket_statuses.order_by("sort_order"))
tickets_qs = (
Ticket.objects.filter(project=selected_project)
@@ -132,25 +130,18 @@ def project_board(request, project_id=None):
first_status_name = None
statuses_json = "[]"
if selected_project:
first_status = (
TicketStatus.objects.filter(project=selected_project)
.order_by("sort_order")
.first()
)
first_status = statuses[0] if statuses else None
if first_status:
first_status_id = first_status.id
first_status_name = first_status.name
statuses_json = json.dumps(
[
{"id": s.id, "name": s.name, "color": s.color}
for s in TicketStatus.objects.filter(project=selected_project).order_by(
"sort_order"
)
]
[{"id": s.id, "name": s.name, "color": s.color} for s in statuses]
)
permissions = (
BoardRolePermission.objects.filter(project=selected_project).order_by("role")
BoardRolePermission.objects.filter(project=selected_project)
.prefetch_related("move_to_statuses")
.order_by("role")
if selected_project
else []
)
+1 -1
View File
@@ -25,7 +25,7 @@ def export_board_tickets(request, project_id):
tickets = (
Ticket.objects.filter(project=project)
.select_related("status", "priority", "epic", "created_by")
.select_related("status", "priority", "epic", "created_by", "project")
.prefetch_related("assignees")
.order_by("-created_at")
)