Tasks Views - REST API Endpoints

File: api/nextango/apps/tasks/views.py Purpose: DRF ViewSets for task CRUD operations Base URL: /api/tasks/ Framework: Django REST Framework (ModelViewSet)

Overview

Tasks views provide RESTful API endpoints for task management using DRF's ModelViewSet pattern. The TaskViewSet overrides the retrieve() method to use TaskService for enhanced task details with computed fields.

Architecture:

HTTP Request

    ├─ URL Routing (urls.py)


TaskViewSet

    ├─ Standard CRUD (list, create, update, delete)
    ├─ Custom retrieve → TaskService.get_task_with_details()


JSON Response

TaskViewSet

Purpose: Full CRUD operations for Task model with service-enhanced retrieval

Source Code:

# From views.py:9-27

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    lookup_field = 'task_id'

    def retrieve(self, request, *args, **kwargs):
        """Override retrieve method to use TaskService for enhanced task data."""
        # Get the task_id from the URL
        task_id = kwargs.get('task_id')

        try:
            # Get enhanced task details from service
            task_details = TaskService.get_task_with_details(task_id)
            return Response(task_details, status=status.HTTP_200_OK)
        except Task.DoesNotExist:
            return Response(
                {'error': 'Task not found'},
                status=status.HTTP_404_NOT_FOUND
            )

Configuration

Queryset:

queryset = Task.objects.all()

Performance Note: Should use select_related() to avoid N+1 queries for list endpoint:

# Recommended
queryset = Task.objects.select_related(
    'assigned_to', 'completed_by', 'store'
).all()

Serializer:

serializer_class = TaskSerializer

Lookup Field:

lookup_field = 'task_id'
  • Uses task_id instead of default pk (UUID)
  • URL pattern: /api/tasks/{task_id}/ instead of /api/tasks/{uuid}/
  • Business-friendly identifiers in URLs

API Endpoints

List Tasks

Endpoint: GET /api/tasks/

Purpose: Retrieve all tasks (paginated)

Authentication: Required

Query Parameters:

  • Standard DRF filtering/pagination parameters
  • page - Page number (if pagination enabled)
  • page_size - Items per page

Response:

HTTP 200 OK

[
  {
    "id": "uuid-1",
    "task_id": "task_12345",
    "title": "Restock dairy section",
    "description": "Restock milk, yogurt, and cheese products",
    "assigned_to": "user-uuid-1",
    "assigned_to_name": "John Doe",
    "store": "store-uuid-1",
    "store_name": "Downtown Location",
    "priority": "high",
    "status": "in_progress",
    "due_date": "2025-11-20",
    "completed_by": null,
    "completed_by_name": null,
    "completed_at": null,
    "created_at": "2025-11-18T10:30:00Z"
  },
  ...
]

Uses: TaskSerializer (standard ModelViewSet behavior)


Retrieve Task

Endpoint: GET /api/tasks/{task_id}/

Purpose: Get detailed task information with computed fields

Authentication: Required

Path Parameters:

  • task_id (string) - Business identifier (e.g., "task_12345")

Response:

HTTP 200 OK

{
  "id": "uuid-1",
  "task_id": "task_12345",
  "title": "Restock dairy section",
  "description": "Restock milk, yogurt, and cheese products",
  "priority": "high",
  "status": "in_progress",
  "due_date": "2025-11-20",
  "completed_at": null,
  "created_at": "2025-11-18T10:30:00Z",
  "assigned_to": {
    "id": "user-uuid-1",
    "name": "John Doe",
    "employee_id": "EMP-001"
  },
  "completed_by": null,
  "store": {
    "id": "store-uuid-1",
    "name": "Downtown Location",
    "store_code": "DT-01"
  },
  "is_overdue": false,
  "days_since_created": 0
}

Error Response (Not Found):

HTTP 404 Not Found

{
  "error": "Task not found"
}

Uses: TaskService.get_task_with_details() (custom override)

Key Differences from List:

  • Nested objects for relationships (assigned_to, completed_by, store)
  • Computed fields (is_overdue, days_since_created)
  • Enhanced data structure optimized for detail views

Create Task

