Files
project-managment/src/config/views.py
T
sankalp 70fd9711b5 feat(src): replace config with Django settings and add board view with context processors for dashboard metrics
Replace decouple config calls for Google OAuth credentials with settings; add new board view and associated context processors to render project board and dashboard charts; update serializer import path; centralize configuration and improve maintainability.
2026-06-25 23:31:04 +05:45

215 lines
6.3 KiB
Python

import json
from datetime import date, timedelta
from django.contrib.auth import get_user_model
from django.db.models import Count, Q
from django.http import JsonResponse
from django.shortcuts import render
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from src.projects.models import Project
from src.tickets.models import Ticket, TicketHistory
User = get_user_model()
def dashboard(request):
return render(
request,
"dashboard/index.html",
{
"project_count": Project.objects.count(),
"ticket_count": Ticket.objects.count(),
"user_count": User.objects.count(),
"my_assigned": Ticket.objects.filter(assignees=request.user).count(),
},
)
@require_http_methods(["GET"])
def stats_cards(request):
user = request.user
now = timezone.now()
today = now.date()
week_start = today - timedelta(days=today.weekday())
if user.is_superuser:
total_projects = Project.objects.count()
total_tickets = Ticket.objects.count()
team_members = User.objects.count()
else:
total_projects = Project.objects.filter(members=user).count()
total_tickets = (
Ticket.objects.filter(Q(project__members=user) | Q(assignees=user))
.distinct()
.count()
)
team_members = (
User.objects.filter(project_memberships__project__members=user)
.distinct()
.count()
)
my_assigned = Ticket.objects.filter(assignees=user).count()
my_created = Ticket.objects.filter(created_by=user).count()
my_overdue = Ticket.objects.filter(
assignees=user, due_date__lt=today, status__is_completed=False
).count()
completed_this_week = Ticket.objects.filter(
Q(assignees=user) | Q(created_by=user),
status__is_completed=True,
updated_at__gte=week_start,
).count()
new_this_week = Ticket.objects.filter(
Q(assignees=user) | Q(created_by=user),
created_at__gte=week_start,
).count()
return render(
request,
"dashboard/partials/stats_cards.html",
{
"total_projects": total_projects,
"total_tickets": total_tickets,
"team_members": team_members,
"my_assigned": my_assigned,
"my_created": my_created,
"my_overdue": my_overdue,
"completed_this_week": completed_this_week,
"new_this_week": new_this_week,
"is_superuser": user.is_superuser,
},
)
@require_http_methods(["GET"])
def tickets_per_project_chart(request):
data = list(
Project.objects.annotate(count=Count("tickets")).values(
"name", "count", "color"
)
)
max_count = max((d["count"] for d in data), default=1)
for d in data:
d["pct"] = round(d["count"] / max_count * 100, 1) if max_count else 0
return render(
request,
"dashboard/partials/tickets_per_project_chart.html",
{
"chart_data": data,
},
)
@require_http_methods(["GET"])
def user_statistics_data(request):
users = User.objects.annotate(
project_count=Count("projects", distinct=True),
assigned_count=Count("assigned_tickets", distinct=True),
).values("username", "project_count", "assigned_count")[:10]
return JsonResponse(
{
"labels": [u["username"] for u in users],
"projects": [u["project_count"] for u in users],
"assigned": [u["assigned_count"] for u in users],
}
)
@require_http_methods(["GET"])
def monthly_trend_chart(request):
now = timezone.now()
months = []
for i in range(5, -1, -1):
m = now.month - i
y = now.year
while m < 1:
m += 12
y -= 1
months.append({"year": y, "month": m, "label": date(y, m, 1).strftime("%b %Y")})
data = []
for m in months:
count = Ticket.objects.filter(
created_at__year=m["year"],
created_at__month=m["month"],
).count()
data.append({"label": m["label"], "count": count})
return render(
request,
"dashboard/partials/monthly_trend_chart.html",
{
"chart_data": json.dumps(data),
},
)
@require_http_methods(["GET"])
def recent_activity(request):
histories = TicketHistory.objects.select_related(
"ticket", "user", "status", "ticket__project"
).order_by("-created_at")[:20]
return render(
request,
"dashboard/partials/recent_activity.html",
{
"histories": histories,
},
)
@require_http_methods(["GET"])
def project_timeline_widget(request):
filter_type = request.GET.get("filter", "pinned")
qs = Project.objects.filter(start_date__isnull=False, end_date__isnull=False)
pinned_count = qs.filter(pinned_date__isnull=False).count()
all_count = qs.count()
if filter_type == "pinned":
qs = qs.filter(pinned_date__isnull=False).order_by("-pinned_date")
else:
qs = qs.order_by("start_date")
today = date.today()
for p in qs:
total = (p.end_date - p.start_date).days or 1
elapsed = (today - p.start_date).days
p.progress = max(0, min(100, int(elapsed / total * 100)))
remaining = (p.end_date - today).days
if remaining < 0:
p.status_label = "Overdue"
p.status_color = "badge-error"
p.bar_color = "bg-red-500"
elif remaining <= 7:
p.status_label = "Approaching Deadline"
p.status_color = "badge-warning"
p.bar_color = "bg-yellow-500"
elif p.progress >= 100:
p.status_label = "Completed"
p.status_color = "badge-success"
p.bar_color = "bg-emerald-500"
elif p.progress > 0:
p.status_label = "In Progress"
p.status_color = "badge-info"
p.bar_color = "bg-blue-500"
else:
p.status_label = "Not Started"
p.status_color = "badge-ghost"
p.bar_color = "bg-gray-400"
return render(
request,
"dashboard/partials/project_timeline.html",
{
"projects": qs,
"current_filter": filter_type,
"pinned_count": pinned_count,
"all_count": all_count,
},
)