diff --git a/TODO.md b/TODO.md index 9a84ec0..42779e2 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,5 @@ ## Backend - Serve data to views in an intuitive way. As an object of a custom class? - Fetch data to serve to views -- Write data received from views -- Implement views serve data to the templates +- Implement views to serve data to the templates - Handle requests corrrectly in views and urls diff --git a/bin/pyproj b/bin/pyproj new file mode 100755 index 0000000..7563158 --- /dev/null +++ b/bin/pyproj @@ -0,0 +1,8 @@ +#!/home/johannes/code/transport-accessibility/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pyproj.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/requirements.txt b/requirements.txt index c367d11..1efa95f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ Django==5.0.6 docutils==0.20.1 feedparser==6.0.11 future==1.0.0 +geopandas==1.0.0 h11==0.14.0 idna==3.7 imagesize==1.4.1 @@ -36,8 +37,10 @@ MarkupSafe==2.0.1 mccabe==0.7.0 more-itertools==10.2.0 mysqlclient==2.2.4 +networkx==3.3 nltk==3.8.1 numpy==1.26.4 +osmnx==1.9.3 packaging==24.0 pandas==2.2.2 parsimonious==0.10.0 @@ -49,6 +52,8 @@ portend==3.2.0 pycparser==2.22 Pygments==2.18.0 pylint==3.2.2 +pyogrio==0.9.0 +pyproj==3.6.1 python-dateutil==2.9.0.post0 python-docx==1.1.2 pytz==2024.1 @@ -57,6 +62,7 @@ requests==2.32.3 scipy==1.13.1 setuptools==70.0.0 sgmllib3k==1.0.0 +shapely==2.0.4 six==1.16.0 sniffio==1.3.1 snowballstemmer==2.2.0 diff --git a/transport_accessibility/pt_map/migrations/0009_rename_curreny_fareproduct_currency_and_more.py b/transport_accessibility/pt_map/migrations/0009_rename_curreny_fareproduct_currency_and_more.py new file mode 100644 index 0000000..28ed9e2 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0009_rename_curreny_fareproduct_currency_and_more.py @@ -0,0 +1,279 @@ +# Generated by Django 5.0.6 on 2024-06-26 21:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0008_alter_timeframe_service_id_alter_trip_service_id'), + ] + + operations = [ + migrations.RenameField( + model_name='fareproduct', + old_name='curreny', + new_name='currency', + ), + migrations.RemoveField( + model_name='bookingrule', + name='booking_rule_instructions', + ), + migrations.RemoveField( + model_name='bookingrule', + name='end_time', + ), + migrations.RemoveField( + model_name='bookingrule', + name='rule_criteria', + ), + migrations.RemoveField( + model_name='bookingrule', + name='start_time', + ), + migrations.RemoveField( + model_name='bookingrule', + name='trip_id', + ), + migrations.RemoveField( + model_name='faremedium', + name='fare_media_description', + ), + migrations.RemoveField( + model_name='fareproduct', + name='fare_product_description', + ), + migrations.AddField( + model_name='attribution', + name='is_authority', + field=models.BooleanField(null=True), + ), + migrations.AddField( + model_name='attribution', + name='is_operator', + field=models.BooleanField(null=True), + ), + migrations.AddField( + model_name='attribution', + name='is_producer', + field=models.BooleanField(null=True), + ), + migrations.AddField( + model_name='attribution', + name='organization_name', + field=models.CharField(default='Test', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='bookingrule', + name='booking_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='drop_off_message', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='info_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='message', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='phone_number', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='pickup_message', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_duration_max', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_duration_min', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_last_day', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_last_time', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_service_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_start_day', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='bookingrule', + name='prior_notice_start_time', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='farelegrule', + name='leg_group_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='farelegrule', + name='rule_priority', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='faremedium', + name='fare_media_type', + field=models.IntegerField(choices=[('None', 0), ('Physical paper ticket that allows a passenger to take either a certain number of pre-purchased trips or unlimited trips within a fixed period of time', 1), ('Physical transit card that has stored tickets, passes or monetary value', 2), ('cEMV (contactless Europay, Mastercard and Visa) as an open-loop token container for account-based ticketing', 3), ('Mobile app that have stored virtual transit cards, tickets, passes, or monetary value', 4)], default=1), + preserve_default=False, + ), + migrations.AddField( + model_name='fareproduct', + name='fare_media_id', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pt_map.faremedium'), + ), + migrations.AddField( + model_name='faretransferrule', + name='duration_limit', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='faretransferrule', + name='duration_limit_type', + field=models.CharField(choices=[('Between the departure fare validation of the current leg and the arrival fare validation of the next leg', 0), ('Between the departure fare validation of the current leg and the departure fare validation of the next leg', 1), ('Between the arrival fare validation of the current leg and the departure fare validation of the next leg', 2), ('Between the arrival fare validation of the current leg and the arrival fare validation of the next leg', 3)], default=1, max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='faretransferrule', + name='fare_transfer_type', + field=models.CharField(choices=[('From-leg fare_leg_rules.fare_product_id plus fare_transfer_rules.fare_product_id; A + AB', 0), ('From-leg fare_leg_rules.fare_product_id plus fare_transfer_rules.fare_product_id plus to-leg fare_leg_rules.fare_product_id; A + AB + B', 1), ('fare_transfer_rules.fare_product_id; AB', 2)], default=1, max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='faretransferrule', + name='transfer_count', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='route', + name='network_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='stop', + name='tts_stop_name', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='stoptime', + name='continuous_drop_off', + field=models.IntegerField(choices=[('Continous stopping drop off', 0), ('No continuous stopping drop off', 1), ('Must phone agency to arrange continuous stopping drop off', 2), ('Must coordinate with driver to arrange continuous stopping drop off', 3)], default=1), + preserve_default=False, + ), + migrations.AddField( + model_name='stoptime', + name='continuous_pickup', + field=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)], default=1), + preserve_default=False, + ), + migrations.AddField( + model_name='stoptime', + name='drop_off_booking_rule_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stop_times_drop_off', to='pt_map.bookingrule'), + ), + migrations.AddField( + model_name='stoptime', + name='end_pickup_drop_off_window', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='stoptime', + name='pickup_booking_rule_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stop_times_pickup', to='pt_map.bookingrule'), + ), + migrations.AddField( + model_name='stoptime', + name='start_pickup_drop_off_window', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='translation', + name='field_value', + field=models.CharField(default='hi', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='translation', + name='record_id', + field=models.CharField(default='rec1', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='translation', + name='record_sub_id', + field=models.CharField(default='rec1', max_length=255), + preserve_default=False, + ), + migrations.AlterField( + model_name='attribution', + name='attribution_name', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='bookingrule', + name='booking_type', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.RemoveField( + model_name='faretransferrule', + name='from_leg_group_id', + ), + migrations.RemoveField( + model_name='faretransferrule', + name='to_leg_group_id', + ), + migrations.AlterField( + model_name='route', + name='continuous_drop_off', + field=models.IntegerField(blank=True, 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), + ), + migrations.AlterField( + model_name='route', + name='continuous_pickup', + field=models.IntegerField(blank=True, 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), + ), + migrations.AlterField( + model_name='translation', + name='table_name', + field=models.CharField(choices=[('agency', 'agency'), ('stops', 'stops'), ('routes', 'routes'), ('trips', 'trips'), ('stop_times', 'stop_times'), ('pathways', 'pathways'), ('levels', 'levels'), ('feed_info', 'feed_info'), ('attributions', 'attributions')], max_length=255), + ), + migrations.AddField( + model_name='faretransferrule', + name='from_leg_group_id', + field=models.ManyToManyField(blank=True, related_name='fare_transfer_rules_from', to='pt_map.farelegrule'), + ), + migrations.AddField( + model_name='faretransferrule', + name='to_leg_group_id', + field=models.ManyToManyField(blank=True, related_name='fare_transfer_rules_to', to='pt_map.farelegrule'), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0010_alter_route_route_color_alter_route_route_short_name_and_more.py b/transport_accessibility/pt_map/migrations/0010_alter_route_route_color_alter_route_route_short_name_and_more.py new file mode 100644 index 0000000..6f9e118 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0010_alter_route_route_color_alter_route_route_short_name_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-06-26 23:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0009_rename_curreny_fareproduct_currency_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='route', + name='route_color', + field=models.CharField(blank=True, max_length=10, null=True), + ), + migrations.AlterField( + model_name='route', + name='route_short_name', + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name='route', + name='route_text_color', + field=models.CharField(blank=True, max_length=10, null=True), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0011_remove_timeframe_end_date_and_more.py b/transport_accessibility/pt_map/migrations/0011_remove_timeframe_end_date_and_more.py new file mode 100644 index 0000000..e2b0e1a --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0011_remove_timeframe_end_date_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.6 on 2024-06-26 23:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0010_alter_route_route_color_alter_route_route_short_name_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='timeframe', + name='end_date', + ), + migrations.RemoveField( + model_name='timeframe', + name='start_date', + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0012_rename_fare_product_id_fareproduct_fare_product_pk.py b/transport_accessibility/pt_map/migrations/0012_rename_fare_product_id_fareproduct_fare_product_pk.py new file mode 100644 index 0000000..9a9a01f --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0012_rename_fare_product_id_fareproduct_fare_product_pk.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 08:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0011_remove_timeframe_end_date_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='fareproduct', + old_name='fare_product_id', + new_name='fare_product_pk', + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0013_fareproduct_fare_product_id_and_more.py b/transport_accessibility/pt_map/migrations/0013_fareproduct_fare_product_id_and_more.py new file mode 100644 index 0000000..4fdd4e1 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0013_fareproduct_fare_product_id_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-27 08:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0012_rename_fare_product_id_fareproduct_fare_product_pk'), + ] + + operations = [ + migrations.AddField( + model_name='fareproduct', + name='fare_product_id', + field=models.CharField(default='1', max_length=255), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='fareproduct', + unique_together={('fare_product_id', 'fare_media_id')}, + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0014_alter_farelegrule_network_id.py b/transport_accessibility/pt_map/migrations/0014_alter_farelegrule_network_id.py new file mode 100644 index 0000000..e508d68 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0014_alter_farelegrule_network_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0013_fareproduct_fare_product_id_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='farelegrule', + name='network_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0015_alter_bookingrule_prior_notice_last_day_and_more.py b/transport_accessibility/pt_map/migrations/0015_alter_bookingrule_prior_notice_last_day_and_more.py new file mode 100644 index 0000000..d01a918 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0015_alter_bookingrule_prior_notice_last_day_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-06-27 09:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0014_alter_farelegrule_network_id'), + ] + + operations = [ + migrations.AlterField( + model_name='bookingrule', + name='prior_notice_last_day', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='bookingrule', + name='prior_notice_last_time', + field=models.TimeField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='bookingrule', + name='prior_notice_start_day', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='bookingrule', + name='prior_notice_start_time', + field=models.TimeField(blank=True, max_length=255, null=True), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0016_alter_attribution_attribution_id.py b/transport_accessibility/pt_map/migrations/0016_alter_attribution_attribution_id.py new file mode 100644 index 0000000..bc03305 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0016_alter_attribution_attribution_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 09:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0015_alter_bookingrule_prior_notice_last_day_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='attribution', + name='attribution_id', + field=models.CharField(max_length=255, primary_key=True, serialize=False), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0017_alter_fareattribute_transfers.py b/transport_accessibility/pt_map/migrations/0017_alter_fareattribute_transfers.py new file mode 100644 index 0000000..b6e77a6 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0017_alter_fareattribute_transfers.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0016_alter_attribution_attribution_id'), + ] + + operations = [ + migrations.AlterField( + model_name='fareattribute', + name='transfers', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0018_alter_stop_stop_lat_alter_stop_stop_lon.py b/transport_accessibility/pt_map/migrations/0018_alter_stop_stop_lat_alter_stop_stop_lon.py new file mode 100644 index 0000000..4c3813a --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0018_alter_stop_stop_lat_alter_stop_stop_lon.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-27 10:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0017_alter_fareattribute_transfers'), + ] + + operations = [ + migrations.AlterField( + model_name='stop', + name='stop_lat', + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name='stop', + name='stop_lon', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0019_alter_stoptime_stop_id.py b/transport_accessibility/pt_map/migrations/0019_alter_stoptime_stop_id.py new file mode 100644 index 0000000..aeacde9 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0019_alter_stoptime_stop_id.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.6 on 2024-06-27 10:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0018_alter_stop_stop_lat_alter_stop_stop_lon'), + ] + + operations = [ + migrations.AlterField( + model_name='stoptime', + name='stop_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='pt_map.stop'), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0020_alter_stoptime_continuous_pickup_and_more.py b/transport_accessibility/pt_map/migrations/0020_alter_stoptime_continuous_pickup_and_more.py new file mode 100644 index 0000000..3d42847 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0020_alter_stoptime_continuous_pickup_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.6 on 2024-06-27 10:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0019_alter_stoptime_stop_id'), + ] + + operations = [ + migrations.AlterField( + model_name='stoptime', + name='continuous_pickup', + field=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), + ), + migrations.AlterField( + model_name='transfer', + name='from_stop_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfers_from_stop', to='pt_map.stop'), + ), + migrations.AlterField( + model_name='transfer', + name='to_stop_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transfers_to_stop', to='pt_map.stop'), + ), + ] diff --git a/transport_accessibility/pt_map/migrations/0021_alter_stoptime_continuous_drop_off.py b/transport_accessibility/pt_map/migrations/0021_alter_stoptime_continuous_drop_off.py new file mode 100644 index 0000000..191dee2 --- /dev/null +++ b/transport_accessibility/pt_map/migrations/0021_alter_stoptime_continuous_drop_off.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 10:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pt_map', '0020_alter_stoptime_continuous_pickup_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='stoptime', + name='continuous_drop_off', + field=models.IntegerField(choices=[('Continous stopping drop off', 0), ('No continuous stopping drop off', 1), ('Must phone agency to arrange continuous stopping drop off', 2), ('Must coordinate with driver to arrange continuous stopping drop off', 3)], null=True), + ), + ] diff --git a/transport_accessibility/pt_map/model_test_fields.py b/transport_accessibility/pt_map/model_test_fields.py index 2f36559..cc25d99 100644 --- a/transport_accessibility/pt_map/model_test_fields.py +++ b/transport_accessibility/pt_map/model_test_fields.py @@ -63,7 +63,7 @@ field_requirements = \ }, { "name": "stop_code", - "type": "str", + "type": "short", "required": "false", }, { @@ -134,7 +134,7 @@ field_requirements = \ }, { "name": "platform_code", - "type": "str", + "type": "short", "required": "false", }, ], @@ -151,12 +151,13 @@ field_requirements = \ { "name": "agency_id", "type": "fk", + "references": Agency, "required": "false", "default": "yes", }, { "name": "route_short_name", - "type": "str", + "type": "short", "required": "if", "required_if": ["route_long_name", None], }, @@ -183,12 +184,12 @@ field_requirements = \ }, { "name": "route_color", - "type": "str", + "type": "color", "required": "false", }, { "name": "route_text_color", - "type": "str", + "type": "color", "required": "false", }, { @@ -198,7 +199,7 @@ field_requirements = \ "required": "false", }, { - "name": "continuous_dropoff", + "name": "continuous_drop_off", "type": "enum", "allowed_values": [0,1,2,3,], "required": "false", @@ -240,7 +241,7 @@ field_requirements = \ }, { "name": "trip_short_name", - "type": "str", + "type": "short", "required": "false", }, { @@ -257,7 +258,7 @@ field_requirements = \ }, { "name": "shape_id", - "type": "fk", + "type": "mtm", "references": Shape, "required": "false", }, @@ -315,6 +316,7 @@ field_requirements = \ { "name": "location_id", "type": "fk", + "required": "false", "references": LocationsGeojson, "forbidden_if_not": [(["stop_id", None], ["location_group_id", None])], }, @@ -527,7 +529,7 @@ field_requirements = \ "pk": "fare_id", }, { - "model": "FareRules", + "model": "FareRule", "fields": [ { "name": "fare_id", @@ -609,7 +611,7 @@ field_requirements = \ "name": "fare_media_type", "type": "enum", "allowed_values": [0,1,2,3,4,], - "required": "false", + "required": "true", }, ], "pk": "fare_media_id", @@ -619,7 +621,7 @@ field_requirements = \ "fields": [ { "name": "fare_product_id", - "type": "str", + "type": "pk", "required": "true", }, { @@ -728,6 +730,7 @@ field_requirements = \ "name": "duration_limit_type", "type": "enum", "allowed_values": [0,1,2,3,], + "required": "if", "required_if_not": [["duration_limit", None]], "forbidden_if": [["duration_limit", None]], }, @@ -879,7 +882,7 @@ field_requirements = \ "pk": ["trip_id", "start_time"], }, { - "model": "Transfers", + "model": "Transfer", "fields": [ { "name": "from_stop_id", @@ -1167,7 +1170,7 @@ field_requirements = \ }, { "name": "language", - "type": "lancode", + "type": "langcode", "required": "true", }, { @@ -1177,7 +1180,7 @@ field_requirements = \ }, { "name": "record_id", - "type": "fk", + "type": "str", "references": "table_name", "required": "if", "required_if": [["field_value", None]], @@ -1299,13 +1302,13 @@ field_requirements = \ }, { "name": "is_authority", - "type": "str", + "type": "enum", + "allowed_values": [0,1,], "required": "false", }, { "name": "attribution_url", - "type": "enum", - "allowed_values": [0,1,], + "type": "url", "required": "false", }, { diff --git a/transport_accessibility/pt_map/models.py b/transport_accessibility/pt_map/models.py index 9d3cd89..f25c35a 100644 --- a/transport_accessibility/pt_map/models.py +++ b/transport_accessibility/pt_map/models.py @@ -59,8 +59,9 @@ class Stop(models.Model): stop_code = models.CharField(max_length=50, blank=True, null=True) stop_name = models.CharField(max_length=255) stop_desc = models.TextField(blank=True, null=True) - stop_lat = models.FloatField() - stop_lon = models.FloatField() + tts_stop_name = models.CharField(max_length=255, blank=True) + stop_lat = models.FloatField(blank=True, null=True) + stop_lon = models.FloatField(blank=True, null=True) zone_id = models.CharField(max_length=255, blank=True, null=True) stop_url = models.URLField(blank=True, null=True) location_type = models.IntegerField(blank=True, null=True) @@ -77,16 +78,17 @@ class Route(models.Model): """ route_id = models.CharField(max_length=255,primary_key=True) agency_id = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) - route_short_name = models.CharField(max_length=50) + route_short_name = models.CharField(max_length=255) route_long_name = models.CharField(max_length=255, blank=True, null=True) route_desc = models.TextField(blank=True, null=True) route_type = models.IntegerField(default=0) route_url = models.URLField(blank=True, null=True) - route_color = models.CharField(max_length=6, blank=True, null=True) - route_text_color = models.CharField(max_length=6, blank=True, null=True) + route_color = models.CharField(max_length=10, blank=True, null=True) + route_text_color = models.CharField(max_length=10, blank=True, null=True) 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) + 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) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) class Shape(models.Model): @@ -140,6 +142,7 @@ class Trip(models.Model): trip_id = models.CharField(max_length=255, primary_key=True) route_id = models.ForeignKey(Route, on_delete=models.CASCADE) service_id = models.CharField(max_length=255) + shape_id = models.ForeignKey(Shape, on_delete=models.SET_NULL, null=True) trip_headsign = models.CharField(max_length=255, blank=True, null=True) trip_short_name = models.CharField(max_length=255, blank=True, null=True) direction_id = models.IntegerField(blank=True, null=True) @@ -185,6 +188,27 @@ class LocationsGeojson(models.Model): wheelchair_boarding = models.BooleanField(blank=True, null=True) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) +class BookingRule(models.Model): + """ + Represents booking_rule.txt from the GTFS Reference. + """ + booking_rule_id = models.CharField(max_length=255, primary_key=True) + booking_type = models.CharField(max_length=255, blank=True, null=True) + prior_notice_duration_min = models.IntegerField(blank=True, null=True) + prior_notice_duration_max = models.IntegerField(blank=True, null=True) + prior_notice_last_day = models.IntegerField(blank=True, null=True) + prior_notice_last_time = models.TimeField(max_length=255, blank=True, null=True) + prior_notice_start_day = models.IntegerField(blank=True, null=True) + prior_notice_start_time = models.TimeField(max_length=255, blank=True, null=True) + prior_notice_service_id = models.CharField(max_length=255, blank=True, null=True) + message = models.CharField(max_length=255, blank=True, null=True) + pickup_message = models.CharField(max_length=255, blank=True, null=True) + drop_off_message = models.CharField(max_length=255, blank=True, null=True) + phone_number = models.CharField(max_length=255, blank=True, null=True) + info_url = models.URLField(blank=True, null=True) + booking_url = models.URLField(blank=True, null=True) + feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) + class StopTime(models.Model): """ Represents stop_time.txt from the GTFS Reference. @@ -193,7 +217,7 @@ class StopTime(models.Model): trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) arrival_time = models.CharField(max_length=255, blank=True, null=True) departure_time = models.CharField(max_length=255, blank=True, null=True) - stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + 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) stop_sequence = models.IntegerField() @@ -202,6 +226,12 @@ 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) + 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) + continuous_drop_off = models.IntegerField(choices=[("Continous stopping drop off", 0), ("No continuous stopping drop off", 1), ("Must phone agency to arrange continuous stopping drop off", 2), ("Must coordinate with driver to arrange continuous stopping drop off", 3)], null=True) + pickup_booking_rule_id = models.ForeignKey(BookingRule, related_name="stop_times_pickup", null=True, on_delete=models.SET_NULL) + drop_off_booking_rule_id = models.ForeignKey(BookingRule, related_name="stop_times_drop_off", null=True, on_delete=models.SET_NULL) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) class Meta: @@ -215,7 +245,7 @@ class FareAttribute(models.Model): price = models.FloatField() currency_type = models.CharField(max_length=3) payment_method = models.IntegerField() - transfers = models.IntegerField() + transfers = models.IntegerField(blank=True, null=True) 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(FeedInfo, on_delete=models.CASCADE) @@ -249,8 +279,8 @@ class Transfer(models.Model): Represents transfer.txt from the GTFS Reference. """ transfer_id = models.BigAutoField(primary_key=True) - from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_from_stop') - to_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_to_stop') + from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, null=True, related_name='transfers_from_stop') + to_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, null=True, related_name='transfers_to_stop') from_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_route') to_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_route') from_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_trip') @@ -280,39 +310,33 @@ class Pathway(models.Model): reversed_signposted_as = models.CharField(max_length=255, blank=True, null=True) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) -class BookingRule(models.Model): - """ - Represents booking_rule.txt from the GTFS Reference. - """ - booking_rule_id = models.CharField(max_length=255, primary_key=True) - trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) - start_time = models.CharField(max_length=255, blank=True, null=True) - end_time = models.CharField(max_length=255, blank=True, null=True) - 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(FeedInfo, on_delete=models.CASCADE) - class Translation(models.Model): """ Represents translation.txt from the GTFS Reference. """ translation_id = models.BigAutoField(primary_key=True) - table_name = models.CharField(max_length=255) + table_name = models.CharField(max_length=255, choices={"agency": "agency", "stops": "stops", "routes": "routes", "trips": "trips", "stop_times": "stop_times", "pathways": "pathways", "levels": "levels", "feed_info": "feed_info", "attributions": "attributions"}) field_name = models.CharField(max_length=255) language = models.CharField(max_length=2) translation = models.TextField() + record_id = models.CharField(max_length=255) + record_sub_id = models.CharField(max_length=255) + field_value = models.CharField(max_length=255) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) class Attribution(models.Model): """ Represents attribution.txt from the GTFS Reference. """ - attribution_id = models.BigAutoField(primary_key=True) + attribution_id = models.CharField(max_length=255, primary_key=True) agency_id = models.ForeignKey(Agency, on_delete=models.SET_NULL, blank=True, null=True) route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True) trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True) - attribution_name = models.CharField(max_length=255) + organization_name = models.CharField(max_length=255) + is_producer = models.BooleanField(null=True) + is_operator = models.BooleanField(null=True) + is_authority = models.BooleanField(null=True) + attribution_name = models.CharField(max_length=255, blank=True, null=True) attribution_url = models.URLField() attribution_email = models.EmailField(blank=True, null=True) attribution_phone = models.CharField(max_length=50, blank=True, null=True) @@ -371,28 +395,30 @@ 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) + fare_media_type = models.IntegerField(choices=[("None", 0), ("Physical paper ticket that allows a passenger to take either a certain number of pre-purchased trips or unlimited trips within a fixed period of time", 1), ("Physical transit card that has stored tickets, passes or monetary value", 2), ("cEMV (contactless Europay, Mastercard and Visa) as an open-loop token container for account-based ticketing", 3), ("Mobile app that have stored virtual transit cards, tickets, passes, or monetary value", 4)]) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) class FareProduct(models.Model): """ Represents fare_product.txt from the GTFS Reference. """ - fare_product_id = models.BigAutoField(primary_key=True) + fare_product_pk = models.BigAutoField(primary_key=True) + fare_product_id = models.CharField(max_length=255) fare_product_name = models.CharField(max_length=255) - fare_product_description = models.TextField(blank=True, null=True) + fare_media_id = models.ForeignKey(FareMedium, on_delete=models.SET_NULL, blank=True, null=True) amount = models.FloatField() - curreny = models.CharField(max_length=64) + currency = models.CharField(max_length=64) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) + class Meta: + unique_together = (("fare_product_id", "fare_media_id"),) + class Timeframe(models.Model): """ Represents timeframe.txt from the GTFS Reference. """ timeframe_group_id = models.CharField(max_length=255,primary_key=True) service_id = models.CharField(max_length=255) - start_date = models.DateField() - end_date = models.DateField() start_time = models.CharField(max_length=255) end_time = models.CharField(max_length=255) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) @@ -404,12 +430,14 @@ class FareLegRule(models.Model): fare_leg_rule_id = models.CharField(max_length=255, primary_key=True) fare_leg_rule_name = models.CharField(max_length=255) fare_leg_rule_description = models.TextField(blank=True, null=True) - network_id = models.ForeignKey(Network, blank=True, null=True, on_delete=models.SET_NULL) + leg_group_id = models.CharField(max_length=255, blank=True, null=True) + network_id = models.CharField(max_length=255, blank=True, 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') 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) class FareTransferRule(models.Model): @@ -419,8 +447,12 @@ class FareTransferRule(models.Model): fare_transfer_rule_id = models.CharField(max_length=255, primary_key=True) fare_transfer_rule_name = models.CharField(max_length=255) fare_transfer_rule_description = models.TextField(blank=True, null=True) - from_leg_group_id = models.IntegerField(blank=True, null=True) - to_leg_group_id = models.IntegerField(blank=True, null=True) + transfer_count = models.IntegerField(blank=True, null=True) + duration_limit = models.IntegerField(blank=True, null=True) + duration_limit_type = models.CharField(max_length=255, choices=[("Between the departure fare validation of the current leg and the arrival fare validation of the next leg", 0), ("Between the departure fare validation of the current leg and the departure fare validation of the next leg", 1), ("Between the arrival fare validation of the current leg and the departure fare validation of the next leg", 2), ("Between the arrival fare validation of the current leg and the arrival fare validation of the next leg", 3)]) + fare_transfer_type = models.CharField(max_length=255, choices=[("From-leg fare_leg_rules.fare_product_id plus fare_transfer_rules.fare_product_id; A + AB", 0), ("From-leg fare_leg_rules.fare_product_id plus fare_transfer_rules.fare_product_id plus to-leg fare_leg_rules.fare_product_id; A + AB + B", 1), ("fare_transfer_rules.fare_product_id; AB", 2)]) + from_leg_group_id = models.ManyToManyField(FareLegRule, related_name="fare_transfer_rules_from", blank=True) + to_leg_group_id = models.ManyToManyField(FareLegRule, related_name="fare_transfer_rules_to", blank=True) fare_product_id = models.ForeignKey(FareProduct, on_delete=models.SET_NULL, blank=True, null=True) feed_info_id = models.ForeignKey(FeedInfo, on_delete=models.CASCADE) diff --git a/transport_accessibility/pt_map/test_data.py b/transport_accessibility/pt_map/test_data.py index fa2c53b..5f9dd2f 100644 --- a/transport_accessibility/pt_map/test_data.py +++ b/transport_accessibility/pt_map/test_data.py @@ -36,134 +36,193 @@ def get_conditionally_forbidden_fields(model_name): data = [ { - "name": "Fulton Conley", - "telephone": "1-538-373-2858", - "email": "duis@google.net", - "url": "http://bbc.co.uk", - "pk": "WKQ42OFT7XX", - "str": "tincidunt orci quis lectus. Nullam suscipit, est ac", - "time": "00:34:56", - "date": "20231223", - "curcode": "TRY", + "name": "Urielle Hinton", + "telephone": "1-823-274-4664", + "email": "tempor.est.ac@aol.ca", + "url": "http://instagram.com", + "pk": "PLG52MKV4VD", + "str": "nascetur ridiculus mus. Proin vel arcu eu odio", + "time": "19:53:50", + "date": "20240728", + "curcode": "HNL", "timezone": "Europe/Paris", - "langcode": "ZO" + "langcode": "CH", + "float": "-69.3649485824", + "int": "553828", + "color": "#f9a3a2", + "unsigned": "550", + "positive": "688", + "short": "TEJTELBBFRNFHULDITOJGGJCOSXUAT" }, { - "name": "Donna Collins", - "telephone": "1-858-166-4735", - "email": "arcu.vestibulum.ante@yahoo.org", - "url": "http://youtube.com", - "pk": "TEP72JJR8XE", - "str": "gravida molestie arcu. Sed eu nibh vulputate mauris", - "time": "04:14:54", - "date": "20240903", - "curcode": "HKD", - "timezone": "Africa/Abidjan", - "langcode": "RQ" - }, - { - "name": "Tad Jensen", - "telephone": "(767) 770-8531", - "email": "dolor.egestas.rhoncus@icloud.com", - "url": "http://baidu.com", - "pk": "UVO18XXG8IB", - "str": "montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique", - "time": "20:27:48", - "date": "20240322", - "curcode": "KZT", - "timezone": "Europe/Paris", - "langcode": "GI" - }, - { - "name": "Calvin Harrison", - "telephone": "1-547-884-7735", - "email": "etiam.laoreet@google.net", - "url": "https://instagram.com", - "pk": "NCV69RJX3HR", - "str": "aptent taciti sociosqu ad litora", - "time": "18:55:30", - "date": "20240720", - "curcode": "PLN", - "timezone": "Asia/Atyrau", - "langcode": "DP" - }, - { - "name": "Phillip Britt", - "telephone": "(445) 832-4949", - "email": "nulla.tincidunt@protonmail.net", - "url": "http://naver.com", - "pk": "HGW71LUD1QL", - "str": "ultricies dignissim", - "time": "00:42:36", - "date": "20230627", - "curcode": "CHF", - "timezone": "Africa/Addis_Ababa", - "langcode": "CB" - }, - { - "name": "Branden Leblanc", - "telephone": "(621) 519-7201", - "email": "erat.etiam@google.edu", - "url": "https://google.com", - "pk": "BIT71WZZ5MT", - "str": "Nunc pulvinar arcu et pede.", - "time": "13:57:23", - "date": "20250116", - "curcode": "MUR", - "timezone": "Asia/Atyrau", - "langcode": "YD" - }, - { - "name": "Bradley Larson", - "telephone": "1-699-788-9354", - "email": "et.commodo@icloud.edu", - "url": "https://whatsapp.com", - "pk": "UON48QSJ0RD", - "str": "dui,", - "time": "01:26:35", - "date": "20231004", - "curcode": "EUR", + "name": "Gavin Church", + "telephone": "1-815-563-6268", + "email": "in.dolor@aol.org", + "url": "https://facebook.com", + "pk": "SVH10TTO8FS", + "str": "commodo hendrerit. Donec porttitor tellus non magna.", + "time": "14:36:36", + "date": "20231029", + "curcode": "XAF", "timezone": "Europe/Berlin", - "langcode": "BU" + "langcode": "EL", + "float": "61.1484903424", + "int": "822377", + "color": "#01682d", + "unsigned": "477", + "positive": "373", + "short": "CBHMCOUXHTVWLKJMUZHRDFOYMUTTUQ" }, { - "name": "Scarlet Patterson", - "telephone": "(451) 444-7817", - "email": "scelerisque.scelerisque@outlook.edu", - "url": "http://wikipedia.org", - "pk": "LHE29UCR4BJ", - "str": "lacus. Cras", - "time": "13:51:51", - "date": "20240602", - "curcode": "SEK", - "timezone": "Asia/Damascus", - "langcode": "DY" + "name": "Chase Frazier", + "telephone": "192-7642", + "email": "lectus.ante@outlook.com", + "url": "http://yahoo.com", + "pk": "EIL46GVS6WN", + "str": "nec, diam. Duis mi enim,", + "time": "18:18:18", + "date": "20240122", + "curcode": "BBD", + "timezone": "Europe/Berlin", + "langcode": "JX", + "float": "-31.8895552512", + "int": "567911", + "color": "#db9111", + "unsigned": "874", + "positive": "275", + "short": "MMJUPLDYDWXDUGQCWUTULEOTTIVUQT" }, { - "name": "Latifah Alvarez", - "telephone": "(266) 713-8186", - "email": "mauris.magna@icloud.edu", - "url": "https://youtube.com", - "pk": "VOQ57DYR9CU", - "str": "orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare,", - "time": "03:54:44", - "date": "20240923", - "curcode": "XCD", + "name": "Abbot Peck", + "telephone": "406-5404", + "email": "cum.sociis@protonmail.edu", + "url": "https://naver.com", + "pk": "OPA71PJD1JD", + "str": "convallis ligula. Donec luctus aliquet", + "time": "16:40:51", + "date": "20241111", + "curcode": "BWP", + "timezone": "Europe/Berlin", + "langcode": "NP", + "float": "-33.0700278784", + "int": "620510", + "color": "#1e2184", + "unsigned": "15", + "positive": "865", + "short": "ZXXMJWMOHHLKFHLXSFXUGRFETRMXYE" + }, + { + "name": "Anne Mcclure", + "telephone": "1-966-352-1956", + "email": "quam.quis@protonmail.net", + "url": "http://whatsapp.com", + "pk": "ZYL48JIM2TD", + "str": "Aliquam erat volutpat. Nulla", + "time": "19:12:49", + "date": "20240616", + "curcode": "PYG", + "timezone": "Europe/Berlin", + "langcode": "TD", + "float": "84.7384747008", + "int": "203213", + "color": "#b662e0", + "unsigned": "944", + "positive": "849", + "short": "FJDMMMBWCTQGQLJBPFSJQXUYQPXJEE" + }, + { + "name": "Ciaran Bauer", + "telephone": "938-3385", + "email": "quam.quis@outlook.couk", + "url": "https://walmart.com", + "pk": "BUE33XNN4MW", + "str": "nec, diam. Duis mi enim, condimentum eget, volutpat ornare,", + "time": "14:23:19", + "date": "20240504", + "curcode": "USD", + "timezone": "Europe/Paris", + "langcode": "QL", + "float": "25.9520198656", + "int": "834581", + "color": "#67ef86", + "unsigned": "143", + "positive": "881", + "short": "BLQIYHRHUOSICXWQTNVNCXJHSMQQEH" + }, + { + "name": "Quinn Figueroa", + "telephone": "247-1914", + "email": "fermentum.risus@outlook.couk", + "url": "https://reddit.com", + "pk": "YBT78HLB5QV", + "str": "vitae, aliquet nec,", + "time": "16:37:34", + "date": "20231212", + "curcode": "CHF", + "timezone": "Asia/Atyrau", + "langcode": "SG", + "float": "60.218623488", + "int": "331387", + "color": "#f2dd21", + "unsigned": "587", + "positive": "212", + "short": "MBIIAFIJQTDAQBUNRNSPKLCPEWRHVO" + }, + { + "name": "Drew Mathews", + "telephone": "1-450-583-2435", + "email": "ipsum@protonmail.edu", + "url": "https://guardian.co.uk", + "pk": "NRR44WHI7WJ", + "str": "vehicula aliquet libero. Integer in magna. Phasellus dolor", + "time": "06:35:13", + "date": "20240418", + "curcode": "MDL", + "timezone": "Europe/Berlin", + "langcode": "MV", + "float": "83.2199729152", + "int": "479720", + "color": "#80e579", + "unsigned": "963", + "positive": "460", + "short": "TYXLDGCBDESSFDNXKEVJFTUOTZGBSJ" + }, + { + "name": "Tara Ward", + "telephone": "675-7635", + "email": "egestas@icloud.ca", + "url": "http://google.com", + "pk": "LGD99AWO9PL", + "str": "lorem, sit", + "time": "02:46:45", + "date": "20240409", + "curcode": "GTQ", "timezone": "Africa/Addis_Ababa", - "langcode": "OT" + "langcode": "RN", + "float": "65.537725952", + "int": "769626", + "color": "#94f271", + "unsigned": "403", + "positive": "897", + "short": "ERNQOFFSYEDQCDTNQIQWVLMYLQBLDM" }, { - "name": "Thane Moran", - "telephone": "1-674-463-6771", - "email": "odio.etiam.ligula@icloud.edu", - "url": "https://pinterest.com", - "pk": "CGR74BDF1DH", - "str": "eu metus. In lorem. Donec elementum,", - "time": "15:58:50", - "date": "20231225", - "curcode": "RUB", - "timezone": "Africa/Abidjan", - "langcode": "KS" + "name": "Acton Chandler", + "telephone": "903-4706", + "email": "orci.donec@aol.ca", + "url": "https://nytimes.com", + "pk": "CQX45VAV8XG", + "str": "Nunc ullamcorper, velit in aliquet lobortis, nisi", + "time": "08:55:49", + "date": "20231107", + "curcode": "JOD", + "timezone": "Europe/Paris", + "langcode": "XV", + "float": "-77.2519374848", + "int": "329555", + "color": "#fc64fc", + "unsigned": "509", + "positive": "85", + "short": "MUPETOMWOPXIRLFNUFYMQGPCHYQACV" } ] - diff --git a/transport_accessibility/pt_map/tests.py b/transport_accessibility/pt_map/tests.py.m4 similarity index 59% rename from transport_accessibility/pt_map/tests.py rename to transport_accessibility/pt_map/tests.py.m4 index 3a07b9e..36fe544 100644 --- a/transport_accessibility/pt_map/tests.py +++ b/transport_accessibility/pt_map/tests.py.m4 @@ -4,11 +4,12 @@ from pt_map.models import * import unittest from django.db import models import random - -class AgencyTestCase(TestCase): +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 Agency._meta.fields] - self.gtfs_fields = get_all_fields("Agency") + 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.""" @@ -22,14 +23,14 @@ class AgencyTestCase(TestCase): """Fixed subTest""" d = data[0] values = {f["name"]: d[f["type"]] for f in self.gtfs_fields} - obj = Agency(**values) + 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 = Agency(**values) + obj = $1(**values) self.assertIsNotNone(obj) - self.assertIsInstance(obj, models.Model) + self.assertIsInstance(obj, models.Model)')') diff --git a/transport_accessibility/pt_map/tests_gtfs_compliance.py b/transport_accessibility/pt_map/tests_gtfs_compliance.py new file mode 100644 index 0000000..334376c --- /dev/null +++ b/transport_accessibility/pt_map/tests_gtfs_compliance.py @@ -0,0 +1,109 @@ +#TODO: Test LocationsGeojson +#TODO: Test MTM + +from django.test import TestCase, TransactionTestCase +from pt_map.test_data import * +from pt_map.models import * +import unittest +from django.db import models, transaction +import random +import time +import inspect +import pt_map.models +import datetime + +def _get_test_data(f): + """Get test data for a field type.""" + def wrapper(self, field, model, d, *args, **kwargs): + if field["name"] == "service_id": + self.add += 1 + return f"{d['pk']}{self.add}" + match field["type"]: + 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}) + return fk + return None + case "unique": + return (d[field["pk"]], "unique") + 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}" + case _: + return d[field["type"]] + return wrapper + +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]]: + 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} + obj = model.objects.create(**values) + self.assertIsNotNone(obj) + self.assertIsInstance(obj, models.Model) + return wrapper + return decorate + +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} + + def test_all_fields_present(self): + """Make sure the model has properties for all fields - regardless if required - provided by the GTFS standard.""" + for name in self.model_fields.keys(): + with self.subTest(name=name): + for f in self.gtfs_fields[name]: + with self.subTest(f=f): + self.assertIn(f["name"], self.model_fields[name]) + +class ConstructorAllFieldsTestCase(TransactionTestCase): + """Test all the models can be initialized using appropriate data as specified by the GTFS.""" + def setUp(self): + self.model_fields = {name: [f.name for f in model._meta.fields] for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo} + self.model_mtm = {name: [f.name for f in model._meta.many_to_many] for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo} + self.gtfs_fields = {name: get_all_fields(name) for name, model in inspect.getmembers(pt_map.models, inspect.isclass) if not model == FeedInfo} + self.feed_info = FeedInfo.objects.create(**{f["name"]: data[0][f["type"]] for f in get_all_fields("FeedInfo")}) + self.add = 0 + + @_get_test_data + def get_test_data(self, field, model, d): + pass + + @_test_constructor(0) + def test_constructor_all_fields_fixed(self): + """Make sure all of the model's fields are initializable with appropriate values. Fixed test data.""" + pass + + @_test_constructor(random.randint(0, len(data)-1)) + def test_constructor_all_fields_other(self): + """Make sure all of the model's fields are initializable with appropriate values. A second set of test data.""" + pass + +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.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): + pass + + @_test_constructor(0) + def test_constructor_required_fields_only_fixed(self): + pass + + @_test_constructor(random.randint(0, len(data)-1)) + def test_constructor_required_fields_only_other(self): + pass + diff --git a/transport_accessibility/tmp b/transport_accessibility/tmp new file mode 100644 index 0000000..5d228ca --- /dev/null +++ b/transport_accessibility/tmp @@ -0,0 +1 @@ +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)