mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-04-25 01:04:49 +00:00
Compare commits
199 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0d4216ca | ||
|
|
007161adb0 | ||
|
|
57b6827ae3 | ||
|
|
b8f66f76ee | ||
|
|
fcd03ada0f | ||
|
|
0bf416155e | ||
|
|
cec5f7492a | ||
|
|
b8e94fd796 | ||
|
|
f37ed87d6e | ||
|
|
7f0f29d161 | ||
|
|
9688dae5e1 | ||
|
|
4efa08f81a | ||
|
|
c67eab3507 | ||
|
|
711fa66654 | ||
|
|
448c5ab1b6 | ||
|
|
65d326a95a | ||
|
|
63cac8d54e | ||
|
|
ba24d3c324 | ||
|
|
e1091160b1 | ||
|
|
880484d3b7 | ||
|
|
ef4f49d236 | ||
|
|
6dc2340dfe | ||
|
|
ffe979b63c | ||
|
|
eba6e2c6d9 | ||
|
|
38641a9dea | ||
|
|
9b7bd34812 | ||
|
|
df2a7f18fd | ||
|
|
27f67a58a4 | ||
|
|
dce5f37a93 | ||
|
|
2278975744 | ||
|
|
cad6dcb7f0 | ||
|
|
adaf92085f | ||
|
|
a1f072ebf3 | ||
|
|
6f47271526 | ||
|
|
befe7f1e0d | ||
|
|
352d95b2ab | ||
|
|
1f8bac5ba4 | ||
|
|
17ca033d33 | ||
|
|
7520ae4123 | ||
|
|
ac5408d7eb | ||
|
|
e2861e6327 | ||
|
|
40d244d865 | ||
|
|
8923ef8c49 | ||
|
|
7e473d0f9b | ||
|
|
794b858548 | ||
|
|
141d8ef2c4 | ||
|
|
fcba8b6d92 | ||
|
|
861935fd45 | ||
|
|
5f438451ea | ||
|
|
45a4557efe | ||
|
|
bb2523ddb9 | ||
|
|
32a41d62b9 | ||
|
|
ded548b19b | ||
|
|
c3b5e8627b | ||
|
|
93e628e870 | ||
|
|
7ff29852ab | ||
|
|
2655ecdd4f | ||
|
|
750f143724 | ||
|
|
dcce8bccfd | ||
|
|
1f17d675a1 | ||
|
|
381809d88c | ||
|
|
5698fe35b2 | ||
|
|
9f38e87a58 | ||
|
|
5562322599 | ||
|
|
cea79d1a7a | ||
|
|
03dc29848e | ||
|
|
596ab9a1bf | ||
|
|
80f2cee84b | ||
|
|
089a039efa | ||
|
|
1dce659946 | ||
|
|
f5d6ef7877 | ||
|
|
4bd4cf5dd4 | ||
|
|
91b359f74a | ||
|
|
d373c9ab75 | ||
|
|
62d34c2a16 | ||
|
|
02e8f55ac8 | ||
|
|
6c2ea44352 | ||
|
|
2d9e145a0f | ||
|
|
b75c8d7a7a | ||
|
|
c9c44c59b7 | ||
|
|
38059e1417 | ||
|
|
df967f4d76 | ||
|
|
6da8420635 | ||
|
|
a155ac54ad | ||
|
|
40bdab3f4a | ||
|
|
b1f62da419 | ||
|
|
f4e2241f44 | ||
|
|
c9c4a02169 | ||
|
|
ec12828877 | ||
|
|
91ef9fd8ad | ||
|
|
df51535afc | ||
|
|
7c9ac5e53f | ||
|
|
36265ab3f1 | ||
|
|
a5542b2bfc | ||
|
|
3d1554b4f8 | ||
|
|
3ac97e539f | ||
|
|
a56889dbc1 | ||
|
|
09dfa471e0 | ||
|
|
71924a195f | ||
|
|
55a92d86ad | ||
|
|
fad40b8003 | ||
|
|
8ec465de77 | ||
|
|
7e4b425ea3 | ||
|
|
12e033ed1e | ||
|
|
38534a7caf | ||
|
|
96ef5cc182 | ||
|
|
fa026af595 | ||
|
|
68cbb437cf | ||
|
|
6a4a620891 | ||
|
|
2cae9838b5 | ||
|
|
c1cb3874f2 | ||
|
|
1ada7d14f7 | ||
|
|
271f6fb5bb | ||
|
|
d715a719a4 | ||
|
|
58a4f689ff | ||
|
|
f85ec1fb89 | ||
|
|
d89fe5a2cb | ||
|
|
856b55bea8 | ||
|
|
3da6af02fb | ||
|
|
c8153bc081 | ||
|
|
6fa45aba1b | ||
|
|
58575bfb84 | ||
|
|
184df10a66 | ||
|
|
9b9ff4c0a2 | ||
|
|
c3d5b4b715 | ||
|
|
add9b3ce80 | ||
|
|
6cb932e47f | ||
|
|
fcdbfc0bcb | ||
|
|
dd5d6974cb | ||
|
|
78f9824f0c | ||
|
|
f5bebd4ecc | ||
|
|
5053da4357 | ||
|
|
eea5b9ad34 | ||
|
|
64eefaef1a | ||
|
|
b540ceadb3 | ||
|
|
3305de960a | ||
|
|
4fb5928a9c | ||
|
|
b9648bddb3 | ||
|
|
566af30ce6 | ||
|
|
ac6d31bb83 | ||
|
|
3b3f5db60d | ||
|
|
601d52218a | ||
|
|
6314038a77 | ||
|
|
8d7036d253 | ||
|
|
42641f5eb4 | ||
|
|
bd61710591 | ||
|
|
b9f6aa98ec | ||
|
|
c15996a0fa | ||
|
|
8e32e5b3c7 | ||
|
|
3de33277a7 | ||
|
|
7f469b6d6e | ||
|
|
0f486ef6e4 | ||
|
|
843271a701 | ||
|
|
12ea3c0a5b | ||
|
|
51e0265276 | ||
|
|
7e74d67308 | ||
|
|
9c9b07dca5 | ||
|
|
0f479a56f5 | ||
|
|
f387e1ee63 | ||
|
|
41b905c92e | ||
|
|
be2a0dc18f | ||
|
|
12036fabc8 | ||
|
|
bda8c22065 | ||
|
|
934ca95883 | ||
|
|
ec8a8e7df4 | ||
|
|
b754256413 | ||
|
|
f887ad5821 | ||
|
|
2db80d8c34 | ||
|
|
f65000b38e | ||
|
|
4c64bb602f | ||
|
|
ec56685da4 | ||
|
|
690e8784f6 | ||
|
|
f38e07b94a | ||
|
|
3c8590a8d4 | ||
|
|
e7d63e0815 | ||
|
|
4480bdb4ad | ||
|
|
79b92372f7 | ||
|
|
605e6fe296 | ||
|
|
55592874b4 | ||
|
|
aa7864f722 | ||
|
|
efea2224da | ||
|
|
06f2d57386 | ||
|
|
0e45c7b3e4 | ||
|
|
a2dffdb8e4 | ||
|
|
6bc31023d2 | ||
|
|
54e4e76af9 | ||
|
|
cfdaf6caca | ||
|
|
9dcb47dc55 | ||
|
|
18dcab03f9 | ||
|
|
ea060f0985 | ||
|
|
cc2a821579 | ||
|
|
5ea0db0aee | ||
|
|
a0a43494d0 | ||
|
|
8f199eb40c | ||
|
|
a045609934 | ||
|
|
52a86327fc | ||
|
|
89cdca07ae | ||
|
|
b2d27dc016 | ||
|
|
2d885caa46 |
56 changed files with 1080 additions and 1240 deletions
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Keep GitHub Actions up to date with GitHub's Dependabot...
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*" # Group all Actions updates into a single larger pull request
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
53
.github/workflows/release.yml
vendored
Normal file
53
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: github.repository == 'jazzband/django-configurations'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
|
||||||
|
- name: Get pip cache dir
|
||||||
|
id: pip-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
|
key: release-${{ hashFiles('**/setup.py') }}
|
||||||
|
restore-keys: |
|
||||||
|
release-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install --upgrade setuptools twine wheel
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
python setup.py --version
|
||||||
|
python setup.py sdist --format=gztar bdist_wheel
|
||||||
|
twine check dist/*
|
||||||
|
|
||||||
|
- name: Upload packages to Jazzband
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
user: jazzband
|
||||||
|
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||||
|
repository-url: https://jazzband.co/projects/django-configurations/upload
|
||||||
57
.github/workflows/test.yml
vendored
Normal file
57
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 6
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Get pip cache dir
|
||||||
|
id: pip-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
|
key:
|
||||||
|
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ matrix.python-version }}-v1-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install --upgrade "tox<4" "tox-gh-actions<3"
|
||||||
|
|
||||||
|
- name: Tox tests
|
||||||
|
run: |
|
||||||
|
tox --verbose
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
uses: codecov/codecov-action@v5
|
||||||
|
with:
|
||||||
|
name: coverage-data-${{ matrix.python-version }}
|
||||||
|
path: ".coverage.*"
|
||||||
|
include-hidden-files: true
|
||||||
|
merge-multiple: true
|
||||||
|
fail_ci_if_error: true
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
.coverage
|
.coverage
|
||||||
|
coverage.xml
|
||||||
docs/_build
|
docs/_build
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.egg
|
*.egg
|
||||||
|
|
@ -8,4 +9,4 @@ build/
|
||||||
htmlcov/
|
htmlcov/
|
||||||
*.pyc
|
*.pyc
|
||||||
dist/
|
dist/
|
||||||
tests/docs/_build/
|
.eggs/
|
||||||
|
|
|
||||||
1
.pre-commit-config.yaml
Normal file
1
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
repos: []
|
||||||
16
.readthedocs.yaml
Normal file
16
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
version: 2
|
||||||
|
build:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
tools:
|
||||||
|
python: "3.10"
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
formats:
|
||||||
|
- epub
|
||||||
|
- pdf
|
||||||
86
.travis.yml
86
.travis.yml
|
|
@ -1,86 +0,0 @@
|
||||||
language: python
|
|
||||||
sudo: false
|
|
||||||
cache: pip
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- env: TOXENV=flake8-py27
|
|
||||||
python: 2.7
|
|
||||||
- env: TOXENV=flake8-py36
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=readme-py27
|
|
||||||
python: 2.7
|
|
||||||
- env: TOXENV=py27-dj18
|
|
||||||
python: 2.7
|
|
||||||
- env: TOXENV=py27-dj110
|
|
||||||
python: 2.7
|
|
||||||
- env: TOXENV=py27-dj111
|
|
||||||
python: 2.7
|
|
||||||
- env: TOXENV=py34-dj18
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py34-dj110
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py34-dj111
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py35-dj18
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py35-dj110
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py35-dj111
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py36-dj18
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=py36-dj110
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=py36-dj111
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=pypy-dj18
|
|
||||||
python: pypy
|
|
||||||
- env: TOXENV=pypy-dj110
|
|
||||||
python: pypy
|
|
||||||
- env: TOXENV=pypy-dj111
|
|
||||||
python: pypy
|
|
||||||
- env: TOXENV=py34-dj20
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py34-djmaster
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py35-dj20
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py35-djmaster
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py36-dj20
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=py36-dj21
|
|
||||||
python: 3.6
|
|
||||||
- env: TOXENV=py36-djmaster
|
|
||||||
python: 3.6
|
|
||||||
allow_failures:
|
|
||||||
- env: TOXENV=py34-djmaster
|
|
||||||
python: 3.4
|
|
||||||
- env: TOXENV=py35-djmaster
|
|
||||||
python: 3.5
|
|
||||||
- env: TOXENV=py36-djmaster
|
|
||||||
python: 3.6
|
|
||||||
install:
|
|
||||||
- pip install tox
|
|
||||||
script: tox -v
|
|
||||||
after_success:
|
|
||||||
- |
|
|
||||||
if [ "$TOXENV" = py* ]; then
|
|
||||||
pip install codecov
|
|
||||||
coverage xml
|
|
||||||
coverage report -m
|
|
||||||
codecov --required -X gcov fix pycov -f coverage.xml --flags ${TOXENV//-/ }
|
|
||||||
fi
|
|
||||||
branches:
|
|
||||||
except: templates/1.5.x templates/1.6.x
|
|
||||||
deploy:
|
|
||||||
provider: pypi
|
|
||||||
user: jazzband
|
|
||||||
server: https://jazzband.co/projects/django-configurations/upload
|
|
||||||
distributions: sdist bdist_wheel
|
|
||||||
password:
|
|
||||||
secure: LuserSjUTGSsls9zrvck/FbfL+gFpNU/ywOQ/67ufEbbpGCeDBEgxDzgb0acfHNk8wlAkaPvaAejQBFtcUulhdNT/g0NsmaEAjd6HhCGM+FRJAnYFaj33Js6C+N2tX5wznL7uCBxqgtaaH0hf6ucqC8OXqwoCVGgdxAEnUlC/fY=
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
repo: jazzband/django-configurations
|
|
||||||
condition: "$TOXENV = py36-dj111"
|
|
||||||
18
AUTHORS
18
AUTHORS
|
|
@ -1,6 +1,22 @@
|
||||||
|
Arseny Sokolov
|
||||||
|
Asif Saif Uddin
|
||||||
|
Baptiste Mispelon
|
||||||
|
Brian Helba
|
||||||
Bruno Clermont
|
Bruno Clermont
|
||||||
|
Christoph Krybus
|
||||||
|
Finn-Thorben Sell
|
||||||
Gilles Fabio
|
Gilles Fabio
|
||||||
Jannis Leidel
|
Jannis Leidel
|
||||||
|
John Franey
|
||||||
Marc Abramowitz
|
Marc Abramowitz
|
||||||
|
Michael Käufl
|
||||||
Michael van Tellingen
|
Michael van Tellingen
|
||||||
Mike Fogel
|
Mike Fogel
|
||||||
|
Miro Hrončok
|
||||||
|
Nicholas Dujay
|
||||||
|
Paolo Melchiorre
|
||||||
|
Peter Bittner
|
||||||
|
Richard de Wit
|
||||||
|
Thomas Grainger
|
||||||
|
Tim Gates
|
||||||
|
Victor Seva
|
||||||
|
|
|
||||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of the Jazzband projects, and in the interest of
|
||||||
|
fostering an open and welcoming community, we pledge to respect all people who
|
||||||
|
contribute through reporting issues, posting feature requests, updating documentation,
|
||||||
|
submitting pull requests or patches, and other activities.
|
||||||
|
|
||||||
|
We are committed to making participation in the Jazzband a harassment-free experience
|
||||||
|
for everyone, regardless of the level of experience, gender, gender identity and
|
||||||
|
expression, sexual orientation, disability, personal appearance, body size, race,
|
||||||
|
ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery
|
||||||
|
- Personal attacks
|
||||||
|
- Trolling or insulting/derogatory comments
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing other's private information, such as physical or electronic addresses,
|
||||||
|
without explicit permission
|
||||||
|
- Other unethical or unprofessional conduct
|
||||||
|
|
||||||
|
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are not
|
||||||
|
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
||||||
|
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
||||||
|
consistently applying these principles to every aspect of managing the jazzband
|
||||||
|
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
||||||
|
removed from the Jazzband roadies.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces when an
|
||||||
|
individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||||
|
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
||||||
|
investigated and will result in a response that is deemed necessary and appropriate to
|
||||||
|
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
||||||
|
reporter of an incident.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
||||||
|
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
||||||
|
|
||||||
|
[homepage]: https://contributor-covenant.org
|
||||||
|
[version]: https://contributor-covenant.org/version/1/3/0/
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2012-2014, Jannis Leidel and other contributors.
|
Copyright (c) 2012-2023, Jannis Leidel and other contributors.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
|
|
||||||
14
MANIFEST.in
14
MANIFEST.in
|
|
@ -1,7 +1,11 @@
|
||||||
include README.rst
|
include .pre-commit-config.yaml
|
||||||
|
include .readthedocs.yaml
|
||||||
include AUTHORS
|
include AUTHORS
|
||||||
include .travis.yml
|
include CODE_OF_CONDUCT.md
|
||||||
include tasks.py
|
include CONTRIBUTING.md
|
||||||
recursive-include tests *
|
|
||||||
recursive-include docs *
|
|
||||||
include LICENSE
|
include LICENSE
|
||||||
|
include README.rst
|
||||||
|
include tox.ini
|
||||||
|
recursive-include docs *
|
||||||
|
recursive-include test_project *
|
||||||
|
recursive-include tests *
|
||||||
|
|
|
||||||
82
README.rst
82
README.rst
|
|
@ -1,27 +1,44 @@
|
||||||
django-configurations
|
django-configurations |latest-version|
|
||||||
=====================
|
======================================
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/jazzband/django-configurations.svg?branch=master
|
|jazzband| |build-status| |codecov| |docs| |python-support| |django-support|
|
||||||
:alt: Build Status
|
|
||||||
:target: https://travis-ci.org/jazzband/django-configurations
|
|
||||||
|
|
||||||
.. image:: https://jazzband.co/static/img/badge.svg
|
|
||||||
:alt: Jazzband
|
|
||||||
:target: https://jazzband.co/
|
|
||||||
|
|
||||||
.. image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
|
|
||||||
:alt: Codecov
|
|
||||||
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
|
|
||||||
|
|
||||||
django-configurations eases Django project configuration by relying
|
django-configurations eases Django project configuration by relying
|
||||||
on the composability of Python classes. It extends the notion of
|
on the composability of Python classes. It extends the notion of
|
||||||
Django's module based settings loading with well established
|
Django's module based settings loading with well established
|
||||||
object oriented programming patterns.
|
object oriented programming patterns.
|
||||||
|
|
||||||
Check out the `documentation`__ for more complete examples.
|
Check out the `documentation`_ for more complete examples.
|
||||||
|
|
||||||
.. __: https://django-configurations.readthedocs.io/en/latest/
|
.. |latest-version| image:: https://img.shields.io/pypi/v/django-configurations.svg
|
||||||
|
:target: https://pypi.python.org/pypi/django-configurations
|
||||||
|
:alt: Latest version on PyPI
|
||||||
|
|
||||||
|
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||||
|
:target: https://jazzband.co/
|
||||||
|
:alt: Jazzband
|
||||||
|
|
||||||
|
.. |build-status| image:: https://github.com/jazzband/django-configurations/workflows/Test/badge.svg
|
||||||
|
:target: https://github.com/jazzband/django-configurations/actions
|
||||||
|
:alt: Build Status
|
||||||
|
|
||||||
|
.. |codecov| image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
|
||||||
|
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
|
||||||
|
:alt: Test coverage status
|
||||||
|
|
||||||
|
.. |docs| image:: https://img.shields.io/readthedocs/django-configurations/latest.svg
|
||||||
|
:target: https://readthedocs.org/projects/django-configurations/
|
||||||
|
:alt: Documentation status
|
||||||
|
|
||||||
|
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-configurations.svg
|
||||||
|
:target: https://pypi.python.org/pypi/django-configurations
|
||||||
|
:alt: Supported Python versions
|
||||||
|
|
||||||
|
.. |django-support| image:: https://img.shields.io/pypi/djversions/django-configurations
|
||||||
|
:target: https://pypi.org/project/django-configurations
|
||||||
|
:alt: Supported Django versions
|
||||||
|
|
||||||
|
.. _documentation: https://django-configurations.readthedocs.io/en/latest/
|
||||||
|
|
||||||
Quickstart
|
Quickstart
|
||||||
----------
|
----------
|
||||||
|
|
@ -30,7 +47,13 @@ Install django-configurations:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
pip install django-configurations
|
$ python -m pip install django-configurations
|
||||||
|
|
||||||
|
or, alternatively, if you want to use URL-based values:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python -m pip install django-configurations[cache,database,email,search]
|
||||||
|
|
||||||
Then subclass the included ``configurations.Configuration`` class in your
|
Then subclass the included ``configurations.Configuration`` class in your
|
||||||
project's **settings.py** or any other module you're using to store the
|
project's **settings.py** or any other module you're using to store the
|
||||||
|
|
@ -50,23 +73,25 @@ you just created, e.g. in bash:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
export DJANGO_CONFIGURATION=Dev
|
$ export DJANGO_CONFIGURATION=Dev
|
||||||
|
|
||||||
and the ``DJANGO_SETTINGS_MODULE`` environment variable to the module
|
and the ``DJANGO_SETTINGS_MODULE`` environment variable to the module
|
||||||
import path as usual, e.g. in bash:
|
import path as usual, e.g. in bash:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||||
|
|
||||||
*Alternatively* supply the ``--configuration`` option when using Django
|
*Alternatively* supply the ``--configuration`` option when using Django
|
||||||
management commands along the lines of Django's default ``--settings``
|
management commands along the lines of Django's default ``--settings``
|
||||||
command line option, e.g.::
|
command line option, e.g.
|
||||||
|
|
||||||
python manage.py runserver --settings=mysite.settings --configuration=Dev
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python -m manage runserver --settings=mysite.settings --configuration=Dev
|
||||||
|
|
||||||
To enable Django to use your configuration you now have to modify your
|
To enable Django to use your configuration you now have to modify your
|
||||||
**manage.py** or **wsgi.py** script to use django-configurations's versions
|
**manage.py**, **wsgi.py** or **asgi.py** script to use django-configurations's versions
|
||||||
of the appropriate starter functions, e.g. a typical **manage.py** using
|
of the appropriate starter functions, e.g. a typical **manage.py** using
|
||||||
django-configurations would look like this:
|
django-configurations would look like this:
|
||||||
|
|
||||||
|
|
@ -105,5 +130,18 @@ The same applies to your **wsgi.py** file, e.g.:
|
||||||
Here we don't use the default ``django.core.wsgi.get_wsgi_application``
|
Here we don't use the default ``django.core.wsgi.get_wsgi_application``
|
||||||
function but instead ``configurations.wsgi.get_wsgi_application``.
|
function but instead ``configurations.wsgi.get_wsgi_application``.
|
||||||
|
|
||||||
|
Or if you are not serving your app via WSGI but ASGI instead, you need to modify your **asgi.py** file too.:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
|
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
|
||||||
|
|
||||||
|
from configurations.asgi import get_asgi_application
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
|
|
||||||
That's it! You can now use your project with ``manage.py`` and your favorite
|
That's it! You can now use your project with ``manage.py`` and your favorite
|
||||||
WSGI enabled server.
|
WSGI/ASGI enabled server.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# flake8: noqa
|
from .base import Configuration # noqa
|
||||||
from .base import Configuration
|
from .decorators import pristinemethod # noqa
|
||||||
from .decorators import pristinemethod
|
from .version import __version__ # noqa
|
||||||
|
|
||||||
|
|
||||||
__version__ = '2.1'
|
|
||||||
__all__ = ['Configuration', 'pristinemethod']
|
__all__ = ['Configuration', 'pristinemethod']
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,8 +11,10 @@ def _setup():
|
||||||
|
|
||||||
importer.install()
|
importer.install()
|
||||||
|
|
||||||
import django
|
from django.apps import apps
|
||||||
django.setup()
|
if not apps.ready:
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
def load_ipython_extension(ipython):
|
def load_ipython_extension(ipython):
|
||||||
|
|
|
||||||
10
configurations/__main__.py
Normal file
10
configurations/__main__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
"""
|
||||||
|
invokes django-cadmin when the configurations module is run as a script.
|
||||||
|
|
||||||
|
Example: python -m configurations check
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .management import execute_from_command_line
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
execute_from_command_line()
|
||||||
8
configurations/asgi.py
Normal file
8
configurations/asgi.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import importer
|
||||||
|
|
||||||
|
importer.install()
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application # noqa: E402
|
||||||
|
|
||||||
|
# this is just for the crazy ones
|
||||||
|
application = get_asgi_application()
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.utils import six
|
|
||||||
from django.conf import global_settings
|
from django.conf import global_settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
|
@ -32,15 +31,50 @@ class ConfigurationBase(type):
|
||||||
if parents:
|
if parents:
|
||||||
for base in bases[::-1]:
|
for base in bases[::-1]:
|
||||||
settings_vars.update(uppercase_attributes(base))
|
settings_vars.update(uppercase_attributes(base))
|
||||||
attrs = dict(settings_vars, **attrs)
|
|
||||||
return super(ConfigurationBase, cls).__new__(cls, name, bases, attrs)
|
deprecated_settings = {
|
||||||
|
# DEFAULT_HASHING_ALGORITHM is always deprecated, as it's a
|
||||||
|
# transitional setting
|
||||||
|
# https://docs.djangoproject.com/en/3.1/releases/3.1/#default-hashing-algorithm-settings
|
||||||
|
"DEFAULT_HASHING_ALGORITHM",
|
||||||
|
# DEFAULT_CONTENT_TYPE and FILE_CHARSET are deprecated in
|
||||||
|
# Django 2.2 and are removed in Django 3.0
|
||||||
|
"DEFAULT_CONTENT_TYPE",
|
||||||
|
"FILE_CHARSET",
|
||||||
|
# When DEFAULT_AUTO_FIELD is not explicitly set, Django's emits a
|
||||||
|
# system check warning models.W042. This warning should not be
|
||||||
|
# suppressed, as downstream users are expected to make a decision.
|
||||||
|
# https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
|
||||||
|
"DEFAULT_AUTO_FIELD",
|
||||||
|
# FORMS_URLFIELD_ASSUME_HTTPS is a transitional setting introduced
|
||||||
|
# in Django 5.0.
|
||||||
|
# https://docs.djangoproject.com/en/5.0/releases/5.0/#id2
|
||||||
|
"FORMS_URLFIELD_ASSUME_HTTPS"
|
||||||
|
}
|
||||||
|
# PASSWORD_RESET_TIMEOUT_DAYS is deprecated in favor of
|
||||||
|
# PASSWORD_RESET_TIMEOUT in Django 3.1
|
||||||
|
# https://github.com/django/django/commit/226ebb17290b604ef29e82fb5c1fbac3594ac163#diff-ec2bed07bb264cb95a80f08d71a47c06R163-R170
|
||||||
|
if "PASSWORD_RESET_TIMEOUT" in settings_vars:
|
||||||
|
deprecated_settings.add("PASSWORD_RESET_TIMEOUT_DAYS")
|
||||||
|
# DEFAULT_FILE_STORAGE and STATICFILES_STORAGE are deprecated
|
||||||
|
# in favor of STORAGES.
|
||||||
|
# https://docs.djangoproject.com/en/dev/releases/4.2/#custom-file-storages
|
||||||
|
if "STORAGES" in settings_vars:
|
||||||
|
deprecated_settings.add("DEFAULT_FILE_STORAGE")
|
||||||
|
deprecated_settings.add("STATICFILES_STORAGE")
|
||||||
|
for deprecated_setting in deprecated_settings:
|
||||||
|
if deprecated_setting in settings_vars:
|
||||||
|
del settings_vars[deprecated_setting]
|
||||||
|
attrs = {**settings_vars, **attrs}
|
||||||
|
|
||||||
|
return super().__new__(cls, name, bases, attrs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
return "<Configuration '{}.{}'>".format(self.__module__,
|
||||||
self.__name__)
|
self.__name__)
|
||||||
|
|
||||||
|
|
||||||
class Configuration(six.with_metaclass(ConfigurationBase)):
|
class Configuration(metaclass=ConfigurationBase):
|
||||||
"""
|
"""
|
||||||
The base configuration class to inherit from.
|
The base configuration class to inherit from.
|
||||||
|
|
||||||
|
|
@ -73,10 +107,10 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||||
environment variables from a .env file located in the project root
|
environment variables from a .env file located in the project root
|
||||||
or provided directory.
|
or provided directory.
|
||||||
|
|
||||||
http://www.wellfireinteractive.com/blog/easier-12-factor-django/
|
https://wellfire.co/learn/easier-12-factor-django/
|
||||||
https://gist.github.com/bennylope/2999704
|
https://gist.github.com/bennylope/2999704
|
||||||
"""
|
"""
|
||||||
# check if the class has DOTENV set wether with a path or None
|
# check if the class has DOTENV set whether with a path or None
|
||||||
dotenv = getattr(cls, 'DOTENV', None)
|
dotenv = getattr(cls, 'DOTENV', None)
|
||||||
|
|
||||||
# if DOTENV is falsy we want to disable it
|
# if DOTENV is falsy we want to disable it
|
||||||
|
|
@ -85,12 +119,12 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||||
|
|
||||||
# now check if we can access the file since we know we really want to
|
# now check if we can access the file since we know we really want to
|
||||||
try:
|
try:
|
||||||
with open(dotenv, 'r') as f:
|
with open(dotenv) as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
raise ImproperlyConfigured("Couldn't read .env file "
|
raise ImproperlyConfigured("Couldn't read .env file "
|
||||||
"with the path {}. Error: "
|
"with the path {}. Error: "
|
||||||
"{}".format(dotenv, e))
|
"{}".format(dotenv, e)) from e
|
||||||
else:
|
else:
|
||||||
for line in content.splitlines():
|
for line in content.splitlines():
|
||||||
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import imp
|
from importlib.machinery import PathFinder
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -46,12 +46,12 @@ def install(check_options=False):
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
base.BaseCommand.create_parser = create_parser
|
base.BaseCommand.create_parser = create_parser
|
||||||
importer = ConfigurationImporter(check_options=check_options)
|
importer = ConfigurationFinder(check_options=check_options)
|
||||||
sys.meta_path.insert(0, importer)
|
sys.meta_path.insert(0, importer)
|
||||||
installed = True
|
installed = True
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationImporter(object):
|
class ConfigurationFinder(PathFinder):
|
||||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||||
error_msg = ("Configuration cannot be imported, "
|
error_msg = ("Configuration cannot be imported, "
|
||||||
|
|
@ -70,7 +70,7 @@ class ConfigurationImporter(object):
|
||||||
self.announce()
|
self.announce()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<ConfigurationImporter for '{0}.{1}'>".format(self.module,
|
return "<ConfigurationFinder for '{}.{}'>".format(self.module,
|
||||||
self.name)
|
self.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -82,16 +82,10 @@ class ConfigurationImporter(object):
|
||||||
return os.environ.get(self.namevar)
|
return os.environ.get(self.namevar)
|
||||||
|
|
||||||
def check_options(self):
|
def check_options(self):
|
||||||
try:
|
parser = base.CommandParser(
|
||||||
parser = base.CommandParser(
|
usage="%(prog)s subcommand [options] [args]",
|
||||||
usage="%(prog)s subcommand [options] [args]",
|
add_help=False,
|
||||||
add_help=False)
|
)
|
||||||
except TypeError:
|
|
||||||
# Django before 2.1 used a `cmd` argument.
|
|
||||||
parser = base.CommandParser(
|
|
||||||
None,
|
|
||||||
usage="%(prog)s subcommand [options] [args]",
|
|
||||||
add_help=False)
|
|
||||||
parser.add_argument('--settings')
|
parser.add_argument('--settings')
|
||||||
parser.add_argument('--pythonpath')
|
parser.add_argument('--pythonpath')
|
||||||
parser.add_argument(CONFIGURATION_ARGUMENT,
|
parser.add_argument(CONFIGURATION_ARGUMENT,
|
||||||
|
|
@ -124,61 +118,63 @@ class ConfigurationImporter(object):
|
||||||
def stylize(text):
|
def stylize(text):
|
||||||
return colorize(text, fg='green')
|
return colorize(text, fg='green')
|
||||||
|
|
||||||
if (self.argv[1] == 'runserver' and
|
if (self.argv[1] == 'runserver'
|
||||||
os.environ.get('RUN_MAIN') == 'true'):
|
and os.environ.get('RUN_MAIN') == 'true'):
|
||||||
|
|
||||||
message = ("django-configurations version {0}, using "
|
message = ("django-configurations version {}, using "
|
||||||
"configuration '{1}'".format(__version__,
|
"configuration {}".format(__version__ or "",
|
||||||
self.name))
|
self.name))
|
||||||
self.logger.debug(stylize(message))
|
self.logger.debug(stylize(message))
|
||||||
|
|
||||||
def find_module(self, fullname, path=None):
|
def find_spec(self, fullname, path=None, target=None):
|
||||||
if fullname is not None and fullname == self.module:
|
if fullname is not None and fullname == self.module:
|
||||||
module = fullname.rsplit('.', 1)[-1]
|
spec = super().find_spec(fullname, path, target)
|
||||||
return ConfigurationLoader(self.name,
|
if spec is not None:
|
||||||
imp.find_module(module, path))
|
wrap_loader(spec.loader, self.name)
|
||||||
return None
|
return spec
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationLoader(object):
|
|
||||||
|
|
||||||
def __init__(self, name, location):
|
|
||||||
self.name = name
|
|
||||||
self.location = location
|
|
||||||
|
|
||||||
def load_module(self, fullname):
|
|
||||||
if fullname in sys.modules:
|
|
||||||
mod = sys.modules[fullname] # pragma: no cover
|
|
||||||
else:
|
else:
|
||||||
mod = imp.load_module(fullname, *self.location)
|
return None
|
||||||
cls_path = '{0}.{1}'.format(mod.__name__, self.name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
cls = getattr(mod, self.name)
|
|
||||||
except AttributeError as err: # pragma: no cover
|
|
||||||
reraise(err, "Couldn't find configuration '{0}' "
|
|
||||||
"in module '{1}'".format(self.name,
|
|
||||||
mod.__package__))
|
|
||||||
try:
|
|
||||||
cls.pre_setup()
|
|
||||||
cls.setup()
|
|
||||||
obj = cls()
|
|
||||||
attributes = uppercase_attributes(obj).items()
|
|
||||||
for name, value in attributes:
|
|
||||||
if callable(value) and not getattr(value, 'pristine', False):
|
|
||||||
value = value()
|
|
||||||
# in case a method returns a Value instance we have
|
|
||||||
# to do the same as the Configuration.setup method
|
|
||||||
if isinstance(value, Value):
|
|
||||||
setup_value(mod, name, value)
|
|
||||||
continue
|
|
||||||
setattr(mod, name, value)
|
|
||||||
|
|
||||||
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname,
|
def wrap_loader(loader, class_name):
|
||||||
self.name))
|
class ConfigurationLoader(loader.__class__):
|
||||||
cls.post_setup()
|
def exec_module(self, module):
|
||||||
|
super().exec_module(module)
|
||||||
|
|
||||||
except Exception as err:
|
mod = module
|
||||||
reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
|
|
||||||
|
|
||||||
return mod
|
cls_path = f'{mod.__name__}.{class_name}'
|
||||||
|
|
||||||
|
try:
|
||||||
|
cls = getattr(mod, class_name)
|
||||||
|
except AttributeError as err: # pragma: no cover
|
||||||
|
reraise(
|
||||||
|
err,
|
||||||
|
(
|
||||||
|
f"Couldn't find configuration '{class_name}' in "
|
||||||
|
f"module '{mod.__package__}'"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
cls.pre_setup()
|
||||||
|
cls.setup()
|
||||||
|
obj = cls()
|
||||||
|
attributes = uppercase_attributes(obj).items()
|
||||||
|
for name, value in attributes:
|
||||||
|
if callable(value) and not getattr(value, 'pristine', False):
|
||||||
|
value = value()
|
||||||
|
# in case a method returns a Value instance we have
|
||||||
|
# to do the same as the Configuration.setup method
|
||||||
|
if isinstance(value, Value):
|
||||||
|
setup_value(mod, name, value)
|
||||||
|
continue
|
||||||
|
setattr(mod, name, value)
|
||||||
|
|
||||||
|
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(module.__name__,
|
||||||
|
class_name))
|
||||||
|
cls.post_setup()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
reraise(err, f"Couldn't setup configuration '{cls_path}'")
|
||||||
|
|
||||||
|
loader.__class__ = ConfigurationLoader
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils import six
|
|
||||||
try:
|
|
||||||
from importlib import import_module
|
|
||||||
except ImportError:
|
|
||||||
from django.utils.importlib import import_module
|
|
||||||
|
|
||||||
|
|
||||||
def isuppercase(name):
|
def isuppercase(name):
|
||||||
|
|
@ -14,8 +13,7 @@ def isuppercase(name):
|
||||||
|
|
||||||
|
|
||||||
def uppercase_attributes(obj):
|
def uppercase_attributes(obj):
|
||||||
return dict((name, getattr(obj, name))
|
return {name: getattr(obj, name) for name in dir(obj) if isuppercase(name)}
|
||||||
for name in filter(isuppercase, dir(obj)))
|
|
||||||
|
|
||||||
|
|
||||||
def import_by_path(dotted_path, error_prefix=''):
|
def import_by_path(dotted_path, error_prefix=''):
|
||||||
|
|
@ -26,25 +24,26 @@ def import_by_path(dotted_path, error_prefix=''):
|
||||||
|
|
||||||
Backported from Django 1.6.
|
Backported from Django 1.6.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn("Function utils.import_by_path is deprecated in favor of "
|
||||||
|
"django.utils.module_loading.import_string.", DeprecationWarning)
|
||||||
try:
|
try:
|
||||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ImproperlyConfigured("{0}{1} doesn't look like "
|
raise ImproperlyConfigured("{}{} doesn't look like "
|
||||||
"a module path".format(error_prefix,
|
"a module path".format(error_prefix,
|
||||||
dotted_path))
|
dotted_path))
|
||||||
try:
|
try:
|
||||||
module = import_module(module_path)
|
module = import_module(module_path)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
msg = '{}Error importing module {}: "{}"'.format(error_prefix,
|
||||||
module_path,
|
module_path,
|
||||||
err)
|
err)
|
||||||
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
|
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
||||||
sys.exc_info()[2])
|
|
||||||
try:
|
try:
|
||||||
attr = getattr(module, class_name)
|
attr = getattr(module, class_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
|
raise ImproperlyConfigured('{}Module "{}" does not define a '
|
||||||
'"{2}" attribute/class'.format(error_prefix,
|
'"{}" attribute/class'.format(error_prefix,
|
||||||
module_path,
|
module_path,
|
||||||
class_name))
|
class_name))
|
||||||
return attr
|
return attr
|
||||||
|
|
@ -62,78 +61,41 @@ def reraise(exc, prefix=None, suffix=None):
|
||||||
suffix = ''
|
suffix = ''
|
||||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||||
suffix = '(' + suffix + ')'
|
suffix = '(' + suffix + ')'
|
||||||
exc.args = ('{0} {1} {2}'.format(prefix, exc.args[0], suffix),) + args[1:]
|
exc.args = (f'{prefix} {args[0]} {suffix}',) + args[1:]
|
||||||
raise
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
# Copied over from Sphinx
|
# Copied over from Sphinx
|
||||||
if sys.version_info >= (3, 0):
|
def getargspec(func):
|
||||||
from functools import partial
|
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||||
|
if inspect.ismethod(func):
|
||||||
def getargspec(func):
|
func = func.__func__
|
||||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
if type(func) is partial:
|
||||||
if inspect.ismethod(func):
|
orig_func = func.func
|
||||||
func = func.__func__
|
argspec = getargspec(orig_func)
|
||||||
if type(func) is partial:
|
args = list(argspec[0])
|
||||||
orig_func = func.func
|
defaults = list(argspec[3] or ())
|
||||||
argspec = getargspec(orig_func)
|
kwoargs = list(argspec[4])
|
||||||
args = list(argspec[0])
|
kwodefs = dict(argspec[5] or {})
|
||||||
defaults = list(argspec[3] or ())
|
if func.args:
|
||||||
kwoargs = list(argspec[4])
|
args = args[len(func.args):]
|
||||||
kwodefs = dict(argspec[5] or {})
|
for arg in func.keywords or ():
|
||||||
if func.args:
|
try:
|
||||||
args = args[len(func.args):]
|
|
||||||
for arg in func.keywords or ():
|
|
||||||
try:
|
|
||||||
i = args.index(arg) - len(args)
|
|
||||||
del args[i]
|
|
||||||
try:
|
|
||||||
del defaults[i]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
except ValueError: # must be a kwonly arg
|
|
||||||
i = kwoargs.index(arg)
|
|
||||||
del kwoargs[i]
|
|
||||||
del kwodefs[arg]
|
|
||||||
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
|
||||||
tuple(defaults), kwoargs,
|
|
||||||
kwodefs, argspec[6])
|
|
||||||
while hasattr(func, '__wrapped__'):
|
|
||||||
func = func.__wrapped__
|
|
||||||
if not inspect.isfunction(func):
|
|
||||||
raise TypeError('%r is not a Python function' % func)
|
|
||||||
return inspect.getfullargspec(func)
|
|
||||||
|
|
||||||
else: # 2.6, 2.7
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
def getargspec(func):
|
|
||||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
|
||||||
if inspect.ismethod(func):
|
|
||||||
func = func.im_func
|
|
||||||
parts = 0, ()
|
|
||||||
if type(func) is partial:
|
|
||||||
keywords = func.keywords
|
|
||||||
if keywords is None:
|
|
||||||
keywords = {}
|
|
||||||
parts = len(func.args), keywords.keys()
|
|
||||||
func = func.func
|
|
||||||
if not inspect.isfunction(func):
|
|
||||||
raise TypeError('%r is not a Python function' % func)
|
|
||||||
args, varargs, varkw = inspect.getargs(func.func_code)
|
|
||||||
func_defaults = func.func_defaults
|
|
||||||
if func_defaults is None:
|
|
||||||
func_defaults = []
|
|
||||||
else:
|
|
||||||
func_defaults = list(func_defaults)
|
|
||||||
if parts[0]:
|
|
||||||
args = args[parts[0]:]
|
|
||||||
if parts[1]:
|
|
||||||
for arg in parts[1]:
|
|
||||||
i = args.index(arg) - len(args)
|
i = args.index(arg) - len(args)
|
||||||
del args[i]
|
del args[i]
|
||||||
try:
|
try:
|
||||||
del func_defaults[i]
|
del defaults[i]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
return inspect.ArgSpec(args, varargs, varkw, func_defaults)
|
except ValueError: # must be a kwonly arg
|
||||||
|
i = kwoargs.index(arg)
|
||||||
|
del kwoargs[i]
|
||||||
|
del kwodefs[arg]
|
||||||
|
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
||||||
|
tuple(defaults), kwoargs,
|
||||||
|
kwodefs, argspec[6])
|
||||||
|
while hasattr(func, '__wrapped__'):
|
||||||
|
func = func.__wrapped__
|
||||||
|
if not inspect.isfunction(func):
|
||||||
|
raise TypeError('%r is not a Python function' % func)
|
||||||
|
return inspect.getfullargspec(func)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import sys
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||||
from django.utils import six
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
from .utils import import_by_path, getargspec
|
from .utils import getargspec
|
||||||
|
|
||||||
|
|
||||||
def setup_value(target, name, value):
|
def setup_value(target, name, value):
|
||||||
|
|
@ -20,7 +20,7 @@ def setup_value(target, name, value):
|
||||||
setattr(target, multiple_name, multiple_value)
|
setattr(target, multiple_name, multiple_value)
|
||||||
|
|
||||||
|
|
||||||
class Value(object):
|
class Value:
|
||||||
"""
|
"""
|
||||||
A single settings value that is able to interpret env variables
|
A single settings value that is able to interpret env variables
|
||||||
and implements a simple validation scheme.
|
and implements a simple validation scheme.
|
||||||
|
|
@ -52,8 +52,8 @@ class Value(object):
|
||||||
instance.late_binding = kwargs.get('late_binding')
|
instance.late_binding = kwargs.get('late_binding')
|
||||||
if not instance.late_binding:
|
if not instance.late_binding:
|
||||||
instance.__init__(*args, **kwargs)
|
instance.__init__(*args, **kwargs)
|
||||||
if ((instance.environ and instance.environ_name) or
|
if ((instance.environ and instance.environ_name)
|
||||||
(not instance.environ and instance.default)):
|
or (not instance.environ and instance.default)):
|
||||||
instance = instance.setup(instance.environ_name)
|
instance = instance.setup(instance.environ_name)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ class Value(object):
|
||||||
else:
|
else:
|
||||||
environ_name = name.upper()
|
environ_name = name.upper()
|
||||||
if self.environ_prefix:
|
if self.environ_prefix:
|
||||||
environ_name = '{0}_{1}'.format(self.environ_prefix, environ_name)
|
environ_name = f'{self.environ_prefix}_{environ_name}'
|
||||||
return environ_name
|
return environ_name
|
||||||
|
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
|
|
@ -102,8 +102,8 @@ class Value(object):
|
||||||
if full_environ_name in os.environ:
|
if full_environ_name in os.environ:
|
||||||
value = self.to_python(os.environ[full_environ_name])
|
value = self.to_python(os.environ[full_environ_name])
|
||||||
elif self.environ_required:
|
elif self.environ_required:
|
||||||
raise ValueError('Value {0!r} is required to be set as the '
|
raise ValueError('Value {!r} is required to be set as the '
|
||||||
'environment variable {1!r}'
|
'environment variable {!r}'
|
||||||
.format(name, full_environ_name))
|
.format(name, full_environ_name))
|
||||||
self.value = value
|
self.value = value
|
||||||
return value
|
return value
|
||||||
|
|
@ -112,12 +112,12 @@ class Value(object):
|
||||||
"""
|
"""
|
||||||
Convert the given value of a environment variable into an
|
Convert the given value of a environment variable into an
|
||||||
appropriate Python representation of the value.
|
appropriate Python representation of the value.
|
||||||
This should be overriden when subclassing.
|
This should be overridden when subclassing.
|
||||||
"""
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class MultipleMixin(object):
|
class MultipleMixin:
|
||||||
multiple = True
|
multiple = True
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -126,9 +126,9 @@ class BooleanValue(Value):
|
||||||
false_values = ('no', 'n', 'false', '0', '')
|
false_values = ('no', 'n', 'false', '0', '')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(BooleanValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default not in (True, False):
|
if self.default not in (True, False):
|
||||||
raise ValueError('Default value {0!r} is not a '
|
raise ValueError('Default value {!r} is not a '
|
||||||
'boolean value'.format(self.default))
|
'boolean value'.format(self.default))
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
|
@ -139,28 +139,30 @@ class BooleanValue(Value):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot interpret '
|
raise ValueError('Cannot interpret '
|
||||||
'boolean value {0!r}'.format(value))
|
'boolean value {!r}'.format(value))
|
||||||
|
|
||||||
|
|
||||||
class CastingMixin(object):
|
class CastingMixin:
|
||||||
exception = (TypeError, ValueError)
|
exception = (TypeError, ValueError)
|
||||||
message = 'Cannot interpret value {0!r}'
|
message = 'Cannot interpret value {0!r}'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(CastingMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if isinstance(self.caster, six.string_types):
|
if isinstance(self.caster, str):
|
||||||
self._caster = import_by_path(self.caster)
|
try:
|
||||||
|
self._caster = import_string(self.caster)
|
||||||
|
except ImportError as err:
|
||||||
|
msg = f"Could not import {self.caster!r}"
|
||||||
|
raise ImproperlyConfigured(msg) from err
|
||||||
elif callable(self.caster):
|
elif callable(self.caster):
|
||||||
self._caster = self.caster
|
self._caster = self.caster
|
||||||
else:
|
else:
|
||||||
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
error = 'Cannot use caster of {} ({!r})'.format(self,
|
||||||
self.caster)
|
self.caster)
|
||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
try:
|
try:
|
||||||
arg_names = getargspec(self._caster)[0]
|
arg_names = getargspec(self._caster)[0]
|
||||||
self._params = dict((name, kwargs[name])
|
self._params = {name: kwargs[name] for name in arg_names if name in kwargs}
|
||||||
for name in arg_names
|
|
||||||
if name in kwargs)
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self._params = {}
|
self._params = {}
|
||||||
|
|
||||||
|
|
@ -181,7 +183,7 @@ class IntegerValue(CastingMixin, Value):
|
||||||
class PositiveIntegerValue(IntegerValue):
|
class PositiveIntegerValue(IntegerValue):
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
int_value = super(PositiveIntegerValue, self).to_python(value)
|
int_value = super().to_python(value)
|
||||||
if int_value < 0:
|
if int_value < 0:
|
||||||
raise ValueError(self.message.format(value))
|
raise ValueError(self.message.format(value))
|
||||||
return int_value
|
return int_value
|
||||||
|
|
@ -213,7 +215,7 @@ class SequenceValue(Value):
|
||||||
converter = kwargs.pop('converter', None)
|
converter = kwargs.pop('converter', None)
|
||||||
if converter is not None:
|
if converter is not None:
|
||||||
self.converter = converter
|
self.converter = converter
|
||||||
super(SequenceValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# make sure the default is the correct sequence type
|
# make sure the default is the correct sequence type
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = self.sequence_type()
|
self.default = self.sequence_type()
|
||||||
|
|
@ -257,7 +259,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.seq_separator = kwargs.pop('seq_separator', ';')
|
self.seq_separator = kwargs.pop('seq_separator', ';')
|
||||||
super(SingleNestedSequenceValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _convert(self, items):
|
def _convert(self, items):
|
||||||
# This could receive either a bare or nested sequence
|
# This could receive either a bare or nested sequence
|
||||||
|
|
@ -266,8 +268,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
||||||
super(SingleNestedSequenceValue, self)._convert(i) for i in items
|
super(SingleNestedSequenceValue, self)._convert(i) for i in items
|
||||||
]
|
]
|
||||||
return self.sequence_type(converted_sequences)
|
return self.sequence_type(converted_sequences)
|
||||||
return self.sequence_type(
|
return self.sequence_type(super()._convert(items))
|
||||||
super(SingleNestedSequenceValue, self)._convert(items))
|
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
split_value = [
|
split_value = [
|
||||||
|
|
@ -293,9 +294,9 @@ class BackendsValue(ListValue):
|
||||||
|
|
||||||
def converter(self, value):
|
def converter(self, value):
|
||||||
try:
|
try:
|
||||||
import_by_path(value)
|
import_string(value)
|
||||||
except ImproperlyConfigured as err:
|
except ImportError as err:
|
||||||
six.reraise(ValueError, ValueError(err), sys.exc_info()[2])
|
raise ValueError(err).with_traceback(sys.exc_info()[2])
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -303,28 +304,28 @@ class SetValue(ListValue):
|
||||||
message = 'Cannot interpret set item {0!r} in set {1!r}'
|
message = 'Cannot interpret set item {0!r} in set {1!r}'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SetValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = set()
|
self.default = set()
|
||||||
else:
|
else:
|
||||||
self.default = set(self.default)
|
self.default = set(self.default)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return set(super(SetValue, self).to_python(value))
|
return set(super().to_python(value))
|
||||||
|
|
||||||
|
|
||||||
class DictValue(Value):
|
class DictValue(Value):
|
||||||
message = 'Cannot interpret dict value {0!r}'
|
message = 'Cannot interpret dict value {0!r}'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DictValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = {}
|
self.default = {}
|
||||||
else:
|
else:
|
||||||
self.default = dict(self.default)
|
self.default = dict(self.default)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
value = super(DictValue, self).to_python(value)
|
value = super().to_python(value)
|
||||||
if not value:
|
if not value:
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
|
|
@ -336,17 +337,21 @@ class DictValue(Value):
|
||||||
return evaled_value
|
return evaled_value
|
||||||
|
|
||||||
|
|
||||||
class ValidationMixin(object):
|
class ValidationMixin:
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ValidationMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if isinstance(self.validator, six.string_types):
|
if isinstance(self.validator, str):
|
||||||
self._validator = import_by_path(self.validator)
|
try:
|
||||||
|
self._validator = import_string(self.validator)
|
||||||
|
except ImportError as err:
|
||||||
|
msg = f"Could not import {self.validator!r}"
|
||||||
|
raise ImproperlyConfigured(msg) from err
|
||||||
elif callable(self.validator):
|
elif callable(self.validator):
|
||||||
self._validator = self.validator
|
self._validator = self.validator
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot use validator of '
|
raise ValueError('Cannot use validator of '
|
||||||
'{0} ({1!r})'.format(self, self.validator))
|
'{} ({!r})'.format(self, self.validator))
|
||||||
if self.default:
|
if self.default:
|
||||||
self.to_python(self.default)
|
self.to_python(self.default)
|
||||||
|
|
||||||
|
|
@ -380,19 +385,19 @@ class RegexValue(ValidationMixin, Value):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
regex = kwargs.pop('regex', None)
|
regex = kwargs.pop('regex', None)
|
||||||
self.validator = validators.RegexValidator(regex=regex)
|
self.validator = validators.RegexValidator(regex=regex)
|
||||||
super(RegexValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PathValue(Value):
|
class PathValue(Value):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.check_exists = kwargs.pop('check_exists', True)
|
self.check_exists = kwargs.pop('check_exists', True)
|
||||||
super(PathValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
value = super(PathValue, self).setup(name)
|
value = super().setup(name)
|
||||||
value = os.path.expanduser(value)
|
value = os.path.expanduser(value)
|
||||||
if self.check_exists and not os.path.exists(value):
|
if self.check_exists and not os.path.exists(value):
|
||||||
raise ValueError('Path {0!r} does not exist.'.format(value))
|
raise ValueError(f'Path {value!r} does not exist.')
|
||||||
return os.path.abspath(value)
|
return os.path.abspath(value)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -401,15 +406,15 @@ class SecretValue(Value):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['environ'] = True
|
kwargs['environ'] = True
|
||||||
kwargs['environ_required'] = True
|
kwargs['environ_required'] = True
|
||||||
super(SecretValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default is not None:
|
if self.default is not None:
|
||||||
raise ValueError('Secret values are only allowed to '
|
raise ValueError('Secret values are only allowed to '
|
||||||
'be set as environment variables')
|
'be set as environment variables')
|
||||||
|
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
value = super(SecretValue, self).setup(name)
|
value = super().setup(name)
|
||||||
if not value:
|
if not value:
|
||||||
raise ValueError('Secret value {0!r} is not set'.format(name))
|
raise ValueError(f'Secret value {name!r} is not set')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -422,7 +427,7 @@ class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
||||||
kwargs.setdefault('environ', True)
|
kwargs.setdefault('environ', True)
|
||||||
kwargs.setdefault('environ_prefix', None)
|
kwargs.setdefault('environ_prefix', None)
|
||||||
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
||||||
super(EmailURLValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = {}
|
self.default = {}
|
||||||
else:
|
else:
|
||||||
|
|
@ -437,14 +442,14 @@ class DictBackendMixin(Value):
|
||||||
kwargs.setdefault('environ', True)
|
kwargs.setdefault('environ', True)
|
||||||
kwargs.setdefault('environ_prefix', None)
|
kwargs.setdefault('environ_prefix', None)
|
||||||
kwargs.setdefault('environ_name', self.environ_name)
|
kwargs.setdefault('environ_name', self.environ_name)
|
||||||
super(DictBackendMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = {}
|
self.default = {}
|
||||||
else:
|
else:
|
||||||
self.default = self.to_python(self.default)
|
self.default = self.to_python(self.default)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
value = super(DictBackendMixin, self).to_python(value)
|
value = super().to_python(value)
|
||||||
return {self.alias: value}
|
return {self.alias: value}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
7
configurations/version.py
Normal file
7
configurations/version.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
|
||||||
|
try:
|
||||||
|
__version__ = version("django-configurations")
|
||||||
|
except PackageNotFoundError:
|
||||||
|
# package is not installed
|
||||||
|
__version__ = None
|
||||||
|
|
@ -2,13 +2,7 @@ from . import importer
|
||||||
|
|
||||||
importer.install()
|
importer.install()
|
||||||
|
|
||||||
try:
|
from django.core.wsgi import get_wsgi_application # noqa: E402
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
|
||||||
|
|
||||||
def get_wsgi_application(): # noqa
|
|
||||||
return WSGIHandler()
|
|
||||||
|
|
||||||
# this is just for the crazy ones
|
# this is just for the crazy ones
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
|
||||||
153
docs/Makefile
153
docs/Makefile
|
|
@ -1,153 +0,0 @@
|
||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-configurations.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-configurations.qhc"
|
|
||||||
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/django-configurations"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-configurations"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
texinfo:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||||
"(use \`make info' here to do that automatically)."
|
|
||||||
|
|
||||||
info:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
|
||||||
make -C $(BUILDDIR)/texinfo info
|
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
|
||||||
|
|
||||||
gettext:
|
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
104
docs/changes.rst
104
docs/changes.rst
|
|
@ -3,6 +3,110 @@
|
||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
- Prevent warning about ``FORMS_URLFIELD_ASSUME_HTTPS`` on Django 5.0.
|
||||||
|
|
||||||
|
v2.5.1 (2023-11-30)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add compatibility with Python 3.12
|
||||||
|
|
||||||
|
v2.5 (2023-10-20)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Update Github actions and fix pipeline warnings
|
||||||
|
- Add compatibility with Django 5.0
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop compatibility for Django 4.0
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop compatibility for Python 3.7 and PyPy < 3.10
|
||||||
|
|
||||||
|
v2.4.2 (2023-09-27)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Replace imp (due for removal in Python 3.12) with importlib
|
||||||
|
- Test on PyPy 3.10.
|
||||||
|
|
||||||
|
v2.4.1 (2023-04-04)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Use furo as documentation theme
|
||||||
|
- Add compatibility with Django 4.2 - fix "STATICFILES_STORAGE/STORAGES are mutually exclusive" error.
|
||||||
|
- Test Django 4.1.3+ on Python 3.11
|
||||||
|
|
||||||
|
v2.4 (2022-08-24)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add compatibility with Django 4.1
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop compatibility for Django < 3.2
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop compatibility for Python 3.6
|
||||||
|
|
||||||
|
v2.3.2 (2022-01-25)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Add compatibility with Django 4.0
|
||||||
|
- Fix regression where settings receiving a default were ignored. #323 #327
|
||||||
|
|
||||||
|
v2.3.1 (2021-11-08)
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Test Django 3.2 on Python 3.10 as well.
|
||||||
|
|
||||||
|
- Test on PyPy 3.6, 3.7 and 3.8.
|
||||||
|
|
||||||
|
- Enforce Python version requirement during installation (>=3.6).
|
||||||
|
|
||||||
|
- Fix and refactor the documentation build process.
|
||||||
|
|
||||||
|
v2.3 (2021-10-27)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support for Python 2.7 and 3.5.
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support for Django < 2.2.
|
||||||
|
|
||||||
|
- Add support for Django 3.1 and 3.2.
|
||||||
|
|
||||||
|
- Add suppport for Python 3.9 and 3.10.
|
||||||
|
|
||||||
|
- Deprecate ``utils.import_by_path`` in favor of
|
||||||
|
``django.utils.module_loading.import_string``.
|
||||||
|
|
||||||
|
- Add ASGI support.
|
||||||
|
|
||||||
|
- Added "python -m configurations" entry point.
|
||||||
|
|
||||||
|
- Make package ``install_requires`` include ``django>=2.2``.
|
||||||
|
|
||||||
|
- Prevent an ImproperlyConfigured warning from ``DEFAULT_HASHING_ALGORITHM``.
|
||||||
|
|
||||||
|
- Prevent warnings for settings deprecated in Django 2.2
|
||||||
|
(``DEFAULT_CONTENT_TYPE`` and ``FILE_CHARSET``).
|
||||||
|
|
||||||
|
- Preserve Django warnings when ``DEFAULT_AUTO_FIELD`` is not set.
|
||||||
|
|
||||||
|
- Miscellaneous documentation fixes.
|
||||||
|
|
||||||
|
- Miscellaneous internal improvements.
|
||||||
|
|
||||||
|
v2.2 (2019-12-03)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support for Python 3.4.
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support for Django < 1.11.
|
||||||
|
|
||||||
|
- Add support for Django 3.0.
|
||||||
|
|
||||||
|
- Add support for Python 3.8.
|
||||||
|
|
||||||
|
- Add support for PyPy 3.
|
||||||
|
|
||||||
|
- Replace ``django.utils.six`` with ``six`` to support Django >= 3.
|
||||||
|
|
||||||
|
- Start using tox-travis and setuptools-scm for simplified test harness
|
||||||
|
and release management.
|
||||||
|
|
||||||
v2.1 (2018-08-16)
|
v2.1 (2018-08-16)
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
||||||
323
docs/conf.py
323
docs/conf.py
|
|
@ -1,301 +1,44 @@
|
||||||
# -*- coding: utf-8 -*-
|
import configurations
|
||||||
#
|
|
||||||
# django-configurations documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Sat Jul 21 15:03:23 2012.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys
|
# -- Project information -----------------------------------------------------
|
||||||
import os
|
project = 'django-configurations'
|
||||||
|
copyright = '2012-2023, Jannis Leidel and other contributors'
|
||||||
|
author = 'Jannis Leidel and other contributors'
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
release = configurations.__version__
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
version = ".".join(release.split(".")[:2])
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
add_function_parentheses = False
|
||||||
|
add_module_names = False
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
extensions = [
|
||||||
#needs_sphinx = '1.0'
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
]
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
intersphinx_mapping = {
|
||||||
templates_path = ['_templates']
|
'python': ('https://docs.python.org/3', None),
|
||||||
|
'sphinx': ('https://www.sphinx-doc.org/en/master', None),
|
||||||
# The suffix of source filenames.
|
'django': ('https://docs.djangoproject.com/en/dev',
|
||||||
source_suffix = '.rst'
|
'https://docs.djangoproject.com/en/dev/_objects/'),
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'django-configurations'
|
|
||||||
copyright = u'2012-2014, Jannis Leidel and other contributors'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
try:
|
|
||||||
from configurations import __version__
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '.'.join(__version__.split('.')[:2])
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = __version__
|
|
||||||
except ImportError:
|
|
||||||
version = release = 'dev'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
#html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
# html_static_path = ['_static']
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'django-configurationsdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#'preamble': '',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
html_theme = 'furo'
|
||||||
latex_documents = [
|
|
||||||
('index', 'django-configurations.tex', u'django-configurations Documentation',
|
|
||||||
u'Jannis Leidel', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output --------------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'django-configurations', u'django-configurations Documentation',
|
|
||||||
[u'Jannis Leidel'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output ------------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
('index', 'django-configurations', u'django-configurations Documentation',
|
|
||||||
u'Jannis Leidel', 'django-configurations', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Epub output ---------------------------------------------------
|
# -- Options for Epub output ---------------------------------------------------
|
||||||
|
epub_title = project
|
||||||
|
epub_author = author
|
||||||
|
epub_publisher = author
|
||||||
|
epub_copyright = copyright
|
||||||
|
|
||||||
# Bibliographic Dublin Core info.
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
epub_title = u'django-configurations'
|
latex_documents = [
|
||||||
epub_author = u'Jannis Leidel'
|
# (source start file, target name, title, author, documentclass)
|
||||||
epub_publisher = u'Jannis Leidel'
|
('index', 'django-configurations.tex',
|
||||||
epub_copyright = u'2012, Jannis Leidel'
|
'django-configurations Documentation', author, 'manual'),
|
||||||
|
]
|
||||||
# The language of the text. It defaults to the language option
|
|
||||||
# or en if the language is not set.
|
|
||||||
#epub_language = ''
|
|
||||||
|
|
||||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
|
||||||
#epub_scheme = ''
|
|
||||||
|
|
||||||
# The unique identifier of the text. This can be a ISBN number
|
|
||||||
# or the project homepage.
|
|
||||||
#epub_identifier = ''
|
|
||||||
|
|
||||||
# A unique identification for the text.
|
|
||||||
#epub_uid = ''
|
|
||||||
|
|
||||||
# A tuple containing the cover image and cover page html template filenames.
|
|
||||||
#epub_cover = ()
|
|
||||||
|
|
||||||
# HTML files that should be inserted before the pages created by sphinx.
|
|
||||||
# The format is a list of tuples containing the path and title.
|
|
||||||
#epub_pre_files = []
|
|
||||||
|
|
||||||
# HTML files shat should be inserted after the pages created by sphinx.
|
|
||||||
# The format is a list of tuples containing the path and title.
|
|
||||||
#epub_post_files = []
|
|
||||||
|
|
||||||
# A list of files that should not be packed into the epub file.
|
|
||||||
#epub_exclude_files = []
|
|
||||||
|
|
||||||
# The depth of the table of contents in toc.ncx.
|
|
||||||
#epub_tocdepth = 3
|
|
||||||
|
|
||||||
# Allow duplicate toc entries.
|
|
||||||
#epub_tocdup = True
|
|
||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
|
||||||
intersphinx_mapping = {
|
|
||||||
'python': ('http://docs.python.org/2.7', None),
|
|
||||||
'sphinx': ('http://sphinx.pocoo.org/', None),
|
|
||||||
'django': ('http://docs.djangoproject.com/en/dev/',
|
|
||||||
'http://docs.djangoproject.com/en/dev/_objects/'),
|
|
||||||
}
|
|
||||||
|
|
||||||
add_function_parentheses = add_module_names = False
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ Calling a Django management command
|
||||||
|
|
||||||
If you want to call a Django management command programmatically, say
|
If you want to call a Django management command programmatically, say
|
||||||
from a script outside of your usual Django code, you can use the
|
from a script outside of your usual Django code, you can use the
|
||||||
equivalent of Django's :func:`~django.core.management.call_command` function
|
equivalent of Django's :func:`~django.core.management.call_command`
|
||||||
with django-configurations, too.
|
function with django-configurations, too.
|
||||||
|
|
||||||
Simply import it from ``configurations.management`` instead:
|
Simply import it from ``configurations.management`` instead:
|
||||||
|
|
||||||
|
|
@ -20,6 +20,43 @@ Simply import it from ``configurations.management`` instead:
|
||||||
|
|
||||||
call_command('dumpdata', exclude=['contenttypes', 'auth'])
|
call_command('dumpdata', exclude=['contenttypes', 'auth'])
|
||||||
|
|
||||||
|
Read .env file
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Configurations can read values for environment variables out of an ``.env``
|
||||||
|
file, and push them into the application's process environment. Simply set
|
||||||
|
the ``DOTENV`` setting to the appropriate file name:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# mysite/settings.py
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
from configurations import Configuration, values
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
class Dev(Configuration):
|
||||||
|
DOTENV = os.path.join(BASE_DIR, '.env')
|
||||||
|
|
||||||
|
SECRET_KEY = values.SecretValue()
|
||||||
|
API_KEY1 = values.Value()
|
||||||
|
API_KEY2 = values.Value()
|
||||||
|
API_KEY3 = values.Value('91011')
|
||||||
|
|
||||||
|
|
||||||
|
A ``.env`` file is a ``.ini``-style file. It must contain a list of
|
||||||
|
``KEY=value`` pairs, just like Shell environment variables:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
# .env
|
||||||
|
|
||||||
|
DJANGO_DEBUG=False
|
||||||
|
DJANGO_SECRET_KEY=1q2w3e4r5t6z7u8i9o0(%&)$§!pqaycz
|
||||||
|
API_KEY1=1234
|
||||||
|
API_KEY2=5678
|
||||||
|
|
||||||
Envdir
|
Envdir
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
@ -37,7 +74,7 @@ Example:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ tree mysite_env/
|
$ tree --noreport mysite_env/
|
||||||
mysite_env/
|
mysite_env/
|
||||||
├── DJANGO_SETTINGS_MODULE
|
├── DJANGO_SETTINGS_MODULE
|
||||||
├── DJANGO_DEBUG
|
├── DJANGO_DEBUG
|
||||||
|
|
@ -45,10 +82,8 @@ Example:
|
||||||
├── DJANGO_CACHE_URL
|
├── DJANGO_CACHE_URL
|
||||||
└── PYTHONSTARTUP
|
└── PYTHONSTARTUP
|
||||||
|
|
||||||
0 directories, 3 files
|
|
||||||
$ cat mysite_env/DJANGO_CACHE_URL
|
$ cat mysite_env/DJANGO_CACHE_URL
|
||||||
redis://user@host:port/1
|
redis://user@host:port/1
|
||||||
$
|
|
||||||
|
|
||||||
Then, to enable the ``mysite_env`` environment variables, simply use the
|
Then, to enable the ``mysite_env`` environment variables, simply use the
|
||||||
``envdir`` command line tool as a prefix for your program, e.g.:
|
``envdir`` command line tool as a prefix for your program, e.g.:
|
||||||
|
|
@ -62,6 +97,42 @@ Python instead of from the command line.
|
||||||
|
|
||||||
.. _envdir: https://pypi.python.org/pypi/envdir
|
.. _envdir: https://pypi.python.org/pypi/envdir
|
||||||
|
|
||||||
|
Sentry (dynamic setup calls)
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
For all tools that require an initialization call you should use
|
||||||
|
:ref:`Setup methods<setup-methods>` (unless you want them activated
|
||||||
|
for all environments).
|
||||||
|
|
||||||
|
Intuitively you might want to add the required setup call like any
|
||||||
|
other setting:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Prod(Base):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
sentry_sdk.init("your dsn", integrations=[DjangoIntegration()])
|
||||||
|
|
||||||
|
But this will activate, in this case, Sentry even when you're running a
|
||||||
|
Dev configuration. What you should do instead, is put that code in the
|
||||||
|
``post_setup`` function. That way Sentry will only ever run when Prod
|
||||||
|
is the selected configuration:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Prod(Base):
|
||||||
|
# ...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_setup(cls):
|
||||||
|
"""Sentry initialization"""
|
||||||
|
super(Prod, cls).post_setup()
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=os.environ.get("your dsn"), integrations=[DjangoIntegration()]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
.. _project-templates:
|
.. _project-templates:
|
||||||
|
|
||||||
Project templates
|
Project templates
|
||||||
|
|
@ -78,13 +149,13 @@ First install Django 1.8.x and django-configurations:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||||
|
|
||||||
Or Django 1.8:
|
Or Django 1.8:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ django-admin.py startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
$ python -m django startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||||
|
|
||||||
Now you have a default Django 1.8.x project in the ``mysite``
|
Now you have a default Django 1.8.x project in the ``mysite``
|
||||||
directory that uses django-configurations.
|
directory that uses django-configurations.
|
||||||
|
|
@ -109,7 +180,7 @@ probably just add the following to the **beginning** of your settings module:
|
||||||
import configurations
|
import configurations
|
||||||
configurations.setup()
|
configurations.setup()
|
||||||
|
|
||||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities.
|
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
|
||||||
This will also call ``django.setup()``.
|
This will also call ``django.setup()``.
|
||||||
|
|
||||||
>= 3.1
|
>= 3.1
|
||||||
|
|
@ -194,6 +265,7 @@ It also works with django-extensions's shell_plus_ management command.
|
||||||
.. _`manage your IPython profile`: http://ipython.org/ipython-doc/dev/config/overview.html#configuration-file-location
|
.. _`manage your IPython profile`: http://ipython.org/ipython-doc/dev/config/overview.html#configuration-file-location
|
||||||
.. _shell_plus: https://django-extensions.readthedocs.io/en/latest/shell_plus.html
|
.. _shell_plus: https://django-extensions.readthedocs.io/en/latest/shell_plus.html
|
||||||
|
|
||||||
|
|
||||||
FastCGI
|
FastCGI
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
@ -258,8 +330,8 @@ Channels
|
||||||
--------
|
--------
|
||||||
|
|
||||||
If you want to deploy a project that uses the Django channels with
|
If you want to deploy a project that uses the Django channels with
|
||||||
`Daphne <http://github.com/django/daphne/>` as the
|
`Daphne <http://github.com/django/daphne/>`_ as the
|
||||||
`interface server <http://channels.readthedocs.io/en/latest/deploying.html#run-interface-servers>`
|
`interface server <http://channels.readthedocs.io/en/latest/deploying.html#run-interface-servers>`_
|
||||||
you have to use a asgi.py script similar to the following:
|
you have to use a asgi.py script similar to the following:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,6 @@ Bugs and feature requests
|
||||||
As always your mileage may vary, so please don't hesitate to send feature
|
As always your mileage may vary, so please don't hesitate to send feature
|
||||||
requests and bug reports:
|
requests and bug reports:
|
||||||
|
|
||||||
https://github.com/jazzband/django-configurations/issues
|
- https://github.com/jazzband/django-configurations/issues
|
||||||
|
|
||||||
Thanks!
|
Thanks!
|
||||||
190
docs/make.bat
190
docs/make.bat
|
|
@ -1,190 +0,0 @@
|
||||||
@ECHO OFF
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
|
||||||
set SPHINXBUILD=sphinx-build
|
|
||||||
)
|
|
||||||
set BUILDDIR=_build
|
|
||||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
|
||||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
|
||||||
if NOT "%PAPER%" == "" (
|
|
||||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
|
||||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
|
||||||
|
|
||||||
if "%1" == "help" (
|
|
||||||
:help
|
|
||||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
|
||||||
echo. html to make standalone HTML files
|
|
||||||
echo. dirhtml to make HTML files named index.html in directories
|
|
||||||
echo. singlehtml to make a single large HTML file
|
|
||||||
echo. pickle to make pickle files
|
|
||||||
echo. json to make JSON files
|
|
||||||
echo. htmlhelp to make HTML files and a HTML help project
|
|
||||||
echo. qthelp to make HTML files and a qthelp project
|
|
||||||
echo. devhelp to make HTML files and a Devhelp project
|
|
||||||
echo. epub to make an epub
|
|
||||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
|
||||||
echo. text to make text files
|
|
||||||
echo. man to make manual pages
|
|
||||||
echo. texinfo to make Texinfo files
|
|
||||||
echo. gettext to make PO message catalogs
|
|
||||||
echo. changes to make an overview over all changed/added/deprecated items
|
|
||||||
echo. linkcheck to check all external links for integrity
|
|
||||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "clean" (
|
|
||||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
|
||||||
del /q /s %BUILDDIR%\*
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "html" (
|
|
||||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "dirhtml" (
|
|
||||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "singlehtml" (
|
|
||||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "pickle" (
|
|
||||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the pickle files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "json" (
|
|
||||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the JSON files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "htmlhelp" (
|
|
||||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
|
||||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "qthelp" (
|
|
||||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
|
||||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
|
||||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-configurations.qhcp
|
|
||||||
echo.To view the help file:
|
|
||||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-configurations.ghc
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "devhelp" (
|
|
||||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "epub" (
|
|
||||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "latex" (
|
|
||||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "text" (
|
|
||||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "man" (
|
|
||||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "texinfo" (
|
|
||||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "gettext" (
|
|
||||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "changes" (
|
|
||||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.The overview file is in %BUILDDIR%/changes.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "linkcheck" (
|
|
||||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Link check complete; look for any errors in the above output ^
|
|
||||||
or in %BUILDDIR%/linkcheck/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "doctest" (
|
|
||||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Testing of doctests in the sources finished, look at the ^
|
|
||||||
results in %BUILDDIR%/doctest/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
:end
|
|
||||||
|
|
@ -3,14 +3,16 @@ Usage patterns
|
||||||
|
|
||||||
There are various configuration patterns that can be implemented with
|
There are various configuration patterns that can be implemented with
|
||||||
django-configurations. The most common pattern is to have a base class
|
django-configurations. The most common pattern is to have a base class
|
||||||
and various subclasses based on the enviroment they are supposed to be
|
and various subclasses based on the environment they are supposed to be
|
||||||
used in, e.g. in production, staging and development.
|
used in, e.g. in production, staging and development.
|
||||||
|
|
||||||
Server specific settings
|
Server specific settings
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
For example, imagine you have a base setting class in your **settings.py**
|
For example, imagine you have a base setting class in your **settings.py**
|
||||||
file::
|
file:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
@ -19,47 +21,54 @@ file::
|
||||||
|
|
||||||
class Dev(Base):
|
class Dev(Base):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
TEMPLATE_DEBUG = DEBUG
|
|
||||||
|
|
||||||
class Prod(Base):
|
class Prod(Base):
|
||||||
TIME_ZONE = 'America/New_York'
|
TIME_ZONE = 'America/New_York'
|
||||||
|
|
||||||
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
|
You can now set the ``DJANGO_CONFIGURATION`` environment variable to
|
||||||
of the class names you've defined, e.g. on your production server it
|
one of the class names you've defined, e.g. on your production server
|
||||||
should be ``Prod``. In bash that would be::
|
it should be ``Prod``. In Bash that would be:
|
||||||
|
|
||||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
.. code-block:: console
|
||||||
export DJANGO_CONFIGURATION=Prod
|
|
||||||
python manage.py runserver
|
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||||
|
$ export DJANGO_CONFIGURATION=Prod
|
||||||
|
$ python -m manage runserver
|
||||||
|
|
||||||
Alternatively you can use the ``--configuration`` option when using Django
|
Alternatively you can use the ``--configuration`` option when using Django
|
||||||
management commands along the lines of Django's default ``--settings``
|
management commands along the lines of Django's default ``--settings``
|
||||||
command line option, e.g.::
|
command line option, e.g.
|
||||||
|
|
||||||
python manage.py runserver --settings=mysite.settings --configuration=Prod
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python -m manage runserver --settings=mysite.settings --configuration=Prod
|
||||||
|
|
||||||
Property settings
|
Property settings
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Use a `property` to allow for computed settings. This pattern can also be used to postpone / lazy evaluate a value. E.g. useful when nesting a Value in a dictionary and a string is required::
|
Use a ``property`` to allow for computed settings. This pattern can
|
||||||
|
also be used to postpone / lazy evaluate a value. E.g., useful when
|
||||||
|
nesting a Value in a dictionary and a string is required:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
class Prod(Configuration):
|
class Prod(Configuration):
|
||||||
SENTRY_DSN = values.Value(None, environ_prefix=None)
|
SOME_VALUE = values.Value(None, environ_prefix=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def RAVEN_CONFIG(self):
|
def SOME_CONFIG(self):
|
||||||
return {
|
return {
|
||||||
'dsn': self.SENTRY_DSN,
|
'some_key': self.SOME_VALUE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Global settings defaults
|
Global settings defaults
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Every ``configurations.Configuration`` subclass will automatically contain
|
Every ``configurations.Configuration`` subclass will automatically
|
||||||
Django's global settings as class attributes, so you can refer to them when
|
contain Django's global settings as class attributes, so you can refer
|
||||||
setting other values, e.g.::
|
to them when setting other values, e.g.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
@ -77,13 +86,17 @@ Configuration mixins
|
||||||
|
|
||||||
You might want to apply some configuration values for each and every
|
You might want to apply some configuration values for each and every
|
||||||
project you're working on without having to repeat yourself. Just define
|
project you're working on without having to repeat yourself. Just define
|
||||||
a few mixin you re-use multiple times::
|
a few mixin you re-use multiple times:
|
||||||
|
|
||||||
class FullPageCaching(object):
|
.. code-block:: python
|
||||||
|
|
||||||
|
class FullPageCaching:
|
||||||
USE_ETAGS = True
|
USE_ETAGS = True
|
||||||
|
|
||||||
Then import that mixin class in your site settings module and use it with
|
Then import that mixin class in your site settings module and use it with
|
||||||
a ``Configuration`` class::
|
a ``Configuration`` class:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
@ -97,8 +110,10 @@ Pristine methods
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
In case one of your settings itself need to be a callable, you need to
|
In case one of your settings itself need to be a callable, you need to
|
||||||
tell that django-configurations by using the ``pristinemethod`` decorator,
|
tell that django-configurations by using the ``pristinemethod``
|
||||||
e.g.::
|
decorator, e.g.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration, pristinemethod
|
from configurations import Configuration, pristinemethod
|
||||||
|
|
||||||
|
|
@ -108,13 +123,18 @@ e.g.::
|
||||||
def ACCESS_FUNCTION(user):
|
def ACCESS_FUNCTION(user):
|
||||||
return user.is_staff
|
return user.is_staff
|
||||||
|
|
||||||
Lambdas work, too::
|
Lambdas work, too:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration, pristinemethod
|
from configurations import Configuration, pristinemethod
|
||||||
|
|
||||||
class Prod(Configuration):
|
class Prod(Configuration):
|
||||||
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
|
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
|
||||||
|
|
||||||
|
|
||||||
|
.. _setup-methods:
|
||||||
|
|
||||||
Setup methods
|
Setup methods
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
@ -123,7 +143,9 @@ Setup methods
|
||||||
If there is something required to be set up before, during or after the
|
If there is something required to be set up before, during or after the
|
||||||
settings loading happens, please override the ``pre_setup``, ``setup`` or
|
settings loading happens, please override the ``pre_setup``, ``setup`` or
|
||||||
``post_setup`` class methods like so (don't forget to apply the Python
|
``post_setup`` class methods like so (don't forget to apply the Python
|
||||||
``@classmethod`` decorator)::
|
``@classmethod`` decorator):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
@ -154,7 +176,9 @@ Of course that won't work for ``post_setup`` since that's when the
|
||||||
settings setup is already done.
|
settings setup is already done.
|
||||||
|
|
||||||
In fact you can easily do something unrelated to settings, like
|
In fact you can easily do something unrelated to settings, like
|
||||||
connecting to a database::
|
connecting to a database:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
@ -166,13 +190,12 @@ connecting to a database::
|
||||||
import mango
|
import mango
|
||||||
mango.connect('enterprise')
|
mango.connect('enterprise')
|
||||||
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
You could do the same by overriding the ``__init__`` method of your
|
You could do the same by overriding the ``__init__`` method of your
|
||||||
settings class but this may cause hard to debug errors because
|
settings class but this may cause hard to debug errors because
|
||||||
at the time the ``__init__`` method is called (during Django startup)
|
at the time the ``__init__`` method is called (during Django
|
||||||
the Django setting system isn't fully loaded yet.
|
startup) the Django setting system isn't fully loaded yet.
|
||||||
|
|
||||||
So anything you do in ``__init__`` that may require
|
So anything you do in ``__init__`` that may require
|
||||||
``django.conf.settings`` or Django models there is a good chance it
|
``django.conf.settings`` or Django models there is a good chance it
|
||||||
|
|
@ -181,15 +204,16 @@ connecting to a database::
|
||||||
.. versionchanged:: 0.4
|
.. versionchanged:: 0.4
|
||||||
|
|
||||||
A new ``setup`` method was added to be able to handle the new
|
A new ``setup`` method was added to be able to handle the new
|
||||||
:class:`~configurations.values.Value` classes and allow an in-between
|
:class:`~configurations.values.Value` classes and allow an
|
||||||
modification of the configuration values.
|
in-between modification of the configuration values.
|
||||||
|
|
||||||
|
|
||||||
Standalone scripts
|
Standalone scripts
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If you want to run scripts outside of your project you need to add these lines
|
If you want to run scripts outside of your project you need to add
|
||||||
on top of your file::
|
these lines on top of your file:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
import configurations
|
import configurations
|
||||||
configurations.setup()
|
configurations.setup()
|
||||||
|
|
|
||||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Sphinx>4
|
||||||
|
furo
|
||||||
|
docutils
|
||||||
|
|
@ -46,7 +46,7 @@ value:
|
||||||
|
|
||||||
class Dev(Configuration):
|
class Dev(Configuration):
|
||||||
DEBUG = values.BooleanValue(True)
|
DEBUG = values.BooleanValue(True)
|
||||||
TEMPLATE_DEBUG = values.BooleanValue(DEBUG)
|
DEBUG_PROPAGATE_EXCEPTIONS = values.BooleanValue(DEBUG)
|
||||||
|
|
||||||
See the list of :ref:`built-in value classes<built-ins>` for more information.
|
See the list of :ref:`built-in value classes<built-ins>` for more information.
|
||||||
|
|
||||||
|
|
@ -86,9 +86,11 @@ prefixed with ``DJANGO_``. E.g.:
|
||||||
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
|
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
|
||||||
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
|
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
|
||||||
When you run the web server simply specify that environment variable
|
When you run the web server simply specify that environment variable
|
||||||
(e.g. in your init script)::
|
(e.g. in your init script):
|
||||||
|
|
||||||
DJANGO_ROOT_URLCONF=mysite.debugging_urls gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_ROOT_URLCONF=mysite.debugging_urls gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
If the environment variable can't be found it'll use the default
|
If the environment variable can't be found it'll use the default
|
||||||
``'mysite.urls'``.
|
``'mysite.urls'``.
|
||||||
|
|
@ -125,7 +127,9 @@ Allow final value to be used outside the configuration context
|
||||||
|
|
||||||
You may use the ``environ_name`` parameter to allow a :class:`~Value` to be
|
You may use the ``environ_name`` parameter to allow a :class:`~Value` to be
|
||||||
directly converted to its final value for use outside of the configuration
|
directly converted to its final value for use outside of the configuration
|
||||||
context::
|
context:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
>>> type(values.Value([]))
|
>>> type(values.Value([]))
|
||||||
<class 'configurations.values.Value'>
|
<class 'configurations.values.Value'>
|
||||||
|
|
@ -160,12 +164,12 @@ the prefix.
|
||||||
|
|
||||||
:param default: the default value of the setting
|
:param default: the default value of the setting
|
||||||
:param environ: toggle for environment use
|
:param environ: toggle for environment use
|
||||||
:param environ_name: name of environment variable to look for
|
:param environ_name: capitalized name of environment variable to look for
|
||||||
:param environ_prefix: prefix to use when looking for environment variable
|
:param environ_prefix: capitalized prefix to use when looking for environment variable
|
||||||
:param environ_required: wheter or not the value is required to be set as an environment variable
|
:param environ_required: whether or not the value is required to be set as an environment variable
|
||||||
:type environ: bool
|
:type environ: bool
|
||||||
:type environ_name: capitalized string or None
|
:type environ_name: str or None
|
||||||
:type environ_prefix: capitalized string
|
:type environ_prefix: str
|
||||||
:type environ_required: bool
|
:type environ_required: bool
|
||||||
|
|
||||||
The ``default`` parameter is effectively the value the setting has
|
The ``default`` parameter is effectively the value the setting has
|
||||||
|
|
@ -256,6 +260,10 @@ Type values
|
||||||
|
|
||||||
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
|
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
|
||||||
|
|
||||||
|
.. class:: SequenceValue
|
||||||
|
|
||||||
|
Common base class for sequence values.
|
||||||
|
|
||||||
.. class:: ListValue(default, [separator=',', converter=None])
|
.. class:: ListValue(default, [separator=',', converter=None])
|
||||||
|
|
||||||
A :class:`~SequenceValue` subclass that handles list values.
|
A :class:`~SequenceValue` subclass that handles list values.
|
||||||
|
|
@ -279,17 +287,21 @@ Type values
|
||||||
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
||||||
converter=check_monty_python)
|
converter=check_monty_python)
|
||||||
|
|
||||||
You can override this list with an environment variable like this::
|
You can override this list with an environment variable like this:
|
||||||
|
|
||||||
DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
Use a custom separator::
|
Use a custom separator::
|
||||||
|
|
||||||
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';')
|
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';')
|
||||||
|
|
||||||
And override it::
|
And override it:
|
||||||
|
|
||||||
DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
|
||||||
|
|
||||||
.. class:: TupleValue
|
.. class:: TupleValue
|
||||||
|
|
||||||
|
|
@ -301,6 +313,10 @@ Type values
|
||||||
|
|
||||||
See the :class:`~ListValue` examples above.
|
See the :class:`~ListValue` examples above.
|
||||||
|
|
||||||
|
.. class:: SingleNestedSequenceValue
|
||||||
|
|
||||||
|
Common base class for nested sequence values.
|
||||||
|
|
||||||
.. class:: SingleNestedTupleValue(default, [seq_separator=';', separator=',', converter=None])
|
.. class:: SingleNestedTupleValue(default, [seq_separator=';', separator=',', converter=None])
|
||||||
|
|
||||||
A :class:`~SingleNestedSequenceValue` subclass that handles single nested tuple values,
|
A :class:`~SingleNestedSequenceValue` subclass that handles single nested tuple values,
|
||||||
|
|
@ -354,6 +370,10 @@ Type values
|
||||||
'it': ['Mike', 'Joe'],
|
'it': ['Mike', 'Joe'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Override using environment variables like this::
|
||||||
|
|
||||||
|
DJANGO_DEPARTMENTS={'it':['Mike','Joe'],'hr':['Emma','Olivia']}
|
||||||
|
|
||||||
Validator values
|
Validator values
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -547,7 +567,7 @@ Other values
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = values.BackendsValue([
|
MIDDLEWARE = values.BackendsValue([
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
|
@ -561,7 +581,8 @@ Other values
|
||||||
A :class:`~Value` subclass that doesn't allow setting a default value
|
A :class:`~Value` subclass that doesn't allow setting a default value
|
||||||
during instantiation and force-enables the use of an environment variable
|
during instantiation and force-enables the use of an environment variable
|
||||||
to reduce the risk of accidentally storing secret values in the settings
|
to reduce the risk of accidentally storing secret values in the settings
|
||||||
file.
|
file. This usually resolves to ``DJANGO_SECRET_KEY`` unless you have
|
||||||
|
customized the environment variable names.
|
||||||
|
|
||||||
:raises: ``ValueError`` when given a default value
|
:raises: ``ValueError`` when given a default value
|
||||||
|
|
||||||
|
|
@ -583,7 +604,7 @@ Value mixins
|
||||||
requires a ``caster`` class attribute of one of the following types:
|
requires a ``caster`` class attribute of one of the following types:
|
||||||
|
|
||||||
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
|
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
|
||||||
- a callable, e.g. :func:`int`
|
- a callable, e.g. :class:`int`
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
|
@ -604,7 +625,7 @@ Value mixins
|
||||||
validation attempt.
|
validation attempt.
|
||||||
|
|
||||||
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
|
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
|
||||||
- a callable, e.g. :func:`bool`
|
- a callable, e.g. :class:`bool`
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
[wheel]
|
|
||||||
universal = 1
|
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
source = .
|
source = .
|
||||||
include = configurations/*,tests/*
|
|
||||||
branch = 1
|
branch = 1
|
||||||
|
parallel = 1
|
||||||
[coverage:report]
|
[coverage:report]
|
||||||
include = configurations/*,tests/*
|
include = configurations/*,tests/*
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
exclude = .tox,docs/*,.eggs
|
||||||
|
ignore = E501,E127,E128,E124,W503
|
||||||
|
|
|
||||||
58
setup.py
58
setup.py
|
|
@ -1,38 +1,26 @@
|
||||||
from __future__ import print_function
|
|
||||||
import ast
|
|
||||||
import os
|
import os
|
||||||
import codecs
|
import codecs
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
class VersionFinder(ast.NodeVisitor):
|
|
||||||
def __init__(self):
|
|
||||||
self.version = None
|
|
||||||
|
|
||||||
def visit_Assign(self, node):
|
|
||||||
if node.targets[0].id == '__version__':
|
|
||||||
self.version = node.value.s
|
|
||||||
|
|
||||||
|
|
||||||
def read(*parts):
|
def read(*parts):
|
||||||
filename = os.path.join(os.path.dirname(__file__), *parts)
|
filename = os.path.join(os.path.dirname(__file__), *parts)
|
||||||
with codecs.open(filename, encoding='utf-8') as fp:
|
with codecs.open(filename, encoding='utf-8') as fp:
|
||||||
return fp.read()
|
return fp.read()
|
||||||
|
|
||||||
|
|
||||||
def find_version(*parts):
|
|
||||||
finder = VersionFinder()
|
|
||||||
finder.visit(ast.parse(read(*parts)))
|
|
||||||
return finder.version
|
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="django-configurations",
|
name="django-configurations",
|
||||||
version=find_version("configurations", "__init__.py"),
|
use_scm_version={"version_scheme": "post-release", "local_scheme": "dirty-tag"},
|
||||||
|
setup_requires=["setuptools_scm"],
|
||||||
url='https://django-configurations.readthedocs.io/',
|
url='https://django-configurations.readthedocs.io/',
|
||||||
|
project_urls={
|
||||||
|
'Source': 'https://github.com/jazzband/django-configurations',
|
||||||
|
},
|
||||||
license='BSD',
|
license='BSD',
|
||||||
description="A helper for organizing Django settings.",
|
description="A helper for organizing Django settings.",
|
||||||
long_description=read('README.rst'),
|
long_description=read('README.rst'),
|
||||||
|
long_description_content_type='text/x-rst',
|
||||||
author='Jannis Leidel',
|
author='Jannis Leidel',
|
||||||
author_email='jannis@leidel.info',
|
author_email='jannis@leidel.info',
|
||||||
packages=['configurations'],
|
packages=['configurations'],
|
||||||
|
|
@ -41,18 +29,42 @@ setup(
|
||||||
'django-cadmin = configurations.management:execute_from_command_line',
|
'django-cadmin = configurations.management:execute_from_command_line',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
install_requires=[
|
||||||
|
'django>=3.2',
|
||||||
|
],
|
||||||
|
python_requires='>=3.9, <4.0',
|
||||||
|
extras_require={
|
||||||
|
'cache': ['django-cache-url'],
|
||||||
|
'database': ['dj-database-url'],
|
||||||
|
'email': ['dj-email-url'],
|
||||||
|
'search': ['dj-search-url'],
|
||||||
|
'testing': [
|
||||||
|
'django-cache-url>=1.0.0',
|
||||||
|
'dj-database-url',
|
||||||
|
'dj-email-url',
|
||||||
|
'dj-search-url',
|
||||||
|
],
|
||||||
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Framework :: Django',
|
'Framework :: Django',
|
||||||
|
'Framework :: Django :: 3.2',
|
||||||
|
'Framework :: Django :: 4.1',
|
||||||
|
'Framework :: Django :: 4.2',
|
||||||
|
'Framework :: Django :: 5.0',
|
||||||
|
'Framework :: Django :: 5.1',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.2',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.10',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.11',
|
||||||
|
'Programming Language :: Python :: 3.12',
|
||||||
|
'Programming Language :: Python :: 3.13',
|
||||||
|
'Programming Language :: Python :: Implementation :: PyPy',
|
||||||
'Topic :: Utilities',
|
'Topic :: Utilities',
|
||||||
],
|
],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ class Base(Configuration):
|
||||||
# Django settings for test_project project.
|
# Django settings for test_project project.
|
||||||
|
|
||||||
DEBUG = values.BooleanValue(True, environ=True)
|
DEBUG = values.BooleanValue(True, environ=True)
|
||||||
TEMPLATE_DEBUG = DEBUG
|
|
||||||
|
|
||||||
ADMINS = (
|
ADMINS = (
|
||||||
# ('Your Name', 'your_email@example.com'),
|
# ('Your Name', 'your_email@example.com'),
|
||||||
|
|
@ -84,7 +83,6 @@ class Base(Configuration):
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = (
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
# Make this unique, and don't share it with anybody.
|
||||||
|
|
@ -96,7 +94,7 @@ class Base(Configuration):
|
||||||
'django.template.loaders.app_directories.Loader',
|
'django.template.loaders.app_directories.Loader',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns
|
||||||
|
|
||||||
# Uncomment the next two lines to enable the admin:
|
# Uncomment the next two lines to enable the admin:
|
||||||
# from django.contrib import admin
|
# from django.contrib import admin
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
|
||||||
# This application object is used by any WSGI server configured to use this
|
# This application object is used by any WSGI server configured to use this
|
||||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||||
# setting points here.
|
# setting points here.
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application # noqa
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
||||||
# Apply WSGI middleware here.
|
# Apply WSGI middleware here.
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
|
||||||
|
|
||||||
# setup Django
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main")
|
|
||||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'Test')
|
|
||||||
|
|
||||||
extensions = [
|
|
||||||
'configurations.sphinx',
|
|
||||||
]
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = 'django-configurations'
|
|
||||||
copyright = '2012-2014, Jannis Leidel and other contributors'
|
|
||||||
|
|
||||||
|
|
||||||
version = release = 'test'
|
|
||||||
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Test Documentation
|
|
||||||
^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
from optparse import make_option
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
|
|
||||||
# Used by a specific test to see how unupgraded
|
|
||||||
# management commands play with configurations.
|
|
||||||
# See the test code for more details.
|
|
||||||
|
|
||||||
option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--arg1', action='store_true'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
pass
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
coverage
|
|
||||||
django-discover-runner
|
|
||||||
mock
|
|
||||||
dj-database-url
|
|
||||||
dj-email-url
|
|
||||||
dj-search-url
|
|
||||||
django-cache-url>=1.0.0
|
|
||||||
six
|
|
||||||
Sphinx>=1.4
|
|
||||||
|
|
@ -6,3 +6,6 @@ class DotEnvConfiguration(Configuration):
|
||||||
DOTENV = 'test_project/.env'
|
DOTENV = 'test_project/.env'
|
||||||
|
|
||||||
DOTENV_VALUE = values.Value()
|
DOTENV_VALUE = values.Value()
|
||||||
|
|
||||||
|
def DOTENV_VALUE_METHOD(self):
|
||||||
|
return values.Value(environ_name="DOTENV_VALUE")
|
||||||
|
|
|
||||||
8
tests/settings/error.py
Normal file
8
tests/settings/error.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorConfiguration(Configuration):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_setup(cls):
|
||||||
|
raise ValueError("Error in pre_setup")
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import django
|
|
||||||
|
|
||||||
from configurations import Configuration, pristinemethod
|
from configurations import Configuration, pristinemethod
|
||||||
from configurations.values import BooleanValue
|
|
||||||
|
|
||||||
|
|
||||||
class Test(Configuration):
|
class Test(Configuration):
|
||||||
|
|
@ -11,8 +9,6 @@ class Test(Configuration):
|
||||||
os.path.join(os.path.dirname(
|
os.path.join(os.path.dirname(
|
||||||
os.path.abspath(__file__)), os.pardir))
|
os.path.abspath(__file__)), os.pardir))
|
||||||
|
|
||||||
ENV_LOADED = BooleanValue(False)
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
@ -31,18 +27,14 @@ class Test(Configuration):
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.admin',
|
|
||||||
'tests',
|
'tests',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'tests.urls'
|
ROOT_URLCONF = 'tests.urls'
|
||||||
|
|
||||||
if django.VERSION[:2] < (1, 6):
|
|
||||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Test, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('base')
|
allowed_hosts.append('base')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
||||||
|
|
@ -72,3 +64,7 @@ class Test(Configuration):
|
||||||
@classmethod
|
@classmethod
|
||||||
def post_setup(cls):
|
def post_setup(cls):
|
||||||
cls.POST_SETUP_TEST_SETTING = 7
|
cls.POST_SETUP_TEST_SETTING = 7
|
||||||
|
|
||||||
|
|
||||||
|
class TestWithDefaultSetExplicitely(Test):
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
||||||
class Mixin1(object):
|
class Mixin1:
|
||||||
@property
|
@property
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Mixin1, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('test1')
|
allowed_hosts.append('test1')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
||||||
|
|
||||||
class Mixin2(object):
|
class Mixin2:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Mixin2, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('test2')
|
allowed_hosts.append('test2')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
||||||
|
|
@ -21,6 +21,6 @@ class Mixin2(object):
|
||||||
class Inheritance(Mixin2, Mixin1, Configuration):
|
class Inheritance(Mixin2, Mixin1, Configuration):
|
||||||
|
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('test3')
|
allowed_hosts.append('test3')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,6 @@ from .single_inheritance import Inheritance as BaseInheritance
|
||||||
class Inheritance(BaseInheritance):
|
class Inheritance(BaseInheritance):
|
||||||
|
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('test-test')
|
allowed_hosts.append('test-test')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@ class Inheritance(Base):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ALLOWED_HOSTS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
allowed_hosts.append('test')
|
allowed_hosts.append('test')
|
||||||
return allowed_hosts
|
return allowed_hosts
|
||||||
|
|
|
||||||
14
tests/setup_test.py
Normal file
14
tests/setup_test.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Used by tests to ensure logging is kept when calling setup() twice."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import configurations
|
||||||
|
|
||||||
|
print('setup_1')
|
||||||
|
configurations.setup()
|
||||||
|
|
||||||
|
with mock.patch('django.setup', side_effect=Exception('setup called twice')):
|
||||||
|
print('setup_2')
|
||||||
|
configurations.setup()
|
||||||
|
|
||||||
|
print('setup_done')
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
class DotEnvLoadingTests(TestCase):
|
class DotEnvLoadingTests(TestCase):
|
||||||
|
|
@ -11,4 +11,5 @@ class DotEnvLoadingTests(TestCase):
|
||||||
def test_env_loaded(self):
|
def test_env_loaded(self):
|
||||||
from tests.settings import dot_env
|
from tests.settings import dot_env
|
||||||
self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
|
self.assertEqual(dot_env.DOTENV_VALUE, 'is set')
|
||||||
|
self.assertEqual(dot_env.DOTENV_VALUE_METHOD, 'is set')
|
||||||
self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)
|
self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)
|
||||||
|
|
|
||||||
22
tests/test_error.py
Normal file
22
tests/test_error.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import os
|
||||||
|
from django.test import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorTests(TestCase):
|
||||||
|
|
||||||
|
@patch.dict(os.environ, clear=True,
|
||||||
|
DJANGO_CONFIGURATION='ErrorConfiguration',
|
||||||
|
DJANGO_SETTINGS_MODULE='tests.settings.error')
|
||||||
|
def test_env_loaded(self):
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
from tests.settings import error # noqa: F401
|
||||||
|
|
||||||
|
self.assertIsInstance(cm.exception, ValueError)
|
||||||
|
self.assertEqual(
|
||||||
|
cm.exception.args,
|
||||||
|
(
|
||||||
|
"Couldn't setup configuration "
|
||||||
|
"'tests.settings.error.ErrorConfiguration': Error in pre_setup ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -2,7 +2,7 @@ import os
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
class InheritanceTests(TestCase):
|
class InheritanceTests(TestCase):
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,12 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django import VERSION as DJANGO_VERSION
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from unittest import skipIf
|
from unittest.mock import patch
|
||||||
|
|
||||||
from mock import patch
|
from configurations.importer import ConfigurationFinder
|
||||||
|
|
||||||
from configurations.importer import ConfigurationImporter
|
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
||||||
|
|
@ -45,12 +42,14 @@ class MainTests(TestCase):
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
|
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
|
||||||
def test_empty_module_var(self):
|
def test_empty_module_var(self):
|
||||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
ConfigurationFinder()
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
||||||
def test_empty_class_var(self):
|
def test_empty_class_var(self):
|
||||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
ConfigurationFinder()
|
||||||
|
|
||||||
def test_global_settings(self):
|
def test_global_settings(self):
|
||||||
from configurations.base import Configuration
|
from configurations.base import Configuration
|
||||||
|
|
@ -58,6 +57,12 @@ class MainTests(TestCase):
|
||||||
self.assertEqual(repr(Configuration),
|
self.assertEqual(repr(Configuration),
|
||||||
"<Configuration 'configurations.base.Configuration'>")
|
"<Configuration 'configurations.base.Configuration'>")
|
||||||
|
|
||||||
|
def test_deprecated_settings_but_set_by_user(self):
|
||||||
|
from tests.settings.main import TestWithDefaultSetExplicitely
|
||||||
|
TestWithDefaultSetExplicitely.setup()
|
||||||
|
self.assertEqual(TestWithDefaultSetExplicitely.DEFAULT_AUTO_FIELD,
|
||||||
|
"django.db.models.BigAutoField")
|
||||||
|
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
from tests.settings.main import Test
|
from tests.settings.main import Test
|
||||||
self.assertEqual(repr(Test),
|
self.assertEqual(repr(Test),
|
||||||
|
|
@ -67,21 +72,21 @@ class MainTests(TestCase):
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||||
DJANGO_CONFIGURATION='Test')
|
DJANGO_CONFIGURATION='Test')
|
||||||
def test_initialization(self):
|
def test_initialization(self):
|
||||||
importer = ConfigurationImporter()
|
finder = ConfigurationFinder()
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'Test')
|
self.assertEqual(finder.name, 'Test')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
repr(importer),
|
repr(finder),
|
||||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
"<ConfigurationFinder for 'tests.settings.main.Test'>")
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
||||||
DJANGO_CONFIGURATION='Inheritance')
|
DJANGO_CONFIGURATION='Inheritance')
|
||||||
def test_initialization_inheritance(self):
|
def test_initialization_inheritance(self):
|
||||||
importer = ConfigurationImporter()
|
finder = ConfigurationFinder()
|
||||||
self.assertEqual(importer.module,
|
self.assertEqual(finder.module,
|
||||||
'tests.settings.inheritance')
|
'tests.settings.inheritance')
|
||||||
self.assertEqual(importer.name, 'Inheritance')
|
self.assertEqual(finder.name, 'Inheritance')
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||||
|
|
@ -90,12 +95,12 @@ class MainTests(TestCase):
|
||||||
'--settings=tests.settings.main',
|
'--settings=tests.settings.main',
|
||||||
'--configuration=Test'])
|
'--configuration=Test'])
|
||||||
def test_configuration_option(self):
|
def test_configuration_option(self):
|
||||||
importer = ConfigurationImporter(check_options=False)
|
finder = ConfigurationFinder(check_options=False)
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'NonExisting')
|
self.assertEqual(finder.name, 'NonExisting')
|
||||||
importer = ConfigurationImporter(check_options=True)
|
finder = ConfigurationFinder(check_options=True)
|
||||||
self.assertEqual(importer.module, 'tests.settings.main')
|
self.assertEqual(finder.module, 'tests.settings.main')
|
||||||
self.assertEqual(importer.name, 'Test')
|
self.assertEqual(finder.name, 'Test')
|
||||||
|
|
||||||
def test_configuration_argument_in_cli(self):
|
def test_configuration_argument_in_cli(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -109,20 +114,45 @@ class MainTests(TestCase):
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||||
|
|
||||||
@skipIf(DJANGO_VERSION >= (1, 10), 'only applies to Django < 1.10')
|
def test_configuration_argument_in_runypy_cli(self):
|
||||||
def test_deprecated_option_list_command(self):
|
|
||||||
"""
|
"""
|
||||||
Verify that the configuration option is correctly added to any
|
Verify that's configuration option has been added to managements
|
||||||
management commands which are still relying on option_list to
|
commands when using the -m entry point
|
||||||
add their own custom arguments
|
|
||||||
|
|
||||||
Specific test for a pattern which was deprecated in Django 1.8
|
|
||||||
and which will become completely unsupported in Django 1.10.
|
|
||||||
https://docs.djangoproject.com/en/1.8/howto/custom-management-commands/#custom-commands-options
|
|
||||||
"""
|
"""
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
['django-cadmin', 'old_optparse_command', '--help'],
|
[sys.executable, '-m', 'configurations', 'test', '--help'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, '-m', 'configurations', 'runserver', '--help'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||||
|
|
||||||
|
def test_django_setup_only_called_once(self):
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, os.path.join(os.path.dirname(__file__),
|
||||||
|
'setup_test.py')],
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
output = proc.communicate()[0].decode('utf-8')
|
res = proc.communicate()
|
||||||
self.assertIn('--configuration', output)
|
stdout = res[0].decode('utf-8')
|
||||||
self.assertIn('--arg1', output)
|
|
||||||
|
self.assertIn('setup_1', stdout)
|
||||||
|
self.assertIn('setup_2', stdout)
|
||||||
|
self.assertIn('setup_done', stdout)
|
||||||
|
self.assertEqual(proc.returncode, 0)
|
||||||
|
|
||||||
|
def test_utils_reraise(self):
|
||||||
|
from configurations.utils import reraise
|
||||||
|
|
||||||
|
class CustomException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(CustomException) as cm:
|
||||||
|
try:
|
||||||
|
raise CustomException
|
||||||
|
except Exception as exc:
|
||||||
|
reraise(exc, "Couldn't setup configuration", None)
|
||||||
|
|
||||||
|
self.assertEqual(cm.exception.args, ("Couldn't setup configuration: ",))
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class SphinxTests(TestCase):
|
|
||||||
docs_dir = os.path.join(settings.BASE_DIR, 'docs')
|
|
||||||
|
|
||||||
def test_multiprocessing(self):
|
|
||||||
output = subprocess.check_output([
|
|
||||||
'sphinx-build',
|
|
||||||
'-b',
|
|
||||||
'html',
|
|
||||||
'-j 2',
|
|
||||||
'.',
|
|
||||||
'_build/html',
|
|
||||||
], cwd=self.docs_dir, stderr=subprocess.STDOUT)
|
|
||||||
self.assertIn(b'build succeeded.', output)
|
|
||||||
|
|
@ -2,10 +2,11 @@ import decimal
|
||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from configurations.values import (Value, BooleanValue, IntegerValue,
|
from configurations.values import (Value, BooleanValue, IntegerValue,
|
||||||
FloatValue, DecimalValue, ListValue,
|
FloatValue, DecimalValue, ListValue,
|
||||||
|
|
@ -33,7 +34,7 @@ class ValueTests(TestCase):
|
||||||
|
|
||||||
def test_value_with_default(self):
|
def test_value_with_default(self):
|
||||||
value = Value('default', environ=False)
|
value = Value('default', environ=False)
|
||||||
self.assertEqual(type(value), type('default'))
|
self.assertEqual(type(value), str)
|
||||||
self.assertEqual(value, 'default')
|
self.assertEqual(value, 'default')
|
||||||
self.assertEqual(str(value), 'default')
|
self.assertEqual(str(value), 'default')
|
||||||
|
|
||||||
|
|
@ -43,17 +44,17 @@ class ValueTests(TestCase):
|
||||||
with env(DJANGO_TEST='override'):
|
with env(DJANGO_TEST='override'):
|
||||||
self.assertEqual(value.setup('TEST'), 'default')
|
self.assertEqual(value.setup('TEST'), 'default')
|
||||||
value = Value(environ_name='TEST')
|
value = Value(environ_name='TEST')
|
||||||
self.assertEqual(type(value), type('override'))
|
self.assertEqual(type(value), str)
|
||||||
self.assertEqual(value, 'override')
|
self.assertEqual(value, 'override')
|
||||||
self.assertEqual(str(value), 'override')
|
self.assertEqual(str(value), 'override')
|
||||||
self.assertEqual('{0}'.format(value), 'override')
|
self.assertEqual(f'{value}', 'override')
|
||||||
self.assertEqual('%s' % value, 'override')
|
self.assertEqual('%s' % value, 'override')
|
||||||
|
|
||||||
value = Value(environ_name='TEST', late_binding=True)
|
value = Value(environ_name='TEST', late_binding=True)
|
||||||
self.assertEqual(type(value), Value)
|
self.assertEqual(type(value), Value)
|
||||||
self.assertEqual(value.value, 'override')
|
self.assertEqual(value.value, 'override')
|
||||||
self.assertEqual(str(value), 'override')
|
self.assertEqual(str(value), 'override')
|
||||||
self.assertEqual('{0}'.format(value), 'override')
|
self.assertEqual(f'{value}', 'override')
|
||||||
self.assertEqual('%s' % value, 'override')
|
self.assertEqual('%s' % value, 'override')
|
||||||
|
|
||||||
self.assertEqual(repr(value), repr('override'))
|
self.assertEqual(repr(value), repr('override'))
|
||||||
|
|
@ -270,9 +271,9 @@ class ValueTests(TestCase):
|
||||||
def test_set_values_default(self):
|
def test_set_values_default(self):
|
||||||
value = SetValue()
|
value = SetValue()
|
||||||
with env(DJANGO_TEST='2,2'):
|
with env(DJANGO_TEST='2,2'):
|
||||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
self.assertEqual(value.setup('TEST'), {'2', '2'})
|
||||||
with env(DJANGO_TEST='2, 2 ,'):
|
with env(DJANGO_TEST='2, 2 ,'):
|
||||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
self.assertEqual(value.setup('TEST'), {'2', '2'})
|
||||||
with env(DJANGO_TEST=''):
|
with env(DJANGO_TEST=''):
|
||||||
self.assertEqual(value.setup('TEST'), set())
|
self.assertEqual(value.setup('TEST'), set())
|
||||||
|
|
||||||
|
|
@ -372,16 +373,23 @@ class ValueTests(TestCase):
|
||||||
value = DatabaseURLValue()
|
value = DatabaseURLValue()
|
||||||
self.assertEqual(value.default, {})
|
self.assertEqual(value.default, {})
|
||||||
with env(DATABASE_URL='sqlite://'):
|
with env(DATABASE_URL='sqlite://'):
|
||||||
self.assertEqual(value.setup('DATABASE_URL'), {
|
settings_value = value.setup('DATABASE_URL')
|
||||||
'default': {
|
# Compare the embedded dicts in the "default" entry so that the difference can be seen if
|
||||||
|
# it fails ... DatabaseURLValue(|) uses an external app that can add additional entries
|
||||||
|
self.assertDictEqual(
|
||||||
|
{
|
||||||
|
'CONN_HEALTH_CHECKS': False,
|
||||||
'CONN_MAX_AGE': 0,
|
'CONN_MAX_AGE': 0,
|
||||||
|
'DISABLE_SERVER_SIDE_CURSORS': False,
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'HOST': '',
|
'HOST': '',
|
||||||
'NAME': ':memory:',
|
'NAME': ':memory:',
|
||||||
'PASSWORD': '',
|
'PASSWORD': '',
|
||||||
'PORT': '',
|
'PORT': '',
|
||||||
'USER': '',
|
'USER': '',
|
||||||
}})
|
},
|
||||||
|
settings_value['default']
|
||||||
|
)
|
||||||
|
|
||||||
def test_database_url_additional_args(self):
|
def test_database_url_additional_args(self):
|
||||||
|
|
||||||
|
|
@ -411,6 +419,7 @@ class ValueTests(TestCase):
|
||||||
'EMAIL_HOST_PASSWORD': 'password',
|
'EMAIL_HOST_PASSWORD': 'password',
|
||||||
'EMAIL_HOST_USER': 'user@domain.com',
|
'EMAIL_HOST_USER': 'user@domain.com',
|
||||||
'EMAIL_PORT': 587,
|
'EMAIL_PORT': 587,
|
||||||
|
'EMAIL_TIMEOUT': None,
|
||||||
'EMAIL_USE_SSL': False,
|
'EMAIL_USE_SSL': False,
|
||||||
'EMAIL_USE_TLS': True})
|
'EMAIL_USE_TLS': True})
|
||||||
with env(EMAIL_URL='console://'):
|
with env(EMAIL_URL='console://'):
|
||||||
|
|
@ -421,6 +430,7 @@ class ValueTests(TestCase):
|
||||||
'EMAIL_HOST_PASSWORD': None,
|
'EMAIL_HOST_PASSWORD': None,
|
||||||
'EMAIL_HOST_USER': None,
|
'EMAIL_HOST_USER': None,
|
||||||
'EMAIL_PORT': None,
|
'EMAIL_PORT': None,
|
||||||
|
'EMAIL_TIMEOUT': None,
|
||||||
'EMAIL_USE_SSL': False,
|
'EMAIL_USE_SSL': False,
|
||||||
'EMAIL_USE_TLS': False})
|
'EMAIL_USE_TLS': False})
|
||||||
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'): # noqa: E501
|
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'): # noqa: E501
|
||||||
|
|
@ -429,7 +439,7 @@ class ValueTests(TestCase):
|
||||||
def test_cache_url_value(self):
|
def test_cache_url_value(self):
|
||||||
cache_setting = {
|
cache_setting = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django_redis.cache.RedisCache',
|
'BACKEND': 'django_redis.cache.RedisCache' if DJANGO_VERSION < (4,) else 'django.core.cache.backends.redis.RedisCache', # noqa: E501
|
||||||
'LOCATION': 'redis://host:6379/1',
|
'LOCATION': 'redis://host:6379/1',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -485,12 +495,12 @@ class ValueTests(TestCase):
|
||||||
self.assertEqual(value.value, set())
|
self.assertEqual(value.value, set())
|
||||||
|
|
||||||
value = SetValue([1, 2])
|
value = SetValue([1, 2])
|
||||||
self.assertEqual(value.default, set([1, 2]))
|
self.assertEqual(value.default, {1, 2})
|
||||||
self.assertEqual(value.value, set([1, 2]))
|
self.assertEqual(value.value, {1, 2})
|
||||||
|
|
||||||
def test_setup_value(self):
|
def test_setup_value(self):
|
||||||
|
|
||||||
class Target(object):
|
class Target:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
value = EmailURLValue()
|
value = EmailURLValue()
|
||||||
|
|
@ -503,6 +513,7 @@ class ValueTests(TestCase):
|
||||||
'EMAIL_HOST_PASSWORD': 'password',
|
'EMAIL_HOST_PASSWORD': 'password',
|
||||||
'EMAIL_HOST_USER': 'user@domain.com',
|
'EMAIL_HOST_USER': 'user@domain.com',
|
||||||
'EMAIL_PORT': 587,
|
'EMAIL_PORT': 587,
|
||||||
|
'EMAIL_TIMEOUT': None,
|
||||||
'EMAIL_USE_SSL': False,
|
'EMAIL_USE_SSL': False,
|
||||||
'EMAIL_USE_TLS': True
|
'EMAIL_USE_TLS': True
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,2 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
91
tox.ini
91
tox.ini
|
|
@ -1,47 +1,72 @@
|
||||||
[tox]
|
[tox]
|
||||||
skipsdist = True
|
skipsdist = true
|
||||||
usedevelop = True
|
usedevelop = true
|
||||||
minversion = 1.8
|
minversion = 1.8
|
||||||
whitelist_externals=sphinx-build
|
|
||||||
envlist =
|
envlist =
|
||||||
flake8-py27,
|
py311-checkqa
|
||||||
flake8-py36,
|
docs
|
||||||
readme-py27,
|
py{39}-dj{32,41,42}
|
||||||
py{27,34,35,36,py}-dj{18,110,111}
|
py{310,py310}-dj{32,41,42,50,main}
|
||||||
py{34,35,36,37}-dj20
|
py{311}-dj{41,42,50,51,main}
|
||||||
py{35,36,37}-dj{21,master}
|
py{312}-dj{50,51,main}
|
||||||
|
py{313}-dj{50,51,main}
|
||||||
|
|
||||||
|
[gh-actions]
|
||||||
|
python =
|
||||||
|
3.9: py39
|
||||||
|
3.10: py310
|
||||||
|
3.11: py311,flake8,readme
|
||||||
|
3.12: py312
|
||||||
|
3.13: py313
|
||||||
|
pypy-3.10: pypy310
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = true
|
usedevelop = true
|
||||||
setenv =
|
setenv =
|
||||||
DJANGO_SETTINGS_MODULE = tests.settings.main
|
DJANGO_SETTINGS_MODULE = tests.settings.main
|
||||||
DJANGO_CONFIGURATION = Test
|
DJANGO_CONFIGURATION = Test
|
||||||
|
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||||
deps =
|
deps =
|
||||||
-rtests/requirements.txt
|
dj32: django~=3.2.9
|
||||||
dj18: django>=1.8,<1.9
|
dj41: django~=4.1.3
|
||||||
dj110: django>=1.10,<1.11
|
dj42: django~=4.2.0
|
||||||
dj111: django>=1.11,<2.0
|
dj50: django~=5.0.0
|
||||||
dj20: django>=2.0a1,<2.1
|
dj51: django~=5.1.0
|
||||||
dj21: django>=2.1a1,<2.2
|
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||||
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
py312: setuptools
|
||||||
|
py312: wheel
|
||||||
|
py313: setuptools
|
||||||
|
py313: wheel
|
||||||
|
coverage
|
||||||
|
coverage_enable_subprocess
|
||||||
|
extras = testing
|
||||||
commands =
|
commands =
|
||||||
python --version
|
python --version
|
||||||
coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
{envbindir}/coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||||
coverage report
|
coverage combine . tests docs
|
||||||
|
coverage report -m --skip-covered
|
||||||
|
coverage xml
|
||||||
|
|
||||||
[testenv:readme-py27]
|
[testenv:py311-checkqa]
|
||||||
commands = python setup.py check -r -s
|
commands =
|
||||||
deps = readme_renderer
|
flake8 {toxinidir}
|
||||||
|
check-manifest -v
|
||||||
|
python setup.py sdist
|
||||||
|
twine check dist/*
|
||||||
|
deps =
|
||||||
|
flake8
|
||||||
|
twine
|
||||||
|
check-manifest
|
||||||
|
|
||||||
[testenv:flake8-py27]
|
[testenv:docs]
|
||||||
commands = flake8 configurations tests
|
setenv =
|
||||||
deps = flake8
|
deps =
|
||||||
|
-r docs/requirements.txt
|
||||||
[testenv:flake8-py36]
|
commands =
|
||||||
commands = flake8 configurations tests
|
sphinx-build \
|
||||||
deps = flake8
|
-b html \
|
||||||
|
-a \
|
||||||
[flake8]
|
-W \
|
||||||
exclude=.tox
|
-n \
|
||||||
ignore=E501,E127,E128,E124
|
docs \
|
||||||
|
docs/_build/html
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue