Started adding tests to tests for the models' integrity with GTFS

This commit is contained in:
Johannes Randerath 2024-06-27 13:01:56 +02:00
parent 7962ec4b6c
commit cf6fcda0ed
22 changed files with 943 additions and 180 deletions

View File

@ -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

8
bin/pyproj Executable file
View File

@ -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())

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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',
),
]

View File

@ -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',
),
]

View File

@ -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')},
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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",
},
{

View File

@ -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)

View File

@ -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"
}
]

View File

@ -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)')')

View File

@ -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

View File

@ -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)