UPDATE
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
from datetime import date
|
||||
|
||||
from django.db.models import Q
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from farm_hub.models import FarmHub
|
||||
from farmer_calendar.enums import FARMER_TAG_ITEMS
|
||||
from farmer_calendar.models import FarmerCalendarZone
|
||||
|
||||
from .models import FarmerTodoTask
|
||||
from .serializers import (
|
||||
FarmerTodoListQuerySerializer,
|
||||
FarmerTodoTagSerializer,
|
||||
FarmerTodoTaskResponseSerializer,
|
||||
FarmerTodoTaskWriteSerializer,
|
||||
FarmerTodoZoneSerializer,
|
||||
)
|
||||
|
||||
|
||||
class FarmerTodoBaseView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@staticmethod
|
||||
def error_response(message, details=None, code="TASK_VALIDATION_ERROR", status_code=status.HTTP_400_BAD_REQUEST):
|
||||
payload = {"code": code, "message": message}
|
||||
if details is not None:
|
||||
payload["details"] = details
|
||||
return Response(payload, status=status_code)
|
||||
|
||||
def handle_exception(self, exc):
|
||||
if isinstance(exc, serializers.ValidationError):
|
||||
details = exc.detail
|
||||
message = "Invalid farmer todo payload"
|
||||
if isinstance(details, dict):
|
||||
first_value = next(iter(details.values()), None)
|
||||
if isinstance(first_value, list) and first_value:
|
||||
message = str(first_value[0])
|
||||
elif first_value:
|
||||
message = str(first_value)
|
||||
elif isinstance(details, list) and details:
|
||||
message = str(details[0])
|
||||
return self.error_response(message=message, details=details)
|
||||
if isinstance(exc, NotFound):
|
||||
return self.error_response(
|
||||
message=str(exc.detail),
|
||||
code="TASK_NOT_FOUND",
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
return super().handle_exception(exc)
|
||||
|
||||
def _get_user_farms(self, request):
|
||||
return FarmHub.objects.filter(owner=request.user).order_by("id")
|
||||
|
||||
def _resolve_farm(self, request, farm_uuid=None, required=False):
|
||||
farms = self._get_user_farms(request)
|
||||
if farm_uuid:
|
||||
try:
|
||||
return farms.get(farm_uuid=farm_uuid)
|
||||
except FarmHub.DoesNotExist as exc:
|
||||
raise serializers.ValidationError({"farm_uuid": ["Farm not found."]}) from exc
|
||||
if required:
|
||||
farm_count = farms.count()
|
||||
if farm_count == 1:
|
||||
return farms.first()
|
||||
if farm_count == 0:
|
||||
raise serializers.ValidationError({"farm_uuid": ["No farm found for this user."]})
|
||||
raise serializers.ValidationError({"farm_uuid": ["farm_uuid is required when multiple farms exist."]})
|
||||
return None
|
||||
|
||||
def _get_task(self, request, task_id):
|
||||
queryset = FarmerTodoTask.objects.select_related("farm", "zone").prefetch_related("tags")
|
||||
try:
|
||||
return queryset.get(uuid=task_id, farm__owner=request.user)
|
||||
except FarmerTodoTask.DoesNotExist as exc:
|
||||
raise NotFound("Task not found.") from exc
|
||||
|
||||
|
||||
class FarmerTodoListCreateView(FarmerTodoBaseView):
|
||||
@extend_schema(
|
||||
tags=["Farmer Todos"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="status", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="priority", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="date", type=OpenApiTypes.DATE, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="from", type=OpenApiTypes.DATE, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="to", type=OpenApiTypes.DATE, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="zone", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="search", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False),
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
)
|
||||
def get(self, request):
|
||||
query_data = request.query_params.copy()
|
||||
if "from" in query_data and "from_date" not in query_data:
|
||||
query_data["from_date"] = query_data["from"]
|
||||
query_serializer = FarmerTodoListQuerySerializer(data=query_data)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
filters = query_serializer.validated_data
|
||||
|
||||
queryset = FarmerTodoTask.objects.filter(farm__owner=request.user).select_related("zone").prefetch_related("tags")
|
||||
farm = self._resolve_farm(request, filters.get("farm_uuid"), required=False)
|
||||
if farm is not None:
|
||||
queryset = queryset.filter(farm=farm)
|
||||
if filters.get("status"):
|
||||
queryset = queryset.filter(status=filters["status"])
|
||||
if filters.get("priority"):
|
||||
queryset = queryset.filter(priority=filters["priority"])
|
||||
if filters.get("date"):
|
||||
queryset = queryset.filter(scheduled_date=filters["date"])
|
||||
if filters.get("from"):
|
||||
queryset = queryset.filter(scheduled_date__gte=filters["from"])
|
||||
if filters.get("to"):
|
||||
queryset = queryset.filter(scheduled_date__lte=filters["to"])
|
||||
if filters.get("zone"):
|
||||
queryset = queryset.filter(zone__value=filters["zone"].strip())
|
||||
if filters.get("search"):
|
||||
search_value = filters["search"].strip()
|
||||
queryset = queryset.filter(Q(title__icontains=search_value) | Q(description__icontains=search_value))
|
||||
|
||||
tasks = queryset.order_by("scheduled_date", "time", "created_at")
|
||||
data = FarmerTodoTaskResponseSerializer(tasks, many=True).data
|
||||
return Response({"tasks": data, "meta": {"total": len(data)}}, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(tags=["Farmer Todos"])
|
||||
def post(self, request):
|
||||
serializer = FarmerTodoTaskWriteSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
farm = self._resolve_farm(request, serializer.validated_data.get("farm_uuid"), required=True)
|
||||
task = serializer.save(farm=farm)
|
||||
data = FarmerTodoTaskResponseSerializer(task).data
|
||||
return Response({"task": data}, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class FarmerTodoDetailView(FarmerTodoBaseView):
|
||||
@extend_schema(tags=["Farmer Todos"])
|
||||
def put(self, request, task_id):
|
||||
task = self._get_task(request, task_id)
|
||||
serializer = FarmerTodoTaskWriteSerializer(task, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
requested_farm_uuid = serializer.validated_data.get("farm_uuid")
|
||||
if requested_farm_uuid and str(task.farm.farm_uuid) != str(requested_farm_uuid):
|
||||
return self.error_response(
|
||||
message="farm_uuid cannot change an existing task",
|
||||
details={"farm_uuid": ["farm_uuid cannot change an existing task"]},
|
||||
)
|
||||
|
||||
task = serializer.save()
|
||||
data = FarmerTodoTaskResponseSerializer(task).data
|
||||
return Response({"task": data}, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(tags=["Farmer Todos"])
|
||||
def delete(self, request, task_id):
|
||||
task = self._get_task(request, task_id)
|
||||
task.delete()
|
||||
return Response({"success": True}, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(tags=["Farmer Todos"])
|
||||
def get(self, request, task_id):
|
||||
task = self._get_task(request, task_id)
|
||||
data = FarmerTodoTaskResponseSerializer(task).data
|
||||
return Response({"task": data}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class FarmerTodoZonesView(FarmerTodoBaseView):
|
||||
@extend_schema(
|
||||
tags=["Farmer Todos"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
)
|
||||
def get(self, request):
|
||||
farm = self._resolve_farm(request, request.query_params.get("farm_uuid"), required=False)
|
||||
queryset = FarmerCalendarZone.objects.filter(farm__owner=request.user, is_active=True)
|
||||
if farm is not None:
|
||||
queryset = queryset.filter(farm=farm)
|
||||
if farm is None:
|
||||
unique_zones = {}
|
||||
for zone in queryset.order_by("label", "created_at"):
|
||||
unique_zones.setdefault(zone.value, zone)
|
||||
zones = list(unique_zones.values())
|
||||
else:
|
||||
zones = queryset.order_by("label")
|
||||
data = FarmerTodoZoneSerializer(zones, many=True).data
|
||||
return Response({"zones": data, "meta": {"total": len(data)}}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class FarmerTodoTagsView(FarmerTodoBaseView):
|
||||
@extend_schema(
|
||||
tags=["Farmer Todos"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
)
|
||||
def get(self, request):
|
||||
self._resolve_farm(request, request.query_params.get("farm_uuid"), required=False)
|
||||
data = FarmerTodoTagSerializer(FARMER_TAG_ITEMS, many=True).data
|
||||
return Response({"tags": data, "meta": {"total": len(data)}}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class FarmerTodoSummaryView(FarmerTodoBaseView):
|
||||
@extend_schema(
|
||||
tags=["Farmer Todos"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
)
|
||||
def get(self, request):
|
||||
farm = self._resolve_farm(request, request.query_params.get("farm_uuid"), required=False)
|
||||
queryset = FarmerTodoTask.objects.filter(farm__owner=request.user).select_related("zone").prefetch_related("tags")
|
||||
if farm is not None:
|
||||
queryset = queryset.filter(farm=farm)
|
||||
|
||||
today = date.today()
|
||||
total_count = queryset.count()
|
||||
today_count = queryset.filter(scheduled_date=today).count()
|
||||
completed_count = queryset.filter(status=FarmerTodoTask.STATUS_DONE).count()
|
||||
urgent_count = queryset.filter(priority=FarmerTodoTask.PRIORITY_HIGH, status=FarmerTodoTask.STATUS_OPEN).count()
|
||||
next_task = queryset.filter(
|
||||
status=FarmerTodoTask.STATUS_OPEN,
|
||||
).filter(
|
||||
Q(scheduled_date__gt=today) | Q(scheduled_date=today)
|
||||
).order_by("scheduled_date", "time", "created_at").first()
|
||||
|
||||
progress_value = int((completed_count / total_count) * 100) if total_count else 0
|
||||
next_task_data = FarmerTodoTaskResponseSerializer(next_task).data if next_task else None
|
||||
return Response(
|
||||
{
|
||||
"todayTasksCount": today_count,
|
||||
"completedCount": completed_count,
|
||||
"urgentCount": urgent_count,
|
||||
"progressValue": progress_value,
|
||||
"nextTask": next_task_data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
Reference in New Issue
Block a user