Skip to content

Commit

Permalink
feat: Implement user management features with authentication, profile…
Browse files Browse the repository at this point in the history
… handling, and error logging
  • Loading branch information
AhmedNassar7 committed Feb 28, 2025
1 parent c915b13 commit 36ce8cb
Show file tree
Hide file tree
Showing 67 changed files with 3,386 additions and 1,038 deletions.
102 changes: 42 additions & 60 deletions apps/trains/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from import_export.admin import ImportExportModelAdmin
from rangefilter.filters import DateRangeFilter

from .models import ActualSchedule, CrowdMeasurement, Schedule, Train, TrainCar
from .models import CrowdMeasurement, Schedule, Train, TrainCar


class TrainCarInline(admin.TabularInline):
Expand Down Expand Up @@ -48,12 +48,10 @@ class ScheduleInline(admin.TabularInline):
fields = (
"station",
"arrival_time",
"departure_time",
"day_type",
"sequence_number",
"expected_crowd_level",
"is_active",
)
ordering = ("day_type", "sequence_number")
ordering = ("arrival_time",)


@admin.register(Train)
Expand Down Expand Up @@ -251,87 +249,71 @@ class ScheduleAdmin(ImportExportModelAdmin):
"train_link",
"station_link",
"arrival_time",
"departure_time",
"day_type",
"sequence_number",
"status",
"expected_crowd_level",
"is_active",
)
list_filter = (
"status",
"is_active",
"expected_crowd_level",
"train__line",
("created_at", DateRangeFilter)
)
list_filter = ("day_type", "is_active", "train__line", ("last_updated", DateRangeFilter))
search_fields = ("train__train_id", "station__name")
ordering = ("train", "day_type", "sequence_number")
ordering = ("arrival_time",)
actions = ["activate_schedules", "deactivate_schedules"]
readonly_fields = ("last_updated",)
readonly_fields = ("created_at", "updated_at")

fieldsets = (
("Basic Information", {
"fields": (
"train",
"station",
)
}),
("Timing", {
"fields": (
"arrival_time",
)
}),
("Status", {
"fields": (
"status",
"is_active",
"expected_crowd_level",
)
}),
("Metadata", {
"fields": (
"created_at",
"updated_at",
),
"classes": ("collapse",)
}),
)

def train_link(self, obj):
url = reverse("admin:trains_train_change", args=[obj.train.id])
return format_html('<a href="{}">{}</a>', url, obj.train.train_id)

train_link.short_description = "Train"

def station_link(self, obj):
url = reverse("admin:stations_station_change", args=[obj.station.id])
return format_html('<a href="{}">{}</a>', url, obj.station.name)

station_link.short_description = "Station"

def activate_schedules(self, request, queryset):
updated = queryset.update(is_active=True)
self.message_user(request, f"{updated} schedules activated.")

activate_schedules.short_description = "Activate selected schedules"

def deactivate_schedules(self, request, queryset):
updated = queryset.update(is_active=False)
self.message_user(request, f"{updated} schedules deactivated.")

deactivate_schedules.short_description = "Deactivate selected schedules"


@admin.register(ActualSchedule)
class ActualScheduleAdmin(admin.ModelAdmin):
list_display = (
"schedule_link",
"status_badge",
"actual_arrival",
"actual_departure",
"delay_minutes",
"created_at",
)
list_filter = ("status", ("created_at", DateRangeFilter), "schedule__train__line")
search_fields = ("schedule__train__train_id", "schedule__station__name")
readonly_fields = ("delay_minutes", "created_at", "updated_at")
fieldsets = (
("Schedule Information", {"fields": ("schedule", "status", "reason")}),
("Timing Information", {"fields": ("actual_arrival", "actual_departure", "delay_minutes")}),
("Metadata", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}),
)

def schedule_link(self, obj):
url = reverse("admin:trains_schedule_change", args=[obj.schedule.id])
return format_html('<a href="{}">{} - {}</a>', url, obj.schedule.train.train_id, obj.schedule.station.name)

schedule_link.short_description = "Schedule"

def status_badge(self, obj):
colors = {
"ON_TIME": "green",
"DELAYED": "orange",
"CANCELLED": "red",
"SKIPPED": "gray",
"DIVERTED": "purple",
}
return format_html(
'<span style="color: white; background-color: {}; padding: 3px 7px;'
' border-radius: 3px;">'
"{}"
"</span>",
colors.get(obj.status, "black"),
obj.status,
)

status_badge.short_description = "Status"


