Skip to content

Commit

Permalink
feat: Restructure stations and lines apps by removing unused files, a…
Browse files Browse the repository at this point in the history
…dding new models, and enhancing admin configurations
  • Loading branch information
AhmedNassar7 committed Dec 11, 2024
1 parent 75188da commit 6d2be92
Show file tree
Hide file tree
Showing 19 changed files with 189 additions and 54 deletions.
3 changes: 0 additions & 3 deletions apps/lines/admin.py

This file was deleted.

6 changes: 0 additions & 6 deletions apps/lines/apps.py

This file was deleted.

Empty file removed apps/lines/migrations/__init__.py
Empty file.
20 changes: 0 additions & 20 deletions apps/lines/models.py

This file was deleted.

3 changes: 0 additions & 3 deletions apps/lines/tests.py

This file was deleted.

3 changes: 0 additions & 3 deletions apps/lines/views.py

This file was deleted.

47 changes: 46 additions & 1 deletion apps/stations/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
# apps/stations/admin.py

from django.contrib import admin
from .models import Line, Station, LineStation

class LineStationInline(admin.TabularInline):
"""
Inline admin for LineStation to manage the relationship between lines and stations.
"""
model = LineStation
extra = 1
fields = ["station", "order"]
ordering = ["order"]


@admin.register(Line)
class LineAdmin(admin.ModelAdmin):
"""
Admin configuration for the Line model.
"""
list_display = ("name", "color_code", "total_stations")
search_fields = ("name",)
inlines = [LineStationInline]


@admin.register(Station)
class StationAdmin(admin.ModelAdmin):
"""
Admin configuration for the Station model.
"""
list_display = ("name", "latitude", "longitude", "is_interchange")
search_fields = ("name",)
inlines = [LineStationInline] # Use the inline for managing lines
fieldsets = (
(None, {
"fields": ("name", "latitude", "longitude"),
}),
)


# Register your models here.
@admin.register(LineStation)
class LineStationAdmin(admin.ModelAdmin):
"""
Admin configuration for the LineStation model.
"""
list_display = ("line", "station", "order")
list_filter = ("line",)
ordering = ["line", "order"]
3 changes: 2 additions & 1 deletion apps/stations/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.apps import AppConfig
# apps/stations/apps.py

from django.apps import AppConfig

class StationsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
Expand Down
53 changes: 53 additions & 0 deletions apps/stations/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 5.1.3 on 2024-12-11 19:48

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


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Line',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('color_code', models.CharField(blank=True, help_text='Format: #RRGGBB', max_length=10, null=True)),
],
),
migrations.CreateModel(
name='LineStation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField()),
('line', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='line_stations', to='stations.line')),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='Station',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('latitude', models.FloatField(blank=True, null=True)),
('longitude', models.FloatField(blank=True, null=True)),
('lines', models.ManyToManyField(related_name='stations', through='stations.LineStation', to='stations.line')),
],
),
migrations.AddField(
model_name='linestation',
name='station',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='station_lines', to='stations.station'),
),
migrations.AlterUniqueTogether(
name='linestation',
unique_together={('line', 'station')},
),
]
32 changes: 26 additions & 6 deletions apps/stations/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
# apps/stations/models.py

from django.db import models
from lines.models import Line
from geopy.distance import geodesic # type: ignore

# Create your models here.
class Line(models.Model):
"""Represents a metro line with stations connected in order."""
name = models.CharField(max_length=255, unique=True, null=False) # Line name
color_code = models.CharField(
max_length=10, null=True, blank=True, help_text="Format: #RRGGBB"
) # Optional color code

def __str__(self):
return self.name

def total_stations(self):
"""Returns the total number of stations on this line."""
return self.line_stations.count()

def ordered_stations(self):
"""Returns all stations in order."""
return self.line_stations.order_by("order")


class Station(models.Model):
name = models.CharField(max_length=255, unique=True, null=False) # Station name
latitude = models.FloatField(null=True, blank=True) # GPS latitude
Expand All @@ -22,11 +41,12 @@ def is_interchange(self):
return self.lines.count() > 1

