diff --git a/examples/explore/exploreproj/settings.py b/examples/explore/exploreproj/settings.py
index 5f30f6c..c23f048 100644
--- a/examples/explore/exploreproj/settings.py
+++ b/examples/explore/exploreproj/settings.py
@@ -21,7 +21,11 @@
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
# Application definition
+# NB new apps need to be placed first for their templates
+# to override subsequent apps
INSTALLED_APPS = [
+ 'exploreapp',
+ 'multigtfs',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -29,8 +33,6 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.gis',
- 'exploreapp',
- 'multigtfs',
]
LOCAL_INSTALLED_APPS = list(config('LOCAL_INSTALLED_APPS',
diff --git a/examples/gtfsproj/.env.example b/examples/gtfsproj/.env.example
new file mode 100644
index 0000000..fd2fc93
--- /dev/null
+++ b/examples/gtfsproj/.env.example
@@ -0,0 +1,7 @@
+SECRET_KEY=l$hc*-biy7*#7cic5q5r^mtf-2&l34pq4k7znn%)si+$h(i%e&
+
+DEBUG=True
+ALLOWED_HOSTS=*
+
+DATABASE_URL=spatialite:///db.sqlite3
+#DATABASE_URL=postgis://USER:PASSWORD@HOST:PORT/DBNAME
diff --git a/examples/gtfsproj/Dockerfile b/examples/gtfsproj/Dockerfile
new file mode 100644
index 0000000..617281a
--- /dev/null
+++ b/examples/gtfsproj/Dockerfile
@@ -0,0 +1,28 @@
+FROM python:3
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y \
+ binutils \
+ gdal-bin \
+ libproj-dev \
+ postgresql-client
+
+# Copy project code (current folder)
+COPY . /code
+
+# Install project dependencies, please note uses
+# pypi version of gtfsmulti not local version.
+# NB: To use local you will need to copy multigtfs
+# code to image and update requirements.txt
+WORKDIR /code
+RUN pip install -r requirements.txt
+
+# Create volume for feeds
+VOLUME /gtfs/feeds
+
+# run_project script passes all arguments except
+# bash to django manage.py script for project
+# runs development server by default
+RUN chmod +x /code/run_project.sh
+ENTRYPOINT ["/code/run_project.sh"]
+CMD ["./manage.py", "runserver", "0.0.0.0:8000"]
diff --git a/examples/gtfsproj/README.md b/examples/gtfsproj/README.md
new file mode 100644
index 0000000..d00a438
--- /dev/null
+++ b/examples/gtfsproj/README.md
@@ -0,0 +1,127 @@
+
Multi GTFS project
+
+This is an alternative example project. It is largely based on the
+"explore" example project but has been recreated with slightly different
+structure.
+
+The intention is that this example will ultimately provide a
+slightly different experience. This will include more mapping (possibly
+using leaflet), new template (e.g. bootstrap 4) and different navigation.
+
+Run with Docker
+
+Build and run
+
+Build docker image. This will use the settings in teh .env.example
+file, which can be replaced by creating a ".env" file before building
+the image (eg. cp env.example .env).
+
+ # Get source code
+ git clone https://github.com/alaw005/django-multi-gtfs.git
+ cd django-multi-gtfs/examples/gtfsproj
+
+ # Build image
+ sudo docker build -t multiproj .
+
+Run docker container as background (to run in forground change
+"-d" to "-it". This will automatically run the development server
+using the settings in the .env.example file.
+
+ # Create container
+ sudo docker run -d -p 8000:8000 --name multiproj1 multiproj
+
+Now setup database and superuser, refer management commands below.
+This is only required if database does not already exist or multgtfs
+has been updated (in which case need to migrate only)
+
+ # Setup database (if required)
+ sudo docker exec -it multiproj1 ./manage.py migrate
+ sudo docker exec -it multiproj1 ./manage.py createsuperuser
+
+Now you can access from browser using host IP address and port 8000.
+
+Management commands
+
+You can run django management commands as follows:
+
+ sudo docker exec -it multiproj1 ./manage.py help
+
+For example, to load a new feed you can do the following (assuming
+container name is "multiproj1"). NB: The first command copies a
+file from host system to the container.
+
+ cat /host/feeds/feed.zip | sudo docker exec -i multiproj1 sh -c 'cat > /gtfs/feeds/feed.zip'
+ sudo docker exec -it multiproj1 ./manage.py importgtfs /gtfs/feeds/feed.zip
+
+To specify a feed name rather than the defaul use the following.
+
+ sudo docker exec -it multiproj1 ./manage.py importgtfs --name "Feed name" /feeds/feed.zip
+
+Creating postgres database (assumes server already running)
+
+At the command line (as long as postgres is installed):
+
+ # Create database
+ createdb -h HOST -p 5432 -U USERNAME multigtfs
+
+ # Install postgis extension
+ psql -h HOST -p 5432 -U USERNAME -d multigtfs -c "CREATE EXTENSION postgis;"
+
+Now initiatise the database using multigtfs:
+
+ # Configure database
+ sudo docker exec -it multigtfs ./manage.py migrate
+
+ # Create superuser
+ sudo docker exec -it multigtfs ./manage.py createsuperuser
+
+That's it.
+
+Run without Docker
+
+The following applies to Ubunutu
+
+ sudo apt-get install -y git python-pip python-virtualenv spatialite-bin gdal-bin
+
+ cd ~/
+ git clone https://github.com/alaw005/django-multi-gtfs.git
+
+ # Navigate to project folder
+ cd django-multi-gtfs/examples/multiproj
+
+ # Create and activate new virtual environment (myenv).
+ # Use "deactivate" to deactivate virtualenv.
+ virtualenv --always-copy myenv
+ source myenv/bin/activate
+
+ pip install -r requirements.txt
+
+ # Create multigtfs tables in database
+ ./manage.py migrate
+
+ # Create db.sqlite3, superuser. Enter "yes" to create superuser and
+ # then enter username and password when prompted (you can leave email address blank)
+ ./manage.py createsuperuser
+
+ # Run
+ ./manage.py runserver 0.0.0.0:8000
+
+Now you can access from browser using host IP address and port 8000.
+
+Environment variables
+
+You can either set environment variables in the .env file as per above or directly
+at command line when you create the container. For example, to use existing postgis database:
+
+ sudo docker run -d -p 8000:8000 --name multiproj1 -e DATABASE_URL=postgis://USER:PASSWORD@HOST:PORT/DBNAME multiproj
+
+The environment variables you can change are identified in the following file:
+
+ django-multi-gtfs/examples/gtfsproj/gtfsproj/settings.py
+
+They all use the config() function e.g. SECRET_KEY is set in the following example,
+indicated by the "config('SECRET_KEY'" section.
+
+ SECRET_KEY = config('SECRET_KEY', default='+$8pgzf)luxklr(rhzg4!$6+b^2hbw*-frvh_2-7an9-_==n_u', cast=str)
+
+
diff --git a/examples/gtfsproj/gtfsapp/__init__.py b/examples/gtfsproj/gtfsapp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/gtfsproj/gtfsapp/admin.py b/examples/gtfsproj/gtfsapp/admin.py
new file mode 100644
index 0000000..13be29d
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/admin.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+# Register your models here.
diff --git a/examples/gtfsproj/gtfsapp/apps.py b/examples/gtfsproj/gtfsapp/apps.py
new file mode 100644
index 0000000..47dddba
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/apps.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class GtfsappConfig(AppConfig):
+ name = 'gtfsapp'
diff --git a/examples/gtfsproj/gtfsapp/migrations/__init__.py b/examples/gtfsproj/gtfsapp/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/gtfsproj/gtfsapp/models.py b/examples/gtfsproj/gtfsapp/models.py
new file mode 100644
index 0000000..1dfab76
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/models.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+
+# Create your models here.
diff --git a/examples/gtfsproj/gtfsapp/static/css/explore.css b/examples/gtfsproj/gtfsapp/static/css/explore.css
new file mode 100644
index 0000000..4151d28
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/static/css/explore.css
@@ -0,0 +1,8 @@
+body {
+ padding-top: 20px;
+}
+
+.body-top {
+ padding: 40px 15px;
+ text-align: center;
+}
diff --git a/examples/gtfsproj/gtfsapp/static/js/explore.js b/examples/gtfsproj/gtfsapp/static/js/explore.js
new file mode 100644
index 0000000..1e6ae2c
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/static/js/explore.js
@@ -0,0 +1,44 @@
+explore = {
+ mapPoint: function(map_id, x, y, zoom) {
+ var map = new OpenLayers.Map(map_id);
+ var osm = new OpenLayers.Layer.OSM("OpenStreetMap Map");
+ var fromProjection = new OpenLayers.Projection("EPSG:4326");
+ var toProjection = new OpenLayers.Projection("EPSG:900913");
+ var position = new OpenLayers.LonLat(x, y).transform(fromProjection, toProjection);
+ map.addLayer(osm);
+
+ var markers = new OpenLayers.Layer.Markers( "Markers" );
+ map.addLayer(markers);
+ markers.addMarker(new OpenLayers.Marker(position));
+
+ map.setCenter(position, zoom);
+ },
+ mapLine: function(map_id, line_wkt) {
+ var map = new OpenLayers.Map(map_id);
+ var osm = new OpenLayers.Layer.OSM("OpenStreetMap Map");
+ var fromProjection = new OpenLayers.Projection("EPSG:4326");
+ var toProjection = new OpenLayers.Projection("EPSG:900913");
+ map.addLayer(osm);
+
+ var style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
+ style.strokeColor = "red";
+ style.strokeColor = "red";
+ style.fillColor = "red";
+ style.pointRadius = 10;
+ style.strokeWidth = 3;
+ style.rotation = 45;
+ style.strokeLinecap = "butt";
+ var vectorLayer = new OpenLayers.Layer.Vector("Line", {'style': style});
+ var wkt = new OpenLayers.Format.WKT({
+ 'internalProjection': toProjection,
+ 'externalProjection': fromProjection,
+ 'style': style });
+ var lineVector = wkt.read(line_wkt);
+ var bounds = lineVector.geometry.getBounds();
+
+ map.addLayer(vectorLayer);
+ vectorLayer.addFeatures([lineVector]);
+
+ map.zoomToExtent(bounds);
+ }
+}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_detail.html
new file mode 100644
index 0000000..4b3e8d7
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_detail.html
@@ -0,0 +1,21 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Agency {{object}}{% endblock %}
+{% block page_title %}Agency {{object}}{% endblock %}
+{% block page_top_content_elem %}
+An Agency represents a transit agency.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ agency_id {{object.agency_id|default:'Not set '}}
+ name {{object.name}}
+ url {% if object.url %}{{object.url}} {% else %}Not set {% endif %}
+ timezone {{object.timezone}}
+ lang {{object.lang|default:'Not set '}}
+ phone {{object.phone|default:'Not set '}}
+ fare_url {% if object.fare_url %}{{object.fare_url}} {% else %}Not set {% endif %}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_list.html
new file mode 100644
index 0000000..2f9b4ec
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/agency_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Agencies{% endblock %}
+{% block page_title %}Agencies{% endblock %}
+{% block page_top_content_elem %}
+An Agency represents a transit agency.
+{% endblock %}
+{% block page_middle_content %}
+{% for agency in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No agencies yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/base.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/base.html
new file mode 100644
index 0000000..775cacf
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/base.html
@@ -0,0 +1,53 @@
+{% load staticfiles %}
+
+
+
+
+
+
+ {% block head_title %}Multi GTFS - django-multi-gtfs{% endblock %}
+
+
+
+
+ {% block head_extra %}{% endblock %}
+
+
+
+
+
+
{% block page_title %}TODO: set page_title{% endblock %}
+ {% block page_top_content_elem %}
+
+ {% block page_top_content %}TODO: set page_top_content{% endblock %}
+
+ {% endblock %}
+
+
{% block page_middle_content %}{% endblock %}
+
+
+
+ {% block body_script %}{% endblock %}
+
+
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/block_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/block_detail.html
new file mode 100644
index 0000000..77c2cd6
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/block_detail.html
@@ -0,0 +1,19 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Block {{object}}{% endblock %}
+{% block page_title %}Block {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Block represents a fare zone for a trip.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ block_id {{object.block_id}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/block_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/block_list.html
new file mode 100644
index 0000000..10bccee
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/block_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Blocks{% endblock %}
+{% block page_title %}Blocks{% endblock %}
+{% block page_top_content_elem %}
+A Block represents a fare zone for a trip.
+{% endblock %}
+{% block page_middle_content %}
+{% for blocks in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No blockss yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/extra_data.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/extra_data.html
new file mode 100644
index 0000000..d04d11e
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/extra_data.html
@@ -0,0 +1,8 @@
+{% if object.extra_data %}
+Extra Attributes
+
+{% for key, val in object.extra_data.iteritems %}
+ {{ key }} {{ val }}
+{% endfor %}
+
+{% endif %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_detail.html
new file mode 100644
index 0000000..5b21737
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_detail.html
@@ -0,0 +1,24 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Fare {{object}}{% endblock %}
+{% block page_title %}Fare {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Fare describes how passengers pay for service.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ fare_id {{object.fare_id}}
+ price {{object.price}}
+ currency_type {{object.currency_type}}
+ payment_method {{object.get_payment_method_display|default:'Not set '}}
+ transfers {{object.get_transfers_display|default:'Not set '}}
+ transfer_duration {{object.transfer_duration|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_list.html
new file mode 100644
index 0000000..0aa4340
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/fare_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}Explore - - django-multi-gtfs - Fares{% endblock %}
+{% block page_title %}Fares{% endblock %}
+{% block page_top_content_elem %}
+A Fare describes how passengers pay for service.
+{% endblock %}
+{% block page_middle_content %}
+{% for fare in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No fares yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_detail.html
new file mode 100644
index 0000000..573bec2
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_detail.html
@@ -0,0 +1,17 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - FareRule {{object}}{% endblock %}
+{% block page_title %}FareRule {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A FareRule is the intersection between a Route and a Fare
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ route Route {{object.route}}
+ fare Fare {{object.fare}}
+ origin {% if object.origin_id %}Zone {{object.origin}} {% else %}Not set {% endif %}
+ destination {% if object.destination_id %}Zone {{object.destination}} {% else %}Not set {% endif %}
+ contains {% if object.contains_id %}Zone {{object.contains}} {% else %}Not set {% endif %}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_list.html
new file mode 100644
index 0000000..d514a6b
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/farerule_list.html
@@ -0,0 +1,20 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - FareRules{% endblock %}
+{% block page_title %}FareRules{% endblock %}
+{% block page_top_content_elem %}
+A FareRule is the intersection between a Fare and a Route.
+{% endblock %}
+{% block page_middle_content %}
+{% if route %}
+FareRules for Route {{route}}
+{% elif fare %}
+FareRules for Fare {{fare}}
+{% endif %}
+{% for farerule in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No farerules yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_detail.html
new file mode 100644
index 0000000..3e4e6c7
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_detail.html
@@ -0,0 +1,25 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Feed {{object}}{% endblock %}
+{% block page_title %}Feed {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Feed represents a single GTFS feed.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ id {{object.id}}
+ name {{object.name}}
+ created {{object.created}}
+
+Related Objects
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_list.html
new file mode 100644
index 0000000..382f8dc
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/feed_list.html
@@ -0,0 +1,20 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Feeds{% endblock %}
+{% block nav_feed_active_class %} class="active"{% endblock %}
+{% block page_title %}Feeds{% endblock %}
+{% block page_top_content_elem %}
+A Feed represents a single GTFS feed.
+{% endblock %}
+{% block page_middle_content %}
+{% for feed in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No feeds yet.
+{% endfor %}
+
+To add a feed, use:
+./manage.py importgtfs --name "Feed Name" /path/to/feed.zip
+{% endblock %}
+
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_detail.html
new file mode 100644
index 0000000..baca415
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_detail.html
@@ -0,0 +1,20 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - FeedInfo {{object}}{% endblock %}
+{% block page_title %}FeedInfo {{object}}{% endblock %}
+{% block page_top_content_elem %}
+FeedInfo contains information about the feed.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ publisher_name {{object.publisher_name}}
+ publisher_url {% if object.publisher_url %}{{object.publisher_url}} {% else %}Not set {% endif %}
+ lang {{object.lang|default:'Not set '}}
+ start_date {{object.start_date|default:'Not set '}}
+ end_date {{object.end_date|default:'Not set '}}
+ version {{object.version|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_list.html
new file mode 100644
index 0000000..0074da5
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/feedinfo_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - FeedInfos{% endblock %}
+{% block page_title %}FeedInfos{% endblock %}
+{% block page_top_content_elem %}
+FeedInfo contains information about the feed.
+{% endblock %}
+{% block page_middle_content %}
+{% for feedinfo in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No feedinfos yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_detail.html
new file mode 100644
index 0000000..e0e2d3b
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_detail.html
@@ -0,0 +1,18 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Frequency {{object}}{% endblock %}
+{% block page_title %}Frequency {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Frequency represents a periodic trip without a set schedule.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ trip Trip {{object.trip}}
+ id {{object.id}}
+ start_time {{object.start_time}}
+ end_time {{object.end_time}}
+ headway_secs {{object.headway_secs}}
+ exact_times {{object.get_exact_times_display|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_list.html
new file mode 100644
index 0000000..5dbf630
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/frequency_list.html
@@ -0,0 +1,19 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Frequencies{% endblock %}
+{% block page_title %}Frequencies{% endblock %}
+{% block page_top_content_elem %}
+A Frequency represents a periodic trip without a set schedule.
+route runs.
+{% endblock %}
+{% block page_middle_content %}
+{% if trip %}
+Frequencies for Trip {{trip}}
+{% endif %}
+{% for frequency in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No frequencies yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/route_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/route_detail.html
new file mode 100644
index 0000000..83fd1c1
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/route_detail.html
@@ -0,0 +1,57 @@
+{% extends "multigtfs/base.html" %}
+{% load staticfiles %}
+{% block head_title %}{{ block.super }} - Route {{object}}{% endblock %}
+
+{% block head_extra %}
+{% if object.geometry %}
+
+
+{% endif %}
+{% endblock %}
+
+{% block page_title %}Route {{object}}{% endblock %}
+
+{% block page_top_content_elem %}
+A Route is a named route in the schedule
+{% endblock %}
+
+{% block page_middle_content %}
+{% if object.geometry %}
+
+{% else %}
+Map
+
+ This route does not have a populated geometry. A route must have at least
+ one related Trip, and update_geometry() must be run on the Route.
+
+{% endif %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ route_id {{object.route_id}}
+ agency {% if object.agency_id %}Agency {{object.agency}} {% else %}Not set {% endif %}
+ short_name {{object.short_name}}
+ long_name {{object.long_name}}
+ desc {{object.desc|default:'Not set '}}
+ rtype {{object.get_rtype_display|default:'Not set '}}
+ url {% if object.url %}{{object.url}} {% else %}Not set {% endif %}
+ color {{object.color|default:'Not set '}}
+ text_color {{object.text_color|default:'Not set '}}
+ {% if object.color and object.text_color %}(Sample Coloring) {{object.short_name}} {{object.long_name}} {% endif %}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
+
+{% block body_script %}
+{% if object.geometry %}
+
+{% endif %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/route_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/route_list.html
new file mode 100644
index 0000000..cd0c706
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/route_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Routes{% endblock %}
+{% block page_title %}Routes{% endblock %}
+{% block page_top_content_elem %}
+A Route is named route in the schedule
+{% endblock %}
+{% block page_middle_content %}
+{% for route in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No routes yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/service_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/service_detail.html
new file mode 100644
index 0000000..cb6a056
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/service_detail.html
@@ -0,0 +1,29 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Service {{object}}{% endblock %}
+{% block page_title %}Service {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Service is one or more days with the same schedule.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ service_id {{object.service_id|default:'Not set '}}
+ monday {{object.monday}}
+ tuesday {{object.tuesday}}
+ wednesday {{object.wednesday}}
+ thursday {{object.thursday}}
+ friday {{object.friday}}
+ saturday {{object.saturday}}
+ sunday {{object.sunday}}
+ start_date {{object.start_date}}
+ end_date {{object.end_date}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/service_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/service_list.html
new file mode 100644
index 0000000..1b67b4d
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/service_list.html
@@ -0,0 +1,16 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Services{% endblock %}
+{% block page_title %}Services{% endblock %}
+{% block page_top_content_elem %}
+A Service is one or more days of the week that a scheduled
+route runs.
+{% endblock %}
+{% block page_middle_content %}
+{% for service in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No services yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_detail.html
new file mode 100644
index 0000000..293e454
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_detail.html
@@ -0,0 +1,16 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Trip {{object}}{% endblock %}
+{% block page_title %}Trip {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A ServiceDate is an exception to a Service's routine schedule
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ service Service {{object.service}}
+ id {{object.id}}
+ date {{object.date}}
+ exception_type {{object.get_exception_type_display|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_list.html
new file mode 100644
index 0000000..c877ab9
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/servicedate_list.html
@@ -0,0 +1,16 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - ServiceDates{% endblock %}
+{% block page_title %}ServiceDates{% endblock %}
+{% block page_top_content_elem %}
+A ServiceDate is an exception to a Service's routine schedule
+{% endblock %}
+{% block page_middle_content %}
+ServiceDates for Service {{service}}
+{% for servicedate in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No servicedates yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_detail.html
new file mode 100644
index 0000000..9562163
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_detail.html
@@ -0,0 +1,48 @@
+{% extends "multigtfs/base.html" %}
+{% load staticfiles %}
+{% block head_title %}{{ block.super }} - Shape {{object}}{% endblock %}
+
+{% block head_extra %}
+{% if object.geometry %}
+
+
+{% endif %}
+{% endblock %}
+
+{% block page_title %}Shape {{object}}{% endblock %}
+
+{% block page_top_content_elem %}
+A Shape is the path a vehicle travels, on the road or on a track.
+{% endblock %}
+
+{% block page_middle_content %}
+{% if object.geometry %}
+
+{% else %}
+Map
+
+ This shape does not have a populated geometry. A shape must have at
+ least 2 ShapePoints, and update_geometry() must be run on the shape.
+
+{% endif %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ shape_id {{object.shape_id|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
+
+{% block body_script %}
+{% if object.geometry %}
+
+{% endif %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_list.html
new file mode 100644
index 0000000..2b5f38e
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/shape_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Shapes{% endblock %}
+{% block page_title %}Shapes{% endblock %}
+{% block page_top_content_elem %}
+A Shape is the path a vehicle travels, on the road or on a track.
+{% endblock %}
+{% block page_middle_content %}
+{% for shape in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No shapes yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_detail.html
new file mode 100644
index 0000000..9aad541
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_detail.html
@@ -0,0 +1,28 @@
+{% extends "multigtfs/base.html" %}
+{% load staticfiles %}
+{% block head_title %}{{ block.super }} - ShapePoint {{object}}{% endblock %}
+{% block head_extra %}
+
+
+{% endblock %}
+{% block page_title %}ShapePoint {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A ShapePoint is a point of a shape
+{% endblock %}
+{% block page_middle_content %}
+
+Attributes
+
+ shape Shape {{object.shape}}
+ id {{object.id}}
+ point {{object.point}}
+ sequence {{object.sequence}}
+ traveled {{object.traveled}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
+{% block body_script %}
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_list.html
new file mode 100644
index 0000000..abcada8
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/shapepoint_list.html
@@ -0,0 +1,18 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - ShapePoints{% endblock %}
+{% block page_title %}ShapePoints{% endblock %}
+{% block page_top_content_elem %}
+A ShapePoint is a point of a shape
+{% endblock %}
+{% block page_middle_content %}
+{% if shape %}
+ShapePoints for Shape {{shape}}
+{% endif %}
+{% for shapepoint in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No shapepoints yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_detail.html
new file mode 100644
index 0000000..2203778
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_detail.html
@@ -0,0 +1,40 @@
+{% extends "multigtfs/base.html" %}
+{% load staticfiles %}
+{% block head_title %}{{ block.super }} - Stop {{object}}{% endblock %}
+{% block head_extra %}
+
+
+{% endblock %}
+{% block page_title %}Stop {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Stop is a stop or station in the schedule.
+{% endblock %}
+{% block page_middle_content %}
+
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ stop_id {{object.stop_id|default:'Not set '}}
+ code {{object.code|default:'Not set '}}
+ name {{object.name}}
+ desc {{object.desc|default:'Not set '}}
+ point {{object.point}}
+ zone {% if object.zone_id %}Zone {{object.zone}} {% else %}No Zone {% endif %}
+ url {% if object.url %}{{object.url}} {% else %}Not set {% endif %}
+ location_type {{object.get_location_type_display|default:'Not set '}}
+ parent_station {% if object.parent_station_id %}Parent Station {{object.name}} {% else %}No Parent Station {% endif %}
+ timezone {{object.timezone|default:'Not set '}}
+ wheelchair_boarding {{object.get_wheelchair_boarding_type_display|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
+{% block body_script %}
+
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_list.html
new file mode 100644
index 0000000..148b753
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/stop_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Stops{% endblock %}
+{% block page_title %}Stops{% endblock %}
+{% block page_top_content_elem %}
+A Stop is a stop or station in the schedule.
+{% endblock %}
+{% block page_middle_content %}
+{% for stop in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No stops yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_detail.html
new file mode 100644
index 0000000..8aaf4ab
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_detail.html
@@ -0,0 +1,22 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - StopTime {{object}}{% endblock %}
+{% block page_title %}StopTime {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A StopTime is the intersection between a Trip and a Stop.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ trip Trip {{object.trip}}
+ stop Stop {{object.stop}}
+ id {{object.id}}
+ arrival_time {{object.arrival_time|default:'Not set '}}
+ departure_time {{object.departure_time|default:'Not set '}}
+ stop_sequence {{object.stop_sequence}}
+ stop_headsign {{object.stop_headsign|default:'Not set '}}
+ pickup_type {{object.get_pickup_type_display|default:'Not set '}}
+ dropoff_type {{object.get_dropoff_type_display|default:'Not set '}}
+ shape_dist_traveled {{object.shape_dist_traveled|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_list.html
new file mode 100644
index 0000000..fb873c9
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/stoptime_list.html
@@ -0,0 +1,20 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - StopTimes{% endblock %}
+{% block page_title %}StopTimes{% endblock %}
+{% block page_top_content_elem %}
+A StopTime is the intersection between a Trip and a Stop.
+{% endblock %}
+{% block page_middle_content %}
+{% if stop %}
+StopTimes for Stop {{stop}}
+{% elif trip %}
+StopTimes for Trip {{trip}}
+{% endif %}
+{% for stoptime in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No stoptimes yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_detail.html
new file mode 100644
index 0000000..39895d3
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_detail.html
@@ -0,0 +1,57 @@
+{% extends "multigtfs/base.html" %}
+{% load staticfiles %}
+{% block head_title %}{{ block.super }} - Trip {{object}}{% endblock %}
+
+{% block head_extra %}
+{% if object.geometry %}
+
+
+{% endif %}
+{% endblock %}
+
+{% block page_title %}Trip {{object}}{% endblock %}
+
+{% block page_top_content_elem %}
+A Trip is a single run of a Route
+{% endblock %}
+
+{% block page_middle_content %}
+{% if object.geometry %}
+
+{% else %}
+Map
+
+ This trip does not have a populated geometry. A trip must have a
+ related Shape or at least 2 StopTrips, and update_geometry() must
+ be run on the Trip.
+
+{% endif %}
+Attributes
+
+ route Route {{object.route}}
+ service Service {{object.service}}
+ id {{object.id}}
+ trip_id {{object.trip_id|default:'Not set '}}
+ headsign {{object.headsign|default:'Not set '}}
+ short_name {{object.short_name|default:'Not set '}}
+ direction {{object.get_direction_display|default:'Not set '}}
+ block {% if object.block_id %}Block {{object.block}} {% else %}No Block {% endif %}
+ shape {% if object.shape_id %}Shape {{object.shape}} {% else %}No Shape {% endif %}
+ wheelchair_accessible {{object.get_wheelchair_accessible_display|default:'Not set '}}
+ bikes_allowed {{object.get_bikes_allowed_display|default:'Not set '}}
+
+{% include "multigtfs/extra_data.html" %}
+Related Objects
+
+{% endblock %}
+
+{% block body_script %}
+{% if object.geometry %}
+
+{% endif %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_list.html
new file mode 100644
index 0000000..6221e90
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/trip_list.html
@@ -0,0 +1,24 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Trips{% endblock %}
+{% block page_title %}Trips{% endblock %}
+{% block page_top_content_elem %}
+A Trip is a single run of a Route
+{% endblock %}
+{% block page_middle_content %}
+{% if route %}
+Trips for Route {{route}}
+{% elif service %}
+Trips for Service {{service}}
+{% elif shape %}
+Trips for Shape {{shape}}
+{% elif the_block %}
+Trips for Block {{the_block}}
+{% endif %}
+{% for trip in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No trips yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_detail.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_detail.html
new file mode 100644
index 0000000..d22b0dc
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_detail.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Zone {{object}}{% endblock %}
+{% block page_title %}Zone {{object}}{% endblock %}
+{% block page_top_content_elem %}
+A Zone is an area with the same fare rules.
+{% endblock %}
+{% block page_middle_content %}
+Attributes
+
+ feed Feed {{object.feed}}
+ id {{object.id}}
+ zone_id {{object.zone_id}}
+
+{% include "multigtfs/extra_data.html" %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_list.html b/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_list.html
new file mode 100644
index 0000000..b1e7b35
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/templates/multigtfs/zone_list.html
@@ -0,0 +1,15 @@
+{% extends "multigtfs/base.html" %}
+{% block head_title %}{{ block.super }} - Zones{% endblock %}
+{% block page_title %}Zones{% endblock %}
+{% block page_top_content_elem %}
+A Zone is an area with the same fare rules.
+{% endblock %}
+{% block page_middle_content %}
+{% for zone in object_list %}
+ {% if forloop.first %}{% endif %}
+{% empty %}
+ No zones yet.
+{% endfor %}
+{% endblock %}
diff --git a/examples/gtfsproj/gtfsapp/tests.py b/examples/gtfsproj/gtfsapp/tests.py
new file mode 100644
index 0000000..5982e6b
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/tests.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/examples/gtfsproj/gtfsapp/urls.py b/examples/gtfsproj/gtfsapp/urls.py
new file mode 100644
index 0000000..4760a3d
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/urls.py
@@ -0,0 +1,62 @@
+from django.conf.urls import url, include
+
+from gtfsapp.views import (
+ FeedList, FeedInfoList, FeedAgencyList, FeedRouteList,
+ FeedStopList, FeedServiceList, FeedShapeList, FeedFareList,
+ FeedZoneList,
+
+ FeedDetail, FeedInfoDetail, AgencyDetail,
+ RouteDetail, TripDetail, StopDetail, StopTimeDetail,
+ ServiceDetail, ShapeDetail, ShapePointDetail, FareDetail,
+ ZoneDetail, ServiceDateDetail,
+
+ StopTimeByStopList, FareRuleByRouteList, TripByRouteList,
+
+ FrequencyByTripList, StopTimeByTripList,
+
+ TripByShapeList, ShapePointByShapeList,
+
+ ServiceDateByServiceList, TripByServiceList,
+)
+
+urlpatterns = [
+ url(r'^$', FeedList.as_view(), name='feed_list'),
+ url(r'feed/(?P\d+)/feedinfo/$', FeedInfoList.as_view(), name='feedinfo_list'),
+ url(r'feed/(?P\d+)/agency/$', FeedAgencyList.as_view(), name='agency_list'),
+ url(r'feed/(?P\d+)/route/$', FeedRouteList.as_view(), name='route_list'),
+ url(r'feed/(?P\d+)/stop/$', FeedStopList.as_view(), name='stop_list'),
+ url(r'feed/(?P\d+)/service/$', FeedServiceList.as_view(), name='service_list'),
+ url(r'feed/(?P\d+)/shape/$', FeedShapeList.as_view(), name='shape_list'),
+ url(r'feed/(?P\d+)/fare/$', FeedFareList.as_view(), name='fare_list'),
+ url(r'feed/(?P\d+)/zone/$', FeedZoneList.as_view(), name='zone_list'),
+
+ url(r'^feed/(?P\d+)/$', FeedDetail.as_view(), name='feed_detail'),
+ url(r'feed/(?P\d+)/feedinfo/(?P\d+)/$', FeedInfoDetail.as_view(), name='feedinfo_detail'),
+ url(r'feed/(?P\d+)/agency/(?P\d+)/$', AgencyDetail.as_view(), name='agency_detail'),
+ url(r'feed/(?P\d+)/route/(?P\d+)/$', RouteDetail.as_view(), name='route_detail'),
+ url(r'feed/(?P\d+)/trip/(?P\d+)/$', TripDetail.as_view(), name='trip_detail'),
+ url(r'feed/(?P\d+)/stop/(?P\d+)/$', StopDetail.as_view(), name='stop_detail'),
+ url(r'feed/(?P\d+)/stoptime/(?P\d+)/$', StopTimeDetail.as_view(), name='stoptime_detail'),
+ url(r'feed/(?P\d+)/service/(?P\d+)/$', ServiceDetail.as_view(), name='service_detail'),
+ url(r'feed/(?P\d+)/shape/(?P\d+)/$', ShapeDetail.as_view(), name='shape_detail'),
+ url(r'feed/(?P\d+)/shapepoint/(?P\d+)$', ShapePointDetail.as_view(), name='shapepoint_detail'),
+ url(r'feed/(?P\d+)/servicedate/(?P\d+)/$', ServiceDateDetail.as_view(), name='servicedate_detail'),
+
+ url(r'feed/(?P\d+)/fare/(?P\d+)/$', FareDetail.as_view(), name='fare_detail'),
+ url(r'feed/(?P\d+)/zone/(?P\d+)/$', ZoneDetail.as_view(), name='zone_detail'),
+
+ url(r'feed/(?P\d+)/route/(?P\d+)/farerule/$', FareRuleByRouteList.as_view(), name='farerule_by_route_list'),
+ url(r'feed/(?P\d+)/route/(?P\d+)/trip/$', TripByRouteList.as_view(), name='trip_by_route_list'),
+
+ url(r'feed/(?P\d+)/trip/(?P\d+)/frequency/$', FrequencyByTripList.as_view(), name='frequency_by_trip_list'),
+ url(r'feed/(?P\d+)/trip/(?P\d+)/stoptime/$', StopTimeByTripList.as_view(), name='stoptime_by_trip_list'),
+
+ url(r'feed/(?P\d+)/stop/(?P\d+)/stoptime/$', StopTimeByStopList.as_view(), name='stoptime_by_stop_list'),
+
+
+ url(r'feed/(?P\d+)/shape/(?P\d+)/shapepoint/$', ShapePointByShapeList.as_view(), name='shapepoint_by_shape_list'),
+ url(r'feed/(?P\d+)/shape/(?P\d+)/trip/$', TripByShapeList.as_view(), name='trip_by_shape_list'),
+
+ url(r'feed/(?P\d+)/service/(?P\d+)/servicedate/$', ServiceDateByServiceList.as_view(), name='servicedate_by_service_list'),
+ url(r'feed/(?P\d+)/service/(?P\d+)/trip/$', TripByServiceList.as_view(), name='trip_by_service_list'), ]
+
diff --git a/examples/gtfsproj/gtfsapp/views.py b/examples/gtfsproj/gtfsapp/views.py
new file mode 100644
index 0000000..ee7337d
--- /dev/null
+++ b/examples/gtfsproj/gtfsapp/views.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.shortcuts import render
+from django.views.generic import ListView, DetailView
+
+from multigtfs.models import (
+ Feed, FeedInfo, Agency, Route, Trip, Service, Stop,
+ Shape, ShapePoint, Fare, Zone, StopTime, FareRule, Frequency,
+ ServiceDate
+)
+
+# List feed
+class FeedList(ListView):
+ queryset = Feed.objects.order_by('name', '-created')
+
+# Filter lists by feed
+class ByFeedListView(ListView):
+ by_col = 'feed_id'
+ by_kwarg = 'feed_id'
+ by_class = Feed
+ by_classname = 'feed'
+
+ def get_context_data(self, **kwargs):
+ context = super(ByFeedListView, self).get_context_data(
+ **kwargs)
+ context[self.by_classname] = self.by_class.objects.get(
+ id=self.kwargs[self.by_kwarg])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ q_filter = {self.by_col: self.kwargs[self.by_kwarg]}
+ qset = super(ByFeedListView, self).get_queryset(**kwargs)
+ return qset.filter(**q_filter)
+
+
+# List
+class FeedInfoList(ByFeedListView):
+ model = FeedInfo
+
+class FeedAgencyList(ByFeedListView):
+ model = Agency
+
+class FeedStopList(ByFeedListView):
+ model = Stop
+
+class FeedRouteList(ByFeedListView):
+ model = Route
+
+class FeedShapeList(ByFeedListView):
+ model = Shape
+
+class FeedServiceList(ByFeedListView):
+ model = Service
+
+class FeedZoneList(ByFeedListView):
+ model = Zone
+
+class FeedFareList(ByFeedListView):
+ model = Fare
+
+
+class StopTimeByStopList(ListView):
+ model = StopTime
+
+ def get_context_data(self, **kwargs):
+ context = super(StopTimeByStopList, self).get_context_data(
+ **kwargs)
+ context['stop'] = Stop.objects.get(id=self.kwargs['stop_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return StopTime.objects.filter(stop_id=self.kwargs['stop_id'])
+
+
+# Detail
+class FeedDetail(DetailView):
+ model = Feed
+
+class FeedInfoDetail(DetailView):
+ model = FeedInfo
+
+class AgencyDetail(DetailView):
+ model = Agency
+
+class StopDetail(DetailView):
+ model = Stop
+
+class StopTimeDetail(DetailView):
+ model = StopTime
+
+class RouteDetail(DetailView):
+ model = Route
+
+class TripDetail(DetailView):
+ model = Trip
+
+class ShapeDetail(DetailView):
+ model = Shape
+
+class ShapePointDetail(DetailView):
+ model = ShapePoint
+
+class ServiceDetail(DetailView):
+ model = Service
+
+class ZoneDetail(DetailView):
+ model = Zone
+
+class FareDetail(DetailView):
+ model = Fare
+
+class ServiceDateDetail(DetailView):
+ model = ServiceDate
+
+
+
+class TripByRouteList(ListView):
+ model = Trip
+
+ def get_context_data(self, **kwargs):
+ context = super(TripByRouteList, self).get_context_data(**kwargs)
+ context['route'] = Route.objects.get(id=self.kwargs['route_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return Trip.objects.filter(route_id=self.kwargs['route_id'])
+
+
+class FareRuleByRouteList(ListView):
+ model = FareRule
+
+ def get_context_data(self, **kwargs):
+ context = super(FareRuleByRouteList, self).get_context_data(
+ **kwargs)
+ context['route'] = Route.objects.get(id=self.kwargs['route_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return FareRule.objects.filter(route_id=self.kwargs['route_id'])
+
+
+class StopTimeByTripList(ListView):
+ model = StopTime
+
+ def get_context_data(self, **kwargs):
+ context = super(StopTimeByTripList, self).get_context_data(
+ **kwargs)
+ context['trip'] = Trip.objects.get(id=self.kwargs['trip_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return StopTime.objects.filter(trip=self.kwargs['trip_id'])
+
+
+class FrequencyByTripList(ListView):
+ model = Frequency
+
+ def get_context_data(self, **kwargs):
+ context = super(FrequencyByTripList, self).get_context_data(
+ **kwargs)
+ context['trip'] = Trip.objects.get(id=self.kwargs['trip_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return Frequency.objects.filter(trip=self.kwargs['trip_id'])
+
+
+
+class TripByShapeList(ListView):
+ model = ShapePoint
+
+ def get_context_data(self, **kwargs):
+ context = super(TripByShapeList, self).get_context_data(**kwargs)
+ context['shape'] = Shape.objects.get(id=self.kwargs['shape_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return Trip.objects.filter(shape=self.kwargs['shape_id'])
+
+class ShapePointByShapeList(ListView):
+ model = ShapePoint
+
+ def get_context_data(self, **kwargs):
+ context = super(ShapePointByShapeList, self).get_context_data(
+ **kwargs)
+ context['shape'] = Shape.objects.get(id=self.kwargs['shape_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return ShapePoint.objects.filter(shape=self.kwargs['shape_id'])
+
+class ServiceDateByServiceList(ListView):
+ model = ServiceDate
+
+ def get_context_data(self, **kwargs):
+ context = super(ServiceDateByServiceList, self).get_context_data(
+ **kwargs)
+ context['service'] = Service.objects.get(id=self.kwargs['service_id'])
+ return context
+
+ def get_queryset(self, **kwargs):
+ return ServiceDate.objects.filter(service=self.kwargs['service_id'])
+
+class TripByServiceList(ListView):
+ model = Trip
+
+ def get_context_data(self, **kwargs):
+ context = super(TripByServiceList, self).get_context_data(**kwargs)
+ context['service'] = Service.objects.get(id=self.kwargs['service_id'])
+ context['feed_id'] = self.kwargs['feed_id']
+ return context
+
+ def get_queryset(self, **kwargs):
+ return Trip.objects.filter(service=self.kwargs['service_id'])
diff --git a/examples/gtfsproj/gtfsproj/__init__.py b/examples/gtfsproj/gtfsproj/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/gtfsproj/gtfsproj/settings.py b/examples/gtfsproj/gtfsproj/settings.py
new file mode 100644
index 0000000..e486728
--- /dev/null
+++ b/examples/gtfsproj/gtfsproj/settings.py
@@ -0,0 +1,126 @@
+"""
+Django settings for gtfsproj project.
+
+Generated by 'django-admin startproject' using Django 1.11.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+import os
+import dj_database_url
+from decouple import config, Csv
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = config('SECRET_KEY', default='+$8pgzf)luxklr(rhzg4!$6+b^2hbw*-frvh_2-7an9-_==n_u', cast=str)
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = config('DEBUG', default=False, cast=bool)
+ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
+
+
+# Application definition
+# NB new apps need to be placed first for their templates
+# to override subsequent apps
+INSTALLED_APPS = [
+ 'gtfsapp',
+ 'multigtfs',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.gis',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'gtfsproj.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'gtfsproj.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
+
+DATABASES = {
+ 'default': config('DATABASE_URL',
+ default='spatialite:///%s' % os.path.join(BASE_DIR, 'db.sqlite3'),
+ cast=dj_database_url.parse)
+}
+
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
+
+LANGUAGE_CODE = config('LANGUAGE_CODE', default='en-us')
+
+TIME_ZONE = config('TIME_ZONE', default='UTC')
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
+
+STATIC_URL = config('STATIC_URL', default='/static/')
+STATIC_ROOT = config('STATIC_ROOT', default='/var/www/multigtfs/static')
diff --git a/examples/gtfsproj/gtfsproj/urls.py b/examples/gtfsproj/gtfsproj/urls.py
new file mode 100644
index 0000000..e057e9e
--- /dev/null
+++ b/examples/gtfsproj/gtfsproj/urls.py
@@ -0,0 +1,22 @@
+"""gtfsproj URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.11/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import include, url
+from django.contrib import admin
+
+urlpatterns = [
+ url(r'^admin/', admin.site.urls),
+ url(r'', include('gtfsapp.urls')),
+]
diff --git a/examples/gtfsproj/gtfsproj/wsgi.py b/examples/gtfsproj/gtfsproj/wsgi.py
new file mode 100644
index 0000000..278abd7
--- /dev/null
+++ b/examples/gtfsproj/gtfsproj/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for gtfsproj project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gtfsproj.settings")
+
+application = get_wsgi_application()
diff --git a/examples/gtfsproj/manage.py b/examples/gtfsproj/manage.py
new file mode 100755
index 0000000..9d97264
--- /dev/null
+++ b/examples/gtfsproj/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gtfsproj.settings")
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError:
+ # The above import may fail for some other reason. Ensure that the
+ # issue is really that Django is missing to avoid masking other
+ # exceptions on Python 2.
+ try:
+ import django
+ except ImportError:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ raise
+ execute_from_command_line(sys.argv)
diff --git a/examples/gtfsproj/requirements.txt b/examples/gtfsproj/requirements.txt
new file mode 100644
index 0000000..0f82aa8
--- /dev/null
+++ b/examples/gtfsproj/requirements.txt
@@ -0,0 +1,16 @@
+# Install multigtfs from PyPi
+multigtfs
+# Or from local source project
+#-e ../..
+
+# Configure the database from the environment
+dj-database-url
+
+# Configure other settings from the environment
+python-decouple
+
+# Connect to postgresql database
+psycopg2
+
+# Install gunicorn server
+gunicorn
diff --git a/examples/gtfsproj/run_project.sh b/examples/gtfsproj/run_project.sh
new file mode 100644
index 0000000..652be41
--- /dev/null
+++ b/examples/gtfsproj/run_project.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+
+# Create environment variables if not exist
+ENVIRONMENT_FILE=".env"
+if [ ! -f $ENVIRONMENT_FILE ]; then
+ echo "Create default parameters using $ENVIRONMENT_FILE file"
+ cp .env.example $ENVIRONMENT_FILE
+fi
+
+# If no arguments provided then run server on port 8000
+if [ $# -eq 0 ]; then
+
+ # If no arguments provided then run server on port 8000
+ echo "Run development server on port 8000"
+ exec ./manage.py runserver 0.0.0.0:8000
+
+elif [ "$1" == "gunicorn" ]; then
+
+ echo "Run gunicorn server on port 8000"
+ exec gunicorn --bind 0.0.0.0:8000 gtfsproj.wsgi
+
+else
+
+ exec "$@"
+
+fi
diff --git a/multigtfs/templates/admin/delete_confirmation.html b/multigtfs/templates/admin/delete_confirmation.html
new file mode 100644
index 0000000..403f560
--- /dev/null
+++ b/multigtfs/templates/admin/delete_confirmation.html
@@ -0,0 +1,50 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_urls static %}
+
+{% block extrahead %}
+ {{ block.super }}
+ {{ media }}
+
+{% endblock %}
+
+{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %}
+
+{% block breadcrumbs %}
+
+{% endblock %}
+
+{% block content %}
+{% if perms_lacking %}
+ {% blocktrans with escaped_object=object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}
+
+ {% for obj in perms_lacking %}
+ {{ obj }}
+ {% endfor %}
+
+{% elif protected %}
+ {% blocktrans with escaped_object=object %}Deleting the {{ object_name }} '{{ escaped_object }}' would require deleting the following protected related objects:{% endblocktrans %}
+
+ {% for obj in protected %}
+ {{ obj }}
+ {% endfor %}
+
+{% else %}
+ {% blocktrans with escaped_object=object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}
+ {% include "admin/includes/object_delete_summary.html" %}
+
+{% endif %}
+{% endblock %}
diff --git a/multigtfs/templates/admin/delete_selected_confirmation.html b/multigtfs/templates/admin/delete_selected_confirmation.html
new file mode 100644
index 0000000..3af3112
--- /dev/null
+++ b/multigtfs/templates/admin/delete_selected_confirmation.html
@@ -0,0 +1,51 @@
+{% extends "admin/base_site.html" %}
+{% load i18n l10n admin_urls static %}
+
+{% block extrahead %}
+ {{ block.super }}
+ {{ media }}
+
+{% endblock %}
+
+{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
+
+{% block breadcrumbs %}
+
+{% endblock %}
+
+{% block content %}
+{% if perms_lacking %}
+ {% blocktrans %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}
+
+ {% for obj in perms_lacking %}
+ {{ obj }}
+ {% endfor %}
+
+{% elif protected %}
+ {% blocktrans %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktrans %}
+
+ {% for obj in protected %}
+ {{ obj }}
+ {% endfor %}
+
+{% else %}
+ {% blocktrans %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktrans %}
+ {% include "admin/includes/object_delete_summary.html" %}
+
+{% endif %}
+{% endblock %}