Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#99 #100 #101 Upgrade to baseline Python 3.7+, Django 3.2+, PostgreSQL 10+ #109

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 8 additions & 43 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
language: python
dist: trusty
dist: bionic
sudo: false
addons:
postgresql: 9.5
postgresql: "10"
apt:
packages:
- postgresql-9.5-postgis-2.3
- postgresql-10-postgis-2.4
install: pip install tox coveralls
script: tox -e $TOX_ENV
after_success: coveralls
Expand All @@ -16,46 +16,11 @@ before_script:
- psql -U postgres -d multigtfs -c "CREATE EXTENSION IF NOT EXISTS postgis; CREATE EXTENSION IF NOT EXISTS postgis_topology;"
matrix:
include:
- env: TOX_ENV=py27-django18-postgis
python: "2.7"
- env: TOX_ENV=py34-django18-postgis
python: "3.4"
- env: TOX_ENV=py27-django19-postgis
python: "2.7"
- env: TOX_ENV=py35-django19-postgis
python: "3.5"
- env: TOX_ENV=py27-django110-postgis
python: "2.7"
- env: TOX_ENV=py35-django110-postgis
python: "3.5"
- env: TOX_ENV=py27-django111-postgis
python: "2.7"
- env: TOX_ENV=py36-django111-postgis
python: "3.6"
- env: TOX_ENV=py35-django20-postgis
python: "3.5"
- env: TOX_ENV=py36-django20-postgis
python: "3.6"
- env: TOX_ENV=py35-django21-postgis
python: "3.5"
- env: TOX_ENV=py36-django21-postgis
python: "3.6"
- env: TOX_ENV=py35-django22-postgis
python: "3.5"
- env: TOX_ENV=py37-django22-postgis
- env: TOX_ENV=py37-django-32-postgis
python: "3.7"
sudo: required
dist: xenial
- env: TOX_ENV=py35-django-master-postgis
python: "3.5"
- env: TOX_ENV=py36-django-master-postgis
python: "3.6"
- env: TOX_ENV=py37-django-master-postgis
python: "3.7"
sudo: required
dist: xenial
- env: TOX_ENV=py38-django-32-postgis
python: "3.8"
- env: TOX_ENV=py39-django-32-postgis
python: "3.9"
allow_failures:
# Django master is allowed to fail
- env: TOX_ENV=py35-django-master-postgis
- env: TOX_ENV=py36-django-master-postgis
- env: TOX_ENV=py37-django-master-postgis
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
FROM python:3.6
FROM python:3.7-buster
ENV PYTHONUNBUFFERED 1

RUN apt-get update
RUN apt-get install -y binutils libproj-dev gdal-bin postgresql-client
RUN pip install "django>=1.11,<2.0" psycopg2
RUN pip install "django>=2.2,<4.0" "psycopg2"

RUN mkdir /code
RUN mkdir /feeds
Expand Down
4 changes: 2 additions & 2 deletions examples/explore/exploreproj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=Csv())

# Application definition
INSTALLED_APPS = [
Expand All @@ -37,7 +37,7 @@
default='', cast=Csv()))
INSTALLED_APPS.extend(LOCAL_INSTALLED_APPS)

MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
Expand Down
2 changes: 1 addition & 1 deletion examples/explore/exploreproj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
admin.autodiscover()

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', admin.site.urls),
url(r'^$', TemplateView.as_view(template_name="explore/home.html"),
name='home'),
url(r'', include('exploreapp.urls'))
Expand Down
29 changes: 6 additions & 23 deletions multigtfs/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"""
from codecs import BOM_UTF8
from distutils.version import LooseVersion
from io import TextIOWrapper
from zipfile import ZipFile, ZIP_DEFLATED

from django import get_version
from django.utils.six import PY3, binary_type, text_type

DJ_VERSION = LooseVersion(get_version())

Expand Down Expand Up @@ -47,15 +47,12 @@ def bom_prefix_csv(text):
- Python 2 returns a UTF-8 encoded bytestring
- Python 3 returns unicode text
"""
if PY3:
return BOM_UTF8.decode('utf-8') + text
else:
return BOM_UTF8 + text.encode('utf-8')
return BOM_UTF8.decode('utf-8') + text


def force_utf8(text):
"""Encode as UTF-8 bytestring if it isn't already."""
if isinstance(text, binary_type):
if isinstance(text, bytes):
return text
else:
return text.encode('utf-8')
Expand All @@ -79,11 +76,7 @@ def opener_from_zipfile(zipfile):

def opener(filename):
inner_file = zipfile.open(filename)
if PY3:
from io import TextIOWrapper
return TextIOWrapper(inner_file)
else:
return inner_file
return TextIOWrapper(inner_file)

return opener

Expand All @@ -97,23 +90,13 @@ def write_text_rows(writer, rows):
# Python 2 csv does badly with unicode outside of ASCII
new_row = []
for item in row:
if isinstance(item, text_type):
if isinstance(item, str):
new_row.append(item.encode('utf-8'))
else:
new_row.append(item)
writer.writerow(new_row)


