Plain is headed towards 1.0! Subscribe for development updates →

Runtime

Leverage user-settings at runtime.

Settings

Single-file

All of your settings go in app/settings.py. That's how you do it!

The file itself is not much different than how Django does it, but the location, and a strong recommendation to only use the one file makes a big difference.

Environment variables

It seems pretty well-accepted these days that storing settings in env vars is a good idea (12factor.net).

Your settings file should be looking at the environment for secrets or other values that might change between environments. For example:

# app/settings.py
STRIPE_SECRET_KEY = environ["STRIPE_SECRET_KEY"]

Local development

In local development, you should use .env files to set these values. The .env should then be in your .gitignore!

It would seem like .env.dev would be a good idea, but there's a chicken-and-egg problem with that. You would then have to prefix most (or all) of your local commands with PLAIN_ENV=dev or otherwise configure your environment to do that for you. Generally speaking, a production .env shouldn't be committed in your repo anyway, so using .env for local development is ok. The downside to this is that it's harder to share your local settings with others, but these also often contain real secrets which shouldn't be committed to your repo either! More advanced .env sharing patterns are currently beyond the scope of Plain...

Production

TODO

Minimum required settings

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = environ["SECRET_KEY"]

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = environ.get("DEBUG", "false").lower() in ("true", "1", "yes")

MIDDLEWARE = [
    "plain.sessions.middleware.SessionMiddleware",
    "plain.auth.middleware.AuthenticationMiddleware",
]

if DEBUG:
    INSTALLED_PACKAGES += [
        "plain.dev",
    ]
    MIDDLEWARE += [
        "plain.dev.RequestsMiddleware",
    ]

TIME_ZONE = "America/Chicago"
  1"""
  2Default Plain settings. Override these with settings in the module pointed to
  3by the PLAIN_SETTINGS_MODULE environment variable.
  4"""
  5
  6from pathlib import Path
  7
  8from plain.runtime import APP_PATH as default_app_path
  9
 10####################
 11# CORE             #
 12####################
 13
 14DEBUG: bool = False
 15
 16PLAIN_TEMP_PATH: Path = default_app_path.parent / ".plain"
 17
 18# Hosts/domain names that are valid for this site.
 19# "*" matches anything, ".example.com" matches example.com and all subdomains
 20ALLOWED_HOSTS: list[str] = []
 21
 22# Local time zone for this installation. All choices can be found here:
 23# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
 24# systems may support all possibilities). This is interpreted as the default
 25# user time zone.
 26TIME_ZONE: str = "UTC"
 27
 28# Default charset to use for all Response objects, if a MIME type isn't
 29# manually specified. It's used to construct the Content-Type header.
 30DEFAULT_CHARSET = "utf-8"
 31
 32# List of strings representing installed packages.
 33INSTALLED_PACKAGES: list[str] = []
 34
 35# Whether to append trailing slashes to URLs.
 36APPEND_SLASH = True
 37
 38# Default headers for all responses.
 39DEFAULT_RESPONSE_HEADERS = {
 40    # "Content-Security-Policy": "default-src 'self'",
 41    # https://hstspreload.org/
 42    # "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
 43    "Cross-Origin-Opener-Policy": "same-origin",
 44    "Referrer-Policy": "same-origin",
 45    "X-Content-Type-Options": "nosniff",
 46    "X-Frame-Options": "DENY",
 47}
 48
 49# Whether to redirect all non-HTTPS requests to HTTPS.
 50HTTPS_REDIRECT_ENABLED = True
 51HTTPS_REDIRECT_EXEMPT = []
 52HTTPS_REDIRECT_HOST = None
 53
 54# If your Plain app is behind a proxy that sets a header to specify secure
 55# connections, AND that proxy ensures that user-submitted headers with the
 56# same name are ignored (so that people can't spoof it), set this value to
 57# a tuple of (header_name, header_value). For any requests that come in with
 58# that header/value, request.is_https() will return True.
 59# WARNING! Only set this if you fully understand what you're doing. Otherwise,
 60# you may be opening yourself up to a security risk.
 61HTTPS_PROXY_HEADER = None
 62
 63# Whether to use the X-Forwarded-Host and X-Forwarded-Port headers
 64# when determining the host and port for the request.
 65USE_X_FORWARDED_HOST = False
 66USE_X_FORWARDED_PORT = False
 67
 68# A secret key for this particular Plain installation. Used in secret-key
 69# hashing algorithms. Set this in your settings, or Plain will complain
 70# loudly.
 71SECRET_KEY: str
 72
 73# List of secret keys used to verify the validity of signatures. This allows
 74# secret key rotation.
 75SECRET_KEY_FALLBACKS: list[str] = []
 76
 77ROOT_URLCONF = "app.urls"
 78
 79# List of upload handler classes to be applied in order.
 80FILE_UPLOAD_HANDLERS = [
 81    "plain.internal.files.uploadhandler.MemoryFileUploadHandler",
 82    "plain.internal.files.uploadhandler.TemporaryFileUploadHandler",
 83]
 84
 85# Maximum size, in bytes, of a request before it will be streamed to the
 86# file system instead of into memory.
 87FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440  # i.e. 2.5 MB
 88
 89# Maximum size in bytes of request data (excluding file uploads) that will be
 90# read before a SuspiciousOperation (RequestDataTooBig) is raised.
 91DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440  # i.e. 2.5 MB
 92
 93# Maximum number of GET/POST parameters that will be read before a
 94# SuspiciousOperation (TooManyFieldsSent) is raised.
 95DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
 96
 97# Maximum number of files encoded in a multipart upload that will be read
 98# before a SuspiciousOperation (TooManyFilesSent) is raised.
 99DATA_UPLOAD_MAX_NUMBER_FILES = 100