@admin.register(CrowdMeasurement)
class CrowdMeasurementAdmin(admin.ModelAdmin):
list_display = (
Expand Down
157 changes: 121 additions & 36 deletions apps/trains/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,155 @@
# apps/trains/api/serializers.py

from rest_framework import serializers
from ..models import Train, Schedule, TrainCar, CrowdMeasurement

from ..models import CrowdMeasurement, Schedule, Train

class CrowdLevelSerializer(serializers.ModelSerializer):
"""Serializer for crowd level information."""

class TrainSerializer(serializers.ModelSerializer):
class Meta:
model = Train
fields = "__all__"
model = CrowdMeasurement
fields = [
'id',
'passenger_count',
'crowd_percentage',
'confidence_score',
'measurement_method',
'timestamp'
]

def to_representation(self, instance):
data = super().to_representation(instance)
data["line_name"] = instance.line.name
data["current_station_name"] = instance.current_station.name if instance.current_station else None
return data

class TrainCarSerializer(serializers.ModelSerializer):
"""Serializer for train car information."""

crowd_level = serializers.SerializerMethodField()

class Meta:
model = TrainCar
fields = [
'car_number',
'capacity',
'current_load',
'crowd_status',
'load_percentage',
'is_operational',
'crowd_level'
]

def get_crowd_level(self, obj):
return {
'level': obj.crowd_status,
'percentage': obj.load_percentage,
'available_seats': max(0, obj.capacity - obj.current_load)
}


class ScheduleSerializer(serializers.ModelSerializer):
"""Serializer for train schedules with computed fields."""

station_name = serializers.CharField(source='station.name', read_only=True)
train_id = serializers.CharField(source='train.train_id', read_only=True)
delay_duration = serializers.IntegerField(read_only=True)
line_name = serializers.CharField(source='train.line.name', read_only=True)

class Meta:
model = Schedule
fields = "__all__"
fields = [
'id',
'train_id',
'station_name',
'line_name',
'arrival_time',
'status',
'expected_crowd_level',
'is_active',
'delay_duration',
]
read_only_fields = ['delay_duration', 'is_active']

def to_representation(self, instance):
"""Add computed fields to the representation"""
data = super().to_representation(instance)
data["station_name"] = instance.station.name
data["train_id"] = instance.train.train_id
data['is_delayed'] = instance.is_delayed
data['is_cancelled'] = instance.is_cancelled
return data


class TrainStatusSerializer(serializers.Serializer):
train_id = serializers.CharField()
current_location = serializers.DictField()
speed = serializers.FloatField()
status = serializers.CharField()
next_station = serializers.CharField()
estimated_arrival = serializers.DateTimeField()
crowd_level = serializers.CharField()
class TrainSerializer(serializers.ModelSerializer):
"""Serializer for train information."""

cars = TrainCarSerializer(many=True, read_only=True)
line_name = serializers.CharField(source='line.name', read_only=True)
current_station_name = serializers.CharField(source='current_station.name', read_only=True)
next_station_name = serializers.CharField(source='next_station.name', read_only=True)

class CrowdLevelSerializer(serializers.ModelSerializer):
class Meta:
model = CrowdMeasurement
fields = ["timestamp", "passenger_count", "crowd_percentage", "confidence_score"]
model = Train
fields = [
'id',
'train_id',
'line_name',
'status',
'has_air_conditioning',
'current_station_name',
'next_station_name',
'direction',
'speed',
'cars',
'last_updated'
]


class TrainDetailSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(many=True, read_only=True, source="schedule_set")
crowd_measurements = CrowdLevelSerializer(many=True, read_only=True, source="crowdmeasurement_set")
"""Detailed train serializer including schedules and crowd information."""

cars = TrainCarSerializer(many=True, read_only=True)
schedules = ScheduleSerializer(many=True, read_only=True)
line_name = serializers.CharField(source='line.name', read_only=True)
current_station_name = serializers.CharField(source='current_station.name', read_only=True)
next_station_name = serializers.CharField(source='next_station.name', read_only=True)
crowd_measurements = CrowdLevelSerializer(many=True, read_only=True)

class Meta:
model = Train
fields = [
"train_id",
"line",
"status",
"current_station",
"next_station",
"last_updated",
"schedule",
"crowd_measurements",
'id',
'train_id',
'line_name',
'status',
'has_air_conditioning',
'current_station_name',
'next_station_name',
'direction',
'speed',
'cars',
'schedules',
'crowd_measurements',
'last_updated'
]

def to_representation(self, instance):
"""Add additional computed fields to the representation."""
data = super().to_representation(instance)
data["line_name"] = instance.line.name
data["current_station_name"] = instance.current_station.name if instance.current_station else None
data["next_station_name"] = instance.next_station.name if instance.next_station else None

# Add real-time information
data['is_delayed'] = instance.status == 'DELAYED'
data['is_operational'] = instance.status in ['IN_SERVICE', 'DELAYED']

# Add crowd summary
cars_data = data.get('cars', [])
if cars_data:
total_load = sum(car['current_load'] for car in cars_data)
total_capacity = sum(car['capacity'] for car in cars_data)
data['total_passengers'] = total_load
data['occupancy_percentage'] = round((total_load / total_capacity) * 100, 1) if total_capacity else 0

return data


class CrowdSummarySerializer(serializers.Serializer):
"""Serializer for crowd summary information."""

train_id = serializers.IntegerField()
crowd_data = serializers.ListField(child=serializers.DictField())
is_ac = serializers.BooleanField()
18 changes: 18 additions & 0 deletions apps/trains/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@
# Additional train views (if needed)
path("list/", views.TrainListView.as_view(), name="train-list-custom"),
path("<str:train_id>/details/", views.TrainDetailView.as_view(), name="train-detail-custom"),
# station schedules
path('station/<str:station_id>/schedules/',
views.StationTrainSchedulesView.as_view(),
name='station-schedules'),
# station schedules using query parameter
path('station-schedules/',
views.StationTrainSchedulesView.as_view(),
name='station-schedules-query'),

# station schedules
path('station-schedules/between/',
views.StationSchedulesView.as_view(),
name='station-schedules-between'),
path(
'stations/upcoming-trains/',
views.StationUpcomingTrainsView.as_view(),
name='station-upcoming-trains'
),
]

# API Endpoint Documentation
Expand Down
Loading

0 comments on commit 36ce8cb

Please sign in to comment.