# The GeoQuerySet is deprecated in Django 1.8
# https://docs.djangoproject.com/en/dev/releases/1.9/#django-contrib-gis
# The GeoManager is deprecated in Django 1.9
# https://docs.djangoproject.com/en/dev/releases/1.9/#geomanager-and-geoqueryset-custom-methods
# They are removed in Django 2.0
# https://docs.djangoproject.com/en/dev/releases/2.0/#features-removed-in-2-0
if DJ_VERSION >= LooseVersion('2.0'):
from django.db.models import Manager, QuerySet
else:
from django.contrib.gis.db.models import GeoManager as Manager
from django.contrib.gis.db.models.query import GeoQuerySet as QuerySet
from django.db.models import Manager, QuerySet
assert Manager
assert QuerySet
29 changes: 14 additions & 15 deletions multigtfs/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import unicode_literals

from django.db import models, migrations
import jsonfield.fields
import django.contrib.gis.db.models.fields
import multigtfs.models.fields.seconds

Expand All @@ -25,7 +24,7 @@ class Migration(migrations.Migration):
('lang', models.CharField(help_text='ISO 639-1 code for the primary language', max_length=2, blank=True)),
('phone', models.CharField(help_text='Voice telephone number', max_length=255, blank=True)),
('fare_url', models.URLField(help_text='URL for purchasing tickets online', blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
],
options={
'db_table': 'agency',
Expand Down Expand Up @@ -54,7 +53,7 @@ class Migration(migrations.Migration):
('payment_method', models.IntegerField(default=1, help_text='When is the fare paid?', choices=[(0, 'Fare is paid on board.'), (1, 'Fare must be paid before boarding.')])),
('transfers', models.IntegerField(default=None, help_text='Are transfers permitted?', null=True, blank=True, choices=[(0, 'No transfers permitted on this fare.'), (1, 'Passenger may transfer once.'), (2, 'Passenger may transfer twice.'), (None, 'Unlimited transfers are permitted.')])),
('transfer_duration', models.IntegerField(help_text='Time in seconds until a ticket or transfer expires', null=True, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
],
options={
'db_table': 'fare',
Expand All @@ -65,7 +64,7 @@ class Migration(migrations.Migration):
name='FareRule',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
],
options={
'db_table': 'fare_rules',
Expand All @@ -78,7 +77,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('created', models.DateTimeField(auto_now_add=True)),
('meta', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('meta', models.JSONField(default={}, null=True, blank=True)),
],
options={
'db_table': 'feed',
Expand All @@ -95,7 +94,7 @@ class Migration(migrations.Migration):
('start_date', models.DateField(help_text='Date that feed starts providing reliable data.', null=True, blank=True)),
('end_date', models.DateField(help_text='Date that feed stops providing reliable data.', null=True, blank=True)),
('version', models.CharField(help_text='Version of feed.', max_length=255, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('feed', models.ForeignKey(to='multigtfs.Feed', on_delete=django.db.models.deletion.CASCADE)),
],
options={
Expand All @@ -112,7 +111,7 @@ class Migration(migrations.Migration):
('end_time', multigtfs.models.fields.seconds.SecondsField(help_text='Time that the service ends at the specified frequency')),
('headway_secs', models.IntegerField(help_text='Time in seconds before returning to same stop')),
('exact_times', models.CharField(blank=True, help_text='Should frequency-based trips be exactly scheduled?', max_length=1, choices=[(0, 'Trips are not exactly scheduled'), (1, 'Trips are exactly scheduled from start time')])),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
],
options={
'db_table': 'frequency',
Expand All @@ -133,7 +132,7 @@ class Migration(migrations.Migration):
('color', models.CharField(help_text='Color of route in hex', max_length=6, blank=True)),
('text_color', models.CharField(help_text='Color of route text in hex', max_length=6, blank=True)),
('geometry', django.contrib.gis.db.models.fields.MultiLineStringField(help_text='Geometry cache of Trips', srid=4326, null=True, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('agency', models.ForeignKey(blank=True, to='multigtfs.Agency', help_text='Agency for this route.', null=True, on_delete=django.db.models.deletion.CASCADE)),
('feed', models.ForeignKey(to='multigtfs.Feed', on_delete=django.db.models.deletion.CASCADE)),
],
Expand All @@ -156,7 +155,7 @@ class Migration(migrations.Migration):
('sunday', models.BooleanField(default=True, help_text='Is the route active on Sunday?')),
('start_date', models.DateField(null=True, blank=True)),
('end_date', models.DateField(null=True, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('feed', models.ForeignKey(to='multigtfs.Feed', on_delete=django.db.models.deletion.CASCADE)),
],
options={
Expand All @@ -170,7 +169,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('date', models.DateField(help_text='Date that the service differs from the norm.')),
('exception_type', models.IntegerField(default=1, help_text='Is service added or removed on this date?', choices=[(1, 'Added'), (2, 'Removed')])),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('service', models.ForeignKey(to='multigtfs.Service', on_delete=django.db.models.deletion.CASCADE)),
],
options={
Expand Down Expand Up @@ -198,7 +197,7 @@ class Migration(migrations.Migration):
('point', django.contrib.gis.db.models.fields.PointField(help_text='WGS 84 latitude/longitude of shape point', srid=4326)),
('sequence', models.IntegerField()),
('traveled', models.FloatField(help_text='Distance of point from start of shape', null=True, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('shape', models.ForeignKey(related_name='points', to='multigtfs.Shape', on_delete=django.db.models.deletion.CASCADE)),
],
options={
Expand All @@ -219,7 +218,7 @@ class Migration(migrations.Migration):
('location_type', models.CharField(blank=True, help_text='Is this a stop or station?', max_length=1, choices=[('0', 'Stop'), ('1', 'Station')])),
('timezone', models.CharField(help_text='Timezone of the stop', max_length=255, blank=True)),
('wheelchair_boarding', models.CharField(blank=True, help_text='Is wheelchair boarding possible?', max_length=1, choices=[('0', 'No information'), ('1', 'Some wheelchair boarding'), ('2', 'No wheelchair boarding')])),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('feed', models.ForeignKey(to='multigtfs.Feed', on_delete=django.db.models.deletion.CASCADE)),
('parent_station', models.ForeignKey(blank=True, to='multigtfs.Stop', help_text='The station associated with the stop', null=True, on_delete=django.db.models.deletion.CASCADE)),
],
Expand All @@ -239,7 +238,7 @@ class Migration(migrations.Migration):
('pickup_type', models.CharField(blank=True, help_text='How passengers are picked up', max_length=1, choices=[('0', 'Regularly scheduled pickup'), ('1', 'No pickup available'), ('2', 'Must phone agency to arrange pickup'), ('3', 'Must coordinate with driver to arrange pickup')])),
('drop_off_type', models.CharField(blank=True, help_text='How passengers are picked up', max_length=1, choices=[('0', 'Regularly scheduled drop off'), ('1', 'No drop off available'), ('2', 'Must phone agency to arrange drop off'), ('3', 'Must coordinate with driver to arrange drop off')])),
('shape_dist_traveled', models.FloatField(help_text='Distance of stop from start of shape', null=True, verbose_name='shape distance traveled', blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('stop', models.ForeignKey(to='multigtfs.Stop', on_delete=django.db.models.deletion.CASCADE)),
],
options={
Expand All @@ -253,7 +252,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('transfer_type', models.IntegerField(default=0, help_text='What kind of transfer?', blank=True, choices=[(0, 'Recommended transfer point'), (1, 'Timed transfer point (vehicle will wait)'), (2, 'min_transfer_time needed to successfully transfer'), (3, 'No transfers possible')])),
('min_transfer_time', models.IntegerField(help_text='How many seconds are required to transfer?', null=True, blank=True)),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('from_stop', models.ForeignKey(related_name='transfer_from_stop', to='multigtfs.Stop', help_text='Stop where a connection between routes begins.', on_delete=django.db.models.deletion.CASCADE)),
('to_stop', models.ForeignKey(related_name='transfer_to_stop', to='multigtfs.Stop', help_text='Stop where a connection between routes ends.', on_delete=django.db.models.deletion.CASCADE)),
],
Expand All @@ -273,7 +272,7 @@ class Migration(migrations.Migration):
('geometry', django.contrib.gis.db.models.fields.LineStringField(help_text='Geometry cache of Shape or Stops', srid=4326, null=True, blank=True)),
('wheelchair_accessible', models.CharField(blank=True, help_text='Are there accommodations for riders with wheelchair?', max_length=1, choices=[('0', 'No information'), ('1', 'Some wheelchair accommodation'), ('2', 'No wheelchair accommodation')])),
('bikes_allowed', models.CharField(blank=True, help_text='Are bicycles allowed?', max_length=1, choices=[('0', 'No information'), ('1', 'Some bicycle accommodation'), ('2', 'No bicycles allowed')])),
('extra_data', jsonfield.fields.JSONField(default={}, null=True, blank=True)),
('extra_data', models.JSONField(default={}, null=True, blank=True)),
('block', models.ForeignKey(blank=True, to='multigtfs.Block', help_text='Block of sequential trips that this trip belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE)),
('route', models.ForeignKey(to='multigtfs.Route', on_delete=django.db.models.deletion.CASCADE)),
('service', models.ForeignKey(blank=True, to='multigtfs.Service', null=True, on_delete=django.db.models.deletion.CASCADE)),
Expand Down
6 changes: 1 addition & 5 deletions multigtfs/models/agency.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@

from __future__ import unicode_literals

from django.utils.encoding import python_2_unicode_compatible
from jsonfield import JSONField

from multigtfs.models.base import models, Base


@python_2_unicode_compatible
class Agency(Base):
"""One or more transit agencies that provide the data in this feed.

Expand All @@ -47,7 +43,7 @@ class Agency(Base):
help_text="Voice telephone number")
fare_url = models.URLField(
blank=True, help_text="URL for purchasing tickets online")
extra_data = JSONField(default={}, blank=True, null=True)
extra_data = models.JSONField(default=dict, blank=True, null=True)

def __str__(self):
return u"%d-%s" % (self.feed.id, self.agency_id)
Expand Down
Loading