diff --git a/.idea/misc.xml b/.idea/misc.xml
index 18690fd..846dc6d 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/entrypoint.sh b/entrypoint.sh
old mode 100644
new mode 100755
index e30b760..1b1af3c
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -7,4 +7,4 @@ echo "Running migrate..."
python manage.py migrate --noinput
echo "Starting Django server on port ${DJANGO_PORT}..."
-python manage.py runserver 0.0.0.0:${DJANGO_PORT}
+python manage.py runserver 0.0.0.0:${DJANGO_PORT} || python manage.py runserver 8001
diff --git a/fleet/admin.py b/fleet/admin.py
index 8af2ac4..6adb333 100644
--- a/fleet/admin.py
+++ b/fleet/admin.py
@@ -5,17 +5,17 @@
@admin.register(Vehicle)
class VehicleAdmin(admin.ModelAdmin):
list_display = (
- 'vehicle_id', 'name', 'capacity', 'status', 'fuel_type',
+ 'vehicle_id', 'model', 'capacity', 'status', 'fuel_type',
'depot_id', 'depot_latitude', 'depot_longitude', 'last_location_update'
)
list_filter = ('status', 'fuel_type')
- search_fields = ('vehicle_id', 'name', 'plate_number', 'depot_id')
+ search_fields = ('vehicle_id', 'model', 'plate_number', 'depot_id')
readonly_fields = ('created_at', 'updated_at', 'last_location_update')
fieldsets = (
('Basic Information', {
'fields': (
- 'vehicle_id', 'name', 'plate_number',
+ 'vehicle_id', 'model', 'plate_number',
'year_of_manufacture', 'status'
)
}),
diff --git a/fleet/migrations/0006_remove_vehicle_name_vehicle_model.py b/fleet/migrations/0006_remove_vehicle_name_vehicle_model.py
new file mode 100644
index 0000000..5a2d5e0
--- /dev/null
+++ b/fleet/migrations/0006_remove_vehicle_name_vehicle_model.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.2.1 on 2025-05-17 08:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('fleet', '0005_vehicle_depot_latitude_vehicle_depot_longitude'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='vehicle',
+ name='name',
+ ),
+ migrations.AddField(
+ model_name='vehicle',
+ name='model',
+ field=models.CharField(blank=True, max_length=100),
+ ),
+ ]
diff --git a/fleet/migrations/0007_vehicle_driver_assigned.py b/fleet/migrations/0007_vehicle_driver_assigned.py
new file mode 100644
index 0000000..86a64ef
--- /dev/null
+++ b/fleet/migrations/0007_vehicle_driver_assigned.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.1 on 2025-05-17 10:31
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('fleet', '0006_remove_vehicle_name_vehicle_model'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vehicle',
+ name='driver_assigned',
+ field=models.BooleanField(default=False, help_text='Indicates if a driver is assigned to this vehicle'),
+ ),
+ ]
diff --git a/fleet/models/core.py b/fleet/models/core.py
index 0b7675b..f53c4b2 100644
--- a/fleet/models/core.py
+++ b/fleet/models/core.py
@@ -21,7 +21,7 @@ class Vehicle(models.Model):
]
vehicle_id = models.CharField(max_length=20, unique=True)
- name = models.CharField(max_length=100, blank=True)
+ model = models.CharField(max_length=100, blank=True)
capacity = models.PositiveIntegerField(help_text="Capacity in kilograms")
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available')
fuel_type = models.CharField(max_length=20, choices=FUEL_TYPE_CHOICES, default='diesel')
@@ -55,6 +55,9 @@ class Vehicle(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+ # In Vehicle model
+ driver_assigned = models.BooleanField(default=False, help_text="Indicates if a driver is assigned to this vehicle")
+
def __str__(self):
return f"{self.vehicle_id} ({self.status})"
diff --git a/fleet/serializers/__init__.py b/fleet/serializers/__init__.py
index 7d545dc..c89c8c2 100644
--- a/fleet/serializers/__init__.py
+++ b/fleet/serializers/__init__.py
@@ -1,7 +1,16 @@
-from .vehicle import VehicleSerializer, VehicleLocationSerializer
+from .vehicle import (
+ VehicleSerializer,
+ VehicleSummarySerializer,
+ VehicleLocationDetailSerializer,
+ LocationPointSerializer
+)
from django.conf import settings
+# Always import base detail serializer
+from .vehicle import VehicleDetailSerializer as BaseVehicleDetailSerializer
+
+# If extended mode is enabled, override VehicleDetailSerializer
if settings.ENABLE_FLEET_EXTENDED_MODELS:
from .maintenance import (
MaintenanceRecordSerializer,
@@ -9,18 +18,16 @@
)
from .fuel import FuelRecordSerializer
from .trip import TripRecordSerializer
-
from rest_framework import serializers
- class VehicleDetailSerializer(VehicleSerializer):
+ class VehicleDetailSerializer(BaseVehicleDetailSerializer):
maintenance_records = MaintenanceRecordSerializer(many=True, read_only=True)
fuel_records = serializers.SerializerMethodField()
trip_records = serializers.SerializerMethodField()
- location_history = serializers.SerializerMethodField()
- class Meta(VehicleSerializer.Meta):
- fields = VehicleSerializer.Meta.fields + [
- 'maintenance_records', 'fuel_records', 'trip_records', 'location_history'
+ class Meta(BaseVehicleDetailSerializer.Meta):
+ fields = BaseVehicleDetailSerializer.Meta.fields + [
+ 'maintenance_records', 'fuel_records', 'trip_records'
]
def get_fuel_records(self, obj):
@@ -30,13 +37,5 @@ def get_fuel_records(self, obj):
def get_trip_records(self, obj):
records = obj.trip_records.all()[:5]
return TripRecordSerializer(records, many=True).data
-
- def get_location_history(self, obj):
- records = obj.location_history.all()[:10]
- return VehicleLocationSerializer(records, many=True).data
-
else:
- class VehicleDetailSerializer(VehicleSerializer):
- """Fallback when extended models are disabled."""
- class Meta(VehicleSerializer.Meta):
- fields = VehicleSerializer.Meta.fields
+ VehicleDetailSerializer = BaseVehicleDetailSerializer
diff --git a/fleet/serializers/vehicle.py b/fleet/serializers/vehicle.py
index 292fdad..3d84105 100644
--- a/fleet/serializers/vehicle.py
+++ b/fleet/serializers/vehicle.py
@@ -11,7 +11,7 @@ class Meta:
fields = [
'id',
'vehicle_id',
- 'name',
+ 'model',
'capacity',
'status',
'fuel_type',
@@ -28,12 +28,70 @@ class Meta:
'created_at',
'updated_at',
'is_available',
- 'location_is_stale'
+ 'location_is_stale',
+ 'driver_assigned'
]
read_only_fields = ['created_at', 'updated_at', 'last_location_update']
-class VehicleLocationSerializer(serializers.ModelSerializer):
+class LocationPointSerializer(serializers.Serializer):
+ latitude = serializers.DecimalField(max_digits=9, decimal_places=6)
+ longitude = serializers.DecimalField(max_digits=9, decimal_places=6)
+ location_name = serializers.CharField()
+ timestamp = serializers.DateTimeField()
+
+
+class VehicleLocationDetailSerializer(serializers.Serializer):
+ plate_number = serializers.CharField()
+ truck_id = serializers.CharField(source='vehicle_id')
+ model = serializers.CharField()
+ status = serializers.SerializerMethodField()
+
+ def get_status(self, obj):
+ history_qs = VehicleLocation.objects.filter(vehicle=obj).order_by('-timestamp')
+ current_location = None
+ location_history = []
+
+ for i, loc in enumerate(history_qs):
+ loc_data = {
+ 'latitude': loc.latitude,
+ 'longitude': loc.longitude,
+ 'timestamp': loc.timestamp,
+ 'location_name': self.get_mock_location_name(loc.latitude, loc.longitude)
+ }
+
+ if i == 0:
+ current_location = loc_data
+ else:
+ location_history.append(loc_data)
+
+ return {
+ "current_location": current_location,
+ "location_history": location_history
+ }
+
+ def get_mock_location_name(self, lat, lon):
+ # This should be replaced with a geocoding service if needed
+ if lat > 6.926:
+ return "Colombo Fort"
+ elif lat > 6.923:
+ return "Slave Island"
+ elif lat > 6.921:
+ return "Kollupitiya"
+ else:
+ return "Bambalapitiya"
+
+# 🔹 Base serializer for all cases
+class VehicleDetailSerializer(VehicleSerializer):
+ location_detail = serializers.SerializerMethodField()
+
+ class Meta(VehicleSerializer.Meta):
+ fields = VehicleSerializer.Meta.fields + ['location_detail']
+
+ def get_location_detail(self, obj):
+ return VehicleLocationDetailSerializer(obj).data
+
+class VehicleSummarySerializer(serializers.ModelSerializer):
class Meta:
- model = VehicleLocation
- fields = ['timestamp', 'latitude', 'longitude', 'speed', 'heading']
+ model = Vehicle
+ fields = ['vehicle_id', 'plate_number', 'model', 'status']
diff --git a/fleet/tests/test_vehicle.py b/fleet/tests/test_vehicle.py
index 932d803..6d0d0b7 100644
--- a/fleet/tests/test_vehicle.py
+++ b/fleet/tests/test_vehicle.py
@@ -9,7 +9,7 @@ class VehicleModelTest(TestCase):
def setUp(self):
self.vehicle = Vehicle.objects.create(
vehicle_id="TRK001",
- name="Test Truck 1",
+ model="Test Truck 1",
capacity=1000,
status="available",
fuel_type="diesel",
@@ -22,7 +22,7 @@ def test_vehicle_fields_and_defaults(self):
"""Test vehicle creation and default values."""
v = self.vehicle
self.assertEqual(v.vehicle_id, "TRK001")
- self.assertEqual(v.name, "Test Truck 1")
+ self.assertEqual(v.model, "Test Truck 1")
self.assertEqual(v.capacity, 1000)
self.assertEqual(v.status, "available")
self.assertEqual(v.fuel_type, "diesel")
diff --git a/fleet/tests/test_vehicle_api.py b/fleet/tests/test_vehicle_api.py
index 502acb4..8972b49 100644
--- a/fleet/tests/test_vehicle_api.py
+++ b/fleet/tests/test_vehicle_api.py
@@ -1,3 +1,5 @@
+from decimal import Decimal
+
from rest_framework.test import APIClient
from django.test import TestCase
from rest_framework import status
@@ -10,44 +12,46 @@ class VehicleAPITest(TestCase):
def setUp(self):
self.client = APIClient()
self.vehicle1 = Vehicle.objects.create(
- vehicle_id="TRK001", name="Truck 1", capacity=1000,
+ vehicle_id="TRK001", model="Truck 1", capacity=1000,
status="available", fuel_type="diesel"
)
self.vehicle2 = Vehicle.objects.create(
- vehicle_id="TRK002", name="Truck 2", capacity=500,
+ vehicle_id="TRK002", model="Truck 2", capacity=500,
status="maintenance", fuel_type="petrol"
)
self.vehicle3 = Vehicle.objects.create(
- vehicle_id="TRK003", name="Truck 3", capacity=750,
+ vehicle_id="TRK003", model="Truck 3", capacity=750,
status="assigned", fuel_type="diesel"
)
- def test_get_all_vehicles(self):
- """GET /api/fleet/vehicles/ should return all vehicles."""
+ def test_list_summary_fields(self):
response = self.client.get('/api/fleet/vehicles/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 3)
+ self.assertIn("vehicles", response.data)
+ self.assertEqual(len(response.data["vehicles"]), 3)
+ for item in response.data["vehicles"]:
+ self.assertIn("vehicle_id", item)
+ self.assertIn("model", item)
+ self.assertIn("plate_number", item)
+ self.assertIn("status", item)
+ self.assertNotIn("capacity", item)
- def test_filter_vehicles_by_status(self):
- """GET /api/fleet/vehicles/?status=available should return only available vehicles."""
- response = self.client.get('/api/fleet/vehicles/', {'status': 'available'})
+ def test_filter_by_status(self):
+ response = self.client.get('/api/fleet/vehicles/', {'status': 'assigned'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 1)
- self.assertEqual(response.data[0]['vehicle_id'], "TRK001")
+ self.assertEqual(len(response.data["vehicles"]), 1)
+ self.assertEqual(response.data["vehicles"][0]['vehicle_id'], "TRK003")
- def test_filter_vehicles_by_min_capacity(self):
- """GET /api/fleet/vehicles/?min_capacity=800 should return vehicles with capacity >= 800."""
+ def test_filter_by_min_capacity(self):
response = self.client.get('/api/fleet/vehicles/', {'min_capacity': 800})
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 1)
- self.assertEqual(response.data[0]['vehicle_id'], "TRK001")
+ self.assertEqual(len(response.data["vehicles"]), 1)
+ self.assertEqual(response.data["vehicles"][0]['vehicle_id'], "TRK001")
- def test_filter_vehicles_by_fuel_type(self):
- """GET /api/fleet/vehicles/?fuel_type=diesel should return vehicles with diesel fuel."""
+ def test_filter_by_fuel_type(self):
response = self.client.get('/api/fleet/vehicles/', {'fuel_type': 'diesel'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 2)
- vehicle_ids = [v['vehicle_id'] for v in response.data]
+ vehicle_ids = [v['vehicle_id'] for v in response.data["vehicles"]]
self.assertIn("TRK001", vehicle_ids)
self.assertIn("TRK003", vehicle_ids)
@@ -55,7 +59,7 @@ def test_create_vehicle_successfully(self):
"""POST /api/fleet/vehicles/ should create a new vehicle."""
payload = {
"vehicle_id": "TRK004",
- "name": "Truck 4",
+ "model": "Truck 4",
"capacity": 1200,
"status": "available",
"fuel_type": "electric",
@@ -69,7 +73,7 @@ def test_create_vehicle_successfully(self):
def test_patch_update_vehicle_status(self):
"""PATCH /api/fleet/vehicles/{id}/ should update vehicle status."""
response = self.client.patch(
- f'/api/fleet/vehicles/{self.vehicle1.id}/',
+ f'/api/fleet/vehicles/{self.vehicle1.vehicle_id}/',
{"status": "maintenance"},
format='json'
)
@@ -84,7 +88,7 @@ def test_update_vehicle_location(self):
"speed": 65.5
}
response = self.client.post(
- f'/api/fleet/vehicles/{self.vehicle1.id}/update_location/',
+ f'/api/fleet/vehicles/{self.vehicle1.vehicle_id}/update_location/',
payload,
format='json'
)
@@ -99,37 +103,32 @@ def test_update_vehicle_location(self):
self.assertAlmostEqual(float(history[0].speed), 65.5)
self.assertAlmostEqual(float(history[0].latitude), 42.123456)
- def test_list_all_vehicles(self):
- response = self.client.get("/api/fleet/vehicles/")
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 3)
-
- def test_filter_by_status(self):
- response = self.client.get("/api/fleet/vehicles/?status=assigned")
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(len(response.data), 1)
- self.assertEqual(response.data[0]['vehicle_id'], "TRK003")
+ def test_invalid_status_filter(self):
+ response = self.client.get('/api/fleet/vehicles/', {'status': 'nonexistent'})
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_ordering_by_updated_at(self):
+ """Test that vehicles can be ordered by updated_at even if it's not returned."""
response = self.client.get("/api/fleet/vehicles/?ordering=-updated_at")
self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue(len(response.data) >= 1)
- self.assertIn('updated_at', response.data[0])
+ self.assertIn("vehicles", response.data)
+ self.assertTrue(len(response.data["vehicles"]) >= 1)
+ self.assertIn("vehicle_id", response.data["vehicles"][0]) # Confirm summary structure
def test_mark_vehicle_available(self):
- response = self.client.post(f"/api/fleet/vehicles/{self.vehicle3.id}/mark_available/")
+ response = self.client.post(f"/api/fleet/vehicles/{self.vehicle3.vehicle_id}/mark_available/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.vehicle3.refresh_from_db()
self.assertEqual(self.vehicle3.status, 'available')
def test_mark_vehicle_assigned(self):
- response = self.client.post(f"/api/fleet/vehicles/{self.vehicle1.id}/mark_assigned/")
+ response = self.client.post(f"/api/fleet/vehicles/{self.vehicle1.vehicle_id}/mark_assigned/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.vehicle1.refresh_from_db()
self.assertEqual(self.vehicle1.status, 'assigned')
def test_change_status_to_available(self):
- response = self.client.post(f"/api/fleet/vehicles/{self.vehicle2.id}/change_status/", {
+ response = self.client.post(f"/api/fleet/vehicles/{self.vehicle2.vehicle_id}/change_status/", {
"status": "available"
}, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -137,7 +136,81 @@ def test_change_status_to_available(self):
self.assertEqual(self.vehicle2.status, "available")
def test_change_status_invalid(self):
- response = self.client.post(f"/api/fleet/vehicles/{self.vehicle1.id}/change_status/", {
+ response = self.client.post(f"/api/fleet/vehicles/{self.vehicle1.vehicle_id}/change_status/", {
"status": "nonexistent"
}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_location_overview_success(self):
+ # Create location history for vehicle1
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9271, longitude=79.8612)
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9250, longitude=79.8600)
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9225, longitude=79.8590)
+
+ response = self.client.get(f"/api/fleet/vehicles/{self.vehicle1.vehicle_id}/location_overview/")
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ data = response.data
+ self.assertEqual(data["truck_id"], "TRK001")
+ self.assertIn("status", data)
+ self.assertIn("current_location", data["status"])
+ self.assertIn("location_history", data["status"])
+
+ self.assertEqual(len(data["status"]["location_history"]), 2)
+ self.assertEqual(data["status"]["current_location"]["latitude"], Decimal('6.922500'))
+
+ def test_location_overview_empty_history(self):
+ response = self.client.get(f"/api/fleet/vehicles/{self.vehicle2.vehicle_id}/location_overview/")
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ self.assertIsNone(response.data["status"]["current_location"])
+ self.assertEqual(response.data["status"]["location_history"], [])
+
+ def test_location_overview_history_ordering(self):
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9200, longitude=79.8580)
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9250, longitude=79.8600)
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9271, longitude=79.8612)
+
+ response = self.client.get(f"/api/fleet/vehicles/{self.vehicle1.vehicle_id}/location_overview/")
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ locs = response.data["status"]["location_history"]
+ self.assertGreaterEqual(locs[0]["timestamp"], locs[-1]["timestamp"])
+
+ def test_location_overview_has_mock_location_names(self):
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9271, longitude=79.8612)
+ VehicleLocation.objects.create(vehicle=self.vehicle1, latitude=6.9225, longitude=79.8590)
+
+ response = self.client.get(f"/api/fleet/vehicles/{self.vehicle1.vehicle_id}/location_overview/")
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ current = response.data["status"]["current_location"]
+ history = response.data["status"]["location_history"]
+
+ self.assertIn("location_name", current)
+ self.assertIn("location_name", history[0])
+ self.assertTrue(current["location_name"]) # mock name string
+
+ def test_filter_by_driver_assigned_true(self):
+ self.vehicle1.driver_assigned = True
+ self.vehicle1.save()
+
+ response = self.client.get('/api/fleet/vehicles/?driver_assigned=true')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data["vehicles"]), 1)
+ self.assertEqual(response.data["vehicles"][0]["vehicle_id"], "TRK001")
+
+ def test_filter_by_driver_assigned_false(self):
+ self.vehicle1.driver_assigned = True
+ self.vehicle1.save()
+ self.vehicle2.driver_assigned = False
+ self.vehicle2.save()
+ self.vehicle3.driver_assigned = False
+ self.vehicle3.save()
+
+ response = self.client.get('/api/fleet/vehicles/?driver_assigned=false')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ vehicle_ids = [v["vehicle_id"] for v in response.data["vehicles"]]
+ self.assertIn("TRK002", vehicle_ids)
+ self.assertIn("TRK003", vehicle_ids)
+ self.assertNotIn("TRK001", vehicle_ids)
diff --git a/fleet/views/fuel.py b/fleet/views/fuel.py
index 05e18b8..48a354d 100644
--- a/fleet/views/fuel.py
+++ b/fleet/views/fuel.py
@@ -73,7 +73,7 @@ def consumption_stats(self, request):
vehicle_stats.append({
'vehicle_id': vehicle.vehicle_id,
- 'name': vehicle.name,
+ 'name': vehicle.model,
'records_count': vehicle_totals['count'],
'total_cost': vehicle_totals['total_cost'],
'total_amount': vehicle_totals['total_amount']
diff --git a/fleet/views/trip.py b/fleet/views/trip.py
index f38e174..e23c549 100644
--- a/fleet/views/trip.py
+++ b/fleet/views/trip.py
@@ -163,7 +163,7 @@ def stats(self, request):
vehicle_stats.append({
'vehicle_id': vehicle.vehicle_id,
- 'name': vehicle.name,
+ 'name': vehicle.model,
'trip_count': v_trip_count,
'total_distance': v_total_distance,
'total_duration': v_total_duration,
diff --git a/fleet/views/vehicle.py b/fleet/views/vehicle.py
index 8fb24b0..c6e9523 100644
--- a/fleet/views/vehicle.py
+++ b/fleet/views/vehicle.py
@@ -1,18 +1,18 @@
import os
import django
+from fleet.serializers.vehicle import VehicleSummarySerializer, \
+ VehicleLocationDetailSerializer
from fleet.services.status_services import mark_vehicle_assigned, mark_vehicle_available, update_vehicle_status
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'logistics_core.settings')
django.setup()
from django.db.models import Sum, Count
-from django.utils import timezone
-from rest_framework import viewsets, status, filters
+from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
-from datetime import datetime
from fleet.models import Vehicle, VehicleLocation
from django.conf import settings
@@ -29,12 +29,23 @@ class VehicleViewSet(viewsets.ModelViewSet):
"""
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
+ lookup_field = 'vehicle_id'
+ lookup_url_kwarg = 'vehicle_id'
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
- filterset_fields = ['status', 'fuel_type', 'depot_id']
+ filterset_fields = ['status', 'fuel_type', 'depot_id', 'driver_assigned']
search_fields = ['vehicle_id', 'name', 'plate_number', 'depot_id']
ordering_fields = ['vehicle_id', 'capacity', 'status', 'created_at']
ordering = ['vehicle_id']
+ def list(self, request, *args, **kwargs):
+ """
+ Override default list to return limited truck data only.
+ """
+ queryset = self.filter_queryset(self.get_queryset())
+
+ serializer = VehicleSummarySerializer(queryset, many=True)
+ return Response({'vehicles': serializer.data})
+
def get_serializer_class(self):
if self.action == 'retrieve':
return VehicleDetailSerializer
@@ -64,20 +75,20 @@ def get_queryset(self):
return queryset
@action(detail=True, methods=['post'])
- def mark_available(self, request, pk=None):
+ def mark_available(self, request, vehicle_id=None):
vehicle = self.get_object()
mark_vehicle_available(vehicle)
return Response({'vehicle_id': vehicle.vehicle_id, 'status': 'available'})
@action(detail=True, methods=['post'])
- def mark_assigned(self, request, pk=None):
+ def mark_assigned(self, request, vehicle_id=None):
vehicle = self.get_object()
mark_vehicle_assigned(vehicle)
return Response({'vehicle_id': vehicle.vehicle_id, 'status': 'assigned'})
# Admin only
@action(detail=True, methods=['post'])
- def change_status(self, request, pk=None):
+ def change_status(self, request, vehicle_id=None):
vehicle = self.get_object()
new_status = request.data.get('status')
@@ -88,8 +99,9 @@ def change_status(self, request, pk=None):
update_vehicle_status(vehicle, new_status)
return Response({'vehicle_id': vehicle.vehicle_id, 'status': new_status})
+
@action(detail=True, methods=['post'])
- def update_location(self, request, pk=None):
+ def update_location(self, request, vehicle_id=None):
vehicle = self.get_object()
latitude = request.data.get('latitude')
longitude = request.data.get('longitude')
@@ -113,10 +125,10 @@ def update_location(self, request, pk=None):
return Response({'error': str(e)}, status=400)
@action(detail=True, methods=['post'])
- def assign_depot(self, request, pk=None):
- """
+ def assign_depot(self, request, vehicle_id=None):
+ f"""
Assign or update a vehicle's depot.
- POST /api/fleet/vehicles/{id}/assign_depot/
+ POST /api/fleet/vehicles/{vehicle_id}/assign_depot/
{
"depot_id": "WHS001",
"latitude": 6.9271,
@@ -171,7 +183,6 @@ def stats(self, request):
'maintenance_count': maintenance_count,
'utilization_rate': utilization_rate
})
-
@action(detail=False, methods=['get'])
def by_depot(self, request):
depot_id = request.query_params.get('depot_id')
@@ -193,6 +204,15 @@ def depot_stats(self, request):
return Response({'by_depot': stats})
+ @action(detail=True, methods=['get'], url_path='location_overview')
+ def location_overview(self, request, vehicle_id=None):
+ """
+ GET /api/fleet/vehicles//location_overview/
+ """
+ vehicle = self.get_object()
+ serializer = VehicleLocationDetailSerializer(vehicle)
+ return Response(serializer.data)
+
# # To be implemented with maintenance part
# @action(detail=True, methods=['post'])
# def change_status(self, request, pk=None):
diff --git a/logistics_core/settings.py b/logistics_core/settings.py
index bedafec..9ca5b9b 100644
--- a/logistics_core/settings.py
+++ b/logistics_core/settings.py
@@ -12,7 +12,11 @@
from pathlib import Path
import os
+from dotenv import load_dotenv
+load_dotenv()
+
+BASE_DIR = Path(__file__).resolve().parent.parent
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -26,6 +30,7 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
+print(os.getenv('ALLOWED_HOSTS'))
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
diff --git a/requirements.txt b/requirements.txt
index 91625e4..4870216 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
absl-py==2.2.2
asgiref==3.8.1
confluent-kafka==2.10.0
-Django==5.2
+Django~=5.2.1
django-cors-headers==4.7.0
django-filter==25.1
djangorestframework==3.16.0
@@ -24,3 +24,7 @@ six==1.17.0
sqlparse==0.5.3
tzdata==2025.2
uritemplate==4.1.1
+
+kafka~=1.3.5
+requests~=2.32.3
+django-environ~=0.12.0
\ No newline at end of file