diff --git a/transport_accessibility/pt_map/bridge.py b/transport_accessibility/pt_map/bridge.py index 1eeac0a..9350ecc 100644 --- a/transport_accessibility/pt_map/bridge.py +++ b/transport_accessibility/pt_map/bridge.py @@ -50,77 +50,7 @@ import datetime import django.db.models import time from pt_map.gtfs_schema import gtfs_schema - -time_delta = int(datetime.datetime(2024,1,1).timestamp()) - - - -primary_keys = { pt_map.models.Agency: "agency_id", - pt_map.models.Level: "level_id", - pt_map.models.Stop: "stop_id", - pt_map.models.Route: "route_id", - pt_map.models.Shape: "shape_id", - pt_map.models.Calendar: "service_id", - pt_map.models.CalendarDate: None, - pt_map.models.Trip: "trip_id", - pt_map.models.LocationGroup: "location_group_id", - pt_map.models.LocationsGeojson: None, - pt_map.models.StopTime: None, - pt_map.models.FareAttribute: "fare_id", - pt_map.models.FareRule: None, - pt_map.models.Frequency: None, - pt_map.models.Transfer: None, - pt_map.models.Pathway: "pathway_id", - pt_map.models.FeedInfo: None, - pt_map.models.BookingRule: "booking_rule_id", - pt_map.models.Translation: None, - pt_map.models.Attribution: "attribution_id", - pt_map.models.LocationGroupStop: None, - pt_map.models.Network: "network_id", - pt_map.models.RouteNetwork: None, - pt_map.models.Area: None, - pt_map.models.StopArea: None, - pt_map.models.FareMedium: "fare_media_id", - pt_map.models.FareProduct: None, - pt_map.models.Timeframe: None, - pt_map.models.FareLegRule: None, - pt_map.models.FareTransferRule: None, - } - -foreign_keys = [ - (pt_map.models.Agency, []), - (pt_map.models.Level, []), - (pt_map.models.Stop, [(pt_map.models.Stop, 'parent_station'), (pt_map.models.Level, 'level_id'), ]), - (pt_map.models.Route, [(pt_map.models.Agency, 'agency_id'), ]), - (pt_map.models.Shape, []), - (pt_map.models.Calendar, []), - (pt_map.models.CalendarDate, []), - (pt_map.models.Trip, [(pt_map.models.Route, 'route_id'), (pt_map.models.Shape, 'shape_id'), ]), - (pt_map.models.LocationGroup, []), - (pt_map.models.LocationsGeojson, []), - (pt_map.models.StopTime, [(pt_map.models.Trip, 'trip_id'), (pt_map.models.Stop, 'stop_id'), (pt_map.models.LocationGroup, 'location_group_id'), (pt_map.models.LocationsGeojson, 'location_id'), ]), - (pt_map.models.FareAttribute, [(pt_map.models.Agency, 'agency_id'), ]), - (pt_map.models.FareRule, [(pt_map.models.FareAttribute, 'fare_id'), (pt_map.models.Route, 'route_id'), ]), - (pt_map.models.Frequency, [(pt_map.models.Trip, 'trip_id'), ]), - (pt_map.models.Transfer, [(pt_map.models.Stop, 'from_stop_id'), (pt_map.models.Stop, 'to_stop_id'), (pt_map.models.Route, 'from_route_id'), (pt_map.models.Route, 'to_route_id'), (pt_map.models.Trip, 'from_trip_id'), (pt_map.models.Trip, 'to_trip_id'), ]), - (pt_map.models.Pathway, [(pt_map.models.Stop, 'from_stop_id'), (pt_map.models.Stop, 'to_stop_id'), ]), - (pt_map.models.FeedInfo, []), - (pt_map.models.BookingRule, [(pt_map.models.Trip, 'trip_id'), ]), - (pt_map.models.Translation, []), - (pt_map.models.Attribution, [(pt_map.models.Agency, 'agency_id'), (pt_map.models.Route, 'route_id'), (pt_map.models.Trip, 'trip_id'), ]), - (pt_map.models.LocationGroupStop, [(pt_map.models.LocationGroup, 'location_group_id'), (pt_map.models.Stop, 'stop_id'), ]), - (pt_map.models.Network, []), - (pt_map.models.RouteNetwork, [(pt_map.models.Network, 'network_id'), (pt_map.models.Route, 'route_id'), ]), - (pt_map.models.Area, []), - (pt_map.models.StopArea, [(pt_map.models.Area, 'area_id'), (pt_map.models.Stop, 'stop_id'), ]), - (pt_map.models.FareMedium, []), - (pt_map.models.FareProduct, []), - (pt_map.models.Timeframe, []), - (pt_map.models.FareLegRule, [(pt_map.models.Network, 'network_id'), (pt_map.models.Area, 'from_area_id'), (pt_map.models.Area, 'to_area_id'), (pt_map.models.Timeframe, 'from_timeframe_group_id'), (pt_map.models.Timeframe, 'to_timeframe_group_id'), (pt_map.models.FareProduct, 'fare_product_id'), ]), - (pt_map.models.FareTransferRule, [(pt_map.models.FareProduct, 'fare_product_id'), ]), -] - -class_names = {'Agency': 'agency', 'Stop': 'stops', 'Route': 'routes', 'Trip': 'trips', 'StopTime': 'stop_times', 'Calendar': 'calendar', 'CalendarDate': 'calendar_dates', 'FareAttribute': 'fare_attributes', 'FareRule': 'fare_rules', 'Timeframe': 'timeframes', 'FareMedium': 'fare_media', 'FareProduct': 'fare_products', 'FareLegRule': 'fare_leg_rules', 'FareTransferRule': 'fare_transfer_rules', 'Area': 'areas', 'StopArea': 'stop_areas', 'Network': 'networks', 'RouteNetwork': 'route_networks', 'Shape': 'shapes', 'Frequency': 'frequencies', 'Transfer': 'transfers', 'Pathway': 'pathways', 'Level': 'levels', 'LocationGroup': 'location_groups', 'LocationGroupStop': 'location_group_stops', 'LocationsGeojson': 'locations_geojson', 'BookingRule': 'booking_rules', 'Translation': 'translations', 'FeedInfo': 'feed_info', 'Attribution': 'attributions'} +from.class_names import * def toCamelCase(s: str): @@ -275,38 +205,6 @@ def gtfs_to_db(g: pt_map.gtfs.GTFS): m.objects.create(**defaults) -reversed_file_mapping = { - "Agency": "agency", - "Stop": "stops", - "Route": "routes", - "Trip": "trips", - "StopTime": "stop_times", - "Calendar": "calendar", - "CalendarDate": "calendar_dates", - "FareAttribute": "fare_attributes", - "FareRule": "fare_rules", - "Timeframe": "timeframes", - "FareMedium": "fare_media", - "FareProduct": "fare_products", - "FareLegRule": "fare_leg_rules", - "FareTransferRule": "fare_transfer_rules", - "Area": "areas", - "StopArea": "stop_areas", - "Network": "networks", - "RouteNetwork": "route_networks", - "Shape": "shapes", - "Frequency": "frequencies", - "Transfer": "transfers", - "Pathway": "pathways", - "Level": "levels", - "LocationGroup": "location_groups", - "LocationGroupStop": "location_group_stops", - "LocationsGeojson": "locations.geojson", - "BookingRule": "booking_rules", - "Translation": "translations", - "FeedInfo": "feed_info", - "Attribution": "attributions" -} def db_to_gtfs(q: list[django.db.models.query.QuerySet], folder_path: str = ""): diff --git a/transport_accessibility/pt_map/class_names.py b/transport_accessibility/pt_map/class_names.py index fb76571..818200d 100644 --- a/transport_accessibility/pt_map/class_names.py +++ b/transport_accessibility/pt_map/class_names.py @@ -3,6 +3,9 @@ Constant defining different variation of the file names in GTFS / our model name """ import pt_map.models +import datetime + +time_delta = int(datetime.datetime(2024,1,1).timestamp()) class_names = [ { @@ -120,7 +123,7 @@ class_names = [ pt_map.models.StopArea: "stop_areas", pt_map.models.Network: "networks", pt_map.models.RouteNetwork: "route_networks", - pt_map.models.Shape: "shapes", +pt_map.models.Shape: "shapes", pt_map.models.Frequency: "frequencies", pt_map.models.Transfer: "transfers", pt_map.models.Pathway: "pathways", @@ -136,3 +139,123 @@ class_names = [ ] +reversed_file_mapping = { + "Agency": "agency", + "Stop": "stops", + "Route": "routes", + "Trip": "trips", + "StopTime": "stop_times", + "Calendar": "calendar", + "CalendarDate": "calendar_dates", + "FareAttribute": "fare_attributes", + "FareRule": "fare_rules", + "Timeframe": "timeframes", + "FareMedium": "fare_media", + "FareProduct": "fare_products", + "FareLegRule": "fare_leg_rules", + "FareTransferRule": "fare_transfer_rules", + "Area": "areas", + "StopArea": "stop_areas", + "Network": "networks", + "RouteNetwork": "route_networks", + "Shape": "shapes", + "Frequency": "frequencies", + "Transfer": "transfers", + "Pathway": "pathways", + "Level": "levels", + "LocationGroup": "location_groups", + "LocationGroupStop": "location_group_stops", + "LocationsGeojson": "locations.geojson", + "BookingRule": "booking_rules", + "Translation": "translations", + "FeedInfo": "feed_info", + "Attribution": "attributions" +} + + +case_swap = {'Agency': 'agency', 'Stop': 'stops', 'Route': 'routes', 'Trip': 'trips', 'StopTime': 'stop_times', 'Calendar': 'calendar', 'CalendarDate': 'calendar_dates', 'FareAttribute': 'fare_attributes', 'FareRule': 'fare_rules', 'Timeframe': 'timeframes', 'FareMedium': 'fare_media', 'FareProduct': 'fare_products', 'FareLegRule': 'fare_leg_rules', 'FareTransferRule': 'fare_transfer_rules', 'Area': 'areas', 'StopArea': 'stop_areas', 'Network': 'networks', 'RouteNetwork': 'route_networks', 'Shape': 'shapes', 'Frequency': 'frequencies', 'Transfer': 'transfers', 'Pathway': 'pathways', 'Level': 'levels', 'LocationGroup': 'location_groups', 'LocationGroupStop': 'location_group_stops', 'LocationsGeojson': 'locations_geojson', 'BookingRule': 'booking_rules', 'Translation': 'translations', 'FeedInfo': 'feed_info', 'Attribution': 'attributions'} + + +primary_keys = { pt_map.models.Agency: "agency_id", + pt_map.models.Level: "level_id", + pt_map.models.Stop: "stop_id", + pt_map.models.Route: "route_id", + pt_map.models.Shape: "shape_id", + pt_map.models.Calendar: "service_id", + pt_map.models.CalendarDate: None, + pt_map.models.Trip: "trip_id", + pt_map.models.LocationGroup: "location_group_id", + pt_map.models.LocationsGeojson: None, + pt_map.models.StopTime: None, + pt_map.models.FareAttribute: "fare_id", + pt_map.models.FareRule: None, + pt_map.models.Frequency: None, + pt_map.models.Transfer: None, + pt_map.models.Pathway: "pathway_id", + pt_map.models.FeedInfo: None, + pt_map.models.BookingRule: "booking_rule_id", + pt_map.models.Translation: None, + pt_map.models.Attribution: "attribution_id", + pt_map.models.LocationGroupStop: None, + pt_map.models.Network: "network_id", + pt_map.models.RouteNetwork: None, + pt_map.models.Area: None, + pt_map.models.StopArea: None, + pt_map.models.FareMedium: "fare_media_id", + pt_map.models.FareProduct: None, + pt_map.models.Timeframe: None, + pt_map.models.FareLegRule: None, + pt_map.models.FareTransferRule: None, + } + + +classes_by_primary_keys = { "agency_id": pt_map.models.Agency, + "level_id": pt_map.models.Level, + "stop_id": pt_map.models.Stop, + "route_id": pt_map.models.Route, + "shape_id": pt_map.models.Shape, + "service_id": pt_map.models.Calendar, + "trip_id": pt_map.models.Trip, + "location_group_id": pt_map.models.LocationGroup, + "fare_id": pt_map.models.FareAttribute, + "pathway_id": pt_map.models.Pathway, + "booking_rule_id": pt_map.models.BookingRule, + "attribution_id": pt_map.models.Attribution, + "network_id": pt_map.models.Network, + "fare_media_id": pt_map.models.FareMedium, + } + +foreign_keys = [ + (pt_map.models.Agency, []), + (pt_map.models.Level, []), + (pt_map.models.Stop, [(pt_map.models.Stop, 'parent_station'), (pt_map.models.Level, 'level_id'), ]), + (pt_map.models.Route, [(pt_map.models.Agency, 'agency_id'), ]), + (pt_map.models.Shape, []), + (pt_map.models.Calendar, []), + (pt_map.models.CalendarDate, []), + (pt_map.models.Trip, [(pt_map.models.Route, 'route_id'), (pt_map.models.Shape, 'shape_id'), ]), + (pt_map.models.LocationGroup, []), + (pt_map.models.LocationsGeojson, []), + (pt_map.models.StopTime, [(pt_map.models.Trip, 'trip_id'), (pt_map.models.Stop, 'stop_id'), (pt_map.models.LocationGroup, 'location_group_id'), (pt_map.models.LocationsGeojson, 'location_id'), ]), + (pt_map.models.FareAttribute, [(pt_map.models.Agency, 'agency_id'), ]), + (pt_map.models.FareRule, [(pt_map.models.FareAttribute, 'fare_id'), (pt_map.models.Route, 'route_id'), ]), + (pt_map.models.Frequency, [(pt_map.models.Trip, 'trip_id'), ]), + (pt_map.models.Transfer, [(pt_map.models.Stop, 'from_stop_id'), (pt_map.models.Stop, 'to_stop_id'), (pt_map.models.Route, 'from_route_id'), (pt_map.models.Route, 'to_route_id'), (pt_map.models.Trip, 'from_trip_id'), (pt_map.models.Trip, 'to_trip_id'), ]), + (pt_map.models.Pathway, [(pt_map.models.Stop, 'from_stop_id'), (pt_map.models.Stop, 'to_stop_id'), ]), + (pt_map.models.FeedInfo, []), + (pt_map.models.BookingRule, [(pt_map.models.Trip, 'trip_id'), ]), + (pt_map.models.Translation, []), + (pt_map.models.Attribution, [(pt_map.models.Agency, 'agency_id'), (pt_map.models.Route, 'route_id'), (pt_map.models.Trip, 'trip_id'), ]), + (pt_map.models.LocationGroupStop, [(pt_map.models.LocationGroup, 'location_group_id'), (pt_map.models.Stop, 'stop_id'), ]), + (pt_map.models.Network, []), + (pt_map.models.RouteNetwork, [(pt_map.models.Network, 'network_id'), (pt_map.models.Route, 'route_id'), ]), + (pt_map.models.Area, []), + (pt_map.models.StopArea, [(pt_map.models.Area, 'area_id'), (pt_map.models.Stop, 'stop_id'), ]), + (pt_map.models.FareMedium, []), + (pt_map.models.FareProduct, []), + (pt_map.models.Timeframe, []), + (pt_map.models.FareLegRule, [(pt_map.models.Network, 'network_id'), (pt_map.models.Area, 'from_area_id'), (pt_map.models.Area, 'to_area_id'), (pt_map.models.Timeframe, 'from_timeframe_group_id'), (pt_map.models.Timeframe, 'to_timeframe_group_id'), (pt_map.models.FareProduct, 'fare_product_id'), ]), + (pt_map.models.FareTransferRule, [(pt_map.models.FareProduct, 'fare_product_id'), ]), +] + +fk_dict = {fk[0]: fk[1] for fk in foreign_keys} diff --git a/transport_accessibility/pt_map/forms.py b/transport_accessibility/pt_map/forms.py index 5b5ca87..41e53e4 100644 --- a/transport_accessibility/pt_map/forms.py +++ b/transport_accessibility/pt_map/forms.py @@ -3,3 +3,5 @@ from django import forms class Form1(forms.Form): title = forms.CharField(max_length=255) file = forms.FileField() + + diff --git a/transport_accessibility/pt_map/models.py b/transport_accessibility/pt_map/models.py index 1cd8482..0575d5f 100644 --- a/transport_accessibility/pt_map/models.py +++ b/transport_accessibility/pt_map/models.py @@ -25,6 +25,7 @@ class Agency(models.Model): agency_phone = models.CharField(max_length=50, blank=True, null=True) agency_fare_url = models.URLField(blank=True, null=True) agency_email = models.EmailField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Level(models.Model): """ @@ -33,6 +34,7 @@ class Level(models.Model): level_id = models.CharField(max_length=255, primary_key=True) level_index = models.FloatField() level_name = models.CharField(max_length=255, blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Stop(models.Model): """ @@ -52,6 +54,7 @@ class Stop(models.Model): wheelchair_boarding = models.IntegerField(blank=True, null=True) level_id = models.ForeignKey(Level, on_delete=models.SET_NULL, blank=True, null=True) platform_code = models.CharField(max_length=50, blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Route(models.Model): """ @@ -69,6 +72,7 @@ class Route(models.Model): route_sort_order = models.IntegerField(blank=True, null=True) continuous_pickup = models.IntegerField(blank=True, null=True) continuous_drop_off = models.IntegerField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Shape(models.Model): """ @@ -79,6 +83,7 @@ class Shape(models.Model): shape_pt_lon = models.FloatField() shape_pt_sequence = models.IntegerField() shape_dist_traveled = models.FloatField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Meta: unique_together = (('shape_id', 'shape_pt_sequence'),) @@ -87,6 +92,7 @@ class Calendar(models.Model): """ Represents calendar.txt from the GTFS Reference. """ + calendar_id = models.BigAutoField(primary_key=True) service_id = models.CharField(max_length=255,primary_key=True) monday = models.BooleanField() tuesday = models.BooleanField() @@ -97,14 +103,17 @@ class Calendar(models.Model): sunday = models.BooleanField() start_date = models.DateField() end_date = models.DateField() + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class CalendarDate(models.Model): """ Represents calendar_date.txt from the GTFS Reference. """ + calendar_date_id = models.BigAutoField(primary_key=True) service_id = models.CharField(max_length=255) date = models.DateField() exception_type = models.IntegerField() + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Meta: unique_together = (('service_id', 'date'),) @@ -137,6 +146,7 @@ class Trip(models.Model): "wheelchair_accessible": self.wheelchair_accessible, "bikes_allowed": self.bikes_allowed, } + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class LocationGroup(models.Model): """ @@ -145,6 +155,7 @@ class LocationGroup(models.Model): location_group_id = models.CharField(max_length=255, primary_key=True) location_group_name = models.CharField(max_length=255) location_group_type = models.CharField(max_length=255) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class LocationsGeojson(models.Model): """ @@ -157,11 +168,13 @@ class LocationsGeojson(models.Model): location_type = models.CharField(max_length=255) parent_location_id = models.CharField(max_length=255, blank=True, null=True) wheelchair_boarding = models.BooleanField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class StopTime(models.Model): """ Represents stop_time.txt from the GTFS Reference. """ + stop_time_id = models.BigAutoField(primary_key=True) trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) arrival_time = models.TimeField(blank=True, null=True) departure_time = models.TimeField(blank=True, null=True) @@ -174,6 +187,7 @@ class StopTime(models.Model): drop_off_type = models.IntegerField(blank=True, null=True) shape_dist_traveled = models.FloatField(blank=True, null=True) timepoint = models.IntegerField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Meta: unique_together = (('trip_id', 'stop_sequence'),) @@ -189,6 +203,7 @@ class FareAttribute(models.Model): transfers = models.IntegerField() agency_id = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) transfer_duration = models.IntegerField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FareRule(models.Model): """ @@ -199,6 +214,7 @@ class FareRule(models.Model): origin_id = models.IntegerField(blank=True, null=True) destination_id = models.CharField(max_length=255, blank=True, null=True) contains_id = models.CharField(max_length=255, blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Frequency(models.Model): """ @@ -209,6 +225,7 @@ class Frequency(models.Model): end_time = models.TimeField() headway_secs = models.IntegerField() exact_times = models.IntegerField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Transfer(models.Model): """ @@ -222,6 +239,7 @@ class Transfer(models.Model): to_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_trip') transfer_type = models.IntegerField() min_transfer_time = models.IntegerField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Meta: unique_together = (('from_stop_id', 'to_stop_id'),) @@ -242,11 +260,13 @@ class Pathway(models.Model): min_width = models.FloatField(blank=True, null=True) signposted_as = models.CharField(max_length=255, blank=True, null=True) reversed_signposted_as = models.CharField(max_length=255, blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FeedInfo(models.Model): """ Represents feed_info.txt from the GTFS Reference. """ + feed_info_id = models.BigAutoField(primary_key=True) feed_publisher_name = models.CharField(max_length=255) feed_publisher_url = models.URLField() feed_lang = models.CharField(max_length=255) @@ -257,6 +277,7 @@ class FeedInfo(models.Model): feed_contact_email = models.EmailField(blank=True, null=True) feed_contact_url = models.URLField(blank=True, null=True) feed_id = models.BigAutoField(primary_key=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class BookingRule(models.Model): """ @@ -269,6 +290,7 @@ class BookingRule(models.Model): booking_type = models.CharField(max_length=255) rule_criteria = models.TextField(blank=True, null=True) booking_rule_instructions = models.TextField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Translation(models.Model): """ @@ -278,6 +300,7 @@ class Translation(models.Model): field_name = models.CharField(max_length=255) language = models.CharField(max_length=2) translation = models.TextField() + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Attribution(models.Model): """ @@ -291,6 +314,7 @@ class Attribution(models.Model): attribution_url = models.URLField() attribution_email = models.EmailField(blank=True, null=True) attribution_phone = models.CharField(max_length=50, blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class LocationGroupStop(models.Model): """ @@ -298,6 +322,7 @@ class LocationGroupStop(models.Model): """ location_group_id = models.ForeignKey(LocationGroup, on_delete=models.CASCADE) stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Network(models.Model): """ @@ -305,6 +330,7 @@ class Network(models.Model): """ network_id = models.CharField(max_length=255, primary_key=True) network_name = models.CharField(max_length=255) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class RouteNetwork(models.Model): """ @@ -314,6 +340,7 @@ class RouteNetwork(models.Model): route_network_name = models.CharField(max_length=255) network_id = models.ForeignKey(Network, on_delete=models.CASCADE) route_id = models.ForeignKey(Route, on_delete=models.CASCADE) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Area(models.Model): """ @@ -322,6 +349,7 @@ class Area(models.Model): area_id = models.CharField(max_length=255, primary_key=True) area_name = models.CharField(max_length=255) area_description = models.TextField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class StopArea(models.Model): """ @@ -332,6 +360,7 @@ class StopArea(models.Model): stop_area_description = models.TextField(blank=True, null=True) area_id = models.ForeignKey(Area, on_delete=models.CASCADE) stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FareMedium(models.Model): """ @@ -340,6 +369,7 @@ class FareMedium(models.Model): fare_media_id = models.CharField(max_length=255, primary_key=True) fare_media_name = models.CharField(max_length=255) fare_media_description = models.TextField(blank=True, null=True) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FareProduct(models.Model): """ @@ -350,6 +380,7 @@ class FareProduct(models.Model): fare_product_description = models.TextField(blank=True, null=True) amount = models.FloatField() curreny = models.CharField(max_length=64) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class Timeframe(models.Model): """ @@ -361,6 +392,7 @@ class Timeframe(models.Model): end_date = models.DateField() start_time = models.TimeField() end_time = models.TimeField() + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FareLegRule(models.Model): """ @@ -375,6 +407,7 @@ class FareLegRule(models.Model): from_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_timeframe') to_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_timeframe') fare_product_id = models.ForeignKey(FareProduct, on_delete=models.CASCADE) + feed_info_id = models.ForeignKey(Feed_Info, on_delete=models.CASCADE, blank=True, null=True) class FareTransferRule(models.Model): """ @@ -388,4 +421,3 @@ class FareTransferRule(models.Model): fare_product_id = models.ForeignKey(FareProduct, on_delete=models.SET_NULL, blank=True, null=True) - diff --git a/transport_accessibility/pt_map/urls.py b/transport_accessibility/pt_map/urls.py index 920ba02..c18ee77 100644 --- a/transport_accessibility/pt_map/urls.py +++ b/transport_accessibility/pt_map/urls.py @@ -7,4 +7,6 @@ from . import views urlpatterns = [ path("", views.index, name="index"), + path("data/", views.data, name="data"), + path("timetable/", views.timetable, name="timetable") ] diff --git a/transport_accessibility/pt_map/views.py b/transport_accessibility/pt_map/views.py index dd01a17..252f12e 100644 --- a/transport_accessibility/pt_map/views.py +++ b/transport_accessibility/pt_map/views.py @@ -9,13 +9,16 @@ index(request) Home page """ from django.shortcuts import render -from django.http import HttpResponse, HttpRequest +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpRequest +from django.core.exceptions import BadRequest, ObjectDoesNotExist from .models import * from .forms import * import json from datetime import datetime +from django.views.decorators.csrf import csrf_exempt +from .class_names import * -def get_timetable(r, trips, stop_sequences): +def get_timetable(r, trips_r, stop_sequence): """ Given a pt_map.models.Route, calculate the timetable for all its stops. @@ -37,18 +40,17 @@ def get_timetable(r, trips, stop_sequences): "stop_times" : dict(str, list(str)) dict mapping stop_ids from stop_sequence to time strings the route is serving the stop at """ - timetable = {"stop_sequence": stop_sequences[r.route_id]} + timetable = {"stop_sequence": stop_sequence} sts = {} - for stop in stop_sequences[r.route_id]: + for stop in stop_sequence: times = [] - for t in trips[r.route_id]: + for t in trips_r: for st in StopTime.objects.filter(trip_id=t.trip_id): times.append(st.departure_time.strftime("%H:%M")) sts[stop] = times timetable["stop_times"] = sts return timetable - def index(request): stops = {s.stop_id: {name: getattr(s, name) for name in ['stop_name', 'stop_lat', 'stop_lon']} for s in Stop.objects.all()} route_name = lambda r : r.route_short_name if r.route_short_name else r.route_long_name @@ -65,9 +67,114 @@ def index(request): if request.GET.get("timetable"): try: r = Route.objects.get(route_id=request.GET.get("timetable")) - timetable = get_timetable(r, trips, stop_sequences) + timetable = get_timetable(r, trips[r.route_id], stop_sequences[r.route_id]) except Route.DoesNotExist: print(f"Invalid request for Route with id {request.GET['timetable']}") context = {"stops": json.dumps(stops), "routes": json.dumps(routes), "timetable": json.dumps(timetable)} return render(request,"map.html", context) +def get_field_names(model: models.Model): + return [field.name for field in model._meta.fields] + +def timetable(request): + if request.method == "GET": + try: + r = Route.objects.get(route_id=request.GET["route_id"]) + trips_r = [t for t in Trip.objects.filter(route_id_id=r.route_id)] + stop_sequence = [s.stop_id.stop_id for s in sorted([s for s in StopTime.objects.filter(trip_id_id__exact=trips_r[0].trip_id)], key=lambda st : st.stop_sequence)] + timetable = get_timetable(r, trips_r, stop_sequence) + return HttpResponse(json.dumps(timetable), content_type="text/json") + except KeyError: + return HttpResponseBadRequest("route_id missing or malformed.") + except Route.DoesNotExist: + return HttpResponseBadRequest("Route not found.") + return HttpResponseNotAllowed(["GET"]) + +def get_pks_from_get(req_get): + result = {} + for k in req_get.keys(): + if k in classes_by_primary_keys.keys(): + result[classes_by_primary_keys[k]] = req_get.getlist(k) + return result + +def get_obj_by_pk(mdl: models.Model, pks: list[str]): + return [obj for obj in [mdl.objects.get(**{primary_keys[mdl]: pk}) for pk in pks] if obj] + +def obj_from_get(req_get) -> str: + return {mdl: get_obj_by_pk(mdl, keys) for mdl, keys in get_pks_from_get(req_get)} + +def mdl_to_jsnzbl_dict(cls, obj: models.Model) -> dict: + result = {} + fields = get_field_names(cls) + for fk in fk_dict[cls]: + fields.remove(fk[1]) + if getattr(obj, fk[1]): + result[fk[1]] = getattr(getattr(obj, fk[1]), primary_keys[fk[0]]) + for field in fields: + if getattr(obj, field): + result[field] = getattr(obj, field) + return result + +def json_from_pk(req_get) -> str: + for cpk in classes_by_primary_keys: + if req_get.get(cpk): + for key in (req_get[cpk] if isinstance(req_get[cpk], list) else [req_get[cpk]]): + obj = classes_by_primary_keys[cpk].objects.get(**{cpk: key}) + for field in get_field_names(obj): + v = getattr(obj, field) + if v: + if field in [f[1] for f in foreign_keys[classes_by_primary_keys[cpk]]]: + fk = get_attr(v, field) + return json.dumps(obj) + + +def jsnzbl_from_get(req_get) -> str: + return {mdl._meta.object_name: [mdl_to_jsnzbl_dict(mdl, o) for o in get_obj_by_pk(mdl, keys)] for mdl, keys in get_pks_from_get(req_get).items()} + +def jsnz(jsnzbl: dict): + return json.dumps(jsnzbl) + +def rsp_for_json_from_get(req_get): + try: + return HttpResponse(jsnz(jsnzbl_from_get(req_get))) + except ObjectDoesNotExist: + return HttpResponseBadRequest("Object(s) not found.") + + +def data(request): + """ + Handle database requests from the frontend. Using Http semantics to specify what to do with the data. + + Request + ------- + PUT + Create a new object if no object with the given primary key exists in the database or delete and replace an existing object. + Body must be a json dict of lists of fully specified, valid models. + If primary keys are given as GET keys, they are applied to the first elements in their corresponding object lists. Primary keys in body will be ignored. + RETURN 400 if primary keys not existing. + PATCH + Modify an existing objects given the instructions in the body. + Body must be a json dict of lists of fields to change and their valid values existing objects in the database, identified by their valid primary keys as GET keys. + Primary keys in body will be ignored. + GET + Return json of models identified by primary keys. + DELETE + Delete models with given primary keys if they exist. Returns number of deleted models. + """ + if request.method == "PUT": + if not request.META["CONTENT_TYPE"] == 'application/json': + HttpResponseBadRequest('Request must be JSON.') + bdy = json.loads(request.body) + try: + obj = obj_from_get(request.GET) + except ObjectDoesNotExist: + return HttpResponseBadRequest("Did not find models corresponding to given pks.") + return HttpResponse(f"received {obj}") + elif request.method == "PATCH": + if not request.META["CONTENT_TYPE"] == 'application/json': + HttpResponseBadRequest('Request must be JSON.') + return HttpResponse(f"received {obj}") + elif request.method == "GET": + return rsp_for_json_from_get(request.GET) + return HttpResponseNotAllowed(['PUT', 'PATCH', 'GET']) +