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"