django-resonant-settings 0.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_resonant_settings-0.26/.gitignore +176 -0
- django_resonant_settings-0.26/LICENSE +201 -0
- django_resonant_settings-0.26/PKG-INFO +36 -0
- django_resonant_settings-0.26/README.md +9 -0
- django_resonant_settings-0.26/pyproject.toml +77 -0
- django_resonant_settings-0.26/resonant_settings/__init__.py +0 -0
- django_resonant_settings-0.26/resonant_settings/_env.py +3 -0
- django_resonant_settings-0.26/resonant_settings/allauth.py +48 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/__init__.py +0 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/adapter.py +10 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/apps.py +6 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/createsuperuser.py +66 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/management/commands/createsuperuser.py +30 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/receiver.py +37 -0
- django_resonant_settings-0.26/resonant_settings/allauth_support/utils.py +14 -0
- django_resonant_settings-0.26/resonant_settings/celery.py +64 -0
- django_resonant_settings-0.26/resonant_settings/debug_toolbar.py +13 -0
- django_resonant_settings-0.26/resonant_settings/development/__init__.py +0 -0
- django_resonant_settings-0.26/resonant_settings/development/celery.py +7 -0
- django_resonant_settings-0.26/resonant_settings/development/extensions.py +9 -0
- django_resonant_settings-0.26/resonant_settings/django.py +37 -0
- django_resonant_settings-0.26/resonant_settings/logging.py +89 -0
- django_resonant_settings-0.26/resonant_settings/oauth_toolkit.py +32 -0
- django_resonant_settings-0.26/resonant_settings/production/__init__.py +0 -0
- django_resonant_settings-0.26/resonant_settings/production/email.py +16 -0
- django_resonant_settings-0.26/resonant_settings/production/https.py +22 -0
- django_resonant_settings-0.26/resonant_settings/production/s3_storage.py +31 -0
- django_resonant_settings-0.26/resonant_settings/py.typed +0 -0
- django_resonant_settings-0.26/resonant_settings/rest_framework.py +75 -0
- django_resonant_settings-0.26/resonant_settings/testing/__init__.py +0 -0
- django_resonant_settings-0.26/resonant_settings/testing/minio_storage.py +23 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
|
3
|
+
|
|
4
|
+
### Python ###
|
|
5
|
+
# Byte-compiled / optimized / DLL files
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
|
|
13
|
+
# Distribution / packaging
|
|
14
|
+
.Python
|
|
15
|
+
build/
|
|
16
|
+
develop-eggs/
|
|
17
|
+
dist/
|
|
18
|
+
downloads/
|
|
19
|
+
eggs/
|
|
20
|
+
.eggs/
|
|
21
|
+
lib/
|
|
22
|
+
lib64/
|
|
23
|
+
parts/
|
|
24
|
+
sdist/
|
|
25
|
+
var/
|
|
26
|
+
wheels/
|
|
27
|
+
share/python-wheels/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
.installed.cfg
|
|
30
|
+
*.egg
|
|
31
|
+
MANIFEST
|
|
32
|
+
|
|
33
|
+
# PyInstaller
|
|
34
|
+
# Usually these files are written by a python script from a template
|
|
35
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
36
|
+
*.manifest
|
|
37
|
+
*.spec
|
|
38
|
+
|
|
39
|
+
# Installer logs
|
|
40
|
+
pip-log.txt
|
|
41
|
+
pip-delete-this-directory.txt
|
|
42
|
+
|
|
43
|
+
# Unit test / coverage reports
|
|
44
|
+
htmlcov/
|
|
45
|
+
.tox/
|
|
46
|
+
.nox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
nosetests.xml
|
|
51
|
+
coverage.xml
|
|
52
|
+
*.cover
|
|
53
|
+
*.py,cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
.pytest_cache/
|
|
56
|
+
cover/
|
|
57
|
+
|
|
58
|
+
# Translations
|
|
59
|
+
*.mo
|
|
60
|
+
*.pot
|
|
61
|
+
|
|
62
|
+
# Django stuff:
|
|
63
|
+
*.log
|
|
64
|
+
local_settings.py
|
|
65
|
+
db.sqlite3
|
|
66
|
+
db.sqlite3-journal
|
|
67
|
+
|
|
68
|
+
# Flask stuff:
|
|
69
|
+
instance/
|
|
70
|
+
.webassets-cache
|
|
71
|
+
|
|
72
|
+
# Scrapy stuff:
|
|
73
|
+
.scrapy
|
|
74
|
+
|
|
75
|
+
# Sphinx documentation
|
|
76
|
+
docs/_build/
|
|
77
|
+
|
|
78
|
+
# PyBuilder
|
|
79
|
+
.pybuilder/
|
|
80
|
+
target/
|
|
81
|
+
|
|
82
|
+
# Jupyter Notebook
|
|
83
|
+
.ipynb_checkpoints
|
|
84
|
+
|
|
85
|
+
# IPython
|
|
86
|
+
profile_default/
|
|
87
|
+
ipython_config.py
|
|
88
|
+
|
|
89
|
+
# pyenv
|
|
90
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
91
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
92
|
+
# .python-version
|
|
93
|
+
|
|
94
|
+
# pipenv
|
|
95
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
96
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
97
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
98
|
+
# install all needed dependencies.
|
|
99
|
+
#Pipfile.lock
|
|
100
|
+
|
|
101
|
+
# poetry
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
103
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
104
|
+
# commonly ignored for libraries.
|
|
105
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
106
|
+
#poetry.lock
|
|
107
|
+
|
|
108
|
+
# pdm
|
|
109
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
110
|
+
#pdm.lock
|
|
111
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
112
|
+
# in version control.
|
|
113
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
114
|
+
.pdm.toml
|
|
115
|
+
|
|
116
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
117
|
+
__pypackages__/
|
|
118
|
+
|
|
119
|
+
# Celery stuff
|
|
120
|
+
celerybeat-schedule
|
|
121
|
+
celerybeat.pid
|
|
122
|
+
|
|
123
|
+
# SageMath parsed files
|
|
124
|
+
*.sage.py
|
|
125
|
+
|
|
126
|
+
# Environments
|
|
127
|
+
.env
|
|
128
|
+
.venv
|
|
129
|
+
env/
|
|
130
|
+
venv/
|
|
131
|
+
ENV/
|
|
132
|
+
env.bak/
|
|
133
|
+
venv.bak/
|
|
134
|
+
|
|
135
|
+
# Spyder project settings
|
|
136
|
+
.spyderproject
|
|
137
|
+
.spyproject
|
|
138
|
+
|
|
139
|
+
# Rope project settings
|
|
140
|
+
.ropeproject
|
|
141
|
+
|
|
142
|
+
# mkdocs documentation
|
|
143
|
+
/site
|
|
144
|
+
|
|
145
|
+
# mypy
|
|
146
|
+
.mypy_cache/
|
|
147
|
+
.dmypy.json
|
|
148
|
+
dmypy.json
|
|
149
|
+
|
|
150
|
+
# Pyre type checker
|
|
151
|
+
.pyre/
|
|
152
|
+
|
|
153
|
+
# pytype static type analyzer
|
|
154
|
+
.pytype/
|
|
155
|
+
|
|
156
|
+
# Cython debug symbols
|
|
157
|
+
cython_debug/
|
|
158
|
+
|
|
159
|
+
# PyCharm
|
|
160
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
161
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
162
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
163
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
164
|
+
#.idea/
|
|
165
|
+
|
|
166
|
+
### Python Patch ###
|
|
167
|
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
168
|
+
poetry.toml
|
|
169
|
+
|
|
170
|
+
# ruff
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# LSP config files
|
|
174
|
+
pyrightconfig.json
|
|
175
|
+
|
|
176
|
+
# End of https://www.toptal.com/developers/gitignore/api/python
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-resonant-settings
|
|
3
|
+
Version: 0.26
|
|
4
|
+
Summary: Shared Django settings for Resonant applications.
|
|
5
|
+
Project-URL: Repository, https://github.com/kitware-resonant/cookiecutter-resonant
|
|
6
|
+
Project-URL: Bug Reports, https://github.com/kitware-resonant/cookiecutter-resonant/issues
|
|
7
|
+
Maintainer-email: "Kitware, Inc." <kitware@kitware.com>
|
|
8
|
+
License: Apache 2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: django,resonant,setting,settings
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: Django
|
|
14
|
+
Classifier: Framework :: Django :: 5
|
|
15
|
+
Classifier: Framework :: Django :: 5.1
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Requires-Dist: django-environ
|
|
24
|
+
Provides-Extra: allauth
|
|
25
|
+
Requires-Dist: django-allauth; extra == 'allauth'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# django-resonant-settings
|
|
29
|
+
[](https://pypi.org/project/django-resonant-settings/)
|
|
30
|
+
|
|
31
|
+
Shared Django settings for Resonant applications.
|
|
32
|
+
|
|
33
|
+
# Installation and Usage
|
|
34
|
+
This package is tightly coupled to
|
|
35
|
+
[`cookiecutter-resonant`](https://github.com/kitware-resonant/cookiecutter-resonant) and should
|
|
36
|
+
be used within a cookiecutter-derived Resonant application.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# django-resonant-settings
|
|
2
|
+
[](https://pypi.org/project/django-resonant-settings/)
|
|
3
|
+
|
|
4
|
+
Shared Django settings for Resonant applications.
|
|
5
|
+
|
|
6
|
+
# Installation and Usage
|
|
7
|
+
This package is tightly coupled to
|
|
8
|
+
[`cookiecutter-resonant`](https://github.com/kitware-resonant/cookiecutter-resonant) and should
|
|
9
|
+
be used within a cookiecutter-derived Resonant application.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-resonant-settings"
|
|
7
|
+
description = "Shared Django settings for Resonant applications."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
license = { text = "Apache 2.0" }
|
|
11
|
+
maintainers = [{ name = "Kitware, Inc.", email = "kitware@kitware.com" }]
|
|
12
|
+
keywords = [
|
|
13
|
+
"django",
|
|
14
|
+
"resonant",
|
|
15
|
+
"setting",
|
|
16
|
+
"settings",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Environment :: Web Environment",
|
|
21
|
+
"Framework :: Django :: 5",
|
|
22
|
+
"Framework :: Django :: 5.1",
|
|
23
|
+
"Framework :: Django",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: Apache Software License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"django-environ",
|
|
33
|
+
]
|
|
34
|
+
dynamic = ["version"]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
allauth = [
|
|
38
|
+
"django-allauth",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Repository = "https://github.com/kitware-resonant/cookiecutter-resonant"
|
|
43
|
+
"Bug Reports" = "https://github.com/kitware-resonant/cookiecutter-resonant/issues"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build]
|
|
46
|
+
packages = [
|
|
47
|
+
"resonant_settings",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.version]
|
|
51
|
+
source = "vcs"
|
|
52
|
+
raw-options = { root = ".." }
|
|
53
|
+
|
|
54
|
+
[tool.black]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = ["py313"]
|
|
57
|
+
|
|
58
|
+
[tool.isort]
|
|
59
|
+
profile = "black"
|
|
60
|
+
line_length = 100
|
|
61
|
+
# Sort by name, don't cluster "from" vs "import"
|
|
62
|
+
force_sort_within_sections = true
|
|
63
|
+
# Combines "as" imports on the same line
|
|
64
|
+
combine_as_imports = true
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
files = [
|
|
68
|
+
"resonant_settings",
|
|
69
|
+
]
|
|
70
|
+
show_error_codes = true
|
|
71
|
+
|
|
72
|
+
[[tool.mypy.overrides]]
|
|
73
|
+
module = [
|
|
74
|
+
"allauth.*",
|
|
75
|
+
"environ.*",
|
|
76
|
+
]
|
|
77
|
+
ignore_missing_imports = true
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure django-allauth with the following features:
|
|
3
|
+
* Disable usernames for end users, using exclusively email addresses for login
|
|
4
|
+
* Require email verification
|
|
5
|
+
* Quality of life improvements for users
|
|
6
|
+
|
|
7
|
+
This requires the `django-allauth` package to be installed and requires
|
|
8
|
+
`resonant_settings.allauth_support` to be added to INSTALLED_APPS.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# The sites framework requires this to be set.
|
|
12
|
+
# In the unlikely case where a database's pk sequence for the django_site table is not reset,
|
|
13
|
+
# the default site object could have a different pk. Then this will need to be overridden
|
|
14
|
+
# downstream.
|
|
15
|
+
SITE_ID = 1
|
|
16
|
+
|
|
17
|
+
AUTHENTICATION_BACKENDS = [
|
|
18
|
+
# Django's built-in ModelBackend is not necessary, since all users will be
|
|
19
|
+
# authenticated by their email address
|
|
20
|
+
"allauth.account.auth_backends.AuthenticationBackend",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# see configuration documentation at
|
|
24
|
+
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
|
25
|
+
|
|
26
|
+
# Require email verification, but this can be overridden
|
|
27
|
+
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
|
28
|
+
|
|
29
|
+
# Use email as the identifier for login
|
|
30
|
+
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
|
31
|
+
ACCOUNT_EMAIL_REQUIRED = True
|
|
32
|
+
ACCOUNT_USERNAME_REQUIRED = False
|
|
33
|
+
|
|
34
|
+
# Set the username as the email
|
|
35
|
+
ACCOUNT_ADAPTER = "resonant_settings.allauth_support.adapter.EmailAsUsernameAccountAdapter"
|
|
36
|
+
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
|
37
|
+
|
|
38
|
+
# Quality of life improvements, but may not work if the browser is closed
|
|
39
|
+
ACCOUNT_SESSION_REMEMBER = True
|
|
40
|
+
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
|
41
|
+
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
|
|
42
|
+
|
|
43
|
+
# These will permit GET requests to mutate the user state, but significantly improve usability
|
|
44
|
+
ACCOUNT_LOGOUT_ON_GET = True
|
|
45
|
+
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
|
|
46
|
+
|
|
47
|
+
# This will likely become the default in the future, but enable it now
|
|
48
|
+
ACCOUNT_PRESERVE_USERNAME_CASING = False
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from allauth.account.adapter import DefaultAccountAdapter
|
|
2
|
+
from django.contrib.auth.models import AbstractUser
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EmailAsUsernameAccountAdapter(DefaultAccountAdapter):
|
|
7
|
+
"""Automatically populate the username as the email address."""
|
|
8
|
+
|
|
9
|
+
def populate_username(self, request: HttpRequest, user: AbstractUser) -> None:
|
|
10
|
+
user.username = user.email
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from django.contrib.auth.management.commands import createsuperuser
|
|
6
|
+
from django.contrib.auth.models import User, UserManager
|
|
7
|
+
|
|
8
|
+
from resonant_settings.allauth_support.utils import temporarily_change_attributes
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Command(createsuperuser.Command):
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self.UserModel = EmailAsUsernameProxyUser
|
|
15
|
+
self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
|
|
16
|
+
|
|
17
|
+
def _validate_username(self, username, verbose_field_name, database):
|
|
18
|
+
# Since "username" is actually unique, "email" (i.e. "self.username_field") is logically
|
|
19
|
+
# unique too. Explicitly setting the "_unique" attribute ensures that app-level duplicate
|
|
20
|
+
# checking is done by "_validate_username", which produces better, earlier error messages.
|
|
21
|
+
# Out of an abundance of caution, set the "_unique" attribute back to its original value
|
|
22
|
+
# when this is done.
|
|
23
|
+
with temporarily_change_attributes(self.username_field, _unique=True):
|
|
24
|
+
# Normalize (as it would be done before saving) for better duplicate detection
|
|
25
|
+
username = self.UserModel.normalize_username(username)
|
|
26
|
+
return super()._validate_username(username, verbose_field_name, database)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EmailAsUsernameProxyUserManager(UserManager):
|
|
30
|
+
# This version of "create_superuser" makes the "username" argument optional
|
|
31
|
+
def create_superuser(
|
|
32
|
+
self,
|
|
33
|
+
username: str | None = None,
|
|
34
|
+
email: str | None = None,
|
|
35
|
+
password: str | None = None,
|
|
36
|
+
**extra_fields,
|
|
37
|
+
) -> EmailAsUsernameProxyUser:
|
|
38
|
+
# Practically, email will always be provided
|
|
39
|
+
assert email
|
|
40
|
+
user = super().create_superuser(
|
|
41
|
+
username=email, email=email, password=password, **extra_fields
|
|
42
|
+
)
|
|
43
|
+
return user
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EmailAsUsernameProxyUser(User):
|
|
47
|
+
# https://github.com/typeddjango/django-stubs/issues/2112
|
|
48
|
+
class Meta(User.Meta): # type: ignore[name-defined]
|
|
49
|
+
proxy = True
|
|
50
|
+
|
|
51
|
+
objects = EmailAsUsernameProxyUserManager()
|
|
52
|
+
|
|
53
|
+
# "createsuperuser.Command" automatically includes the referent of "USERNAME_FIELD", and we want
|
|
54
|
+
# to apply username labeling, help text, and validation rules from the actual "email" field
|
|
55
|
+
USERNAME_FIELD = "email"
|
|
56
|
+
|
|
57
|
+
# Don't include "email" in "REQUIRED_FIELDS", to prevent adding that field twice to the
|
|
58
|
+
# "createsuperuser.Command" argument parser
|
|
59
|
+
REQUIRED_FIELDS: ClassVar[list[str]] = []
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def normalize_username(cls, username: str) -> str:
|
|
63
|
+
# This method is called from "UserManager._create_user" with the actual "username" field.
|
|
64
|
+
# To ensure that the saved value of the "username" field exactly matches the "email" field,
|
|
65
|
+
# apply the same normalization process.
|
|
66
|
+
return EmailAsUsernameProxyUserManager.normalize_email(username)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
from allauth.account import app_settings as allauth_settings
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.contrib.auth.management.commands import createsuperuser as django_createsuperuser
|
|
6
|
+
from django.contrib.auth.models import AbstractUser
|
|
7
|
+
from django.core.management import BaseCommand
|
|
8
|
+
from django.db.models.signals import post_save
|
|
9
|
+
|
|
10
|
+
from resonant_settings.allauth_support import createsuperuser as allauth_support_createsuperuser
|
|
11
|
+
from resonant_settings.allauth_support.receiver import verify_email_address_on_user_post_save
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
When Allauth is configured to use a User's `email` as the `username`, override the `createsuperuser`
|
|
15
|
+
management command to only prompt for an email address.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# If using email as username
|
|
19
|
+
if not allauth_settings.USERNAME_REQUIRED:
|
|
20
|
+
# Expose the modified command
|
|
21
|
+
Command: type[BaseCommand] = allauth_support_createsuperuser.Command
|
|
22
|
+
user_model: type[AbstractUser] = allauth_support_createsuperuser.EmailAsUsernameProxyUser
|
|
23
|
+
|
|
24
|
+
else:
|
|
25
|
+
# Expose the pristine upstream version of the command
|
|
26
|
+
Command = django_createsuperuser.Command
|
|
27
|
+
user_model = cast(type[AbstractUser], get_user_model())
|
|
28
|
+
|
|
29
|
+
# Always automatically verify email addresses of newly created superusers
|
|
30
|
+
post_save.connect(verify_email_address_on_user_post_save, sender=user_model)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from allauth.account.models import EmailAddress
|
|
4
|
+
from django.contrib.auth.models import AbstractUser
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__file__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def verify_email_address_on_user_post_save(
|
|
10
|
+
sender: type[AbstractUser],
|
|
11
|
+
instance: AbstractUser,
|
|
12
|
+
created: bool,
|
|
13
|
+
**kwargs,
|
|
14
|
+
):
|
|
15
|
+
"""Automatically verify email addresses of newly created superusers."""
|
|
16
|
+
# These should always be true, but it's a final sanity check
|
|
17
|
+
if created and instance.is_superuser:
|
|
18
|
+
# Django is less strict than Allauth about duplicate email addresses (Django lowercases
|
|
19
|
+
# the domain portion or an address, Allauth lowercases the whole address). It's possible
|
|
20
|
+
# an acceptable email address via createsuperuser would be refused as a duplicate via
|
|
21
|
+
# Allauth.
|
|
22
|
+
# It's also possible that in an mature database where users have multiple non-primary
|
|
23
|
+
# emails, the new user's email matches an existing user's non-primary email, violating
|
|
24
|
+
# uniqueness.
|
|
25
|
+
# So, make a conservative effort at setting the email address as verified, since failure is
|
|
26
|
+
# not critical and can be resolved by the user.
|
|
27
|
+
email_address, created = EmailAddress.objects.get_or_create(
|
|
28
|
+
email__iexact=instance.email,
|
|
29
|
+
defaults={
|
|
30
|
+
"user": instance,
|
|
31
|
+
"email": instance.email,
|
|
32
|
+
"verified": True,
|
|
33
|
+
"primary": True,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
if not created:
|
|
37
|
+
logger.warning(f'Could not automatically verify email address "{instance.email}".')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# From https://stackoverflow.com/a/38532086
|
|
5
|
+
@contextmanager
|
|
6
|
+
def temporarily_change_attributes(something, **kwargs):
|
|
7
|
+
previous_values = {k: getattr(something, k) for k in kwargs}
|
|
8
|
+
for k, v in kwargs.items():
|
|
9
|
+
setattr(something, k, v)
|
|
10
|
+
try:
|
|
11
|
+
yield
|
|
12
|
+
finally:
|
|
13
|
+
for k, v in previous_values.items():
|
|
14
|
+
setattr(something, k, v)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Celery with the following features:
|
|
3
|
+
* Disable the results backend
|
|
4
|
+
* Ensure that tasks will never be lost, but tasks themselves must be idempotent
|
|
5
|
+
* Optimize the network connection to CloudAMQP
|
|
6
|
+
|
|
7
|
+
This requires the `celery` package to be installed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from resonant_settings._env import env
|
|
11
|
+
|
|
12
|
+
# Assume AMQP.
|
|
13
|
+
CELERY_BROKER_URL: str = env.str("DJANGO_CELERY_BROKER_URL")
|
|
14
|
+
|
|
15
|
+
# Disable results backend, as this feature has too many weaknesses.
|
|
16
|
+
# The database should be used to communicate results of completed tasks.
|
|
17
|
+
CELERY_RESULT_BACKEND = None
|
|
18
|
+
|
|
19
|
+
# Only acknowledge a task being done after the function finishes.
|
|
20
|
+
# This provides safety against worker crashes, but adds the requirement
|
|
21
|
+
# that tasks must be idempotent (which is a best practice anyway).
|
|
22
|
+
# See: https://docs.celeryproject.org/en/stable/faq.html#should-i-use-retry-or-acks-late
|
|
23
|
+
CELERY_TASK_ACKS_LATE = True
|
|
24
|
+
|
|
25
|
+
# When a worker subprocess abruptly exists, assume it was is killed by the operating system for
|
|
26
|
+
# a cause which is intrinsic (e.g. a segfault or OOM) to the task it was running, so do not
|
|
27
|
+
# requeue. It's expected that the task wouldn't succeed if run again.
|
|
28
|
+
# This should not impact cases where the task fails due to extrinsic causes (e.g. the process
|
|
29
|
+
# supervisor sends a SIGKILL or the machine loses power), as we assume that the parent worker
|
|
30
|
+
# process will immediately die too (and not have a chance to requeue the task).
|
|
31
|
+
# See: https://docs.celeryproject.org/en/stable/userguide/tasks.html#tasks for more explanation
|
|
32
|
+
# of these tradeoffs.
|
|
33
|
+
# None of this affects warm shutdowns from a SIGTERM (which the process supervisor ought to
|
|
34
|
+
# send), as this just allows Celery to complete running tasks; see:
|
|
35
|
+
# https://docs.celeryproject.org/en/stable/userguide/workers.html#process-signals for reference.
|
|
36
|
+
# This is Celery's default.
|
|
37
|
+
CELERY_TASK_REJECT_ON_WORKER_LOST = False
|
|
38
|
+
|
|
39
|
+
# When a task fails due to an internally-raised exception or due to a timeout, do not requeue.
|
|
40
|
+
# It's expected that the task wouldn't succeed if run again.
|
|
41
|
+
# This is Celery's default.
|
|
42
|
+
CELERY_TASK_ACKS_ON_FAILURE_OR_TIMEOUT = True
|
|
43
|
+
|
|
44
|
+
# This is sensible behavior with TASKS_ACKS_LATE, this must be enabled to prevent warnings,
|
|
45
|
+
# and this will be Celery's default in 6.0.
|
|
46
|
+
CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True
|
|
47
|
+
|
|
48
|
+
# CloudAMQP-suggested settings
|
|
49
|
+
# https://www.cloudamqp.com/docs/celery.html
|
|
50
|
+
CELERY_BROKER_POOL_LIMIT = 1
|
|
51
|
+
CELERY_BROKER_HEARTBEAT = None
|
|
52
|
+
CELERY_BROKER_CONNECTION_TIMEOUT = 30
|
|
53
|
+
CELERY_EVENT_QUEUE_EXPIRES = 60
|
|
54
|
+
|
|
55
|
+
# Note, CELERY_WORKER settings could be different on each running worker.
|
|
56
|
+
|
|
57
|
+
# Do not prefetch, as the speed benefit for fast-running tasks may not be
|
|
58
|
+
# worth a potentially unfair allocation with slow-running tasks and
|
|
59
|
+
# multiple workers.
|
|
60
|
+
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
|
|
61
|
+
|
|
62
|
+
# Accept the default of the number of CPU cores.
|
|
63
|
+
# Workers running memory-intensive tasks may need to decrease this.
|
|
64
|
+
CELERY_WORKER_CONCURRENCY: int | None = None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Django Debug Toolbar with the following features:
|
|
3
|
+
* Improve performance with large queries
|
|
4
|
+
|
|
5
|
+
This requires the `django-debug-toolbar` package to be installed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
DEBUG_TOOLBAR_CONFIG = {
|
|
9
|
+
# The default size often is too small, causing an inability to view queries
|
|
10
|
+
"RESULTS_CACHE_SIZE": 250,
|
|
11
|
+
# If this setting is True, large sql queries can cause the page to render slowly
|
|
12
|
+
"PRETTIFY_SQL": False,
|
|
13
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Acknowledge early in development, which will help prevent failing or
|
|
2
|
+
# long-running tasks from being started automatically every time the worker
|
|
3
|
+
# process restarts; this more aggressively flushes the task queue.
|
|
4
|
+
CELERY_TASK_ACKS_LATE = False
|
|
5
|
+
|
|
6
|
+
# In development, run without concurrency.
|
|
7
|
+
CELERY_WORKER_CONCURRENCY: int | None = 1
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure a basic Django project.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
TEMPLATES = [
|
|
6
|
+
{
|
|
7
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
8
|
+
"DIRS": [],
|
|
9
|
+
"APP_DIRS": True,
|
|
10
|
+
"OPTIONS": {
|
|
11
|
+
"context_processors": [
|
|
12
|
+
"django.template.context_processors.debug",
|
|
13
|
+
"django.template.context_processors.request",
|
|
14
|
+
"django.contrib.auth.context_processors.auth",
|
|
15
|
+
"django.contrib.messages.context_processors.messages",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
PASSWORD_HASHERS = [
|
|
22
|
+
# Argon2 is recommended by OWASP, so make it the default for new passwords
|
|
23
|
+
# https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
24
|
+
"django.contrib.auth.hashers.Argon2PasswordHasher",
|
|
25
|
+
# scrypt was the default hasher in older versions of Resonant,
|
|
26
|
+
# so it must be enabled to read old passwords
|
|
27
|
+
"django.contrib.auth.hashers.ScryptPasswordHasher",
|
|
28
|
+
# Support for other hashers isn't needed,
|
|
29
|
+
# since databases shouldn't have entries with other algorithms
|
|
30
|
+
]
|
|
31
|
+
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
|
|
32
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
33
|
+
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
|
34
|
+
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
|
35
|
+
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
|
36
|
+
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
|
37
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Django logging with the following features:
|
|
3
|
+
* Emit all logs to stdout
|
|
4
|
+
* Exclude favicons and static files from request and server logs
|
|
5
|
+
* Improve log formatting and add colorization
|
|
6
|
+
|
|
7
|
+
This requires the `rich` package to be installed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from django.http import HttpRequest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _filter_favicon_requests(record: logging.LogRecord) -> bool:
|
|
16
|
+
if record.name == "django.request":
|
|
17
|
+
request: HttpRequest | None = getattr(record, "request", None)
|
|
18
|
+
if request and request.path == "/favicon.ico":
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
record.name == "django.server"
|
|
23
|
+
and isinstance(record.args, tuple)
|
|
24
|
+
and len(record.args) >= 1
|
|
25
|
+
and str(record.args[0]).startswith("GET /favicon.ico ")
|
|
26
|
+
):
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _filter_static_requests(record: logging.LogRecord) -> bool:
|
|
33
|
+
if (
|
|
34
|
+
record.name == "django.server"
|
|
35
|
+
and isinstance(record.args, tuple)
|
|
36
|
+
and len(record.args) >= 1
|
|
37
|
+
and str(record.args[0]).startswith("GET /static/")
|
|
38
|
+
):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
LOGGING = {
|
|
45
|
+
"version": 1,
|
|
46
|
+
# Replace existing logging configuration
|
|
47
|
+
"incremental": False,
|
|
48
|
+
# This redefines all of Django's declared loggers, but most loggers are implicitly
|
|
49
|
+
# declared on usage, and should not be disabled. They often propagate their output
|
|
50
|
+
# to the root anyway.
|
|
51
|
+
"disable_existing_loggers": False,
|
|
52
|
+
"formatters": {"rich": {"datefmt": "[%X]"}},
|
|
53
|
+
"filters": {
|
|
54
|
+
"filter_favicon_requests": {
|
|
55
|
+
"()": "django.utils.log.CallbackFilter",
|
|
56
|
+
"callback": _filter_favicon_requests,
|
|
57
|
+
},
|
|
58
|
+
"filter_static_requests": {
|
|
59
|
+
"()": "django.utils.log.CallbackFilter",
|
|
60
|
+
"callback": _filter_static_requests,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
"handlers": {
|
|
64
|
+
"console": {
|
|
65
|
+
"class": "rich.logging.RichHandler",
|
|
66
|
+
"formatter": "rich",
|
|
67
|
+
"filters": ["filter_favicon_requests", "filter_static_requests"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
# Existing loggers actually contain direct (non-string) references to existing handlers,
|
|
71
|
+
# so after redefining handlers, all existing loggers must be redefined too
|
|
72
|
+
"loggers": {
|
|
73
|
+
# Configure the root logger to output to the console
|
|
74
|
+
"": {"level": "INFO", "handlers": ["console"], "propagate": False},
|
|
75
|
+
# Django defines special configurations for the "django" and "django.server" loggers,
|
|
76
|
+
# but we will manage all content at the root logger instead, so reset those
|
|
77
|
+
# configurations.
|
|
78
|
+
"django": {
|
|
79
|
+
"handlers": [],
|
|
80
|
+
"level": "NOTSET",
|
|
81
|
+
"propagate": True,
|
|
82
|
+
},
|
|
83
|
+
"django.server": {
|
|
84
|
+
"handlers": [],
|
|
85
|
+
"level": "NOTSET",
|
|
86
|
+
"propagate": True,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Django OAuth Toolkit with the following features:
|
|
3
|
+
* Harden security
|
|
4
|
+
* Improve usability of token scopes
|
|
5
|
+
* Improve quality of live for out of band flows and non-refreshing clients
|
|
6
|
+
|
|
7
|
+
This requires the `django-oauth-toolkit` package to be installed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import timedelta
|
|
11
|
+
|
|
12
|
+
OAUTH2_PROVIDER = {
|
|
13
|
+
"ALLOWED_REDIRECT_URI_SCHEMES": ["https"],
|
|
14
|
+
# Don't require users to re-approve scopes each time
|
|
15
|
+
"REQUEST_APPROVAL_PROMPT": "auto",
|
|
16
|
+
# ERROR_RESPONSE_WITH_SCOPES is only used with the "permission_classes" helpers for scopes.
|
|
17
|
+
# If the scope itself is confidential, this could leak information. but the usability
|
|
18
|
+
# benefit is probably worth it.
|
|
19
|
+
"ERROR_RESPONSE_WITH_SCOPES": True,
|
|
20
|
+
# Allow 5 minutes for a flow to exchange an auth code for a token. This is typically
|
|
21
|
+
# 60 seconds but out-of-band flows may take a bit longer. A maximum of 10 minutes is
|
|
22
|
+
# recommended: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.
|
|
23
|
+
"AUTHORIZATION_CODE_EXPIRE_SECONDS": timedelta(minutes=5).total_seconds(),
|
|
24
|
+
# Django can persist logins for longer than this via cookies,
|
|
25
|
+
# but non-refreshing clients will need to redirect to Django's auth every 24 hours.
|
|
26
|
+
"ACCESS_TOKEN_EXPIRE_SECONDS": timedelta(days=1).total_seconds(),
|
|
27
|
+
# This allows refresh tokens to eventually be removed from the database by
|
|
28
|
+
# "manage.py cleartokens". This value is not actually enforced when refresh tokens are
|
|
29
|
+
# checked, but it can be assumed that all clients will need to redirect to Django's auth
|
|
30
|
+
# every 30 days.
|
|
31
|
+
"REFRESH_TOKEN_EXPIRE_SECONDS": timedelta(days=30).total_seconds(),
|
|
32
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Django's email sending.
|
|
3
|
+
|
|
4
|
+
The following environment variables must be externally set:
|
|
5
|
+
* `DJANGO_EMAIL_URL`, as a URL for login to an STMP server, as parsed by `dj-email-url`. This
|
|
6
|
+
typically will start with `submission:`. Special characters in passwords must be URL-encoded.
|
|
7
|
+
See https://pypi.org/project/dj-email-url/ for full details.
|
|
8
|
+
* `DJANGO_DEFAULT_FROM_EMAIL`, as the default From address for outgoing email.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from resonant_settings._env import env
|
|
12
|
+
|
|
13
|
+
vars().update(env.email_url("DJANGO_EMAIL_URL"))
|
|
14
|
+
|
|
15
|
+
DEFAULT_FROM_EMAIL: str = env.str("DJANGO_DEFAULT_FROM_EMAIL")
|
|
16
|
+
SERVER_EMAIL = DEFAULT_FROM_EMAIL
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Configure Django's security middleware to use and require HTTPS."""
|
|
2
|
+
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
|
|
5
|
+
SECURE_SSL_REDIRECT = True
|
|
6
|
+
|
|
7
|
+
# This needs to be set by the HTTPS terminating reverse proxy.
|
|
8
|
+
# Heroku and Render automatically set this.
|
|
9
|
+
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
10
|
+
|
|
11
|
+
SESSION_COOKIE_SECURE = True
|
|
12
|
+
CSRF_COOKIE_SECURE = True
|
|
13
|
+
|
|
14
|
+
# Enable HSTS
|
|
15
|
+
SECURE_HSTS_SECONDS = timedelta(days=365).total_seconds()
|
|
16
|
+
# This is already False by default, but it's important to ensure HSTS is not forced on other
|
|
17
|
+
# subdomains which may have different HTTPS practices.
|
|
18
|
+
SECURE_HSTS_INCLUDE_SUBDOMAINS = False
|
|
19
|
+
# This is already False by default, but per https://hstspreload.org/#opt-in, projects should
|
|
20
|
+
# opt-in to preload by overriding this setting. Additionally, all subdomains must have HSTS to
|
|
21
|
+
# register for preloading.
|
|
22
|
+
SECURE_HSTS_PRELOAD = False
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure S3Storage.
|
|
3
|
+
|
|
4
|
+
The following environment variables must be externally set:
|
|
5
|
+
* AWS_DEFAULT_REGION
|
|
6
|
+
* AWS_ACCESS_KEY_ID
|
|
7
|
+
* AWS_SECRET_ACCESS_KEY
|
|
8
|
+
* DJANGO_STORAGE_BUCKET_NAME
|
|
9
|
+
|
|
10
|
+
This requires the `django-storages[s3]` package to be installed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from datetime import timedelta
|
|
14
|
+
|
|
15
|
+
from resonant_settings._env import env
|
|
16
|
+
|
|
17
|
+
# These exact environment variable names are important,
|
|
18
|
+
# as direct instantiations of Boto will also respect them.
|
|
19
|
+
AWS_S3_REGION_NAME: str = env.str("AWS_DEFAULT_REGION")
|
|
20
|
+
AWS_S3_ACCESS_KEY_ID: str = env.str("AWS_ACCESS_KEY_ID")
|
|
21
|
+
AWS_S3_SECRET_ACCESS_KEY: str = env.str("AWS_SECRET_ACCESS_KEY")
|
|
22
|
+
|
|
23
|
+
AWS_STORAGE_BUCKET_NAME: str = env.str("DJANGO_STORAGE_BUCKET_NAME")
|
|
24
|
+
|
|
25
|
+
# It's critical to use the v4 signature;
|
|
26
|
+
# it isn't the upstream default only for backwards compatability reasons.
|
|
27
|
+
AWS_S3_SIGNATURE_VERSION = "s3v4"
|
|
28
|
+
|
|
29
|
+
AWS_S3_MAX_MEMORY_SIZE = 5 * 1024 * 1024
|
|
30
|
+
AWS_S3_FILE_OVERWRITE = False
|
|
31
|
+
AWS_QUERYSTRING_EXPIRE = timedelta(hours=6).total_seconds()
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure Django REST framework and drf-yasg.
|
|
3
|
+
|
|
4
|
+
This requires the `django-oauth-toolkit` and `drf-yasg` packages to be installed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
# When SessionAuthentication is allowed, it's critical that the following settings
|
|
10
|
+
# (respectively part of Django and django-cors-headers) are set to these values (although those
|
|
11
|
+
# are the also the default values).
|
|
12
|
+
SESSION_COOKIE_SAMESITE = "Lax"
|
|
13
|
+
CORS_ALLOW_CREDENTIALS = False
|
|
14
|
+
|
|
15
|
+
REST_FRAMEWORK = {
|
|
16
|
+
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
17
|
+
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
|
|
18
|
+
# Allow SessionAuthentication, as this is much more convenient for Ajax requests
|
|
19
|
+
# from server-rendered pages, including:
|
|
20
|
+
# * YASG (Swagger / ReDoc)
|
|
21
|
+
# * The Admin interface, when using interactive fields like S3-file-field
|
|
22
|
+
# * Augmentation of server-rendered views with background Javascript
|
|
23
|
+
# (see https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax )
|
|
24
|
+
# It's important that true SPAs and other clients be forced to go though
|
|
25
|
+
# OAuth2Authentication instead, as this is the only supported auth mechanism which
|
|
26
|
+
# robustly works across origins; however, it turns out that this can only be enforced
|
|
27
|
+
# partially.
|
|
28
|
+
# To understand why, first read https://web.dev/same-site-same-origin/ to understand
|
|
29
|
+
# that even with "SameSite=Lax" (or "SameSite=Strict"), cookies are only technically
|
|
30
|
+
# limited to same-site requests, and do not have the stronger same-origin limitation.
|
|
31
|
+
# If a naive SPA developer configures their client to include credentials
|
|
32
|
+
# ("{withCredentials: true}" in XHR, jQuery, and Axios, or "{credentials: 'include'}"
|
|
33
|
+
# in Fetch; this configuration is often suggested as an "easy" fix for authentication
|
|
34
|
+
# problems by StackOverflow), then the session cookie will be sent with any cross-site
|
|
35
|
+
# requests where the user has logged into the Django server. Note, cross-site requests
|
|
36
|
+
# include origins with a different port (typical in local development) and origins with
|
|
37
|
+
# a different subdomain (common in many deployments). From DRF's perspective, as long
|
|
38
|
+
# as the request uses a safe verb (more on this below), the request will be
|
|
39
|
+
# authenticated transparently. However, since Django is configured to not set
|
|
40
|
+
# "Access-Control-Allow-Credentials", the SPA client will not be able to read the
|
|
41
|
+
# response and get a CORS error; this is the right outcome (client cannot effectively
|
|
42
|
+
# make the request), but with a confusing and hard-to-debug reason (CORS error,
|
|
43
|
+
# instead of a 401/403) and developers may be tempted to "fix" it by enabling
|
|
44
|
+
# "Access-Control-Allow-Credentials" instead of fixing their client to use OAuth
|
|
45
|
+
# correctly, which will likely lead to further bugs when the SPA is deployed to a
|
|
46
|
+
# non-same-site environment. Alternatively, if the request does not use a safe verb
|
|
47
|
+
# (and the request is not preflighted by the browser, which is permitted in some
|
|
48
|
+
# cases), DRF will enforce CSRF protection and the request will 403 fail with a
|
|
49
|
+
# "CSRF Failed: CSRF token missing or incorrect" message, which is also confusing and
|
|
50
|
+
# may lead developers to incorrect fixes.
|
|
51
|
+
# TL;DR: Developers of SPAs may encounter misleading error messages when making Ajax
|
|
52
|
+
# requests "withCredentials", but security is still maintained.
|
|
53
|
+
"rest_framework.authentication.SessionAuthentication",
|
|
54
|
+
],
|
|
55
|
+
# This is a much more sensible degree of basic security
|
|
56
|
+
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticatedOrReadOnly"],
|
|
57
|
+
# BoundedLimitOffsetPagination provides LimitOffsetPagination with a maximum page size
|
|
58
|
+
"DEFAULT_PAGINATION_CLASS": "resonant_utils.rest_framework.BoundedLimitOffsetPagination",
|
|
59
|
+
# This provides a sane default for requests that do not specify a page size.
|
|
60
|
+
# This also ensures that endpoints with pagination will always return a
|
|
61
|
+
# pagination-structured response.
|
|
62
|
+
"PAGE_SIZE": 100,
|
|
63
|
+
# Real clients typically JSON-encode their request bodies, so the test client should too
|
|
64
|
+
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
SWAGGER_SETTINGS: dict[str, Any] = {
|
|
68
|
+
# The default security definition ("basic") is not supported by this DRF configuration,
|
|
69
|
+
# so expect all logins to come via the Django session, which there's no OpenAPI
|
|
70
|
+
# security definition for.
|
|
71
|
+
"SECURITY_DEFINITIONS": None,
|
|
72
|
+
"USE_SESSION_AUTH": True,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
REDOC_SETTINGS: dict[str, Any] = {}
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure MinioMediaStorage.
|
|
3
|
+
|
|
4
|
+
This requires the `django-minio-storage` package to be installed.
|
|
5
|
+
"""
|
|
6
|
+
from urllib.parse import ParseResult
|
|
7
|
+
|
|
8
|
+
from resonant_settings._env import env
|
|
9
|
+
|
|
10
|
+
minio_url: ParseResult = env.url("DJANGO_MINIO_STORAGE_URL")
|
|
11
|
+
MINIO_STORAGE_USE_HTTPS = minio_url.scheme == "https"
|
|
12
|
+
MINIO_STORAGE_ENDPOINT = minio_url.hostname + (f":{minio_url.port}" if minio_url.port else "")
|
|
13
|
+
MINIO_STORAGE_ACCESS_KEY = minio_url.username
|
|
14
|
+
MINIO_STORAGE_SECRET_KEY = minio_url.password
|
|
15
|
+
MINIO_STORAGE_MEDIA_BUCKET_NAME = minio_url.path.lstrip("/")
|
|
16
|
+
|
|
17
|
+
# Setting this allows MinIO to work through network namespace partitions
|
|
18
|
+
# (e.g. when running within Docker Compose)
|
|
19
|
+
MINIO_STORAGE_MEDIA_URL: str | None = env.str("DJANGO_MINIO_STORAGE_MEDIA_URL", default=None)
|
|
20
|
+
|
|
21
|
+
MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True
|
|
22
|
+
MINIO_STORAGE_AUTO_CREATE_MEDIA_POLICY = "READ_WRITE"
|
|
23
|
+
MINIO_STORAGE_MEDIA_USE_PRESIGNED = True
|