feat(reports): add localized workspace reports and exports
This commit is contained in:
1
apps/reports/api/__init__.py
Normal file
1
apps/reports/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
39
apps/reports/api/serializers.py
Normal file
39
apps/reports/api/serializers.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.reports.models import ReportExportJob
|
||||
from apps.reports.services.aggregation import ALLOWED_PERIODS
|
||||
|
||||
|
||||
class ReportExportCreateSerializer(serializers.Serializer):
|
||||
workspace = serializers.UUIDField()
|
||||
period = serializers.ChoiceField(choices=sorted(ALLOWED_PERIODS))
|
||||
from_date = serializers.DateField(required=False, allow_null=True)
|
||||
to_date = serializers.DateField(required=False, allow_null=True)
|
||||
user = serializers.UUIDField(required=False, allow_null=True)
|
||||
client = serializers.UUIDField(required=False, allow_null=True)
|
||||
project = serializers.UUIDField(required=False, allow_null=True)
|
||||
tags = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
allow_empty=True,
|
||||
)
|
||||
language = serializers.ChoiceField(choices=("en", "fa"), required=False, default="en")
|
||||
export_type = serializers.ChoiceField(choices=ReportExportJob.ExportType.choices)
|
||||
|
||||
|
||||
class ReportExportJobSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ReportExportJob
|
||||
fields = (
|
||||
"id",
|
||||
"workspace",
|
||||
"export_type",
|
||||
"status",
|
||||
"filters",
|
||||
"file_name",
|
||||
"error_message",
|
||||
"expires_at",
|
||||
"completed_at",
|
||||
"created_at",
|
||||
)
|
||||
read_only_fields = fields
|
||||
20
apps/reports/api/urls.py
Normal file
20
apps/reports/api/urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from apps.reports.api.views import (
|
||||
ReportChartView,
|
||||
ReportDayDetailsView,
|
||||
ReportExportJobViewSet,
|
||||
ReportTableView,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"exports", ReportExportJobViewSet, basename="report-export-job")
|
||||
|
||||
urlpatterns = [
|
||||
path("chart/", ReportChartView.as_view(), name="report-chart"),
|
||||
path("table/", ReportTableView.as_view(), name="report-table"),
|
||||
path("day-details/", ReportDayDetailsView.as_view(), name="report-day-details"),
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
|
||||
110
apps/reports/api/views.py
Normal file
110
apps/reports/api/views.py
Normal file
@@ -0,0 +1,110 @@
|
||||
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
|
||||
|
||||
|
||||
class ReportChartView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(responses=dict)
|
||||
def get(self, request):
|
||||
return Response(build_chart_report(request.user, request.query_params))
|
||||
|
||||
|
||||
class ReportTableView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(responses=dict)
|
||||
def get(self, request):
|
||||
return Response(build_table_report(request.user, request.query_params))
|
||||
|
||||
|
||||
class ReportDayDetailsView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(responses=dict)
|
||||
def get(self, request):
|
||||
return Response(build_day_details_report(request.user, request.query_params))
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user