Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

urlpatterns = [
path("admin/", admin.site.urls),
path("api/requests/", include("requests.urls")),
# API Routes - Uncomment when ready to use
# path('api/', include('users.urls')),
# path('api/inventory/', include('inventory.urls')),
Expand Down
73 changes: 73 additions & 0 deletions backend/requests/migrations/0003_itemhistory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Generated by Django 5.2.8 on 2025-11-29 17:55

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("inventory", "0002_initial"),
("requests", "0002_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="ItemHistory",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"item_type",
models.CharField(
choices=[
("MOVE_REQUESTED", "Move Requested"),
("MOVE_APPROVED", "Move Approved"),
("MOVE_REJECTED", "Move Rejected"),
("MOVE_CANCELLED", "Move Cancelled"),
],
max_length=50,
),
),
("notes", models.TextField(blank=True)),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"acted_by",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
),
),
(
"from_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="inventory.location",
),
),
("item", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="inventory.collectionitem")),
(
"movement_request",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="requests.itemmovementrequest"
),
),
(
"to_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="inventory.location",
),
),
],
options={
"ordering": ["-timestamp"],
},
),
]
Comment on lines +16 to +73
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This migration creates a duplicate ItemHistory model that conflicts with the existing ItemHistory in the inventory app. The existing model uses 'event_type' as the field name, but this migration creates 'item_type'. This migration should be removed since ItemHistory already exists in inventory.models.

Suggested change
operations = [
migrations.CreateModel(
name="ItemHistory",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"item_type",
models.CharField(
choices=[
("MOVE_REQUESTED", "Move Requested"),
("MOVE_APPROVED", "Move Approved"),
("MOVE_REJECTED", "Move Rejected"),
("MOVE_CANCELLED", "Move Cancelled"),
],
max_length=50,
),
),
("notes", models.TextField(blank=True)),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"acted_by",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
),
),
(
"from_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="inventory.location",
),
),
("item", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="inventory.collectionitem")),
(
"movement_request",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="requests.itemmovementrequest"
),
),
(
"to_location",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="inventory.location",
),
),
],
options={
"ordering": ["-timestamp"],
},
),
]
operations = []

Copilot uses AI. Check for mistakes.
29 changes: 29 additions & 0 deletions backend/requests/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from rest_framework import serializers
from .models import ItemMovementRequest
from inventory.models import ItemHistory

# from .models import Request

Expand All @@ -23,3 +25,30 @@
# """Custom validation for the entire object."""
# # Add any cross-field validation here
# return data


class ItemMovementRequestSerializer(serializers.ModelSerializer):
requested_by_username = serializers.CharField(source="requested_by.username", read_only=True)
admin_username = serializers.CharField(source="admin.username", read_only=True)

class Meta:
model = ItemMovementRequest
fields = [
"id",
"item",
"requested_by",
"requested_by_username",
"from_location",
"to_location",
"status",
"admin",
"admin_username",
"admin_comment",
"created_at",
"updated_at",
]
read_only_fields = ["id", "requested_by", "status", "admin", "admin_comment", "created_at", "updated_at"]

def create(self, validated_data):
# Volunteer is the logged-in user.
return super().create(validated_data)
60 changes: 40 additions & 20 deletions backend/requests/urls.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
# from django.urls import path, include
# from rest_framework.routers import DefaultRouter
# from .views import ItemMovementRequestViewSet

# # from .views import RequestViewSet

# from .views import RequestViewSet
# # Create your URL patterns here.

# Create your URL patterns here.
# # Example: Using DRF Router for ViewSets
# # Uncomment and modify as needed
# #
# # router = DefaultRouter()
# # router.register(r'requests', RequestViewSet, basename='request')
# #
# # urlpatterns = [
# # path('', include(router.urls)),
# # ]

# Example: Using DRF Router for ViewSets
# Uncomment and modify as needed
#
# # This will create the following endpoints:
# # GET /api/requests/ - List all requests
# # POST /api/requests/ - Create a new request
# # GET /api/requests/{id}/ - Retrieve a specific request
# # PUT /api/requests/{id}/ - Update a request
# # PATCH /api/requests/{id}/ - Partial update
# # DELETE /api/requests/{id}/ - Delete a request
# # POST /api/requests/{id}/approve/ - Custom action (if defined)
# # POST /api/requests/{id}/reject/ - Custom action (if defined)

# # create a router and register viewsets
# router = DefaultRouter()
# router.register(r'requests', RequestViewSet, basename='request')
#
# router.register(r'movement-requests', ItemMovementRequestViewSet, basename='movement-request')
# router.register(r'item-history', ItemHistoryViewSet, basename='item-history')

# # wire the router URLs into urlpatterns
# urlpatterns = [
# path('', include(router.urls)),
# ]

# This will create the following endpoints:
# GET /api/requests/ - List all requests
# POST /api/requests/ - Create a new request
# GET /api/requests/{id}/ - Retrieve a specific request
# PUT /api/requests/{id}/ - Update a request
# PATCH /api/requests/{id}/ - Partial update
# DELETE /api/requests/{id}/ - Delete a request
# POST /api/requests/{id}/approve/ - Custom action (if defined)
# POST /api/requests/{id}/reject/ - Custom action (if defined)

urlpatterns = []
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ItemMovementRequestViewSet

router = DefaultRouter()
router.register(r"movement-requests", ItemMovementRequestViewSet, basename="movement-request")

urlpatterns = [
path("", include(router.urls)),
]
53 changes: 53 additions & 0 deletions backend/requests/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import ItemMovementRequest
from .serializers import ItemMovementRequestSerializer

# from .models import Request
# from .serializers import RequestSerializer
Expand Down Expand Up @@ -48,3 +50,54 @@
# request_obj.save()
# serializer = self.get_serializer(request_obj)
# return Response(serializer.data)


class ItemMovementRequestViewSet(viewsets.ModelViewSet):
"""
ViewSet for managing item movement requests.
Supports creation, listing, retrieval, approval, and rejection.
"""

queryset = ItemMovementRequest.objects.all()
serializer_class = ItemMovementRequestSerializer
permission_classes = [permissions.IsAuthenticated]

def get_queryset(self):
# Volunteers see only their own requests
user = self.request.user
if user.is_staff:
return ItemMovementRequest.objects.all() # Admins see all
return ItemMovementRequest.objects.filter(requested_by=user)

def perform_create(self, serializer):
serializer.save(requested_by=self.request.user)

@action(detail=True, methods=["post"], permission_classes=[permissions.IsAdminUser])
def approve(self, request, pk=None):
move_request = self.get_object()

if move_request.status != ItemMovementRequest.Status.WAITING_APPROVAL:
return Response(
{"detail": "This request has already been processed."},
status=status.HTTP_400_BAD_REQUEST,
)

comment = request.data.get("comment", "")
move_request.approve(admin_user=request.user, comment=comment)
serializer = self.get_serializer(move_request)
return Response(serializer.data, status=status.HTTP_200_OK)

@action(detail=True, methods=["post"], permission_classes=[permissions.IsAdminUser])
def reject(self, request, pk=None):
move_request = self.get_object()

if move_request.status != ItemMovementRequest.Status.WAITING_APPROVAL:
return Response(
{"detail": "This request has already been processed."},
status=status.HTTP_400_BAD_REQUEST,
)

comment = request.data.get("comment", "")
move_request.reject(admin_user=request.user, comment=comment)
serializer = self.get_serializer(move_request)
return Response(serializer.data, status=status.HTTP_200_OK)