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