Endpoint: POST /api/tasks/

Purpose: Create a new task

Authentication: Required

Request Body:

{
  "title": "Clean refrigerators",
  "description": "Deep clean all refrigerators in dairy section",
  "assigned_to": "user-uuid-1",
  "store": "store-uuid-1",
  "priority": "medium",
  "status": "pending",
  "due_date": "2025-11-21"
}

Required Fields:

  • title
  • store
  • priority

Optional Fields:

  • description
  • assigned_to
  • status (defaults to 'pending')
  • due_date

Response:

HTTP 201 Created

{
  "id": "new-task-uuid",
  "task_id": "task_67890",
  "title": "Clean refrigerators",
  "description": "Deep clean all refrigerators in dairy section",
  "assigned_to": "user-uuid-1",
  "assigned_to_name": "John Doe",
  "store": "store-uuid-1",
  "store_name": "Downtown Location",
  "priority": "medium",
  "status": "pending",
  "due_date": "2025-11-21",
  "completed_by": null,
  "completed_by_name": null,
  "completed_at": null,
  "created_at": "2025-11-18T14:00:00Z"
}

Validation Errors:

HTTP 400 Bad Request

{
  "title": ["This field is required."],
  "priority": ["\"urgent\" is not a valid choice."]
}

Uses: TaskSerializer (standard ModelViewSet behavior)


Update Task (Full)

Endpoint: PUT /api/tasks/{task_id}/

Purpose: Replace entire task (all fields required)

Authentication: Required

Request Body:

{
  "title": "Restock dairy section",
  "description": "Updated description",
  "assigned_to": "user-uuid-2",
  "store": "store-uuid-1",
  "priority": "high",
  "status": "in_progress",
  "due_date": "2025-11-20"
}

Response:

HTTP 200 OK

{
  "id": "uuid-1",
  "task_id": "task_12345",
  "title": "Restock dairy section",
  ...
}

Uses: TaskSerializer (standard ModelViewSet behavior)


Update Task (Partial)

Endpoint: PATCH /api/tasks/{task_id}/

Purpose: Update specific fields only

Authentication: Required

Common Use Cases:

1. Update Status:

PATCH /api/tasks/task_12345/

{
  "status": "in_progress"
}

2. Complete Task:

PATCH /api/tasks/task_12345/

{
  "status": "completed",
  "completed_by": "user-uuid-2"
}

3. Reassign Task:

PATCH /api/tasks/task_12345/

{
  "assigned_to": "user-uuid-3"
}

Response:

HTTP 200 OK

{
  "id": "uuid-1",
  "task_id": "task_12345",
  "status": "in_progress",
  ...
}

Uses: TaskSerializer with partial=True (standard ModelViewSet behavior)


Delete Task

Endpoint: DELETE /api/tasks/{task_id}/

Purpose: Delete a task

Authentication: Required

Response:

HTTP 204 No Content

Error Response (Not Found):

HTTP 404 Not Found

{
  "detail": "Not found."
}

Uses: Standard ModelViewSet destroy() method


Custom retrieve() Implementation

Why Override retrieve()?

Standard Behavior:

