diff --git a/.env b/.env index fbb3c134e..72dc04c1b 100644 --- a/.env +++ b/.env @@ -3,4 +3,12 @@ # running on Heroku - to set env vars for those, see: # https://devcenter.heroku.com/articles/config-vars +# This is used by gunicorn.conf.py and Django's settings.py to set appropriate +# configuration for development vs production. +ENVIRONMENT="development" + +# Prevent log buffering when using using `heroku local` with Django management commands. +PYTHONUNBUFFERED=1 + +# An example env var used in the tutorial. TIMES=2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 55ffb44be..057ac4674 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,8 @@ # Note: Delete this file if you are copying the code in this repository into your own project. # Default to requesting pull request reviews from the Heroku Languages team. +#ECCN:Open Source +#GUSINFO:Heroku - Languages,Heroku Python Platform * @heroku/languages # However, request review from the Heroku language owner for files that are updated diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 55dbe8564..44f83f6ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,4 @@ updates: directory: "/" schedule: interval: "monthly" + versioning-strategy: increase-if-necessary diff --git a/.python-version b/.python-version index 24ee5b1be..a45d8a607 100644 --- a/.python-version +++ b/.python-version @@ -1 +1,2 @@ -3.13 +# The Python version to use for the project. +3.14 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..9e229777d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,105 @@ +# Salesforce Open Source Community Code of Conduct + +## About the Code of Conduct + +Equality is a core value at Salesforce. We believe a diverse and inclusive +community fosters innovation and creativity, and are committed to building a +culture where everyone feels included. + +Salesforce open-source projects are committed to providing a friendly, safe, and +welcoming environment for all, regardless of gender identity and expression, +sexual orientation, disability, physical appearance, body size, ethnicity, nationality, +race, age, religion, level of experience, education, socioeconomic status, or +other similar personal characteristics. + +The goal of this code of conduct is to specify a baseline standard of behavior so +that people with different social values and communication styles can work +together effectively, productively, and respectfully in our open source community. +It also establishes a mechanism for reporting issues and resolving conflicts. + +All questions and reports of abusive, harassing, or otherwise unacceptable behavior +in a Salesforce open-source project may be reported by contacting the Salesforce +Open Source Conduct Committee at ossconduct@salesforce.com. + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of gender +identity and expression, sexual orientation, disability, physical appearance, +body size, ethnicity, nationality, race, age, religion, level of experience, education, +socioeconomic status, or other similar personal characteristics. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy toward other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Personal attacks, insulting/derogatory comments, or trolling +* Public or private harassment +* Publishing, or threatening to publish, others' private information—such as +a physical or electronic address—without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting +* Advocating for or encouraging any of the above behaviors + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned with this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project email +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the Salesforce Open Source Conduct Committee +at ossconduct@salesforce.com. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the +circumstances. The committee is obligated to maintain confidentiality with +regard to the reporter of an incident. Further details of specific enforcement +policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership and the Salesforce Open Source Conduct +Committee. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], +version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. +It includes adaptations and additions from [Go Community Code of Conduct][golang-coc], +[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. + +This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. + +[contributor-covenant-home]: https://www.contributor-covenant.org +[golang-coc]: https://golang.org/conduct +[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md +[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ +[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..efb980881 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Procfile b/Procfile index c4512ab32..f341392a4 100644 --- a/Procfile +++ b/Procfile @@ -2,6 +2,6 @@ web: gunicorn --config gunicorn.conf.py gettingstarted.wsgi # Uncomment this `release` process if you are using a database, so that Django's model # migrations are run as part of app deployment, using Heroku's Release Phase feature: -# https://docs.djangoproject.com/en/5.1/topics/migrations/ +# https://docs.djangoproject.com/en/6.1/topics/migrations/ # https://devcenter.heroku.com/articles/release-phase #release: ./manage.py migrate --no-input diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..72541d6bd --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Security + +Please report any security issue to [https://www.sfdc.co/SubmitVuln](https://www.sfdc.co/SubmitVuln) +as soon as it is discovered. This project limits its runtime dependencies in +order to reduce the total cost of ownership as much as can be, but all consumers +should remain vigilant and have their security stakeholders review all third-party +products (3PP) like this one and their dependencies. diff --git a/gettingstarted/asgi.py b/gettingstarted/asgi.py index 09246d692..c122599d0 100644 --- a/gettingstarted/asgi.py +++ b/gettingstarted/asgi.py @@ -4,7 +4,7 @@ It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/ """ import os diff --git a/gettingstarted/settings.py b/gettingstarted/settings.py index 087b3a776..51cb9df1b 100644 --- a/gettingstarted/settings.py +++ b/gettingstarted/settings.py @@ -1,13 +1,13 @@ """ Django settings for gettingstarted project. -Generated by 'django-admin startproject' using Django 5.1. +Generated by 'django-admin startproject' using Django 6.1. For more information on this file, see -https://docs.djangoproject.com/en/5.1/topics/settings/ +https://docs.djangoproject.com/en/6.1/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/5.1/ref/settings/ +https://docs.djangoproject.com/en/6.1/ref/settings/ """ import os @@ -21,7 +21,7 @@ # Before using your Heroku app in production, make sure to review Django's deployment checklist: -# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/ # Django requires a unique secret key for each Django app, that is used by several of its # security features. To simplify initial setup (without hardcoding the secret in the source @@ -29,29 +29,46 @@ # Django features break whenever an app restarts (for example, sessions will be logged out). # In your production Heroku apps you should set the `DJANGO_SECRET_KEY` config var explicitly. # Make sure to use a long unique value, like you would for a password. See: -# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-SECRET_KEY +# https://docs.djangoproject.com/en/6.1/ref/settings/#std-setting-SECRET_KEY # https://devcenter.heroku.com/articles/config-vars -# SECURITY WARNING: keep the secret key used in production secret! +# SECURITY WARNING: Keep the secret key used in production secret! SECRET_KEY = os.environ.get( "DJANGO_SECRET_KEY", default=secrets.token_urlsafe(nbytes=64), ) +# Django has a debug mode which shows more detailed error messages and also means static assets +# can be served without having to run the production `collectstatic` command. However, this +# debug mode *must only be enabled in development* for security and performance reasons: +# https://docs.djangoproject.com/en/6.1/ref/settings/#std-setting-DEBUG +# Debug mode will be automatically enabled when the project is run via `heroku local` (which +# loads the environment variables set in the `.env` file, where `ENVIRONMENT=development`). +# SECURITY WARNING: Don't run with debug turned on in production! +DEBUG = os.environ.get("ENVIRONMENT") == "development" + # The `DYNO` env var is set on Heroku CI, but it's not a real Heroku app, so we have to # also explicitly exclude CI: # https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables -IS_HEROKU_APP = "DYNO" in os.environ and not "CI" in os.environ - -# SECURITY WARNING: don't run with debug turned on in production! -if not IS_HEROKU_APP: - DEBUG = True +IS_HEROKU_APP = "DYNO" in os.environ and "CI" not in os.environ -# On Heroku, it's safe to use a wildcard for `ALLOWED_HOSTS``, since the Heroku router performs -# validation of the Host header in the incoming HTTP request. On other platforms you may need to -# list the expected hostnames explicitly in production to prevent HTTP Host header attacks. See: -# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-ALLOWED_HOSTS if IS_HEROKU_APP: + # On Heroku, it's safe to use a wildcard for `ALLOWED_HOSTS`, since the Heroku router performs + # validation of the Host header in the incoming HTTP request. On other platforms you may need to + # list the expected hostnames explicitly in production to prevent HTTP Host header attacks. See: + # https://docs.djangoproject.com/en/6.1/ref/settings/#std-setting-ALLOWED_HOSTS ALLOWED_HOSTS = ["*"] + + # Redirect all non-HTTPS requests to HTTPS. This requires that: + # 1. Your app has a TLS/SSL certificate, which all `*.herokuapp.com` domains do by default. + # When using a custom domain, you must configure one. See: + # https://devcenter.heroku.com/articles/automated-certificate-management + # 2. Your app's WSGI web server is configured to use the `X-Forwarded-Proto` headers set by + # the Heroku Router (otherwise you may encounter infinite HTTP 301 redirects). See this + # app's `gunicorn.conf.py` for how this is done when using gunicorn. + # + # For maximum security, consider enabling HTTP Strict Transport Security (HSTS) headers too: + # https://docs.djangoproject.com/en/6.1/ref/middleware/#http-strict-transport-security + SECURE_SSL_REDIRECT = True else: ALLOWED_HOSTS = [".localhost", "127.0.0.1", "[::1]", "0.0.0.0", "[::]"] @@ -61,11 +78,11 @@ # Several optional Django features that are present in the default `startproject` template have # been disabled since they are not used by this example app. To use them, uncomment the relevant # entries in `INSTALLED_APPS`, `MIDDLEWARE`, `TEMPLATES` and `urls.py`. See: -# https://docs.djangoproject.com/en/5.1/ref/contrib/admin/ -# https://docs.djangoproject.com/en/5.1/topics/auth/ -# https://docs.djangoproject.com/en/5.1/ref/contrib/contenttypes/ -# https://docs.djangoproject.com/en/5.1/topics/http/sessions/ -# https://docs.djangoproject.com/en/5.1/ref/contrib/messages/ +# https://docs.djangoproject.com/en/6.1/ref/contrib/admin/ +# https://docs.djangoproject.com/en/6.1/topics/auth/ +# https://docs.djangoproject.com/en/6.1/ref/contrib/contenttypes/ +# https://docs.djangoproject.com/en/6.1/topics/http/sessions/ +# https://docs.djangoproject.com/en/6.1/ref/contrib/messages/ INSTALLED_APPS = [ # Use WhiteNoise's runserver implementation instead of the Django default, for dev-prod parity. "whitenoise.runserver_nostatic", @@ -102,7 +119,6 @@ "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", @@ -115,7 +131,7 @@ # Database -# https://docs.djangoproject.com/en/5.1/ref/settings/#databases +# https://docs.djangoproject.com/en/6.1/ref/settings/#databases if IS_HEROKU_APP: # In production on Heroku the database configuration is derived from the `DATABASE_URL` @@ -143,7 +159,7 @@ # Password validation -# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators +# https://docs.djangoproject.com/en/6.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { @@ -162,7 +178,7 @@ # Internationalization -# https://docs.djangoproject.com/en/5.1/topics/i18n/ +# https://docs.djangoproject.com/en/6.1/topics/i18n/ LANGUAGE_CODE = "en-us" @@ -174,13 +190,13 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.1/howto/static-files/ +# https://docs.djangoproject.com/en/6.1/howto/static-files/ STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_URL = "static/" STORAGES = { - # Enable WhiteNoise's GZip and Brotli compression of static assets: + # Enable WhiteNoise's GZip (and Brotli, if installed) compression of static assets: # https://whitenoise.readthedocs.io/en/latest/django.html#add-compression-and-caching-support "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", @@ -192,7 +208,52 @@ WHITENOISE_KEEP_ONLY_HASHED_FILES = True -# Default primary key field type -# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field +# Email +# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration + +MAILERS = { + "default": { + "BACKEND": "django.core.mail.backends.console.EmailBackend", + }, +} -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Logging +# Customise the default logging config, since by default full Django logs are only emitted when +# `DEBUG=True` (which otherwise makes diagnosing errors much harder in production): +# https://docs.djangoproject.com/en/6.1/ref/logging/#default-logging-configuration +# For more advanced logging you may want to try: https://django-structlog.readthedocs.io +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": "[{levelname}] {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + # Fallback for anything not configured via `loggers`. + "root": { + "handlers": ["console"], + "level": "INFO", + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + # Prevent double logging due to the root logger. + "propagate": False, + }, + "django.request": { + # Suppress the WARNINGS from any HTTP 4xx responses (in particular for 404s caused by + # web crawlers), but still show any ERRORs from HTTP 5xx responses/exceptions. + "level": "ERROR", + }, + }, +} diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index 741113540..7f3640e3d 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -2,7 +2,7 @@ URL configuration for gettingstarted project. The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.1/topics/http/urls/ + https://docs.djangoproject.com/en/6.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views @@ -24,6 +24,6 @@ path("", hello.views.index, name="index"), path("db/", hello.views.db, name="db"), # Uncomment this and the entry in `INSTALLED_APPS` if you wish to use the Django admin feature: - # https://docs.djangoproject.com/en/5.1/ref/contrib/admin/ + # https://docs.djangoproject.com/en/6.1/ref/contrib/admin/ # path("admin/", admin.site.urls), ] diff --git a/gettingstarted/wsgi.py b/gettingstarted/wsgi.py index 9d8da75b1..77421b123 100644 --- a/gettingstarted/wsgi.py +++ b/gettingstarted/wsgi.py @@ -4,7 +4,7 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/6.1/howto/deployment/wsgi/ """ import os diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 0cd46ed3c..c20e84407 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -1,6 +1,7 @@ # Gunicorn configuration file: # https://docs.gunicorn.org/en/stable/configure.html # https://docs.gunicorn.org/en/stable/settings.html +# # Note: The classic Python buildpack currently sets a few gunicorn settings automatically via # the `GUNICORN_CMD_ARGS` env var (which take priority over the settings in this file): # https://github.com/heroku/heroku-buildpack-python/blob/main/vendor/python.gunicorn.sh @@ -38,9 +39,6 @@ # Each `gthread` worker process will use a pool of this many threads. threads = 5 -# Load the app before the worker processes are forked, to reduce memory usage and boot times. -preload_app = True - # Workers silent for more than this many seconds are killed and restarted. # Note: This only affects the maximum request time when using the `sync` worker. # For all other worker types it acts only as a worker heartbeat timeout. @@ -51,6 +49,13 @@ # https://devcenter.heroku.com/articles/dyno-shutdown-behavior graceful_timeout = 20 +# The number of seconds an idle Keep-Alive connection is kept open. This should be greater than +# the Heroku Router's Keep-Alive idle timeout of 90 seconds, to ensure that the closing of idle +# connections is always initiated by the router and not gunicorn, to prevent a race condition +# if the router sends a request to the app just as gunicorn is closing the connection: +# https://devcenter.heroku.com/articles/http-routing#keepalives +keepalive = 95 + # Enable logging of incoming requests to stdout. accesslog = "-" @@ -58,3 +63,24 @@ # style. The `X-Request-Id` and `X-Forwarded-For` headers are set by the Heroku Router: # https://devcenter.heroku.com/articles/http-routing#heroku-headers access_log_format = 'gunicorn method=%(m)s path="%(U)s" status=%(s)s duration=%(M)sms request_id=%({x-request-id}i)s fwd="%({x-forwarded-for}i)s" user_agent="%(a)s"' + +if os.environ.get("ENVIRONMENT") == "development": + # Automatically restart gunicorn when the app source changes in development. + reload = True +else: + # Load the app before the worker processes are forked, to reduce memory usage and boot times. + # We don't enable this in development, since it's incompatible with `reload = True`. + preload_app = True + + # Use `SO_REUSEPORT` on the listening socket, which allows for more even request + # distribution between workers. See: https://lwn.net/Articles/542629/ + # We don't enable this in development, since it makes it harder to notice when + # duplicate gunicorn processes have accidentally been launched (eg in different + # terminals), since the "address already in use" error no longer occurs. + reuse_port = True + + # Trust the `X-Forwarded-Proto` header set by the Heroku Router during TLS termination, + # (https://devcenter.heroku.com/articles/http-routing#heroku-headers) so that HTTPS requests + # are correctly marked as secure. This allows the WSGI app (in our case, Django) to distinguish + # between HTTP and HTTPS requests for features like HTTP->HTTPS URL redirection. + forwarded_allow_ips = "*" diff --git a/hello/apps.py b/hello/apps.py index 6b541fb46..07ec8c239 100644 --- a/hello/apps.py +++ b/hello/apps.py @@ -2,5 +2,4 @@ class HelloConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" name = "hello" diff --git a/hello/migrations/0001_initial.py b/hello/migrations/0001_initial.py index 5d0aedd71..b1b6656e5 100644 --- a/hello/migrations/0001_initial.py +++ b/hello/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1 on 2024-08-09 09:38 +# Generated by Django 6.1 on 2026-08-19 10:49 from django.db import migrations, models diff --git a/project.toml b/project.toml new file mode 100644 index 000000000..ec4f08cbd --- /dev/null +++ b/project.toml @@ -0,0 +1,19 @@ +[_] +schema-version = "0.2" + +[io.buildpacks] +# Exclude files from local Pack CLI builds, where .gitignore doesn't apply. +# TODO: Add trailing slash to entries that are directories, once this issue is fixed: +# https://github.com/buildpacks/pack/issues/2402 +exclude = [ + "__pycache__", + ".git", + ".gitignore", + ".github", + ".venv", + ".DS_Store", + ".env", + "staticfiles", + "db.sqlite3", + "venv", +] diff --git a/requirements.txt b/requirements.txt index c420fa54c..db5f46b0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,7 @@ -django>=5.1,<5.2 -gunicorn>=23,<24 -dj-database-url>=2,<3 -whitenoise[brotli]>=6,<7 +django>=6.1,<6.2 +gunicorn>=26,<27 +dj-database-url>=3,<4 +whitenoise>=6,<7 -# Uncomment these lines to use a Postgres database. Both are needed, since in production -# (which uses Linux) we want to install from source, so that security updates from the -# underlying Heroku stack image are picked up automatically, thanks to dynamic linking. -# On other platforms/in development, the precompiled binary package is used instead, to -# speed up installation and avoid errors from missing libraries/headers. -#psycopg[c]; sys_platform == "linux" -#psycopg[binary]; sys_platform != "linux" +# Uncomment to use a Postgres database. +#psycopg[binary]