100
101# Directory in which upload streamed files will be temporarily saved. A value of
102# `None` will make Plain use the operating system's default temporary directory
103# (i.e. "/tmp" on *nix systems).
104FILE_UPLOAD_TEMP_DIR = None
105
106# User-defined overrides for error views by status code
107HTTP_ERROR_VIEWS: dict[int] = {}
108
109##############
110# MIDDLEWARE #
111##############
112
113# List of middleware to use. Order is important; in the request phase, these
114# middleware will be applied in the order given, and in the response
115# phase the middleware will be applied in reverse order.
116MIDDLEWARE: list[str] = []
117
118###########
119# SIGNING #
120###########
121
122COOKIE_SIGNING_BACKEND = "plain.signing.TimestampSigner"
123
124########
125# CSRF #
126########
127
128# Settings for CSRF cookie.
129CSRF_COOKIE_NAME = "csrftoken"
130CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52
131CSRF_COOKIE_DOMAIN = None
132CSRF_COOKIE_PATH = "/"
133CSRF_COOKIE_SECURE = True
134CSRF_COOKIE_HTTPONLY = False
135CSRF_COOKIE_SAMESITE = "Lax"
136CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN"
137CSRF_TRUSTED_ORIGINS: list[str] = []
138
139###########
140# LOGGING #
141###########
142
143# Custom logging configuration.
144LOGGING = {}
145
146###############
147# ASSETS #
148###############
149
150# Whether to redirect the original asset path to the fingerprinted path.
151ASSETS_REDIRECT_ORIGINAL = True
152
153# If assets are served by a CDN, use this URL to prefix asset paths.
154# Ex. "https://cdn.example.com/assets/"
155ASSETS_BASE_URL: str = ""
156
157####################
158# PREFLIGHT CHECKS #
159####################
160
161# List of all issues generated by system checks that should be silenced. Light
162# issues like warnings, infos or debugs will not generate a message. Silencing
163# serious issues like errors and criticals does not result in hiding the
164# message, but Plain will not stop you from e.g. running server.
165SILENCED_PREFLIGHT_CHECKS = []
166
167#############
168# Templates #
169#############
170
171TEMPLATES_JINJA_ENVIRONMENT = "plain.templates.jinja.DefaultEnvironment"