def get_station_order(self, line):
"""Get the order of this station on a specific line."""
try:
return self.station_lines.get(line=line).order
except LineStation.DoesNotExist:
return None
"""
Get the order of this station on a specific line.
Returns None if the station is not associated with the given line.
"""
line_station = self.station_lines.filter(line=line).first()
return line_station.order if line_station else None

def distance_to(self, other_station):
""" Calculate distance (in meters) between two stations using lat-long."""
Expand Down
1 change: 1 addition & 0 deletions apps/stations/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file marks the directory as a Python package.
2 changes: 2 additions & 0 deletions apps/stations/services/route_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# apps/stations/services/route_service.py

from collections import defaultdict
import heapq

Expand Down
1 change: 1 addition & 0 deletions apps/stations/services/ticket_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# apps/stations/services/ticket_service.py

def calculate_ticket_price(start_station, end_station):
"""
Calculate the ticket price based on the number of stations between start and end.
Expand Down
File renamed without changes.
14 changes: 8 additions & 6 deletions apps/stations/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# apps/stations/views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http import JsonResponse
from stations.models import Station
from apps.stations.models import Station
from .services.route_service import RouteService
from stations.services.ticket_service import calculate_ticket_price
from .utils.location_helpers import find_nearest_station
from apps.stations.services.ticket_service import calculate_ticket_price
from apps.stations.utils.location_helpers import find_nearest_station
from geopy.distance import geodesic # type: ignore

# Create your views here.
Expand Down Expand Up @@ -42,13 +44,13 @@ def get(self, request, start_station_id, end_station_id):

class NearestStationView(APIView):
def get(self, request):
latitude = request.query_params.get("latitude")
longitude = request.query_params.get("longitude")
latitude = float(request.query_params.get("latitude"))
longitude = float(request.query_params.get("longitude"))

if not latitude or not longitude:
return Response({"error": "Latitude and Longitude are required"}, status=400)

nearest_station, distance = find_nearest_station(float(latitude), float(longitude))
nearest_station, distance = find_nearest_station(latitude, longitude)
return Response({
"nearest_station": nearest_station.name,
"distance": round(distance, 2),
Expand Down
4 changes: 4 additions & 0 deletions apps/users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ class UserAdmin(admin.ModelAdmin):

def has_delete_permission(self, request, obj=None):
return False # Disable user deletion

admin.site.site_header = "Egypt Metro"
admin.site.site_title = "Egypt Metro Admin Portal"
admin.site.index_title = "Welcome to Egypt Metro Admin Portal"
26 changes: 23 additions & 3 deletions egypt_metro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@
'django.contrib.messages',
'django.contrib.staticfiles',
# External packages
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'debug_toolbar',
# Custom apps
'apps.users.apps.UsersConfig',
'apps.stations.apps.UsersConfig',
'apps.lines.apps.UsersConfig',
'apps.stations.apps.StationsConfig',
]

MIDDLEWARE = [
Expand All @@ -58,6 +61,7 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
"allauth.account.middleware.AccountMiddleware",
]

ROOT_URLCONF = 'egypt_metro.urls'
Expand All @@ -76,6 +80,7 @@
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
],
},
},
Expand Down Expand Up @@ -144,6 +149,21 @@
},
]

AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', # For admin logins
'allauth.account.auth_backends.AuthenticationBackend',
]

SOCIALACCOUNT_PROVIDERS = {
'google': {
'APP': {
'client_id': 'your-client-id',
'secret': 'your-secret',
'key': ''
}
}
}

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
Expand All @@ -157,7 +177,7 @@
}

SIMPLE_JWT = {
'SIGNING_KEY': JWT_SECRET,
'SIGNING_KEY': os.getenv("JWT_SECRET"),
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
Expand Down
4 changes: 2 additions & 2 deletions egypt_metro/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

urlpatterns = [
path('admin/', admin.site.urls), # Django admin panel
path('accounts/', include('allauth.urls')), # Allauth authentication routes
path('api/users/', include('apps.users.urls')), # User-related API routes
path('api/stations/', include('apps.stations.urls')), # Station-related API routes
path('health/', health_check, name='health_check'), # Health check endpoint
path('api/stations/', include('apps.stations.urls')),
path('api/lines/', include('apps.lines.urls')),
]

if settings.DEBUG:
Expand Down
Loading

0 comments on commit 6d2be92

Please sign in to comment.