Cookies and tests

- Added cookie banner
- Improved tests and fixed models accordingly
This commit is contained in:
Johannes Randerath 2024-07-03 01:18:17 +02:00
parent cf6fcda0ed
commit 53ab731787
15 changed files with 434 additions and 165 deletions

View File

@ -34,7 +34,7 @@ class_names = [
"Level": pt_map.models.Level,
"LocationGroup": pt_map.models.LocationGroup,
"LocationGroupStop": pt_map.models.LocationGroupStop,
"LocationsGeojson": pt_map.models.LocationsGeojson,
"Location": pt_map.models.Location,
"BookingRule": pt_map.models.BookingRule,
"Translation": pt_map.models.Translation,
"FeedInfo": pt_map.models.FeedInfo,
@ -66,7 +66,7 @@ class_names = [
"levels": pt_map.models.Level,
"location_groups": pt_map.models.LocationGroup,
"location_group_stops": pt_map.models.LocationGroupStop,
"locations": pt_map.models.LocationsGeojson,
"locations": pt_map.models.Location,
"booking_rules": pt_map.models.BookingRule,
"translations": pt_map.models.Translation,
"feed_info": pt_map.models.FeedInfo,
@ -98,7 +98,7 @@ class_names = [
pt_map.models.Level: "Level",
pt_map.models.LocationGroup: "LocationGroup",
pt_map.models.LocationGroupStop: "LocationGroupStop",
pt_map.models.LocationsGeojson: "LocationsGeojson",
pt_map.models.Location: "Location",
pt_map.models.BookingRule: "BookingRule",
pt_map.models.Translation: "Translation",
pt_map.models.FeedInfo: "FeedInfo",
@ -130,7 +130,7 @@ pt_map.models.Shape: "shapes",
pt_map.models.Level: "levels",
pt_map.models.LocationGroup: "location_groups",
pt_map.models.LocationGroupStop: "location_group_stops",
pt_map.models.LocationsGeojson: "locations",
pt_map.models.Location: "locations",
pt_map.models.BookingRule: "booking_rules",
pt_map.models.Translation: "translations",
pt_map.models.FeedInfo: "feed_info",
@ -166,7 +166,7 @@ file_names = {
pt_map.models.Level: "levels.txt",
pt_map.models.LocationGroup: "location_groups.txt",
pt_map.models.LocationGroupStop: "location_group_stops.txt",
pt_map.models.LocationsGeojson: "locations.geojson",
pt_map.models.Location: "locations.geojson",
pt_map.models.BookingRule: "booking_rules.txt",
pt_map.models.Translation: "translations.txt",
pt_map.models.FeedInfo: "feed_info.txt",
@ -199,7 +199,7 @@ reversed_file_mapping = {
"Level": "levels",
"LocationGroup": "location_groups",
"LocationGroupStop": "location_group_stops",
"LocationsGeojson": "locations_geojson",
"Location": "locations_geojson",
"BookingRule": "booking_rules",
"Translation": "translations",
"FeedInfo": "feed_info",
@ -207,7 +207,7 @@ reversed_file_mapping = {
}
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'}
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', 'Location': 'locations_geojson', 'BookingRule': 'booking_rules', 'Translation': 'translations', 'FeedInfo': 'feed_info', 'Attribution': 'attributions'}
primary_keys = {
@ -221,7 +221,7 @@ primary_keys = {
pt_map.models.CalendarDate: "calendar_date_id",
pt_map.models.Trip: "trip_id",
pt_map.models.LocationGroup: "location_group_id",
pt_map.models.LocationsGeojson: "location_id",
pt_map.models.Location: "location_id",
pt_map.models.StopTime: "stop_time_id",
pt_map.models.FareAttribute: "fare_id",
pt_map.models.FareRule: "fare_rule_id",
@ -255,7 +255,7 @@ classes_by_primary_keys = {
'calendar_date_id': pt_map.models.CalendarDate,
'trip_id': pt_map.models.Trip,
'location_group_id': pt_map.models.LocationGroup,
'location_id': pt_map.models.LocationsGeojson,
'location_id': pt_map.models.Location,
'stop_time_id': pt_map.models.StopTime,
'fare_id': pt_map.models.FareAttribute,
'fare_rule_id': pt_map.models.FareRule,
@ -288,8 +288,8 @@ foreign_keys = [
(pt_map.models.CalendarDate, [(pt_map.models.FeedInfo, 'feed_info_id'),]),
(pt_map.models.Trip, [(pt_map.models.FeedInfo, 'feed_info_id'),(pt_map.models.Route, 'route_id'), (pt_map.models.Shape, 'shape_id'), ]),
(pt_map.models.LocationGroup, [(pt_map.models.FeedInfo, 'feed_info_id'),]),
(pt_map.models.LocationsGeojson, [(pt_map.models.FeedInfo, 'feed_info_id'),]),
(pt_map.models.StopTime, [(pt_map.models.FeedInfo, 'feed_info_id'),(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.Location, [(pt_map.models.FeedInfo, 'feed_info_id'),]),
(pt_map.models.StopTime, [(pt_map.models.FeedInfo, 'feed_info_id'),(pt_map.models.Trip, 'trip_id'), (pt_map.models.Stop, 'stop_id'), (pt_map.models.LocationGroup, 'location_group_id'), (pt_map.models.Location, 'location_id'), ]),
(pt_map.models.FareAttribute, [(pt_map.models.FeedInfo, 'feed_info_id'),(pt_map.models.Agency, 'agency_id'), ]),
(pt_map.models.FareRule, [(pt_map.models.FeedInfo, 'feed_info_id'),(pt_map.models.FareAttribute, 'fare_id'), (pt_map.models.Route, 'route_id'), ]),
(pt_map.models.Frequency, [(pt_map.models.FeedInfo, 'feed_info_id'),(pt_map.models.Trip, 'trip_id'), ]),
@ -325,7 +325,7 @@ fks = {
'trip_id': pt_map.models.Trip,
'stop_id': pt_map.models.Stop,
'location_group_id': pt_map.models.LocationGroup,
'location_id': pt_map.models.LocationsGeojson,
'location_id': pt_map.models.Location,
'fare_id': pt_map.models.FareAttribute,
'from_stop_id': pt_map.models.Stop,
'to_stop_id': pt_map.models.Stop,

View File

@ -0,0 +1,34 @@
# Generated by Django 5.0.6 on 2024-07-01 13:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0021_alter_stoptime_continuous_drop_off'),
]
operations = [
migrations.CreateModel(
name='Location',
fields=[
('location_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('stop_name', models.CharField(blank=True, max_length=255, null=True)),
('stop_desc', models.CharField(blank=True, max_length=255, null=True)),
('latitude', models.FloatField()),
('longitude', models.FloatField()),
('location_type', models.CharField(choices=[('Polygon', 'Polygon'), ('MultiPolygon', 'MultiPolygon')], max_length=255)),
('feed_info_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pt_map.feedinfo')),
],
),
migrations.AlterField(
model_name='stoptime',
name='location_id',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pt_map.location'),
),
migrations.DeleteModel(
name='LocationsGeojson',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-01 13:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0022_location_alter_stoptime_location_id_and_more'),
]
operations = [
migrations.RenameField(
model_name='location',
old_name='location_type',
new_name='geometry_type',
),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 5.0.6 on 2024-07-02 14:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0023_rename_location_type_location_geometry_type'),
]
operations = [
migrations.RemoveField(
model_name='farerule',
name='contains_id',
),
migrations.RemoveField(
model_name='farerule',
name='destination_id',
),
migrations.RemoveField(
model_name='farerule',
name='origin_id',
),
migrations.AddField(
model_name='farerule',
name='contains_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_contains', to='pt_map.route'),
),
migrations.AddField(
model_name='farerule',
name='destination_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_as_destination', to='pt_map.route'),
),
migrations.AddField(
model_name='farerule',
name='origin_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_as_origin', to='pt_map.route'),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 5.0.6 on 2024-07-02 14:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0024_remove_farerule_contains_id_and_more'),
]
operations = [
migrations.AlterField(
model_name='farelegrule',
name='network_id',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pt_map.network'),
),
migrations.AlterField(
model_name='network',
name='network_name',
field=models.CharField(default='', max_length=255),
),
migrations.AlterField(
model_name='route',
name='network_id',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pt_map.network'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.0.6 on 2024-07-02 17:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0025_alter_farelegrule_network_id_and_more'),
]
operations = [
migrations.AlterField(
model_name='farerule',
name='contains_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_contains', to='pt_map.stop'),
),
migrations.AlterField(
model_name='farerule',
name='destination_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_as_destination', to='pt_map.stop'),
),
migrations.AlterField(
model_name='farerule',
name='origin_id',
field=models.ManyToManyField(blank=True, null=True, related_name='fare_rules_for_zone_as_origin', to='pt_map.stop'),
),
]

View File

@ -0,0 +1,61 @@
# Generated by Django 5.0.6 on 2024-07-02 22:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0026_alter_farerule_contains_id_and_more'),
]
operations = [
migrations.RemoveField(
model_name='farelegrule',
name='from_timeframe_group_id',
),
migrations.RemoveField(
model_name='farelegrule',
name='to_timeframe_group_id',
),
migrations.AlterField(
model_name='farerule',
name='contains_id',
field=models.ManyToManyField(blank=True, related_name='fare_rules_for_zone_contains', to='pt_map.stop'),
),
migrations.AlterField(
model_name='farerule',
name='destination_id',
field=models.ManyToManyField(blank=True, related_name='fare_rules_for_zone_as_destination', to='pt_map.stop'),
),
migrations.AlterField(
model_name='farerule',
name='origin_id',
field=models.ManyToManyField(blank=True, related_name='fare_rules_for_zone_as_origin', to='pt_map.stop'),
),
migrations.AlterField(
model_name='stoptime',
name='drop_off_type',
field=models.IntegerField(blank=True, choices=[('Regularly scheduled drop off.', 0), ('No drop off available.', 1), ('Must phone agency to arrange drop off.', 2), ('Must coordinate with driver to arrange drop off.', 3)], null=True),
),
migrations.AlterField(
model_name='stoptime',
name='pickup_type',
field=models.IntegerField(blank=True, choices=[('Regularly scheduled pickup.', 0), ('No pickup available.', 1), ('Must phone agency to arrange pickup.', 2), ('Must coordinate with driver to arrange pickup.', 3)], null=True),
),
migrations.AlterField(
model_name='stoptime',
name='timepoint',
field=models.IntegerField(blank=True, choices=[('Times are considered approximate.', 0), ('Times are considered exact.', 1)], null=True),
),
migrations.AddField(
model_name='farelegrule',
name='from_timeframe_group_id',
field=models.ManyToManyField(null=True, related_name='fare_leg_rules_from', to='pt_map.timeframe'),
),
migrations.AddField(
model_name='farelegrule',
name='to_timeframe_group_id',
field=models.ManyToManyField(null=True, related_name='fare_leg_rules_to', to='pt_map.timeframe'),
),
]

View File

@ -206,8 +206,8 @@ field_requirements = \
},
{
"name": "network_id",
"type": "mtm",
"references": Route,
"type": "fk",
"references": Network,
"required": "false",
"forbidden_if": ["RouteNetwork", True]
},
@ -252,8 +252,7 @@ field_requirements = \
},
{
"name": "block_id",
"type": "mtm",
"references": Trip,
"type": "unique",
"required": "false",
},
{
@ -277,6 +276,42 @@ field_requirements = \
],
"pk": "trip_id",
},
{
"model": "Location",
"fields": [
{
"name": "location_id",
"type": "pk",
"required": "true",
},
{
"name": "stop_name",
"type": "str",
"required": "false",
},
{
"name": "stop_desc",
"type": "str",
"required": "false",
},
{
"name": "geometry_type",
"type": "enum",
"allowed_values": ["Polygon", "MultiPolygon"],
"required": "true",
},
{
"name": "latitude",
"type": "lat",
"required": "true"
},
{
"name": "longitude",
"type": "lon",
"required": "true",
},
],
},
{
"model": "StopTime",
"fields": [
@ -308,7 +343,7 @@ field_requirements = \
},
{
"name": "location_group_id",
"type": "mtm",
"type": "fk",
"references": LocationGroup,
"required": "false",
"forbidden_if_not": [(["stop_id", None], ["location_id", None])],
@ -317,7 +352,7 @@ field_requirements = \
"name": "location_id",
"type": "fk",
"required": "false",
"references": LocationsGeojson,
"references": Location,
"forbidden_if_not": [(["stop_id", None], ["location_group_id", None])],
},
{
@ -658,7 +693,8 @@ field_requirements = \
},
{
"name": "network_id",
"type": "str",
"type": "fk",
"references": Network,
"required": "false",
},
{

View File

@ -7,7 +7,7 @@ Attributes
----------
Classes
-------
Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, Pathway, Level, FeedInfo, LocationsGeojson, BookingRule, Translation, Attribution, LocationGroup, LocationGroupStop, RouteNetwork, Network, StopArea, Area, FareMedium, FareProduct, FareLegRule, FareTransferRule, Timeframe
Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, Pathway, Level, FeedInfo, Location, BookingRule, Translation, Attribution, LocationGroup, LocationGroupStop, RouteNetwork, Network, StopArea, Area, FareMedium, FareProduct, FareLegRule, FareTransferRule, Timeframe
Different files as described in the GTFS Reference
"""
from django.db import models
@ -72,6 +72,14 @@ class Stop(models.Model):
platform_code = models.CharField(max_length=50, blank=True, null=True)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class Network(models.Model):
"""
Represents network.txt from the GTFS Reference.
"""
network_id = models.CharField(max_length=255, primary_key=True)
network_name = models.CharField(max_length=255, default="")
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class Route(models.Model):
"""
Represents route.txt from the GTFS Reference.
@ -88,7 +96,7 @@ class Route(models.Model):
route_sort_order = models.IntegerField(blank=True, null=True)
continuous_pickup = models.IntegerField(choices=[("Continous stopping pickup", 0), ("No continuous stopping pickup", 1), ("Must phone agency to arrange continuous stopping pickup off", 2), ("Must coordinate with driver to arrange continuous stopping pickup", 3)], blank=True, null=True)
continuous_drop_off = models.IntegerField(choices=[("Continous stopping pickup", 0), ("No continuous stopping pickup", 1), ("Must phone agency to arrange continuous stopping pickup off", 2), ("Must coordinate with driver to arrange continuous stopping pickup", 3)], blank=True, null=True)
network_id = models.IntegerField(blank=True, null=True)
network_id = models.ForeignKey(Network, on_delete=models.CASCADE, null=True)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class Shape(models.Model):
@ -175,17 +183,16 @@ class LocationGroup(models.Model):
location_group_type = models.CharField(max_length=255)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class LocationsGeojson(models.Model):
class Location(models.Model):
"""
Represents locations.geojson from the GTFS Reference.
"""
location_id = models.CharField(max_length=255, primary_key=True)
location_name = models.CharField(max_length=255)
location_lat = models.FloatField()
location_lon = models.FloatField()
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)
stop_name = models.CharField(max_length=255, blank=True, null=True)
stop_desc = models.CharField(max_length=255, blank=True, null=True)
latitude = models.FloatField()
longitude = models.FloatField()
geometry_type = models.CharField(max_length=255, choices=[("Polygon", "Polygon"), ("MultiPolygon", "MultiPolygon")])
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class BookingRule(models.Model):
@ -219,13 +226,13 @@ class StopTime(models.Model):
departure_time = models.CharField(max_length=255, blank=True, null=True)
stop_id = models.ForeignKey(Stop, null=True, on_delete=models.CASCADE)
location_group_id = models.ForeignKey(LocationGroup, on_delete=models.SET_NULL, blank=True, null=True)
location_id = models.ForeignKey(LocationsGeojson, on_delete=models.SET_NULL, blank=True, null=True)
location_id = models.ForeignKey(Location, on_delete=models.SET_NULL, blank=True, null=True)
stop_sequence = models.IntegerField()
stop_headsign = models.CharField(max_length=255, blank=True, null=True)
pickup_type = models.IntegerField(blank=True, null=True)
drop_off_type = models.IntegerField(blank=True, null=True)
pickup_type = models.IntegerField(blank=True, null=True, choices=[("Regularly scheduled pickup.", 0), ("No pickup available.", 1), ("Must phone agency to arrange pickup.", 2), ("Must coordinate with driver to arrange pickup.", 3)])
drop_off_type = models.IntegerField(blank=True, null=True, choices=[("Regularly scheduled drop off.", 0), ("No drop off available.", 1), ("Must phone agency to arrange drop off.", 2), ("Must coordinate with driver to arrange drop off.", 3)])
shape_dist_traveled = models.FloatField(blank=True, null=True)
timepoint = models.IntegerField(blank=True, null=True)
timepoint = models.IntegerField(blank=True, null=True, choices=[("Times are considered approximate.", 0), ("Times are considered exact.", 1)])
start_pickup_drop_off_window = models.CharField(max_length=255, blank=True)
end_pickup_drop_off_window = models.CharField(max_length=255, blank=True)
continuous_pickup = models.IntegerField(choices=[("Continous stopping pickup", 0), ("No continuous stopping pickup", 1), ("Must phone agency to arrange continuous stopping pickup off", 2), ("Must coordinate with driver to arrange continuous stopping pickup", 3)], null=True)
@ -257,9 +264,9 @@ class FareRule(models.Model):
fare_rule_id = models.BigAutoField(primary_key=True)
fare_id = models.ForeignKey(FareAttribute, on_delete=models.CASCADE)
route_id = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True)
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)
origin_id = models.ManyToManyField(Stop, related_name="fare_rules_for_zone_as_origin", blank=True)
destination_id = models.ManyToManyField(Stop, related_name="fare_rules_for_zone_as_destination", blank=True)
contains_id = models.ManyToManyField(Stop, related_name="fare_rules_for_zone_contains", blank=True)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class Frequency(models.Model):
@ -351,14 +358,6 @@ class LocationGroupStop(models.Model):
stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class Network(models.Model):
"""
Represents network.txt from the GTFS Reference.
"""
network_id = models.CharField(max_length=255, primary_key=True)
network_name = models.CharField(max_length=255)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)
class RouteNetwork(models.Model):
"""
Represents route_network.txt from the GTFS Reference.
@ -431,11 +430,11 @@ class FareLegRule(models.Model):
fare_leg_rule_name = models.CharField(max_length=255)
fare_leg_rule_description = models.TextField(blank=True, null=True)
leg_group_id = models.CharField(max_length=255, blank=True, null=True)
network_id = models.CharField(max_length=255, blank=True, null=True)
network_id = models.ForeignKey(Network, on_delete=models.CASCADE, null=True)
from_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_area')
to_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_area')
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')
from_timeframe_group_id = models.ManyToManyField(Timeframe, related_name="fare_leg_rules_from", blank=True)
to_timeframe_group_id = models.ManyToManyField(Timeframe, related_name="fare_leg_rules_to", blank=True)
fare_product_id = models.ForeignKey(FareProduct, on_delete=models.CASCADE)
rule_priority = models.IntegerField(blank=True, null=True)
feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE)

View File

@ -0,0 +1,52 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenStreetMap with GTFS Toolbar</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.css" />
<style>
body, html {
height: 100%;
margin: 0;
}
#map {
height: 100%;
width: 100%;
}
.sidebar {
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 250px;
background-color: #f8f9fa;
border-right: 1px solid #dee2e6;
padding-top: 20px;
}
.content {
margin-left: 250px;
height: 100%;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
}
.route-list {
max-height: 400px;
overflow-y: scroll;
}
</style>
</head>
<body>
{% block content %}{% endblock %}
<script type="text/javascript" id="cookiebanner" data-zindex="1000" src="https://cdn.jsdelivr.net/gh/dobarkod/cookie-banner@1.2.2/dist/cookiebanner.min.js"></script>
</body>
</html>

View File

@ -1,53 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenStreetMap with GTFS Toolbar</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.css" />
{% extends "base.html" %}
{% load static %}
<style>
body, html {
height: 100%;
margin: 0;
}
#map {
height: 100%;
width: 100%;
}
.sidebar {
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 250px;
background-color: #f8f9fa;
border-right: 1px solid #dee2e6;
padding-top: 20px;
}
.content {
margin-left: 250px;
height: 100%;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
}
.route-list {
max-height: 400px;
overflow-y: scroll;
}
</style>
</style>
</head>
<body>
{% block content %}
<div class="sidebar">
<div class="container">
<h5 class="mt-4">Options</h5>
@ -56,8 +9,8 @@
<button class="list-group-item list-group-item-action" onclick="document.getElementById('fileInput').click();">Load GTFS from computer</button>
<button class="list-group-item list-group-item-action" onclick="importGTFS()">Import existing GTFS</button>
</div>
<h5 id="currentGTFS">
Current GTFS
<div class="currentGTFS">
<h5 id="currentGTFS">Current GTFS</h5>
<h6 id="newShape">
<button class="list-group-item list-group-item-action" onclick="drawNewShape()">Draw New Shape</button>
<button class="list-group-item list-group-item-action" onclick="addNewShape()">Add New Shape</button>
@ -71,7 +24,7 @@
Current GTFS
<!-- Route IDs will be inserted here -->
</h6>
</h5>
</div>
</div>
</div>
<div class="content">
@ -205,5 +158,4 @@
}
</script>
</body>
</html>
{% endblock %}

View File

@ -1,36 +0,0 @@
from django.test import TestCase
from pt_map.test_data import *
from pt_map.models import *
import unittest
from django.db import models
import random
define(`models', `Agency, Area, Attribution, BookingRule, Calendar, CalendarDate, FareAttribute, FareLegRule, FareMedium, FareProduct, FareRule, FareTransferRule, FeedInfo, Frequency, Level, LocationGroup, LocationGroupStop, LocationsGeojson, Network, Pathway, Route, RouteNetwork, Shape, Stop, StopArea, StopTime, Timeframe, Transfer, Translation, Trip')
define(`foreach', `ifelse(`$#', `1',``, `$1' `$2' `foreach(shift($@)', `$2'')')')
foreach(models, `echo(`class $1TestCase(TestCase):
def setUp(self):
self.model_fields = [f.name for f in $1._meta.fields]
self.gtfs_fields = get_all_fields("$1")
def test_all_fields_present(self):
"""Make sure the model has properties for all fields - regardless if required - provided by the GTFS standard."""
for f in self.gtfs_fields:
with self.subTest(f=f):
self.assertIn(f["name"], self.model_fields)
def test_constructor_all_fields(self):
"""Make sure all of the models fields of the model are initializable"""
with self.subTest(name="fixed"):
"""Fixed subTest"""
d = data[0]
values = {f["name"]: d[f["type"]] for f in self.gtfs_fields}
obj = $1(**values)
self.assertIsNotNone(obj)
self.assertIsInstance(obj, models.Model)
with self.subTest(name="other"):
d = data[random.randint(0,len(data))]
values = {f["name"]: d[f["type"]] for f in self.gtfs_fields}
obj = $1(**values)
self.assertIsNotNone(obj)
self.assertIsInstance(obj, models.Model)')')

View File

@ -1,5 +1,38 @@
#TODO: Test LocationsGeojson
#TODO: Test MTM
"""
Test GTFS Compliance
====================
Test suite meant to make sure the models do their best to enforce compliance of the database with the gtfs standard.
In particular we are testing:
1. All of the txt files the reference suggests have a model representation.
2. All of the fields described in each of the files is representable inside the corresponding model.
3. The fields can be set with standard data for the correct data type.
4. None of the optional fields are enforced to be set. (TODO)
5. All of the required fields are enforced to be set with a legal value. (TODO)
6. If set, the optional fields hold only legal values (TODO)
7. All conditional requirements and restrictions are enforced. (TODO)
8. Foreign keys and many to many sets are actually set within the database and are validated. (TODO)
Functions
---------
_get_test_data(f: function):
Decorator, wrapping instance methods to provide an extendable method to get data from the test data set based on the TestCase's requirements.
_test_constructor(f: function):
Decorator. Basic test to validate if the models are instantiable with the given set of fields.
Classes
-------
AllFieldsPresentTestCase(TestCase):
Test case making sure all files are represented with a complete set of fields. (Conditions 1+2 from the above list)
ConstructorAllFieldsTestCase(TestCase):
Test case making sure the models can be instantiated given valid values for all their fields. (3)
ConstructorRequiredFieldsOnlyTestCase(TestCase):
Test case making sure the model creation is fine with just the required fields. (4)
"""
from django.test import TestCase, TransactionTestCase
from pt_map.test_data import *
@ -14,7 +47,8 @@ import datetime
def _get_test_data(f):
"""Get test data for a field type."""
def wrapper(self, field, model, d, *args, **kwargs):
def wrapper(self, field, model, d, i):
f(self, field, model, d, i)
if field["name"] == "service_id":
self.add += 1
return f"{d['pk']}{self.add}"
@ -22,19 +56,38 @@ def _get_test_data(f):
case "fk":
if isinstance(field["references"], list):
field["references"] = random.choice(field["references"])
if not field["references"] == model and field["references"] not in [LocationsGeojson]:
fk = field["references"].objects.create(**{**{f["name"]: self.get_test_data(f, field["references"], d) for f in self.gtfs_fields[field["references"]._meta.object_name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info})
if field["references"].objects.all():
return field["references"].objects.all()[0]
if not field["references"] == model and field["references"]:
fk = field["references"].objects.create(**{**{f["name"]: self.get_test_data(f, field["references"], d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info})
for n, v in {f["name"]: self.get_test_data(f, model, d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if f["type"] == "mtm"}.items():
getattr(fk, n).set(v)
return fk
return None
case "unique":
return (d[field["pk"]], "unique")
return (d['pk'], "unique") #TODO
case "enum":
return random.choice(field["allowed_values"])
case "date":
return datetime.datetime.fromisoformat(d["date"])
case "pk":
self.add += 1
return f"{d['pk']}{self.add}"
return f"{d['pk']}{datetime.datetime.now().timestamp()}"
case "lat":
return d["float"] #TODO
case "lon":
return d["float"] #TODO
case "mtm":
choices = [j for j in range(len(data)-1) if not j == i]
indexes = set([])
while len(indexes) < 3:
indexes.add(random.choice(choices))
indexes = list(indexes)
mtm_test_data = [data[j] for j in indexes if field["references"]._meta.pk.name in data[j].keys() and not field["references"].objects.filter(pk=data[j]['pk'])]
qs = [field["references"].objects.create(**{**{f["name"]: self.get_test_data(f, field["references"], mtm_test_data[j], i) for f in self.gtfs_fields[field["references"]._meta.object_name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info}) for j in range(len(mtm_test_data))]
for m in qs:
for n, v in {f["name"]: self.get_test_data(f, model, d, i) for f in self.gtfs_fields[field["references"]._meta.object_name] if f["type"] == "mtm"}.items():
getattr(m, n).set(v)
return field["references"].objects.all()
case _:
return d[field["type"]]
return wrapper
@ -42,11 +95,14 @@ def _get_test_data(f):
def _test_constructor(index):
def decorate(f):
def wrapper(self, *args, **kwargs):
for name, model in [tpl for tpl in inspect.getmembers(pt_map.models, inspect.isclass) if tpl[1] not in [FeedInfo, LocationsGeojson]]:
f(self, *args, **kwargs)
for name, model in [tpl for tpl in inspect.getmembers(pt_map.models, inspect.isclass) if tpl[1] not in [FeedInfo,]]:
with self.subTest(name=name):
d = data[index]
values = {**{f["name"]: self.get_test_data(f, model, d) for f in self.gtfs_fields[name] if not f["type"] == "mtm"}, "feed_info_id": self.feed_info}
values = {**{field["name"]: self.get_test_data(field, model, d, index) for field in self.gtfs_fields[name] if not field["type"] == "mtm"}, "feed_info_id": self.feed_info}
obj = model.objects.create(**values)
for n, v in {field["name"]: self.get_test_data(field, model, d, index) for field in self.gtfs_fields[name] if field["type"] == "mtm"}.items():
getattr(obj, n).set(v)
self.assertIsNotNone(obj)
self.assertIsInstance(obj, models.Model)
return wrapper
@ -55,8 +111,8 @@ def _test_constructor(index):
class AllFieldsPresentTestCase(TestCase):
"""Test for the presence of all the fields of all the models, the GTFS reference describes."""
def setUp(self):
self.model_fields = {name: [*[f.name for f in model._meta.fields], *[f.name for f in model._meta.many_to_many]] for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == LocationsGeojson}
self.gtfs_fields = {name: get_all_fields(name) for name,model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == LocationsGeojson}
self.model_fields = {name: [*[f.name for f in model._meta.fields], *[f.name for f in model._meta.many_to_many]] for name, model in inspect.getmembers(pt_map.models, inspect.isclass)}
self.gtfs_fields = {name: get_all_fields(name) for name,model in inspect.getmembers(pt_map.models, inspect.isclass)}
def test_all_fields_present(self):
"""Make sure the model has properties for all fields - regardless if required - provided by the GTFS standard."""
@ -76,7 +132,7 @@ class ConstructorAllFieldsTestCase(TransactionTestCase):
self.add = 0
@_get_test_data
def get_test_data(self, field, model, d):
def get_test_data(self, field, model, d, i):
pass
@_test_constructor(0)
@ -91,12 +147,12 @@ class ConstructorAllFieldsTestCase(TransactionTestCase):
class ConstructorRequiredFieldsOnlyTestCase(TransactionTestCase):
def setUp(self):
self.gtfs_fields = {name: get_required_fields(name) for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if model not in [FeedInfo, LocationsGeojson]}
self.gtfs_fields = {name: get_required_fields(name) for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if model not in [FeedInfo]}
self.feed_info = FeedInfo.objects.create(**{f["name"]: data[0][f["type"]] for f in get_required_fields("FeedInfo")})
self.add = 0
@_get_test_data
def get_test_data(self):
def get_test_data(self, field, model, d, i):
pass
@_test_constructor(0)

View File

@ -10,6 +10,7 @@ index
"""
from django.shortcuts import render
from . import query
import json
def index(request):

View File

@ -1 +0,0 @@
def setUp(self): self.model_fields = [f.name for f in Agency._meta.fields] self.gtfs_fields = get_all_fields("Agency") def test_all_fields_present(self): """Make sure the model has properties for all fields - regardless if required - provided by the GTFS standard.""" for f in self.gtfs_fields: with self.subTest(f=f): self.assertIn(f["name"], self.model_fields) def test_constructor_all_fields(self): """Make sure all of the models fields of the model are initializable""" with self.subTest(name="fixed"): """Fixed subTest""" d = data[0] values = {f["name"]: d[f["type"]] for f in self.gtfs_fields} obj = Agency(**values) self.assertIsNotNone(obj) self.assertIsInstance(obj, models.Model) with self.subTest(name="other"): d = data[random.randint(0,len(data))] values = {f["name"]: d[f["type"]] for f in self.gtfs_fields} obj = Agency(**values) self.assertIsNotNone(obj) self.assertIsInstance(obj, models.Model)