145 lines
5.1 KiB
Python
145 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
from django.conf import settings
|
|
from django.http import FileResponse, Http404
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from drf_spectacular.utils import extend_schema
|
|
from rest_framework import mixins, status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from apps.reports.api.serializers import (
|
|
ReportExportCreateSerializer,
|
|
ReportExportJobSerializer,
|
|
)
|
|
from apps.reports.models import ReportExportJob
|
|
from apps.reports.services import (
|
|
build_chart_report,
|
|
build_day_details_report,
|
|
build_table_report,
|
|
load_report_filters,
|
|
)
|
|
from apps.reports.tasks import generate_report_export_task
|
|
from core.services.cache import CACHE_NAMESPACE_REPORTS, get_or_set_cache_payload
|
|
|
|
|
|
REPORT_CACHE_TTL_SECONDS = 90
|
|
|
|
|
|
class ReportChartView(APIView):
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
@extend_schema(responses=dict)
|
|
def get(self, request):
|
|
workspace_id = request.query_params.get("workspace")
|
|
payload = get_or_set_cache_payload(
|
|
CACHE_NAMESPACE_REPORTS,
|
|
ttl_seconds=REPORT_CACHE_TTL_SECONDS,
|
|
builder=lambda: build_chart_report(request.user, request.query_params),
|
|
resource="chart",
|
|
user_id=request.user.id,
|
|
workspace_id=workspace_id,
|
|
params=request.query_params,
|
|
)
|
|
return Response(payload)
|
|
|
|
|
|
class ReportTableView(APIView):
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
@extend_schema(responses=dict)
|
|
def get(self, request):
|
|
workspace_id = request.query_params.get("workspace")
|
|
payload = get_or_set_cache_payload(
|
|
CACHE_NAMESPACE_REPORTS,
|
|
ttl_seconds=REPORT_CACHE_TTL_SECONDS,
|
|
builder=lambda: build_table_report(request.user, request.query_params),
|
|
resource="table",
|
|
user_id=request.user.id,
|
|
workspace_id=workspace_id,
|
|
params=request.query_params,
|
|
)
|
|
return Response(payload)
|
|
|
|
|
|
class ReportDayDetailsView(APIView):
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
@extend_schema(responses=dict)
|
|
def get(self, request):
|
|
workspace_id = request.query_params.get("workspace")
|
|
payload = get_or_set_cache_payload(
|
|
CACHE_NAMESPACE_REPORTS,
|
|
ttl_seconds=REPORT_CACHE_TTL_SECONDS,
|
|
builder=lambda: build_day_details_report(request.user, request.query_params),
|
|
resource="day-details",
|
|
user_id=request.user.id,
|
|
workspace_id=workspace_id,
|
|
params=request.query_params,
|
|
)
|
|
return Response(payload)
|
|
|
|
|
|
class ReportExportJobViewSet(
|
|
mixins.CreateModelMixin,
|
|
mixins.ListModelMixin,
|
|
mixins.RetrieveModelMixin,
|
|
viewsets.GenericViewSet,
|
|
):
|
|
permission_classes = [IsAuthenticated]
|
|
serializer_class = ReportExportJobSerializer
|
|
|
|
def get_queryset(self):
|
|
return ReportExportJob.objects.filter(requesting_user=self.request.user, is_deleted=False)
|
|
|
|
@extend_schema(request=ReportExportCreateSerializer, responses=ReportExportJobSerializer)
|
|
def create(self, request, *args, **kwargs):
|
|
serializer = ReportExportCreateSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
filters = load_report_filters(request.user, serializer.validated_data)
|
|
|
|
job = ReportExportJob.objects.create(
|
|
requesting_user=request.user,
|
|
workspace=filters.workspace,
|
|
export_type=serializer.validated_data["export_type"],
|
|
filters={
|
|
"workspace": str(filters.workspace.id),
|
|
"period": filters.period,
|
|
"from_date": filters.from_date.isoformat(),
|
|
"to_date": filters.to_date.isoformat(),
|
|
"user": filters.user_id,
|
|
"client": filters.client_id,
|
|
"project": filters.project_id,
|
|
"tags": filters.tag_ids,
|
|
"language": serializer.validated_data.get("language", "en"),
|
|
},
|
|
status=ReportExportJob.Status.PENDING,
|
|
)
|
|
generate_report_export_task.delay(str(job.id))
|
|
output = ReportExportJobSerializer(job)
|
|
return Response(output.data, status=status.HTTP_202_ACCEPTED)
|
|
|
|
@action(detail=True, methods=["get"], url_path="download")
|
|
def download(self, request, pk=None):
|
|
job = self.get_object()
|
|
if job.status != ReportExportJob.Status.COMPLETED or not job.file:
|
|
raise Http404("Export file is not available.")
|
|
if job.expires_at and job.expires_at <= timezone.now():
|
|
raise Http404("Export file has expired.")
|
|
response = FileResponse(
|
|
job.file.open("rb"),
|
|
as_attachment=True,
|
|
filename=job.file_name or job.file.name.split("/")[-1],
|
|
)
|
|
return response
|
|
|
|
|
|
def build_export_action_url(job: ReportExportJob) -> str:
|
|
path = reverse("report-export-job-download", kwargs={"pk": job.id})
|
|
if settings.BASE_URL:
|
|
return f"{settings.BASE_URL.rstrip('/')}{path}"
|
|
return path
|