From 24db45975a68b0ffbc663105e8e99209cbe320ba Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 11:41:07 -0500 Subject: [PATCH 01/14] Bump Django to latest 2.2 minor release --- python/cac_tripplanner/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 2b7dd0db8..2d21af3d7 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -1,6 +1,6 @@ base58==2.0.1 boto3==1.15.9 -Django==2.2.16 +Django==2.2.27 django-ckeditor==6.0.0 django-image-cropping==1.5.0 django-extensions==3.0.9 From 0cc53b6b2fa2dfbfc6e46562e089439f785a3b1f Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 11:42:19 -0500 Subject: [PATCH 02/14] Remove deprecated staticfiles in templates --- python/cac_tripplanner/templates/base.html | 2 +- python/cac_tripplanner/templates/event-detail.html | 2 +- python/cac_tripplanner/templates/home.html | 2 +- python/cac_tripplanner/templates/learn-detail.html | 2 +- python/cac_tripplanner/templates/learn-list.html | 2 +- python/cac_tripplanner/templates/place-detail.html | 2 +- python/cac_tripplanner/templates/privacy-policy.html | 2 +- python/cac_tripplanner/templates/terms-of-service.html | 2 +- python/cac_tripplanner/templates/tour-detail.html | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/cac_tripplanner/templates/base.html b/python/cac_tripplanner/templates/base.html index 87a599c36..d6d8e9d7d 100644 --- a/python/cac_tripplanner/templates/base.html +++ b/python/cac_tripplanner/templates/base.html @@ -1,5 +1,5 @@ -{% load staticfiles %} +{% load static %} diff --git a/python/cac_tripplanner/templates/event-detail.html b/python/cac_tripplanner/templates/event-detail.html index 71b484743..6dee23d09 100644 --- a/python/cac_tripplanner/templates/event-detail.html +++ b/python/cac_tripplanner/templates/event-detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | {{ event.name }}{% endblock %} diff --git a/python/cac_tripplanner/templates/home.html b/python/cac_tripplanner/templates/home.html index ae507183d..20add40bf 100644 --- a/python/cac_tripplanner/templates/home.html +++ b/python/cac_tripplanner/templates/home.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% load destination_extras %} {% load tz %} {% block content %} diff --git a/python/cac_tripplanner/templates/learn-detail.html b/python/cac_tripplanner/templates/learn-detail.html index b71de2b3f..409d7947d 100644 --- a/python/cac_tripplanner/templates/learn-detail.html +++ b/python/cac_tripplanner/templates/learn-detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | {{ article.title }}{% endblock %} diff --git a/python/cac_tripplanner/templates/learn-list.html b/python/cac_tripplanner/templates/learn-list.html index 50663073f..d19d928f7 100644 --- a/python/cac_tripplanner/templates/learn-list.html +++ b/python/cac_tripplanner/templates/learn-list.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load staticfiles %} +{% load static %} {% block content %} {% include "partials/header.html" %}
diff --git a/python/cac_tripplanner/templates/place-detail.html b/python/cac_tripplanner/templates/place-detail.html index 9a86f6826..17a05a9d0 100644 --- a/python/cac_tripplanner/templates/place-detail.html +++ b/python/cac_tripplanner/templates/place-detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | {{ destination.name }}{% endblock %} diff --git a/python/cac_tripplanner/templates/privacy-policy.html b/python/cac_tripplanner/templates/privacy-policy.html index c6d42edc4..33b177631 100644 --- a/python/cac_tripplanner/templates/privacy-policy.html +++ b/python/cac_tripplanner/templates/privacy-policy.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | Privacy Policy{% endblock %} diff --git a/python/cac_tripplanner/templates/terms-of-service.html b/python/cac_tripplanner/templates/terms-of-service.html index 700f31e96..d2bd449b0 100644 --- a/python/cac_tripplanner/templates/terms-of-service.html +++ b/python/cac_tripplanner/templates/terms-of-service.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | Terms of Service{% endblock %} diff --git a/python/cac_tripplanner/templates/tour-detail.html b/python/cac_tripplanner/templates/tour-detail.html index c7652824b..86bdf60fd 100644 --- a/python/cac_tripplanner/templates/tour-detail.html +++ b/python/cac_tripplanner/templates/tour-detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load cropping %} -{% load staticfiles %} +{% load static %} {% block pagetitle %} {% block title %}GoPhillyGo | {{ tour.name }}{% endblock %} From e9b060145fc91d93f4262287e4a6990a92e96691 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 12:32:10 -0500 Subject: [PATCH 03/14] Configure black with 100-char line length Also switches to with(open()) as file: construction for opening files to avoid leaving unclosed files lying around (happened primarily during tests). --- .../cac_tripplanner/settings.py | 363 ++++++++---------- python/cac_tripplanner/cms/tests.py | 90 ++--- python/cac_tripplanner/destinations/tests.py | 330 ++++++++-------- python/pyproject.toml | 2 + 4 files changed, 366 insertions(+), 419 deletions(-) create mode 100644 python/pyproject.toml diff --git a/python/cac_tripplanner/cac_tripplanner/settings.py b/python/cac_tripplanner/cac_tripplanner/settings.py index 6dd5ef409..1e271aede 100644 --- a/python/cac_tripplanner/cac_tripplanner/settings.py +++ b/python/cac_tripplanner/cac_tripplanner/settings.py @@ -22,52 +22,49 @@ # Tell image cropping library to use Django admin jquery, # or else loading the image cropper will fail for destinations admin # because it loads jquery for gis.admin.OSMGeoAdmin -IMAGE_CROPPING_JQUERY_URL = '/static/admin/js/vendor/jquery/jquery.js' +IMAGE_CROPPING_JQUERY_URL = "/static/admin/js/vendor/jquery/jquery.js" # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html # inherit the bucket's ACL AWS_DEFAULT_ACL = None try: - secrets = yaml.safe_load(open('/etc/cac_secrets', 'r')) -except (IOError, NameError): + with open("/etc/cac_secrets", "r") as secrets_file: + secrets = yaml.safe_load(secrets_file) +except (OSError, NameError): # Note: secrets are read in via a YAML file, so make sure nothing is added # here that cannot be represented in YAML. One example is a tuple: represent # it as a list here, and then convert to a tuple later on (see internal_ips). secrets = { - 'secret_key': '%&_DEVELOPMENT_SECRET_KEY_#42*pk!3y6lvk&1psyk=e=pr', - 'database': { - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'NAME': 'cac_tripplanner', - 'USER': 'cac_tripplanner', - 'PASSWORD': 'cac_tripplanner', - 'HOST': '192.168.8.25', - 'PORT': '5432' + "secret_key": "%&_DEVELOPMENT_SECRET_KEY_#42*pk!3y6lvk&1psyk=e=pr", + "database": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "cac_tripplanner", + "USER": "cac_tripplanner", + "PASSWORD": "cac_tripplanner", + "HOST": "192.168.8.25", + "PORT": "5432", }, # Note: the OTP URL is called directly from within javascript. In # order to view the page on an external machine, this URL must be # overridden via the secrets file. This can't be automatically set # to the host machine's DNS here, because this code runs in a VM. - 'otp_url': 'http://192.168.8.26/otp/routers/{router}/', - 'internal_ips': ['0.0.0.0', '127.0.0.1'], - 'postgis_version': [2, 5, 2], - 'build_dir': '/opt/app/src', - 'production': False, - + "otp_url": "http://192.168.8.26/otp/routers/{router}/", + "internal_ips": ["0.0.0.0", "127.0.0.1"], + "postgis_version": [2, 5, 2], + "build_dir": "/opt/app/src", + "production": False, # For storing images on s3, set 'use_s3_storage' to True and specify the bucket name. # AWS access key and secret access keys will be obtained via the IAM role. - 'use_s3_storage': False, - 'aws_storage_bucket_name': '', - + "use_s3_storage": False, + "aws_storage_bucket_name": "", # Facebook app ID - 'fb_app_id': '', - + "fb_app_id": "", # API key for posting user flag events - 'user_flag_api_key': '', - - 'default_admin_username': 'admin', - 'default_admin_password': 'admin', - 'default_admin_email': 'systems+cac@azavea.com' + "user_flag_api_key": "", + "default_admin_username": "admin", + "default_admin_password": "admin", + "default_admin_email": "systems+cac@azavea.com", } @@ -75,88 +72,84 @@ # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = secrets['secret_key'] +SECRET_KEY = secrets["secret_key"] # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = not secrets['production'] +DEBUG = not secrets["production"] # String that must be passed to post user flags for destinations or events (liked, been, etc.) -USER_FLAG_API_KEY = secrets['user_flag_api_key'] +USER_FLAG_API_KEY = secrets["user_flag_api_key"] ALLOWED_HOSTS = [ - '.gophillygo.org', - '.elb.amazonaws.com', - 'localhost', - '.ngrok.io', + ".gophillygo.org", + ".elb.amazonaws.com", + "localhost", + ".ngrok.io", ] -if secrets['production']: +if secrets["production"]: instance_metadata = get_instance_metadata() if not instance_metadata: - raise ImproperlyConfigured('Unable to access instance metadata') + raise ImproperlyConfigured("Unable to access instance metadata") # ELBs use the instance IP in the Host header and ALLOWED_HOSTS # checks against the Host header. - ALLOWED_HOSTS.append(instance_metadata['local-ipv4']) + ALLOWED_HOSTS.append(instance_metadata["local-ipv4"]) -INTERNAL_IPS = tuple(secrets['internal_ips']) +INTERNAL_IPS = tuple(secrets["internal_ips"]) # Needed in order to call collectstatic without a DB (during AMI creation) -POSTGIS_VERSION = tuple(secrets['postgis_version']) +POSTGIS_VERSION = tuple(secrets["postgis_version"]) # Application definition INSTALLED_APPS = ( - 'wpadmin', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.gis', - + "wpadmin", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.gis", # Third Party Apps - 'ckeditor', - 'django_extensions', - 'storages', - 'easy_thumbnails', - 'image_cropping', - + "ckeditor", + "django_extensions", + "storages", + "easy_thumbnails", + "image_cropping", # Project Apps - 'cms', - 'destinations', - 'shortlinks', + "cms", + "destinations", + "shortlinks", ) 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' + "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 = 'cac_tripplanner.urls' +ROOT_URLCONF = "cac_tripplanner.urls" -WSGI_APPLICATION = 'cac_tripplanner.wsgi.application' +WSGI_APPLICATION = "cac_tripplanner.wsgi.application" # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases -DATABASES = { - 'default': secrets['database'] -} +DATABASES = {"default": secrets["database"]} # Image processing configuration IMAGE_CROPPING_SIZE_WARNING = True THUMBNAIL_PROCESSORS = ( - 'image_cropping.thumbnail_processors.crop_corners', + "image_cropping.thumbnail_processors.crop_corners", ) + thumbnail_settings.THUMBNAIL_PROCESSORS IMAGE_CROPPER_HELP_TEXT = """Save and return to editing this record to see an uploaded image and @@ -166,9 +159,9 @@ # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'America/New_York' +TIME_ZONE = "America/New_York" USE_I18N = True @@ -182,81 +175,52 @@ # they will be deleted on a backwards migration from 0016 -> 0015 # NOTE: If the images placed here are to be used in production as well, they will need to be copied # to the bucket specified in AWS_STORAGE_BUCKET_NAME -DEFAULT_MEDIA_PATH = 'default_media' +DEFAULT_MEDIA_PATH = "default_media" # src directory for default media images DEFAULT_MEDIA_SRC_PATH = os.path.join(BASE_DIR, DEFAULT_MEDIA_PATH) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = '/srv/cac' +STATIC_URL = "/static/" +STATIC_ROOT = "/srv/cac" STATICFILES_DIRS = () -MEDIA_ROOT = '/media/cac/' -MEDIA_URL = '/media/' +MEDIA_ROOT = "/media/cac/" +MEDIA_URL = "/media/" # LOGGING CONFIGURATION LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'datefmt': '%Y-%m-%d %H:%M:%S %z', - 'format': ('[%(asctime)s] [%(process)d] [%(levelname)s]' - ' %(message)s'), + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "datefmt": "%Y-%m-%d %H:%M:%S %z", + "format": ("[%(asctime)s] [%(process)d] [%(levelname)s]" " %(message)s"), }, }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'INFO', - 'formatter': 'verbose', - }, - 'logfile': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'formatter': 'verbose', - 'filename': os.path.join(BASE_DIR, 'cac-tripplanner-app.log'), + "handlers": { + "console": {"class": "logging.StreamHandler", "level": "INFO", "formatter": "verbose",}, + "logfile": { + "level": "DEBUG", + "class": "logging.FileHandler", + "formatter": "verbose", + "filename": os.path.join(BASE_DIR, "cac-tripplanner-app.log"), }, }, - 'loggers': { - 'django': { - 'handlers': ['console', 'logfile'], - 'level': 'INFO', - 'propagate': True, - }, - 'ckeditor': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'wpadmin': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'destinations': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, + "loggers": { + "django": {"handlers": ["console", "logfile"], "level": "INFO", "propagate": True,}, + "ckeditor": {"handlers": ["console", "logfile"], "level": "DEBUG", "propagate": True,}, + "wpadmin": {"handlers": ["console", "logfile"], "level": "DEBUG", "propagate": True,}, + "destinations": {"handlers": ["console", "logfile"], "level": "DEBUG", "propagate": True,}, + "cms": {"handlers": ["console", "logfile"], "level": "DEBUG", "propagate": True,}, + "shortlinks": {"handlers": ["console", "logfile"], "level": "DEBUG", "propagate": True,}, + "image_cropping.thumbnail_processors": { + "handlers": ["console", "logfile"], + "level": "DEBUG", + "propagate": True, }, - 'cms': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'shortlinks': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, - }, - 'image_cropping.thumbnail_processors': { - 'handlers': ['console', 'logfile'], - 'level': 'DEBUG', - 'propagate': True, - } - } + }, } # TEMPLATE CONFIGURATION @@ -264,110 +228,109 @@ # set renderer # https://docs.djangoproject.com/en/1.11/ref/forms/renderers/#django.forms.renderers.TemplatesSetting -FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' +FORM_RENDERER = "django.forms.renderers.TemplatesSetting" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.normpath(os.path.join(BASE_DIR, 'templates')), - 'django/forms/templates', - 'templates', - os.path.normpath(os.path.join(django.__path__[0] + '/forms/templates')) + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.normpath(os.path.join(BASE_DIR, "templates")), + "django/forms/templates", + "templates", + os.path.normpath(os.path.join(django.__path__[0] + "/forms/templates")), ], - 'APP_DIRS': True, - 'OPTIONS': { - 'debug': DEBUG, - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.request', + "APP_DIRS": True, + "OPTIONS": { + "debug": DEBUG, + "context_processors": [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.request", ], }, }, ] -CKEDITOR_UPLOAD_PATH = 'uploads/' -CKEDITOR_IMAGE_BACKEND = 'pillow' +CKEDITOR_UPLOAD_PATH = "uploads/" +CKEDITOR_IMAGE_BACKEND = "pillow" CKEDITOR_CONFIGS = { - 'default': { - 'toolbar': [ - ["Styles", - "Format", - "Bold", - "Italic", - "Underline", - "Strike", - "SpellChecker", - "Undo", - "Redo"], + "default": { + "toolbar": [ + [ + "Styles", + "Format", + "Bold", + "Italic", + "Underline", + "Strike", + "SpellChecker", + "Undo", + "Redo", + ], ["Link", "Unlink", "Anchor"], ["Table", "HorizontalRule"], ["SpecialChar"], - ["Source"] + ["Source"], ], - 'extraPlugins': ','.join( - ['autolink', - 'autoembed', - 'embed', - 'embedsemantic', - 'autogrow']), + "extraPlugins": ",".join(["autolink", "autoembed", "embed", "embedsemantic", "autogrow"]), }, - 'teaser': { - 'toolbar': [ - ["Styles", - "Format", - "Bold", - "Italic", - "Underline", - "Strike", - "SpellChecker", - "Undo", - "Redo"], + "teaser": { + "toolbar": [ + [ + "Styles", + "Format", + "Bold", + "Italic", + "Underline", + "Strike", + "SpellChecker", + "Undo", + "Redo", + ], [], [], ["SpecialChar"], - [] + [], ], - 'extraPlugins': '', - } + "extraPlugins": "", + }, } WPADMIN = { - 'admin': { - 'title': 'Clean Air Council Content Management System', - 'menu': { - 'top': 'wpadmin.menu.menus.BasicTopMenu', - 'left': 'wpadmin.menu.menus.BasicLeftMenu' - } + "admin": { + "title": "Clean Air Council Content Management System", + "menu": { + "top": "wpadmin.menu.menus.BasicTopMenu", + "left": "wpadmin.menu.menus.BasicLeftMenu", + }, } } # FACEBOOK CONFIGURATION -FB_APP_ID = secrets['fb_app_id'] +FB_APP_ID = secrets["fb_app_id"] # OTP CONFIGURATION -OTP_URL = secrets['otp_url'] -ROUTING_URL = OTP_URL.format(router='default') + 'plan' -ISOCHRONE_URL = OTP_URL.format(router='default') + 'isochrone' +OTP_URL = secrets["otp_url"] +ROUTING_URL = OTP_URL.format(router="default") + "plan" +ISOCHRONE_URL = OTP_URL.format(router="default") + "isochrone" # Settings for S3 storage # No need to specify AWS access and secret keys -- they are pulled from # the instance metadata by boto. -if secrets['use_s3_storage']: - DEFAULT_FILE_STORAGE = 'cac_tripplanner.custom_storages.PublicS3BotoStorage' - AWS_STORAGE_BUCKET_NAME = secrets['aws_storage_bucket_name'] +if secrets["use_s3_storage"]: + DEFAULT_FILE_STORAGE = "cac_tripplanner.custom_storages.PublicS3BotoStorage" + AWS_STORAGE_BUCKET_NAME = secrets["aws_storage_bucket_name"] # Default user -DEFAULT_ADMIN_USERNAME = secrets['default_admin_username'] -DEFAULT_ADMIN_PASSWORD = secrets['default_admin_password'] -DEFAULT_ADMIN_EMAIL = secrets['default_admin_email'] +DEFAULT_ADMIN_USERNAME = secrets["default_admin_username"] +DEFAULT_ADMIN_PASSWORD = secrets["default_admin_password"] +DEFAULT_ADMIN_EMAIL = secrets["default_admin_email"] # Application settings # diff --git a/python/cac_tripplanner/cms/tests.py b/python/cac_tripplanner/cms/tests.py index 56efbee0d..04139a7fc 100644 --- a/python/cac_tripplanner/cms/tests.py +++ b/python/cac_tripplanner/cms/tests.py @@ -11,43 +11,47 @@ class ArticleTests(TestCase): def setUp(self): - user = User.objects.create_user(username='test-user') - test_image = File(open('default_media/square/BartramsGarden.jpg')) - - common_args = dict( - teaser='None', - content='None', - author=user, - narrow_image=test_image, - wide_image=test_image - ) - - publish_date = now() - timedelta(hours=1) - self.client = Client() - - self.unpublished_comm = Article.objects.create( - content_type=Article.ArticleTypes.community_profile, - title='unpublished-comm', - slug='unpublished-comm', - **common_args) - self.unpublished_tips = Article.objects.create( - content_type=Article.ArticleTypes.tips_and_tricks, - title='unpublished-tips', - slug='unpublished-tips', - **common_args) - self.published_comm = Article.objects.create( - content_type=Article.ArticleTypes.community_profile, - publish_date=publish_date, - title='published-comm', - slug='published-comm', - **common_args) - self.published_tips = Article.objects.create( - content_type=Article.ArticleTypes.tips_and_tricks, - publish_date=publish_date, - title='published-tips', - slug='published-tips', - **common_args) + user = User.objects.create_user(username="test-user") + with open("default_media/square/BartramsGarden.jpg") as image_file: + test_image = File(image_file) + + common_args = dict( + teaser="None", + content="None", + author=user, + narrow_image=test_image, + wide_image=test_image, + ) + + publish_date = now() - timedelta(hours=1) + + self.unpublished_comm = Article.objects.create( + content_type=Article.ArticleTypes.community_profile, + title="unpublished-comm", + slug="unpublished-comm", + **common_args + ) + self.unpublished_tips = Article.objects.create( + content_type=Article.ArticleTypes.tips_and_tricks, + title="unpublished-tips", + slug="unpublished-tips", + **common_args + ) + self.published_comm = Article.objects.create( + content_type=Article.ArticleTypes.community_profile, + publish_date=publish_date, + title="published-comm", + slug="published-comm", + **common_args + ) + self.published_tips = Article.objects.create( + content_type=Article.ArticleTypes.tips_and_tricks, + publish_date=publish_date, + title="published-tips", + slug="published-tips", + **common_args + ) def test_home_view(self): """Verify that home view includes one article""" @@ -55,9 +59,9 @@ def test_home_view(self): # Delete second published article so random always returns the same item self.published_tips.delete() - url = reverse('home') + url = reverse("home") response = self.client.get(url) - self.assertContains(response, 'Places we love', status_code=200) + self.assertContains(response, "Places we love", status_code=200) self.assertContains(response, self.published_comm.title, status_code=200) def test_community_profile_manager(self): @@ -93,19 +97,17 @@ def test_article_manager(self): def test_learn_detail_view(self): """Test that learn detail view works""" - url = reverse('learn-detail', - kwargs={'slug': self.published_comm.slug}) + url = reverse("learn-detail", kwargs={"slug": self.published_comm.slug}) response = self.client.get(url) - self.assertContains(response, 'published-comm', status_code=200) + self.assertContains(response, "published-comm", status_code=200) - url = reverse('learn-detail', - kwargs={'slug': self.unpublished_comm.slug}) + url = reverse("learn-detail", kwargs={"slug": self.unpublished_comm.slug}) response_404 = self.client.get(url) self.assertEqual(response_404.status_code, 404) def test_learn_list_view(self): """Test that learn list view works""" - url = reverse('learn-list') + url = reverse("learn-list") response = self.client.get(url) self.assertContains(response, self.published_comm.title, status_code=200) diff --git a/python/cac_tripplanner/destinations/tests.py b/python/cac_tripplanner/destinations/tests.py index 3198f92ac..779be5305 100644 --- a/python/cac_tripplanner/destinations/tests.py +++ b/python/cac_tripplanner/destinations/tests.py @@ -15,29 +15,32 @@ def setUp(self): # Clear DB of objects created by migrations Event.objects.all().delete() - test_image = File(open('default_media/square/BartramsGarden.jpg')) + with open("default_media/square/BartramsGarden.jpg") as image_file: + test_image = File(image_file) - self.now = now() + self.now = now() - common_args = dict( - description='Sample event for tests', - image=test_image, - wide_image=test_image - ) - - self.client = Client() + common_args = dict( + description="Sample event for tests", image=test_image, wide_image=test_image + ) - self.event_1 = Event.objects.create(name='Current Event', - published=True, - start_date=self.now, - end_date=self.now + timedelta(days=1), - **common_args) + self.client = Client() - self.event_2 = Event.objects.create(name='Unpublished Past Event', - published=False, - start_date=self.now - timedelta(days=7), - end_date=self.now - timedelta(days=2), - **common_args) + self.event_1 = Event.objects.create( + name="Current Event", + published=True, + start_date=self.now, + end_date=self.now + timedelta(days=1), + **common_args + ) + + self.event_2 = Event.objects.create( + name="Unpublished Past Event", + published=False, + start_date=self.now - timedelta(days=7), + end_date=self.now - timedelta(days=2), + **common_args + ) def test_event_manager_published(self): self.assertEqual(Event.objects.published().count(), 1) @@ -47,27 +50,25 @@ def test_event_manager_current(self): def test_event_detail_view(self): """Test that event detail view works""" - url = reverse('event-detail', - kwargs={'pk': self.event_1.pk}) + url = reverse("event-detail", kwargs={"pk": self.event_1.pk}) response = self.client.get(url) - self.assertContains(response, 'Current Event', status_code=200) + self.assertContains(response, "Current Event", status_code=200) # can also view detail for unpublished event - url = reverse('event-detail', - kwargs={'pk': self.event_2.pk}) + url = reverse("event-detail", kwargs={"pk": self.event_2.pk}) response = self.client.get(url) - self.assertContains(response, 'Unpublished Past Event', status_code=200) + self.assertContains(response, "Unpublished Past Event", status_code=200) def test_event_search(self): """Test that event shows in search results""" - url = reverse('api_destinations_search') + url = reverse("api_destinations_search") response = self.client.get(url) - self.assertContains(response, 'events', status_code=200) + self.assertContains(response, "events", status_code=200) json_response = json.loads(response.content) - self.assertEqual(len(json_response['events']), 1) - event = json_response['events'][0] - self.assertEqual(event['name'], 'Current Event') - self.assertIn('Events', event['categories']) + self.assertEqual(len(json_response["events"]), 1) + event = json_response["events"][0] + self.assertEqual(event["name"], "Current Event") + self.assertIn("Events", event["categories"]) class EventMultiDestinationTests(TestCase): @@ -77,46 +78,45 @@ def setUp(self): Event.objects.all().delete() Destination.objects.all().delete() - test_image = File(open('default_media/square/BartramsGarden.jpg')) + with open("default_media/square/BartramsGarden.jpg") as image_file: + test_image = File(image_file) - self.now = now() + self.now = now() - dest_args = dict( - description='Sample place for tests', - image=test_image, - wide_image=test_image, - point=Point(0, 0) - ) - - event_args = dict( - description='Sample event for tests', - image=test_image, - wide_image=test_image - ) + dest_args = dict( + description="Sample place for tests", + image=test_image, + wide_image=test_image, + point=Point(0, 0), + ) - self.client = Client() + event_args = dict( + description="Sample event for tests", image=test_image, wide_image=test_image + ) - self.place_1 = Destination.objects.create( - name='place_one', - published=True, - **dest_args) + self.client = Client() - self.place_2 = Destination.objects.create( - name='place_two', - published=False, - **dest_args) + self.place_1 = Destination.objects.create(name="place_one", published=True, **dest_args) - self.event_1 = Event.objects.create(name='single_day_event', - published=True, - start_date=self.now, - end_date=self.now + timedelta(days=1), - **event_args) + self.place_2 = Destination.objects.create( + name="place_two", published=False, **dest_args + ) - self.event_2 = Event.objects.create(name='multi_day_event', - published=True, - start_date=self.now - timedelta(days=7), - end_date=self.now + timedelta(days=2), - **event_args) + self.event_1 = Event.objects.create( + name="single_day_event", + published=True, + start_date=self.now, + end_date=self.now + timedelta(days=1), + **event_args + ) + + self.event_2 = Event.objects.create( + name="multi_day_event", + published=True, + start_date=self.now - timedelta(days=7), + end_date=self.now + timedelta(days=2), + **event_args + ) self.event_1.event_destinations.add( EventDestination.objects.create( @@ -124,8 +124,9 @@ def setUp(self): related_event=self.event_1, order=2, start_date=self.now + timedelta(hours=8), - end_date=self.now + timedelta(hours=14) - )) + end_date=self.now + timedelta(hours=14), + ) + ) self.event_1.event_destinations.add( EventDestination.objects.create( @@ -133,8 +134,9 @@ def setUp(self): related_event=self.event_1, order=1, start_date=self.now + timedelta(hours=14), - end_date=self.now + timedelta(hours=18) - )) + end_date=self.now + timedelta(hours=18), + ) + ) self.event_1.save() @@ -144,8 +146,9 @@ def setUp(self): related_event=self.event_2, order=1, start_date=self.now, - end_date=self.now + timedelta(days=1) - )) + end_date=self.now + timedelta(days=1), + ) + ) self.event_2.event_destinations.add( EventDestination.objects.create( @@ -153,8 +156,9 @@ def setUp(self): related_event=self.event_2, order=2, start_date=self.now + timedelta(days=1), - end_date=self.now + timedelta(days=2) - )) + end_date=self.now + timedelta(days=2), + ) + ) self.event_2.save() @@ -166,15 +170,13 @@ def test_event_manager_current(self): def test_event_detail_view(self): """Test that event detail view works""" - url = reverse('event-detail', - kwargs={'pk': self.event_1.pk}) + url = reverse("event-detail", kwargs={"pk": self.event_1.pk}) response = self.client.get(url) - self.assertContains(response, 'single_day_event', status_code=200) + self.assertContains(response, "single_day_event", status_code=200) - url = reverse('event-detail', - kwargs={'pk': self.event_2.pk}) + url = reverse("event-detail", kwargs={"pk": self.event_2.pk}) response = self.client.get(url) - self.assertContains(response, 'multi_day_event', status_code=200) + self.assertContains(response, "multi_day_event", status_code=200) def test_event_destination_order(self): self.assertEqual(self.event_1.event_destinations.count(), 2) @@ -183,8 +185,7 @@ def test_event_destination_order(self): # second place should have first order, and be returned first event_1_first_dest = self.event_1.event_destinations.first() self.assertEqual(event_1_first_dest.order, 1) - self.assertEqual(event_1_first_dest.destination.id, - self.place_2.id) + self.assertEqual(event_1_first_dest.destination.id, self.place_2.id) # other place has second order self.assertEqual(self.event_1.event_destinations.all()[1].order, 2) @@ -193,25 +194,25 @@ def test_event_destination_order(self): def test_event_search(self): """Test that events show in search results""" - url = reverse('api_destinations_search') + url = reverse("api_destinations_search") response = self.client.get(url) - self.assertContains(response, 'events', status_code=200) + self.assertContains(response, "events", status_code=200) json_response = json.loads(response.content) - self.assertEqual(len(json_response['events']), 2) - first_event = json_response['events'][0] + self.assertEqual(len(json_response["events"]), 2) + first_event = json_response["events"][0] # multi day event started earlier and should be listed first - self.assertEqual(first_event['name'], 'multi_day_event') - self.assertIn('Events', first_event['categories']) - self.assertEqual(len(first_event['destinations']), 2) - self.assertEqual(first_event['destinations'][0]['name'], 'place_one') - self.assertEqual(first_event['destinations'][1]['name'], 'place_two') + self.assertEqual(first_event["name"], "multi_day_event") + self.assertIn("Events", first_event["categories"]) + self.assertEqual(len(first_event["destinations"]), 2) + self.assertEqual(first_event["destinations"][0]["name"], "place_one") + self.assertEqual(first_event["destinations"][1]["name"], "place_two") # single day event orders destinations differently - second_event = json_response['events'][1] - self.assertEqual(second_event['name'], 'single_day_event') - self.assertEqual(len(second_event['destinations']), 2) - self.assertEqual(second_event['destinations'][0]['name'], 'place_two') - self.assertEqual(second_event['destinations'][1]['name'], 'place_one') + second_event = json_response["events"][1] + self.assertEqual(second_event["name"], "single_day_event") + self.assertEqual(len(second_event["destinations"]), 2) + self.assertEqual(second_event["destinations"][0]["name"], "place_two") + self.assertEqual(second_event["destinations"][1]["name"], "place_one") class DestinationTests(TestCase): @@ -220,55 +221,51 @@ def setUp(self): # Clear DB of objects created by migrations Destination.objects.all().delete() - test_image = File(open('default_media/square/BartramsGarden.jpg')) + with open("default_media/square/BartramsGarden.jpg") as image_file: + test_image = File(image_file) - common_args = dict( - description='Sample place for tests', - image=test_image, - wide_image=test_image, - point=Point(0, 0) - ) + common_args = dict( + description="Sample place for tests", + image=test_image, + wide_image=test_image, + point=Point(0, 0), + ) - self.client = Client() + self.client = Client() - self.place_1 = Destination.objects.create( - name='place_one', - published=True, - **common_args) + self.place_1 = Destination.objects.create( + name="place_one", published=True, **common_args + ) - self.place_2 = Destination.objects.create( - name='place_two', - published=True, - **common_args) + self.place_2 = Destination.objects.create( + name="place_two", published=True, **common_args + ) - self.place_3 = Destination.objects.create( - name='place_three', - published=False, - **common_args) + self.place_3 = Destination.objects.create( + name="place_three", published=False, **common_args + ) def test_destination_manager_published(self): self.assertEqual(Destination.objects.published().count(), 2) def test_place_detail_view(self): """Test that place detail view works""" - url = reverse('place-detail', - kwargs={'pk': self.place_1.pk}) + url = reverse("place-detail", kwargs={"pk": self.place_1.pk}) response = self.client.get(url) - self.assertContains(response, 'place_one', status_code=200) + self.assertContains(response, "place_one", status_code=200) # unpublished place should also be viewable - url = reverse('place-detail', - kwargs={'pk': self.place_3.pk}) + url = reverse("place-detail", kwargs={"pk": self.place_3.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_destination_search(self): """Test that destinations show in search results""" - url = reverse('api_destinations_search') + '?text=place' + url = reverse("api_destinations_search") + "?text=place" response = self.client.get(url) - self.assertContains(response, 'destinations', status_code=200) + self.assertContains(response, "destinations", status_code=200) json_response = json.loads(response.content) - self.assertEqual(len(json_response['destinations']), 2) + self.assertEqual(len(json_response["destinations"]), 2) class TourTests(TestCase): @@ -278,70 +275,56 @@ def setUp(self): Tour.objects.all().delete() Destination.objects.all().delete() - test_image = File(open('default_media/square/BartramsGarden.jpg')) + self.client = Client() - self.now = now() + with open("default_media/square/BartramsGarden.jpg") as image_file: + test_image = File(image_file) - dest_args = dict( - description='Sample place for tests', - image=test_image, - wide_image=test_image, - point=Point(0, 0) - ) + self.now = now() - tour_args = dict( - description='Sample tour for tests' - ) + dest_args = dict( + description="Sample place for tests", + image=test_image, + wide_image=test_image, + point=Point(0, 0), + ) - self.client = Client() - - self.place_1 = Destination.objects.create( - name='place_one', - published=True, - **dest_args) + self.place_1 = Destination.objects.create(name="place_one", published=True, **dest_args) - self.place_2 = Destination.objects.create( - name='place_two', - published=False, - **dest_args) + self.place_2 = Destination.objects.create( + name="place_two", published=False, **dest_args + ) - self.tour_1 = Tour.objects.create(name='tour_one', - published=True, - **tour_args) + tour_args = dict(description="Sample tour for tests") + self.tour_1 = Tour.objects.create(name="tour_one", published=True, **tour_args) - self.tour_2 = Tour.objects.create(name='tour_two', - published=False, - **tour_args) + self.tour_2 = Tour.objects.create(name="tour_two", published=False, **tour_args) self.tour_1.tour_destinations.add( TourDestination.objects.create( - destination=self.place_1, - related_tour=self.tour_1, - order=2 - )) + destination=self.place_1, related_tour=self.tour_1, order=2 + ) + ) self.tour_1.tour_destinations.add( TourDestination.objects.create( - destination=self.place_2, - related_tour=self.tour_1, - order=1 - )) + destination=self.place_2, related_tour=self.tour_1, order=1 + ) + ) self.tour_1.save() self.tour_2.tour_destinations.add( TourDestination.objects.create( - destination=self.place_1, - related_tour=self.tour_2, - order=1 - )) + destination=self.place_1, related_tour=self.tour_2, order=1 + ) + ) self.tour_2.tour_destinations.add( TourDestination.objects.create( - destination=self.place_2, - related_tour=self.tour_2, - order=2 - )) + destination=self.place_2, related_tour=self.tour_2, order=2 + ) + ) self.tour_2.save() @@ -351,24 +334,22 @@ def test_tour_manager_published(self): def test_tour_detail_view(self): """Test that tour detail view works""" - url = reverse('tour-detail', - kwargs={'pk': self.tour_1.pk}) + url = reverse("tour-detail", kwargs={"pk": self.tour_1.pk}) response = self.client.get(url) - self.assertContains(response, 'tour_one', status_code=200) + self.assertContains(response, "tour_one", status_code=200) # unpublished tour detail should also be available - url = reverse('tour-detail', - kwargs={'pk': self.tour_2.pk}) + url = reverse("tour-detail", kwargs={"pk": self.tour_2.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_tour_search(self): """Test that tour shows in search results""" - url = reverse('api_destinations_search') + url = reverse("api_destinations_search") response = self.client.get(url) - self.assertContains(response, 'tours', status_code=200) + self.assertContains(response, "tours", status_code=200) json_response = json.loads(response.content) - self.assertEqual(len(json_response['tours']), 1) + self.assertEqual(len(json_response["tours"]), 1) def test_tour_destination_order(self): self.assertEqual(self.tour_1.tour_destinations.count(), 2) @@ -377,8 +358,7 @@ def test_tour_destination_order(self): # second place should have first order, and be returned first tour_1_first_dest = self.tour_1.tour_destinations.first() self.assertEqual(tour_1_first_dest.order, 1) - self.assertEqual(tour_1_first_dest.destination.id, - self.place_2.id) + self.assertEqual(tour_1_first_dest.destination.id, self.place_2.id) # other place has second order self.assertEqual(self.tour_1.tour_destinations.all()[1].order, 2) diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 000000000..5ce78645b --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 100 From e3f73b3a90630568709aeae1865f6ea6bd8621a7 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 14:15:04 -0500 Subject: [PATCH 04/14] Upgrade to Django 3.0 --- python/cac_tripplanner/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 2d21af3d7..816f67e33 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -1,6 +1,6 @@ base58==2.0.1 boto3==1.15.9 -Django==2.2.27 +Django==3.0.14 django-ckeditor==6.0.0 django-image-cropping==1.5.0 django-extensions==3.0.9 From f329c518e8e26711cbf693d7d3a5e4c9d2da7909 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 14:15:25 -0500 Subject: [PATCH 05/14] Remove deprecated ugettext_lazy https://docs.djangoproject.com/en/4.0/releases/3.0/#id3 --- .../cac_tripplanner/cac_tripplanner/publish_utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/cac_tripplanner/cac_tripplanner/publish_utils.py b/python/cac_tripplanner/cac_tripplanner/publish_utils.py index dbd68d7a1..cd5e8ea2c 100644 --- a/python/cac_tripplanner/cac_tripplanner/publish_utils.py +++ b/python/cac_tripplanner/cac_tripplanner/publish_utils.py @@ -1,14 +1,17 @@ -from django.utils.translation import ugettext_lazy +from django.utils.translation import gettext_lazy class PublishableMixin: """Helper to add Django admin actions to publish and unpublish model objects.""" - actions = ('make_published', 'make_unpublished') + + actions = ("make_published", "make_unpublished") def make_published(self, request, queryset): queryset.update(published=True) - make_published.short_description = ugettext_lazy("Publish selected %(verbose_name_plural)s") + + make_published.short_description = gettext_lazy("Publish selected %(verbose_name_plural)s") def make_unpublished(self, request, queryset): queryset.update(published=False) - make_unpublished.short_description = ugettext_lazy("Unpublish selected %(verbose_name_plural)s") + + make_unpublished.short_description = gettext_lazy("Unpublish selected %(verbose_name_plural)s") From 84979e2b47921c11b7fb9c42f41b063d09a7c3c9 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:06:07 -0500 Subject: [PATCH 06/14] Upgrade to Django 3.1 --- python/cac_tripplanner/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 816f67e33..2d6801f7d 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -1,11 +1,11 @@ base58==2.0.1 boto3==1.15.9 -Django==3.0.14 +Django==3.1.14 django-ckeditor==6.0.0 django-image-cropping==1.5.0 django-extensions==3.0.9 django-storages==1.10.1 -easy-thumbnails==2.7 +easy-thumbnails==2.8.1 gunicorn==20.0.4 Pillow==7.2.0 psycopg2-binary==2.8.6 From 0bcbb613f2eaa82ba5cb43597058166bf14f196a Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:14:39 -0500 Subject: [PATCH 07/14] Address deprecation of url() in Django 3.1 The fix is to switch to re_path(), for which url() is an alias. --- .../cac_tripplanner/cac_tripplanner/urls.py | 64 ++++++++----------- python/cac_tripplanner/shortlinks/urls.py | 14 ++-- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/python/cac_tripplanner/cac_tripplanner/urls.py b/python/cac_tripplanner/cac_tripplanner/urls.py index 7647b8a50..9a67d3176 100644 --- a/python/cac_tripplanner/cac_tripplanner/urls.py +++ b/python/cac_tripplanner/cac_tripplanner/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.conf.urls import re_path from django.urls import include from django.views.generic import RedirectView from django.contrib.gis import admin @@ -12,55 +12,47 @@ urlpatterns = [ # Home view, which is also the directions and explore views - url(r'^$', dest_views.home, name='home'), - url(r'^explore$', dest_views.explore, name='explore'), - + re_path(r"^$", dest_views.home, name="home"), + re_path(r"^explore$", dest_views.explore, name="explore"), # App manifest and service worker for PWA app - url('^manifest.json$', dest_views.manifest), - url('^service-worker.js$', dest_views.service_worker), - + re_path("^manifest.json$", dest_views.manifest), + re_path("^service-worker.js$", dest_views.service_worker), # Privacy policy and ToS - url(r'^privacy_policy$', dest_views.privacy_policy, name='privacy_policy'), - url(r'^terms_of_service$', dest_views.terms_of_service, name='terms_of_service'), - + re_path(r"^privacy_policy$", dest_views.privacy_policy, name="privacy_policy"), + re_path(r"^terms_of_service$", dest_views.terms_of_service, name="terms_of_service"), # User destination flags - url(r'^api/user_flag/', dest_views.UserFlagView.as_view()), - + re_path(r"^api/user_flag/", dest_views.UserFlagView.as_view()), # Map - url(r'^api/destinations/search$', dest_views.SearchDestinations.as_view(), - name='api_destinations_search'), - url(r'^map/reachable$', dest_views.FindReachableDestinations.as_view(), name='reachable'), - + re_path( + r"^api/destinations/search$", + dest_views.SearchDestinations.as_view(), + name="api_destinations_search", + ), + re_path(r"^map/reachable$", dest_views.FindReachableDestinations.as_view(), name="reachable"), # Handle pre-redesign URLs by redirecting - url(r'^(?:map/)?directions/', RedirectView.as_view(pattern_name='home', query_string=True, - permanent=True)), - + re_path( + r"^(?:map/)?directions/", + RedirectView.as_view(pattern_name="home", query_string=True, permanent=True), + ), # Places - url(r'^place/(?P[\d-]+)/$', dest_views.place_detail, name='place-detail'), - + re_path(r"^place/(?P[\d-]+)/$", dest_views.place_detail, name="place-detail"), # Events - url(r'^event/(?P[\d-]+)/$', dest_views.event_detail, name='event-detail'), - + re_path(r"^event/(?P[\d-]+)/$", dest_views.event_detail, name="event-detail"), # Tours - url(r'^tour/(?P[\d-]+)/$', dest_views.tour_detail, name='tour-detail'), - + re_path(r"^tour/(?P[\d-]+)/$", dest_views.tour_detail, name="tour-detail"), # About (no more FAQ) - url(r'^(?Pabout)/$', cms_views.about_faq, name='about'), - + re_path(r"^(?Pabout)/$", cms_views.about_faq, name="about"), # All Published Articles - url(r'^api/articles$', cms_views.AllArticles.as_view(), name='api_articles'), - + re_path(r"^api/articles$", cms_views.AllArticles.as_view(), name="api_articles"), # Community Profiles - url(r'^learn/$', cms_views.learn_list, name='learn-list'), - url(r'^learn/(?P[\w-]+)/$', cms_views.learn_detail, name='learn-detail'), - + re_path(r"^learn/$", cms_views.learn_list, name="learn-list"), + re_path(r"^learn/(?P[\w-]+)/$", cms_views.learn_detail, name="learn-detail"), # Link Shortening - url(r'^link/', include('shortlinks.urls', namespace='shortlinks')), - - url(r'^admin/', admin.site.urls), + re_path(r"^link/", include("shortlinks.urls", namespace="shortlinks")), + re_path(r"^admin/", admin.site.urls), ] if settings.DEBUG: urlpatterns += [ - url(r'^static/(?P.*)$', staticviews.serve), + re_path(r"^static/(?P.*)$", staticviews.serve), ] diff --git a/python/cac_tripplanner/shortlinks/urls.py b/python/cac_tripplanner/shortlinks/urls.py index a46721cdf..ce4d9d0bc 100644 --- a/python/cac_tripplanner/shortlinks/urls.py +++ b/python/cac_tripplanner/shortlinks/urls.py @@ -1,13 +1,15 @@ -from django.conf.urls import url +from django.conf.urls import re_path from django.views.decorators.csrf import csrf_exempt from .views import ShortenedLinkRedirectView, ShortenedLinkCreateView -app_name = 'shortlinks' +app_name = "shortlinks" urlpatterns = [ - url(r'^(?P[1-9A-Za-z]{15,30})$', ShortenedLinkRedirectView.as_view(), - name='dereference-shortened'), - url(r'^shorten/$', csrf_exempt(ShortenedLinkCreateView.as_view()), - name='shorten-link') + re_path( + r"^(?P[1-9A-Za-z]{15,30})$", + ShortenedLinkRedirectView.as_view(), + name="dereference-shortened", + ), + re_path(r"^shorten/$", csrf_exempt(ShortenedLinkCreateView.as_view()), name="shorten-link"), ] From b07417e1a4bad3a16a709353b2c53ce5974ff6ab Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:22:46 -0500 Subject: [PATCH 08/14] Upgrade to Django 3.2 --- python/cac_tripplanner/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 2d6801f7d..9faf06caf 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -1,6 +1,6 @@ base58==2.0.1 boto3==1.15.9 -Django==3.1.14 +Django==3.2.12 django-ckeditor==6.0.0 django-image-cropping==1.5.0 django-extensions==3.0.9 From d5a96b663a30e6521fbcb1af580c36f72e125c8c Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:46:04 -0500 Subject: [PATCH 09/14] Upgrade django-extensions to 3.1.5 --- python/cac_tripplanner/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 9faf06caf..f2b17e823 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -3,7 +3,7 @@ boto3==1.15.9 Django==3.2.12 django-ckeditor==6.0.0 django-image-cropping==1.5.0 -django-extensions==3.0.9 +django-extensions==3.1.5 django-storages==1.10.1 easy-thumbnails==2.8.1 gunicorn==20.0.4 From 93aa31dbf770dc3c54f8f3cdfb99ea366fcd184d Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:46:50 -0500 Subject: [PATCH 10/14] Remove default_app_config Django can now determine this automatically --- python/cac_tripplanner/cms/__init__.py | 1 - python/cac_tripplanner/destinations/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/python/cac_tripplanner/cms/__init__.py b/python/cac_tripplanner/cms/__init__.py index 2957bf076..e69de29bb 100644 --- a/python/cac_tripplanner/cms/__init__.py +++ b/python/cac_tripplanner/cms/__init__.py @@ -1 +0,0 @@ -default_app_config = 'cms.apps.CMSConfig' diff --git a/python/cac_tripplanner/destinations/__init__.py b/python/cac_tripplanner/destinations/__init__.py index ea21cb168..e69de29bb 100644 --- a/python/cac_tripplanner/destinations/__init__.py +++ b/python/cac_tripplanner/destinations/__init__.py @@ -1 +0,0 @@ -default_app_config = 'destinations.apps.DestinationsConfig' From 71107fe6c1baa07e3af1b2d868b3aba720bf42f0 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Fri, 4 Mar 2022 15:48:06 -0500 Subject: [PATCH 11/14] Add DEFAULT_AUTO_FIELD This is now necessary to prevent Django from issuing warnings --- python/cac_tripplanner/cac_tripplanner/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/cac_tripplanner/cac_tripplanner/settings.py b/python/cac_tripplanner/cac_tripplanner/settings.py index 1e271aede..2a4faa39e 100644 --- a/python/cac_tripplanner/cac_tripplanner/settings.py +++ b/python/cac_tripplanner/cac_tripplanner/settings.py @@ -179,6 +179,11 @@ # src directory for default media images DEFAULT_MEDIA_SRC_PATH = os.path.join(BASE_DIR, DEFAULT_MEDIA_PATH) +# Added in 3.2. This is used to create id fields for models where it's not explicitly specified in +# the model definition. This requires making an explicit choice or Django will issue warnings, so +# make our choice the same as what the default was before the requirement to make an affirmative +# choice was introduced. +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ From 17cce46d32c446a6fd60aca044bf44d56beb221d Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Wed, 6 Apr 2022 10:09:19 -0400 Subject: [PATCH 12/14] Upgrade to fixed django-wpadmin --- python/cac_tripplanner/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index f2b17e823..63ea39542 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -12,5 +12,5 @@ psycopg2-binary==2.8.6 pytz==2020.1 PyYAML==5.3.1 requests==2.24.0 -git+https://github.com/azavea/django-wpadmin@v2.2#egg=django-wpadmin +git+https://github.com/azavea/django-wpadmin@v3.2#egg=django-wpadmin virtualenv==20.0.31 From 9aa8861a8e52ce1cdcfdfe208b3949e94ca294a0 Mon Sep 17 00:00:00 2001 From: Derek Dohler Date: Wed, 6 Apr 2022 10:38:46 -0400 Subject: [PATCH 13/14] Placate the linter - Many of us have Black configured to run on save, but Black and the version of Flake8 this project uses disagree about whitespace around commas, so this disables that rule for Flake8 (because Flake8 is much easier to configure than Black). --- python/cac_tripplanner/destinations/admin.py | 1 - python/setup.cfg | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/cac_tripplanner/destinations/admin.py b/python/cac_tripplanner/destinations/admin.py index ba9348d12..1f5827798 100644 --- a/python/cac_tripplanner/destinations/admin.py +++ b/python/cac_tripplanner/destinations/admin.py @@ -3,7 +3,6 @@ from django.conf import settings from django.contrib import admin, gis -from django import forms from image_cropping import ImageCroppingMixin diff --git a/python/setup.cfg b/python/setup.cfg index 13bc986ff..3ff6af6ed 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -1,4 +1,4 @@ [flake8] -ignore = W504 +ignore = W504,E231 max-line-length = 100 exclude = tests/* From f8658d364202b8ee392359266a0492eb866f33c1 Mon Sep 17 00:00:00 2001 From: Katie Date: Fri, 6 May 2022 11:40:31 -0400 Subject: [PATCH 14/14] Upgrade to Django 3.2.13 --- python/cac_tripplanner/requirements.txt | 2 +- python/cac_tripplanner/shortlinks/test/urls.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/cac_tripplanner/requirements.txt b/python/cac_tripplanner/requirements.txt index 63ea39542..1b9732adb 100644 --- a/python/cac_tripplanner/requirements.txt +++ b/python/cac_tripplanner/requirements.txt @@ -1,6 +1,6 @@ base58==2.0.1 boto3==1.15.9 -Django==3.2.12 +Django==3.2.13 django-ckeditor==6.0.0 django-image-cropping==1.5.0 django-extensions==3.1.5 diff --git a/python/cac_tripplanner/shortlinks/test/urls.py b/python/cac_tripplanner/shortlinks/test/urls.py index 1a0779b3c..3b14ef2df 100644 --- a/python/cac_tripplanner/shortlinks/test/urls.py +++ b/python/cac_tripplanner/shortlinks/test/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +from django.conf.urls import re_path from django.urls import include, path from shortlinks.test.views import stub_view @@ -6,5 +6,5 @@ urlpatterns = [ path('link/', include('shortlinks.urls')), - url(r'^$', stub_view, name='test-home'), + re_path(r'^$', stub_view, name='test-home'), ]