# Default ModelViewSet.retrieve()
def retrieve(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = self.get_serializer(instance)
    return Response(serializer.data)

Returns: Flat serializer data with UUID foreign keys

Custom Behavior:

# TaskViewSet.retrieve()
def retrieve(self, request, *args, **kwargs):
    task_id = kwargs.get('task_id')
    task_details = TaskService.get_task_with_details(task_id)
    return Response(task_details)

Returns: Enhanced data with:

  • Nested relationship objects
  • Computed fields (is_overdue, days_since_created)
  • Optimized queries (select_related)

Benefits:

  1. Richer Data: Detail view has more information than list view
  2. Computed Fields: Business logic (overdue status, age) included
  3. Performance: Service handles query optimization
  4. Consistency: Service logic reusable across different interfaces

URL Routing

Configuration:

# From urls.py:4-6
router = DefaultRouter()
router.register(r'tasks', TaskViewSet, basename='task')

# From urls.py:10-12
app_name = 'tasks'

urlpatterns = [
    path('', include(router.urls)),
]

Generated URLs:

GET    /api/tasks/                  → TaskViewSet.list()
POST   /api/tasks/                  → TaskViewSet.create()
GET    /api/tasks/{task_id}/        → TaskViewSet.retrieve() (custom)
PUT    /api/tasks/{task_id}/        → TaskViewSet.update()
PATCH  /api/tasks/{task_id}/        → TaskViewSet.partial_update()
DELETE /api/tasks/{task_id}/        → TaskViewSet.destroy()

Note: Uses task_id in URLs (not id or pk) due to lookup_field = 'task_id'


DeliveryScheduleViewSet (Commented Out)

Source Code:

# From views.py:29-32 (commented out)

# class DeliveryScheduleViewSet(viewsets.ModelViewSet):
#     queryset = DeliverySchedule.objects.all()
#     serializer_class = DeliveryScheduleSerializer
#     lookup_field = 'delivery_id'

Status:

  • ViewSet implementation commented out
  • Model exists in models.py
  • Serializer commented out in serializers.py
  • URL routing commented out in urls.py

If Activated:

  • Would provide CRUD for DeliverySchedule model
  • URL pattern: /api/delivery-schedules/{delivery_id}/
  • Standard ModelViewSet behavior (no custom retrieve)

Authentication & Permissions

Current Configuration:

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    # No authentication_classes specified
    # No permission_classes specified

Behavior:

  • Uses project-wide default authentication (likely JWT or session)
  • Uses project-wide default permissions (likely IsAuthenticated)

Recommended Explicit Configuration:

from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.select_related('assigned_to', 'completed_by', 'store').all()
    serializer_class = TaskSerializer
    lookup_field = 'task_id'
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    # ... custom retrieve()

Performance Optimization

Current Issue: N+1 Queries on List

Problem:

queryset = Task.objects.all()

When listing tasks:

  1. Query 1: SELECT * FROM tasks_task
  2. Query 2-N: SELECT name FROM users_user WHERE id = task.assigned_to_id (for each task)
  3. Query N+1: SELECT name FROM stores_store WHERE id = task.store_id (for each task)

Solution:

queryset = Task.objects.select_related(
    'assigned_to', 'completed_by', 'store'
).all()

Result: Single query with SQL JOINs

Retrieve Endpoint Already Optimized

Pattern: Service handles optimization

# From services.py:22-24
task = Task.objects.select_related(
    'assigned_to', 'completed_by', 'store'
).get(task_id=task_id)

Usage Examples

List Tasks for Store

from rest_framework.test import APIClient

client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Bearer <jwt_token>')

response = client.get('/api/tasks/')
tasks = response.json()

for task in tasks:
    print(f"{task['task_id']}: {task['title']} ({task['status']})")

Create Task

task_data = {
    'title': 'Emergency cleanup',
    'store': 'store-uuid',
    'priority': 'high',
    'status': 'pending'
}

response = client.post('/api/tasks/', data=task_data, format='json')
new_task = response.json()
print(f"Created task: {new_task['task_id']}")

Get Task Details

response = client.get('/api/tasks/task_12345/')
task_details = response.json()

if task_details['is_overdue']:
    print(f"OVERDUE: {task_details['title']}")

if task_details['assigned_to']:
    print(f"Assigned to: {task_details['assigned_to']['name']}")

Update Task Status

response = client.patch(
    '/api/tasks/task_12345/',
    data={'status': 'completed'},
    format='json'
)

updated_task = response.json()
print(f"Task status: {updated_task['status']}")

Integration Points

Dependencies

Models:

# From views.py:5-6
from .models import Task, DeliverySchedule

Serializers:

# From views.py:6
from .serializers import TaskSerializer

Services:

# From views.py:7
from .services import TaskService

DRF Imports:

# From views.py:1-4
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status

Called By

Frontend Applications:

  • Dashboard (task management UI)
  • POS terminals (task assignment/completion)
  • Mobile apps (employee task lists)

Background Jobs:

  • Scheduled task creation (e.g., daily cleaning tasks)
  • Task reminder notifications
  • Overdue task alerts

Was this page helpful?