mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-04-28 02:34:53 +00:00
Compare commits
364 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 | ||
|
|
c804a30aa2 | ||
|
|
fbb0ff8884 | ||
|
|
b1d92cf85d | ||
|
|
c0ac52f948 | ||
|
|
80a648bb03 | ||
|
|
5770f02bab | ||
|
|
537fde7f5b | ||
|
|
b43bf2dd47 | ||
|
|
ba346e2af4 | ||
|
|
2b0d2cee0a | ||
|
|
ee43a1d872 | ||
|
|
1c6fd0f505 | ||
|
|
3883cc4fe4 | ||
|
|
51e2d3e7d2 | ||
|
|
e3b547f5e1 | ||
|
|
d364802a8a | ||
|
|
1060acaf78 | ||
|
|
8f318991c2 | ||
|
|
deb94fd61e | ||
|
|
a7dfb7dfcf | ||
|
|
ece5a35790 | ||
|
|
abfc8a7002 | ||
|
|
6360a34c55 | ||
|
|
6f163727dc | ||
|
|
6ed4559c92 | ||
|
|
4fea22bd16 | ||
|
|
645395af47 | ||
|
|
a606003c77 | ||
|
|
ea8d4deb58 | ||
|
|
9d8f12ea27 | ||
|
|
eb0cde231b | ||
|
|
5dba80b313 | ||
|
|
7aaffd3a95 | ||
|
|
1c5bd06c68 | ||
|
|
9592356572 | ||
|
|
4158480c91 | ||
|
|
cd596f6788 | ||
|
|
7701e86b75 | ||
|
|
e4ab567e1e | ||
|
|
407af2e27d | ||
|
|
7b4df1f6ce | ||
|
|
aba5100e4f | ||
|
|
d66ecc6d6e | ||
|
|
cf3961ea1d | ||
|
|
063bf61a16 | ||
|
|
abe1890c5b | ||
|
|
6d52e560a3 | ||
|
|
4f91fcc6a7 | ||
|
|
0a4cae9c2b | ||
|
|
4615e1e8fb | ||
|
|
320324b511 | ||
|
|
8f68d93a3c | ||
|
|
0aba2276bc | ||
|
|
2ad095ebfa | ||
|
|
376b5947a8 | ||
|
|
261817d965 | ||
|
|
4da8939df0 | ||
|
|
d827664e92 | ||
|
|
5560a21d09 | ||
|
|
8e2eb88ef1 | ||
|
|
1b75984f57 | ||
|
|
067cfaf6e6 | ||
|
|
66bb6a8725 | ||
|
|
4fe1c74b35 | ||
|
|
cd0f1b4d0c | ||
|
|
afb057f33b | ||
|
|
cb3a02ee51 | ||
|
|
06fa8d3eab | ||
|
|
b5e1eb6ade | ||
|
|
3a1a88bd15 | ||
|
|
11990d1db6 | ||
|
|
09eae37b23 | ||
|
|
3fe709196a | ||
|
|
dd128b586d | ||
|
|
85fe4c0ae5 | ||
|
|
0ff4c7612d | ||
|
|
010067b433 | ||
|
|
8ba1804ff2 | ||
|
|
5ece107044 | ||
|
|
e09e1e0f42 | ||
|
|
e332d5eff4 | ||
|
|
d9f60cfaef | ||
|
|
01274d4965 | ||
|
|
6f6930495c | ||
|
|
cc869bf577 | ||
|
|
7a0b2b378a | ||
|
|
c60b7daac4 | ||
|
|
4d35ad346c | ||
|
|
b91ebf083f | ||
|
|
8f617ad98c | ||
|
|
13980b3d80 | ||
|
|
f7629aa84c | ||
|
|
6ce3740365 | ||
|
|
f35e7e57e0 | ||
|
|
9be0c4f700 | ||
|
|
61d162d376 | ||
|
|
8be47c0813 | ||
|
|
ea28a6ebd6 | ||
|
|
c6b3d05f71 | ||
|
|
21d1712143 | ||
|
|
e0e12b1b9f | ||
|
|
2e57cde3ea | ||
|
|
546f488197 | ||
|
|
36a7061a61 | ||
|
|
ae767eaf2d | ||
|
|
d9b2815526 | ||
|
|
a8bf15b358 | ||
|
|
f53e999041 | ||
|
|
ffbf79c95d | ||
|
|
186f50c2a4 | ||
|
|
e20c9b5939 | ||
|
|
8287ab6f7f | ||
|
|
e0a68fdbb6 | ||
|
|
fe96f5b46a | ||
|
|
14dd728ad4 | ||
|
|
8a34b53500 | ||
|
|
f2a46fb009 | ||
|
|
f1f9973547 | ||
|
|
7799241900 | ||
|
|
aba18a4cd8 | ||
|
|
44476bdd1d | ||
|
|
01e3f5837f | ||
|
|
ebc0c51dd8 | ||
|
|
98de57b27e | ||
|
|
cb9f7c36fb | ||
|
|
a8643a1af5 | ||
|
|
9dd8ba2a6e | ||
|
|
41dfcee46c | ||
|
|
3f892f9814 | ||
|
|
c4ba5ca559 | ||
|
|
5d1a8d7887 | ||
|
|
45c9127d4e | ||
|
|
fb9275744e | ||
|
|
ecb3db3762 | ||
|
|
08eabf2ae0 | ||
|
|
82f2514b81 | ||
|
|
6ddd647361 | ||
|
|
8d366fca44 | ||
|
|
55a2cf6a36 | ||
|
|
d97afc163a | ||
|
|
f8ebb564da | ||
|
|
f0d956740c | ||
|
|
738b79bc70 | ||
|
|
7af6d9e7c5 | ||
|
|
1998ba8d8c | ||
|
|
95e378aec7 | ||
|
|
4ec540c32c | ||
|
|
467e4ebe52 | ||
|
|
4018f7b42a | ||
|
|
27eb748d68 | ||
|
|
f5c8b49842 | ||
|
|
8919aa163e | ||
|
|
5279ae4ace | ||
|
|
8a8f99ab68 | ||
|
|
280be2e3e2 | ||
|
|
e1e02c2e8b | ||
|
|
1701b95fa8 | ||
|
|
035c3301a8 | ||
|
|
3ae1e368c2 | ||
|
|
e33eeeb871 | ||
|
|
88ae1f28cd | ||
|
|
d99758d4af | ||
|
|
a55bca2d0c | ||
|
|
97ac529254 | ||
|
|
9da8256062 |
54 changed files with 1951 additions and 1289 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 }}
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,7 +1,12 @@
|
||||||
.coverage
|
.coverage
|
||||||
|
coverage.xml
|
||||||
docs/_build
|
docs/_build
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.egg
|
*.egg
|
||||||
test.db
|
test.db
|
||||||
build/
|
build/
|
||||||
.tox/
|
.tox/
|
||||||
|
htmlcov/
|
||||||
|
*.pyc
|
||||||
|
dist/
|
||||||
|
.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
|
||||||
29
.travis.yml
29
.travis.yml
|
|
@ -1,29 +0,0 @@
|
||||||
language: python
|
|
||||||
env:
|
|
||||||
- TOXENV=py26-1.4.x
|
|
||||||
- TOXENV=py26-1.5.x
|
|
||||||
- TOXENV=py26-1.6.x
|
|
||||||
- TOXENV=py27-1.4.x
|
|
||||||
- TOXENV=py27-1.5.x
|
|
||||||
- TOXENV=py27-1.6.x
|
|
||||||
- TOXENV=py27-1.7.x
|
|
||||||
- TOXENV=py32-1.5.x
|
|
||||||
- TOXENV=py32-1.6.x
|
|
||||||
- TOXENV=py32-1.7.x
|
|
||||||
- TOXENV=py33-1.5.x
|
|
||||||
- TOXENV=py33-1.6.x
|
|
||||||
- TOXENV=py33-1.7.x
|
|
||||||
- TOXENV=pypy-1.4.x
|
|
||||||
- TOXENV=pypy-1.5.x
|
|
||||||
- TOXENV=pypy-1.6.x
|
|
||||||
- TOXENV=pypy-1.7.x
|
|
||||||
- TOXENV=flake827
|
|
||||||
- TOXENV=flake833
|
|
||||||
install:
|
|
||||||
- pip install tox
|
|
||||||
script:
|
|
||||||
- tox
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
templates/1.5.x
|
|
||||||
templates/1.6.x
|
|
||||||
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/
|
||||||
3
CONTRIBUTING.md
Normal file
3
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[](https://jazzband.co/)
|
||||||
|
|
||||||
|
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).
|
||||||
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 manage.py
|
include CONTRIBUTING.md
|
||||||
include tasks.py
|
include LICENSE
|
||||||
recursive-include tests *
|
include README.rst
|
||||||
|
include tox.ini
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
|
recursive-include test_project *
|
||||||
|
recursive-include tests *
|
||||||
|
|
|
||||||
77
README.rst
77
README.rst
|
|
@ -1,15 +1,45 @@
|
||||||
django-configurations
|
django-configurations |latest-version|
|
||||||
=====================
|
======================================
|
||||||
|
|
||||||
.. image:: https://secure.travis-ci.org/jezdez/django-configurations.png
|
|jazzband| |build-status| |codecov| |docs| |python-support| |django-support|
|
||||||
:alt: Build Status
|
|
||||||
:target: https://travis-ci.org/jezdez/django-configurations
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
.. |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
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
@ -17,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
|
||||||
|
|
@ -37,28 +73,29 @@ 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:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 10
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
|
@ -80,7 +117,6 @@ Notice in line 10 we don't use the common tool
|
||||||
The same applies to your **wsgi.py** file, e.g.:
|
The same applies to your **wsgi.py** file, e.g.:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 6
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -94,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,15 +1,31 @@
|
||||||
# flake8: noqa
|
from .base import Configuration # noqa
|
||||||
from .base import Settings, Configuration
|
from .decorators import pristinemethod # noqa
|
||||||
from .decorators import pristinemethod
|
from .version import __version__ # noqa
|
||||||
|
|
||||||
__version__ = '0.8'
|
|
||||||
__all__ = ['Configuration', 'pristinemethod', 'Settings']
|
|
||||||
|
|
||||||
|
|
||||||
def load_ipython_extension(ipython):
|
__all__ = ['Configuration', 'pristinemethod']
|
||||||
# The `ipython` argument is the currently active `InteractiveShell`
|
|
||||||
# instance, which can be used in any way. This allows you to register
|
|
||||||
# new magics or aliases, for example.
|
def _setup():
|
||||||
from . import importer
|
from . import importer
|
||||||
|
|
||||||
importer.install()
|
importer.install()
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
if not apps.ready:
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
|
def load_ipython_extension(ipython):
|
||||||
|
"""
|
||||||
|
The `ipython` argument is the currently active `InteractiveShell`
|
||||||
|
instance, which can be used in any way. This allows you to register
|
||||||
|
new magics or aliases, for example.
|
||||||
|
"""
|
||||||
|
_setup()
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app=None):
|
||||||
|
"""Function used to initialize configurations similar to :func:`.django.setup`."""
|
||||||
|
_setup()
|
||||||
|
|
|
||||||
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,6 +1,6 @@
|
||||||
import warnings
|
import os
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -13,16 +13,13 @@ __all__ = ['Configuration']
|
||||||
install_failure = ("django-configurations settings importer wasn't "
|
install_failure = ("django-configurations settings importer wasn't "
|
||||||
"correctly installed. Please use one of the starter "
|
"correctly installed. Please use one of the starter "
|
||||||
"functions to install it as mentioned in the docs: "
|
"functions to install it as mentioned in the docs: "
|
||||||
"http://django-configurations.readthedocs.org/")
|
"https://django-configurations.readthedocs.io/")
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationBase(type):
|
class ConfigurationBase(type):
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
# also check for "Configuration" here to handle the Settings class
|
if bases not in ((object,), ()) and bases[0].__name__ != 'NewBase':
|
||||||
# below remove it when we deprecate the Settings class
|
|
||||||
if (bases not in ((object,), ()) and
|
|
||||||
bases[0].__name__ not in ('NewBase', 'Configuration')):
|
|
||||||
# if this is actually a subclass in a settings module
|
# if this is actually a subclass in a settings module
|
||||||
# we better check if the importer was correctly installed
|
# we better check if the importer was correctly installed
|
||||||
from . import importer
|
from . import importer
|
||||||
|
|
@ -34,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.
|
||||||
|
|
||||||
|
|
@ -66,9 +98,53 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||||
to the name of the class.
|
to the name of the class.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
DOTENV_LOADED = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_dotenv(cls):
|
||||||
|
"""
|
||||||
|
Pulled from Honcho code with minor updates, reads local default
|
||||||
|
environment variables from a .env file located in the project root
|
||||||
|
or provided directory.
|
||||||
|
|
||||||
|
https://wellfire.co/learn/easier-12-factor-django/
|
||||||
|
https://gist.github.com/bennylope/2999704
|
||||||
|
"""
|
||||||
|
# check if the class has DOTENV set whether with a path or None
|
||||||
|
dotenv = getattr(cls, 'DOTENV', None)
|
||||||
|
|
||||||
|
# if DOTENV is falsy we want to disable it
|
||||||
|
if not dotenv:
|
||||||
|
return
|
||||||
|
|
||||||
|
# now check if we can access the file since we know we really want to
|
||||||
|
try:
|
||||||
|
with open(dotenv) as f:
|
||||||
|
content = f.read()
|
||||||
|
except OSError as e:
|
||||||
|
raise ImproperlyConfigured("Couldn't read .env file "
|
||||||
|
"with the path {}. Error: "
|
||||||
|
"{}".format(dotenv, e)) from e
|
||||||
|
else:
|
||||||
|
for line in content.splitlines():
|
||||||
|
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
||||||
|
if not m1:
|
||||||
|
continue
|
||||||
|
key, val = m1.group(1), m1.group(2)
|
||||||
|
m2 = re.match(r"\A'(.*)'\Z", val)
|
||||||
|
if m2:
|
||||||
|
val = m2.group(1)
|
||||||
|
m3 = re.match(r'\A"(.*)"\Z', val)
|
||||||
|
if m3:
|
||||||
|
val = re.sub(r'\\(.)', r'\1', m3.group(1))
|
||||||
|
os.environ.setdefault(key, val)
|
||||||
|
|
||||||
|
cls.DOTENV_LOADED = dotenv
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pre_setup(cls):
|
def pre_setup(cls):
|
||||||
pass
|
if cls.DOTENV_LOADED is None:
|
||||||
|
cls.load_dotenv()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def post_setup(cls):
|
def post_setup(cls):
|
||||||
|
|
@ -79,13 +155,3 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||||
for name, value in uppercase_attributes(cls).items():
|
for name, value in uppercase_attributes(cls).items():
|
||||||
if isinstance(value, Value):
|
if isinstance(value, Value):
|
||||||
setup_value(cls, name, value)
|
setup_value(cls, name, value)
|
||||||
|
|
||||||
|
|
||||||
class Settings(Configuration):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def pre_setup(cls):
|
|
||||||
# make sure to remove the handling of the Settings class above when deprecating
|
|
||||||
warnings.warn("configurations.Settings was renamed to "
|
|
||||||
"settings.Configuration and will be "
|
|
||||||
"removed in 1.0", PendingDeprecationWarning)
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import imp
|
from importlib.machinery import PathFinder
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from optparse import make_option
|
from optparse import OptionParser, make_option
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.core.management import LaxOptionParser
|
|
||||||
from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
|
from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.management import base
|
||||||
|
|
||||||
from .utils import uppercase_attributes, reraise
|
from .utils import uppercase_attributes, reraise
|
||||||
from .values import Value, setup_value
|
from .values import Value, setup_value
|
||||||
|
|
@ -14,30 +14,44 @@ from .values import Value, setup_value
|
||||||
installed = False
|
installed = False
|
||||||
|
|
||||||
CONFIGURATION_ENVIRONMENT_VARIABLE = 'DJANGO_CONFIGURATION'
|
CONFIGURATION_ENVIRONMENT_VARIABLE = 'DJANGO_CONFIGURATION'
|
||||||
|
CONFIGURATION_ARGUMENT = '--configuration'
|
||||||
|
CONFIGURATION_ARGUMENT_HELP = ('The name of the configuration class to load, '
|
||||||
|
'e.g. "Development". If this isn\'t provided, '
|
||||||
|
'the DJANGO_CONFIGURATION environment '
|
||||||
|
'variable will be used.')
|
||||||
|
|
||||||
|
|
||||||
configuration_options = (
|
configuration_options = (make_option(CONFIGURATION_ARGUMENT,
|
||||||
make_option('--configuration',
|
help=CONFIGURATION_ARGUMENT_HELP),)
|
||||||
help='The name of the configuration class to load, e.g. '
|
|
||||||
'"Development". If this isn\'t provided, the '
|
|
||||||
'DJANGO_CONFIGURATION environment variable will '
|
|
||||||
'be used.'),)
|
|
||||||
|
|
||||||
|
|
||||||
def install(check_options=False):
|
def install(check_options=False):
|
||||||
global installed
|
global installed
|
||||||
if not installed:
|
if not installed:
|
||||||
from django.core.management import base
|
orig_create_parser = base.BaseCommand.create_parser
|
||||||
|
|
||||||
# add the configuration option to all management commands
|
def create_parser(self, prog_name, subcommand):
|
||||||
base.BaseCommand.option_list += configuration_options
|
parser = orig_create_parser(self, prog_name, subcommand)
|
||||||
|
if isinstance(parser, OptionParser):
|
||||||
|
# in case the option_list is set the create_parser
|
||||||
|
# will actually return a OptionParser for backward
|
||||||
|
# compatibility. In that case we should tack our
|
||||||
|
# options on to the end of the parser on the way out.
|
||||||
|
for option in configuration_options:
|
||||||
|
parser.add_option(option)
|
||||||
|
else:
|
||||||
|
# probably argparse, let's not import argparse though
|
||||||
|
parser.add_argument(CONFIGURATION_ARGUMENT,
|
||||||
|
help=CONFIGURATION_ARGUMENT_HELP)
|
||||||
|
return parser
|
||||||
|
|
||||||
importer = ConfigurationImporter(check_options=check_options)
|
base.BaseCommand.create_parser = create_parser
|
||||||
|
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, "
|
||||||
|
|
@ -56,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
|
||||||
|
|
@ -68,13 +82,22 @@ class ConfigurationImporter(object):
|
||||||
return os.environ.get(self.namevar)
|
return os.environ.get(self.namevar)
|
||||||
|
|
||||||
def check_options(self):
|
def check_options(self):
|
||||||
parser = LaxOptionParser(option_list=configuration_options,
|
parser = base.CommandParser(
|
||||||
add_help_option=False)
|
usage="%(prog)s subcommand [options] [args]",
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
parser.add_argument('--settings')
|
||||||
|
parser.add_argument('--pythonpath')
|
||||||
|
parser.add_argument(CONFIGURATION_ARGUMENT,
|
||||||
|
help=CONFIGURATION_ARGUMENT_HELP)
|
||||||
|
|
||||||
|
parser.add_argument('args', nargs='*') # catch-all
|
||||||
try:
|
try:
|
||||||
options, args = parser.parse_args(self.argv)
|
options, args = parser.parse_known_args(self.argv[2:])
|
||||||
if options.configuration:
|
if options.configuration:
|
||||||
os.environ[self.namevar] = options.configuration
|
os.environ[self.namevar] = options.configuration
|
||||||
except:
|
base.handle_default_options(options)
|
||||||
|
except base.CommandError:
|
||||||
pass # Ignore any option errors at this point.
|
pass # Ignore any option errors at this point.
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
|
@ -87,72 +110,71 @@ class ConfigurationImporter(object):
|
||||||
if len(self.argv) > 1:
|
if len(self.argv) > 1:
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from django.utils.termcolors import colorize
|
from django.utils.termcolors import colorize
|
||||||
# Django >= 1.7 supports hiding the colorization in the shell
|
from django.core.management.color import no_style
|
||||||
try:
|
|
||||||
from django.core.management.color import no_style
|
|
||||||
except ImportError:
|
|
||||||
no_style = None
|
|
||||||
|
|
||||||
if no_style is not None and '--no-color' in self.argv:
|
if '--no-color' in self.argv:
|
||||||
stylize = no_style()
|
stylize = no_style()
|
||||||
else:
|
else:
|
||||||
stylize = lambda text: colorize(text, fg='green')
|
def stylize(text):
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ from . import importer
|
||||||
|
|
||||||
importer.install(check_options=True)
|
importer.install(check_options=True)
|
||||||
|
|
||||||
from django.core.management import execute_from_command_line # noqa
|
from django.core.management import (execute_from_command_line, # noqa
|
||||||
|
call_command)
|
||||||
|
|
|
||||||
12
configurations/sphinx.py
Normal file
12
configurations/sphinx.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from . import _setup, __version__
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app=None):
|
||||||
|
"""
|
||||||
|
The callback for Sphinx that acts as a Sphinx extension.
|
||||||
|
|
||||||
|
Add ``'configurations'`` to the ``extensions`` config variable
|
||||||
|
in your docs' ``conf.py``.
|
||||||
|
"""
|
||||||
|
_setup()
|
||||||
|
return {'version': __version__, 'parallel_read_safe': True}
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
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
|
|
||||||
from django.utils.importlib import import_module
|
|
||||||
|
|
||||||
|
|
||||||
def isuppercase(name):
|
def isuppercase(name):
|
||||||
|
|
@ -10,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=''):
|
||||||
|
|
@ -22,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
|
||||||
|
|
@ -58,5 +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
|
||||||
|
def getargspec(func):
|
||||||
|
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||||
|
if inspect.ismethod(func):
|
||||||
|
func = func.__func__
|
||||||
|
if type(func) is partial:
|
||||||
|
orig_func = func.func
|
||||||
|
argspec = getargspec(orig_func)
|
||||||
|
args = list(argspec[0])
|
||||||
|
defaults = list(argspec[3] or ())
|
||||||
|
kwoargs = list(argspec[4])
|
||||||
|
kwodefs = dict(argspec[5] or {})
|
||||||
|
if func.args:
|
||||||
|
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)
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,61 @@ 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
|
from .utils import getargspec
|
||||||
|
|
||||||
|
|
||||||
def setup_value(target, name, value):
|
def setup_value(target, name, value):
|
||||||
actual_value = value.setup(name)
|
actual_value = value.setup(name)
|
||||||
# overwriting the original Value class with the result
|
# overwriting the original Value class with the result
|
||||||
setattr(target, name, actual_value)
|
setattr(target, name, value.value)
|
||||||
if value.multiple:
|
if value.multiple:
|
||||||
for multiple_name, multiple_value in actual_value.items():
|
for multiple_name, multiple_value in actual_value.items():
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
multiple = False
|
multiple = False
|
||||||
|
late_binding = False
|
||||||
|
environ_required = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
value = self.default
|
||||||
|
if not hasattr(self, '_value') and self.environ_name:
|
||||||
|
self.setup(self.environ_name)
|
||||||
|
if hasattr(self, '_value'):
|
||||||
|
value = self._value
|
||||||
|
return value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
checks if the creation can end up directly in the final value.
|
||||||
|
That is the case whenever environ = False or environ_name is given.
|
||||||
|
"""
|
||||||
|
instance = object.__new__(cls)
|
||||||
|
if 'late_binding' in kwargs:
|
||||||
|
instance.late_binding = kwargs.get('late_binding')
|
||||||
|
if not instance.late_binding:
|
||||||
|
instance.__init__(*args, **kwargs)
|
||||||
|
if ((instance.environ and instance.environ_name)
|
||||||
|
or (not instance.environ and instance.default)):
|
||||||
|
instance = instance.setup(instance.environ_name)
|
||||||
|
return instance
|
||||||
|
|
||||||
def __init__(self, default=None, environ=True, environ_name=None,
|
def __init__(self, default=None, environ=True, environ_name=None,
|
||||||
environ_prefix='DJANGO', *args, **kwargs):
|
environ_prefix='DJANGO', environ_required=False,
|
||||||
if isinstance(default, Value):
|
*args, **kwargs):
|
||||||
|
if isinstance(default, Value) and default.default is not None:
|
||||||
self.default = copy.copy(default.default)
|
self.default = copy.copy(default.default)
|
||||||
else:
|
else:
|
||||||
self.default = default
|
self.default = default
|
||||||
|
|
@ -38,36 +69,55 @@ class Value(object):
|
||||||
environ_prefix = environ_prefix[:-1]
|
environ_prefix = environ_prefix[:-1]
|
||||||
self.environ_prefix = environ_prefix
|
self.environ_prefix = environ_prefix
|
||||||
self.environ_name = environ_name
|
self.environ_name = environ_name
|
||||||
|
self.environ_required = environ_required
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Value default: {0}>".format(self.default)
|
return repr(self.value)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self.value)
|
||||||
|
|
||||||
|
# Compatibility with python 2
|
||||||
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
|
def full_environ_name(self, name):
|
||||||
|
if self.environ_name:
|
||||||
|
environ_name = self.environ_name
|
||||||
|
else:
|
||||||
|
environ_name = name.upper()
|
||||||
|
if self.environ_prefix:
|
||||||
|
environ_name = f'{self.environ_prefix}_{environ_name}'
|
||||||
|
return environ_name
|
||||||
|
|
||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
value = self.default
|
value = self.default
|
||||||
if self.environ:
|
if self.environ:
|
||||||
if self.environ_name is None:
|
full_environ_name = self.full_environ_name(name)
|
||||||
environ_name = name.upper()
|
|
||||||
else:
|
|
||||||
environ_name = self.environ_name
|
|
||||||
if self.environ_prefix:
|
|
||||||
full_environ_name = '{0}_{1}'.format(self.environ_prefix,
|
|
||||||
environ_name)
|
|
||||||
else:
|
|
||||||
full_environ_name = environ_name
|
|
||||||
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:
|
||||||
|
raise ValueError('Value {!r} is required to be set as the '
|
||||||
|
'environment variable {!r}'
|
||||||
|
.format(name, full_environ_name))
|
||||||
|
self.value = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,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):
|
||||||
|
|
@ -89,27 +139,39 @@ 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:
|
||||||
|
arg_names = getargspec(self._caster)[0]
|
||||||
|
self._params = {name: kwargs[name] for name in arg_names if name in kwargs}
|
||||||
|
except TypeError:
|
||||||
|
self._params = {}
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
try:
|
try:
|
||||||
return self._caster(value)
|
if self._params:
|
||||||
|
return self._caster(value, **self._params)
|
||||||
|
else:
|
||||||
|
return self._caster(value)
|
||||||
except self.exception:
|
except self.exception:
|
||||||
raise ValueError(self.message.format(value))
|
raise ValueError(self.message.format(value))
|
||||||
|
|
||||||
|
|
@ -118,6 +180,15 @@ class IntegerValue(CastingMixin, Value):
|
||||||
caster = int
|
caster = int
|
||||||
|
|
||||||
|
|
||||||
|
class PositiveIntegerValue(IntegerValue):
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
int_value = super().to_python(value)
|
||||||
|
if int_value < 0:
|
||||||
|
raise ValueError(self.message.format(value))
|
||||||
|
return int_value
|
||||||
|
|
||||||
|
|
||||||
class FloatValue(CastingMixin, Value):
|
class FloatValue(CastingMixin, Value):
|
||||||
caster = float
|
caster = float
|
||||||
|
|
||||||
|
|
@ -127,89 +198,134 @@ class DecimalValue(CastingMixin, Value):
|
||||||
exception = decimal.InvalidOperation
|
exception = decimal.InvalidOperation
|
||||||
|
|
||||||
|
|
||||||
class ListValue(Value):
|
class SequenceValue(Value):
|
||||||
|
"""
|
||||||
|
Common code for sequence-type values (lists and tuples).
|
||||||
|
Do not use this class directly. Instead use a subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Specify this value in subclasses, e.g. with 'list' or 'tuple'
|
||||||
|
sequence_type = None
|
||||||
converter = None
|
converter = None
|
||||||
message = 'Cannot interpret list item {0!r} in list {1!r}'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
msg = 'Cannot interpret {0} item {{0!r}} in {0} {{1!r}}'
|
||||||
|
self.message = msg.format(self.sequence_type.__name__)
|
||||||
self.separator = kwargs.pop('separator', ',')
|
self.separator = kwargs.pop('separator', ',')
|
||||||
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(ListValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# make sure the default is a list
|
# make sure the default is the correct sequence type
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
self.default = []
|
self.default = self.sequence_type()
|
||||||
|
else:
|
||||||
|
self.default = self.sequence_type(self.default)
|
||||||
# initial conversion
|
# initial conversion
|
||||||
if self.converter is not None:
|
if self.converter is not None:
|
||||||
self.default = [self.converter(value) for value in self.default]
|
self.default = self._convert(self.default)
|
||||||
|
|
||||||
|
def _convert(self, sequence):
|
||||||
|
converted_values = []
|
||||||
|
for value in sequence:
|
||||||
|
try:
|
||||||
|
converted_values.append(self.converter(value))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise ValueError(self.message.format(value, value))
|
||||||
|
return self.sequence_type(converted_values)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
split_value = [v.strip() for v in value.strip().split(self.separator)]
|
split_value = [v.strip() for v in value.strip().split(self.separator)]
|
||||||
# removing empty items
|
# removing empty items
|
||||||
value_list = filter(None, split_value)
|
value_list = self.sequence_type(filter(None, split_value))
|
||||||
if self.converter is None:
|
if self.converter is not None:
|
||||||
return list(value_list)
|
value_list = self._convert(value_list)
|
||||||
|
return self.sequence_type(value_list)
|
||||||
|
|
||||||
converted_values = []
|
|
||||||
for list_value in value_list:
|
class ListValue(SequenceValue):
|
||||||
try:
|
sequence_type = list
|
||||||
converted_values.append(self.converter(list_value))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise ValueError(self.message.format(list_value, value))
|
class TupleValue(SequenceValue):
|
||||||
return converted_values
|
sequence_type = tuple
|
||||||
|
|
||||||
|
|
||||||
|
class SingleNestedSequenceValue(SequenceValue):
|
||||||
|
"""
|
||||||
|
Common code for nested sequences (list of lists, or tuple of tuples).
|
||||||
|
Do not use this class directly. Instead use a subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.seq_separator = kwargs.pop('seq_separator', ';')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _convert(self, items):
|
||||||
|
# This could receive either a bare or nested sequence
|
||||||
|
if items and isinstance(items[0], self.sequence_type):
|
||||||
|
converted_sequences = [
|
||||||
|
super(SingleNestedSequenceValue, self)._convert(i) for i in items
|
||||||
|
]
|
||||||
|
return self.sequence_type(converted_sequences)
|
||||||
|
return self.sequence_type(super()._convert(items))
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
split_value = [
|
||||||
|
v.strip() for v in value.strip().split(self.seq_separator)
|
||||||
|
]
|
||||||
|
# Remove empty items
|
||||||
|
filtered = self.sequence_type(filter(None, split_value))
|
||||||
|
sequence = [
|
||||||
|
super(SingleNestedSequenceValue, self).to_python(f) for f in filtered
|
||||||
|
]
|
||||||
|
return self.sequence_type(sequence)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleNestedListValue(SingleNestedSequenceValue):
|
||||||
|
sequence_type = list
|
||||||
|
|
||||||
|
|
||||||
|
class SingleNestedTupleValue(SingleNestedSequenceValue):
|
||||||
|
sequence_type = tuple
|
||||||
|
|
||||||
|
|
||||||
class BackendsValue(ListValue):
|
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
|
||||||
|
|
||||||
|
|
||||||
class TupleValue(ListValue):
|
|
||||||
message = 'Cannot interpret tuple item {0!r} in tuple {1!r}'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(TupleValue, self).__init__(*args, **kwargs)
|
|
||||||
if self.default is None:
|
|
||||||
self.default = ()
|
|
||||||
else:
|
|
||||||
self.default = tuple(self.default)
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return tuple(super(TupleValue, self).to_python(value))
|
|
||||||
|
|
||||||
|
|
||||||
class SetValue(ListValue):
|
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:
|
||||||
|
|
@ -221,18 +337,23 @@ 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))
|
||||||
self.to_python(self.default)
|
if self.default:
|
||||||
|
self.to_python(self.default)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
try:
|
try:
|
||||||
|
|
@ -264,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)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -284,27 +405,29 @@ class SecretValue(Value):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs['environ'] = True
|
kwargs['environ'] = True
|
||||||
super(SecretValue, self).__init__(*args, **kwargs)
|
kwargs['environ_required'] = True
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
||||||
caster = 'dj_email_url.parse'
|
caster = 'dj_email_url.parse'
|
||||||
message = 'Cannot interpret email URL value {0!r}'
|
message = 'Cannot interpret email URL value {0!r}'
|
||||||
|
late_binding = True
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
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:
|
||||||
|
|
@ -319,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}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -334,15 +457,18 @@ class DatabaseURLValue(DictBackendMixin, CastingMixin, Value):
|
||||||
caster = 'dj_database_url.parse'
|
caster = 'dj_database_url.parse'
|
||||||
message = 'Cannot interpret database URL value {0!r}'
|
message = 'Cannot interpret database URL value {0!r}'
|
||||||
environ_name = 'DATABASE_URL'
|
environ_name = 'DATABASE_URL'
|
||||||
|
late_binding = True
|
||||||
|
|
||||||
|
|
||||||
class CacheURLValue(DictBackendMixin, CastingMixin, Value):
|
class CacheURLValue(DictBackendMixin, CastingMixin, Value):
|
||||||
caster = 'django_cache_url.parse'
|
caster = 'django_cache_url.parse'
|
||||||
message = 'Cannot interpret cache URL value {0!r}'
|
message = 'Cannot interpret cache URL value {0!r}'
|
||||||
environ_name = 'CACHE_URL'
|
environ_name = 'CACHE_URL'
|
||||||
|
late_binding = True
|
||||||
|
|
||||||
|
|
||||||
class SearchURLValue(DictBackendMixin, CastingMixin, Value):
|
class SearchURLValue(DictBackendMixin, CastingMixin, Value):
|
||||||
caster = 'dj_search_url.parse'
|
caster = 'dj_search_url.parse'
|
||||||
message = 'Cannot interpret Search URL value {0!r}'
|
message = 'Cannot interpret Search URL value {0!r}'
|
||||||
environ_name = 'SEARCH_URL'
|
environ_name = 'SEARCH_URL'
|
||||||
|
late_binding = True
|
||||||
|
|
|
||||||
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."
|
|
||||||
160
docs/changes.rst
160
docs/changes.rst
|
|
@ -3,6 +3,166 @@
|
||||||
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)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support of Python 3.3.
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support of Django 1.9.
|
||||||
|
|
||||||
|
- Add support for Django 2.1.
|
||||||
|
|
||||||
|
- Add ``PositiveIntegerValue`` configuration value.
|
||||||
|
|
||||||
|
- Fix ``bool(BooleanValue)`` to behave as one would expect (e.g.
|
||||||
|
``bool(BooleanValue(False))`` returns ``False``).
|
||||||
|
|
||||||
|
- Miscellaneous documentation improvements and bug fixes.
|
||||||
|
|
||||||
|
v2.0 (2016-07-29)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support of Python 2.6 and 3.2
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Drop support of Django < 1.8
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Moved sphinx callable has been moved from
|
||||||
|
``configurations`` to ``configurations.sphinx``.
|
||||||
|
|
||||||
|
- **BACKWARD INCOMPATIBLE** Removed the previously deprecated
|
||||||
|
``configurations.Settings`` class in favor of the
|
||||||
|
``configurations.Configuration`` added in 0.4. This removal was planned for
|
||||||
|
the 1.0 release and is now finally enacted.
|
||||||
|
|
||||||
|
- Add multiprocessing support for sphinx integration
|
||||||
|
|
||||||
|
- Fix a RemovedInDjango19Warning warning
|
||||||
|
|
||||||
|
v1.0 (2016-01-04)
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- Project has moved to `Jazzband <https://jazzband.co/>`_. See guidelines for
|
||||||
|
contributing.
|
||||||
|
|
||||||
|
- Support for Django 1.8 and above.
|
||||||
|
|
||||||
|
- Allow ``Value`` classes to be used outside of ``Configuration`` classes. (#62)
|
||||||
|
|
||||||
|
- Fixed "Value with ValidationMixin will raise ValueError if no default assigned". (#69)
|
||||||
|
|
||||||
|
- Fixed wrong behaviour when assigning BooleanValue. (#83)
|
||||||
|
|
||||||
|
- Add ability to programmatically call Django commands from configurations using
|
||||||
|
``call_command``.
|
||||||
|
|
||||||
|
- Added SingleNestedTupleValue and SingleNestedListValue classes. (#85)
|
||||||
|
|
||||||
|
- Several other miscellaneous bugfixes.
|
||||||
|
|
||||||
v0.8 (2014-01-16)
|
v0.8 (2014-01-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
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,62 @@
|
||||||
Cookbook
|
Cookbook
|
||||||
========
|
========
|
||||||
|
|
||||||
|
Calling a Django management command
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.9
|
||||||
|
|
||||||
|
If you want to call a Django management command programmatically, say
|
||||||
|
from a script outside of your usual Django code, you can use the
|
||||||
|
equivalent of Django's :func:`~django.core.management.call_command`
|
||||||
|
function with django-configurations, too.
|
||||||
|
|
||||||
|
Simply import it from ``configurations.management`` instead:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 1
|
||||||
|
|
||||||
|
from configurations.management import call_command
|
||||||
|
|
||||||
|
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
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
@ -14,9 +70,11 @@ Imagine for example you want to set a few environment variables, all you
|
||||||
have to do is to create a directory with files that have capitalized names
|
have to do is to create a directory with files that have capitalized names
|
||||||
and contain the values you want to set.
|
and contain the values you want to set.
|
||||||
|
|
||||||
Example::
|
Example:
|
||||||
|
|
||||||
$ tree mysite_env/
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ tree --noreport mysite_env/
|
||||||
mysite_env/
|
mysite_env/
|
||||||
├── DJANGO_SETTINGS_MODULE
|
├── DJANGO_SETTINGS_MODULE
|
||||||
├── DJANGO_DEBUG
|
├── DJANGO_DEBUG
|
||||||
|
|
@ -24,13 +82,13 @@ 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.:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
$ envdir mysite_env python manage.py runserver
|
$ envdir mysite_env python manage.py runserver
|
||||||
|
|
||||||
|
|
@ -39,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
|
||||||
|
|
@ -48,38 +142,27 @@ You can use a special Django project template that is a copy of the one
|
||||||
included in Django 1.5.x and 1.6.x. The following examples assumes you're
|
included in Django 1.5.x and 1.6.x. The following examples assumes you're
|
||||||
using pip_ to install packages.
|
using pip_ to install packages.
|
||||||
|
|
||||||
Django 1.5.x
|
Django 1.8.x
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
First install Django 1.5.x and django-configurations::
|
First install Django 1.8.x and django-configurations:
|
||||||
|
|
||||||
pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.5.x/requirements.txt
|
.. code-block:: console
|
||||||
|
|
||||||
Then create your new Django project with the provided template::
|
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||||
|
|
||||||
django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.5.x.zip
|
Or Django 1.8:
|
||||||
|
|
||||||
See the repository of the template for more information:
|
.. code-block:: console
|
||||||
|
|
||||||
https://github.com/jezdez/django-configurations/tree/templates/1.5.x
|
$ python -m django startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||||
|
|
||||||
Django 1.6.x
|
Now you have a default Django 1.8.x project in the ``mysite``
|
||||||
^^^^^^^^^^^^
|
|
||||||
|
|
||||||
First install Django 1.6.x and django-configurations::
|
|
||||||
|
|
||||||
pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.6.x/requirements.txt
|
|
||||||
|
|
||||||
Or Django 1.6::
|
|
||||||
|
|
||||||
django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.6.x.zip
|
|
||||||
|
|
||||||
Now you have a default Django 1.5.x or 1.6.x project in the ``mysite``
|
|
||||||
directory that uses django-configurations.
|
directory that uses django-configurations.
|
||||||
|
|
||||||
See the repository of the template for more information:
|
See the repository of the template for more information:
|
||||||
|
|
||||||
https://github.com/jezdez/django-configurations/tree/templates/1.6.x
|
https://github.com/jazzband/django-configurations/tree/templates/1.8.x
|
||||||
|
|
||||||
.. _pip: http://pip-installer.org/
|
.. _pip: http://pip-installer.org/
|
||||||
|
|
||||||
|
|
@ -90,12 +173,15 @@ Celery
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
Given Celery's way to load Django settings in worker processes you should
|
Given Celery's way to load Django settings in worker processes you should
|
||||||
probably just add the following to the **beginning** of your settings module::
|
probably just add the following to the **beginning** of your settings module:
|
||||||
|
|
||||||
from configurations import importer
|
.. code-block:: python
|
||||||
importer.install()
|
|
||||||
|
|
||||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities.
|
import configurations
|
||||||
|
configurations.setup()
|
||||||
|
|
||||||
|
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
|
||||||
|
This will also call ``django.setup()``.
|
||||||
|
|
||||||
>= 3.1
|
>= 3.1
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
@ -121,8 +207,8 @@ Celery's documentation`_:
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
|
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
|
||||||
|
|
||||||
from configurations import importer
|
import configurations
|
||||||
importer.install()
|
configurations.setup()
|
||||||
|
|
||||||
app = Celery('mysite')
|
app = Celery('mysite')
|
||||||
app.config_from_object('django.conf:settings')
|
app.config_from_object('django.conf:settings')
|
||||||
|
|
@ -145,46 +231,55 @@ enable an extension in your IPython configuration. See the IPython
|
||||||
documentation for how to create and `manage your IPython profile`_ correctly.
|
documentation for how to create and `manage your IPython profile`_ correctly.
|
||||||
|
|
||||||
Here's a quick how-to in case you don't have a profile yet. Type in your
|
Here's a quick how-to in case you don't have a profile yet. Type in your
|
||||||
command line shell::
|
command line shell:
|
||||||
|
|
||||||
ipython profile create
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ ipython profile create
|
||||||
|
|
||||||
Then let IPython show you where the configuration file ``ipython_config.py``
|
Then let IPython show you where the configuration file ``ipython_config.py``
|
||||||
was created::
|
was created:
|
||||||
|
|
||||||
ipython locate profile
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ ipython locate profile
|
||||||
|
|
||||||
That should print a directory path where you can find the
|
That should print a directory path where you can find the
|
||||||
``ipython_config.py`` configuration file. Now open that file and extend the
|
``ipython_config.py`` configuration file. Now open that file and extend the
|
||||||
``c.InteractiveShellApp.extensions`` configuration value. It may be commented
|
``c.InteractiveShellApp.extensions`` configuration value. It may be commented
|
||||||
out from when IPython created the file or it may not exist in the file at all.
|
out from when IPython created the file or it may not exist in the file at all.
|
||||||
In either case make sure it's not a Python comment anymore and reads like this::
|
In either case make sure it's not a Python comment anymore and reads like this:
|
||||||
|
|
||||||
# A list of dotted module names of IPython extensions to load.
|
.. code-block:: python
|
||||||
c.InteractiveShellApp.extensions = [
|
|
||||||
# .. your other extensions if available
|
# A list of dotted module names of IPython extensions to load.
|
||||||
'configurations',
|
c.InteractiveShellApp.extensions = [
|
||||||
]
|
# .. your other extensions if available
|
||||||
|
'configurations',
|
||||||
|
]
|
||||||
|
|
||||||
That will tell IPython to load django-configurations correctly on startup.
|
That will tell IPython to load django-configurations correctly on startup.
|
||||||
It also works with django-extensions's shell_plus_ management command.
|
It also works with django-extensions's shell_plus_ management command.
|
||||||
|
|
||||||
.. _IPython: http://ipython.org/
|
.. _IPython: http://ipython.org/
|
||||||
.. _`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: http://django-extensions.readthedocs.org/en/latest/shell_plus.html
|
.. _shell_plus: https://django-extensions.readthedocs.io/en/latest/shell_plus.html
|
||||||
|
|
||||||
|
|
||||||
FastCGI
|
FastCGI
|
||||||
-------
|
-------
|
||||||
|
|
||||||
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
|
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
|
||||||
allowed to us Django's runfcgi_ management command (that would automatically
|
allowed to use Django's runfcgi_ management command (that would automatically
|
||||||
handle the setup for your if you've followed the quickstart guide above), make
|
handle the setup for your if you've followed the quickstart guide above), make
|
||||||
sure to use something like the following script::
|
sure to use something like the following script:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
|
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
|
||||||
|
|
||||||
|
|
@ -196,3 +291,60 @@ As you can see django-configurations provides a helper module
|
||||||
``configurations.fastcgi`` that handles the setup of your configurations.
|
``configurations.fastcgi`` that handles the setup of your configurations.
|
||||||
|
|
||||||
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
|
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
|
||||||
|
|
||||||
|
|
||||||
|
Sphinx
|
||||||
|
------
|
||||||
|
|
||||||
|
In case you would like to user the amazing `autodoc` feature of the
|
||||||
|
documentation tool `Sphinx <http://sphinx-doc.org/>`_, you need add
|
||||||
|
django-configurations to your ``extensions`` config variable and set
|
||||||
|
the environment variable accordingly:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 2-3, 12
|
||||||
|
|
||||||
|
# My custom Django environment variables
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||||
|
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
# ...
|
||||||
|
'configurations.sphinx',
|
||||||
|
]
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
|
||||||
|
Please note that the sphinx callable has been moved from ``configurations`` to
|
||||||
|
``configurations.sphinx``.
|
||||||
|
|
||||||
|
|
||||||
|
Channels
|
||||||
|
--------
|
||||||
|
|
||||||
|
If you want to deploy a project that uses the Django channels with
|
||||||
|
`Daphne <http://github.com/django/daphne/>`_ as the
|
||||||
|
`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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import os
|
||||||
|
from configurations import importer
|
||||||
|
from channels.asgi import get_channel_layer
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
|
||||||
|
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')
|
||||||
|
|
||||||
|
importer.install()
|
||||||
|
|
||||||
|
channel_layer = get_channel_layer()
|
||||||
|
|
||||||
|
That will properly load your django-configurations powered settings.
|
||||||
|
|
|
||||||
|
|
@ -93,8 +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/jezdez/django-configurations/issues
|
- https://github.com/jazzband/django-configurations/issues
|
||||||
|
|
||||||
Thanks! Feel free to leave a tip, too:
|
Thanks!
|
||||||
|
|
||||||
https://www.gittip.com/jezdez/
|
|
||||||
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,31 +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
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
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):
|
||||||
|
SOME_VALUE = values.Value(None, environ_prefix=None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def SOME_CONFIG(self):
|
||||||
|
return {
|
||||||
|
'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
|
||||||
|
|
||||||
|
|
@ -54,24 +79,28 @@ setting other values, e.g.::
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def LANGUAGES(self):
|
def LANGUAGES(self):
|
||||||
return Configuration.LANGUAGES + (('tlh', 'Klingon'),)
|
return list(Configuration.LANGUAGES) + [('tlh', 'Klingon')]
|
||||||
|
|
||||||
Configuration mixins
|
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
|
||||||
|
|
||||||
class Prod(Configuration, FullPageCaching):
|
class Prod(FullPageCaching, Configuration):
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
|
|
@ -81,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
|
||||||
|
|
||||||
|
|
@ -92,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
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
@ -107,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
|
||||||
|
|
@ -138,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
|
||||||
|
|
||||||
|
|
@ -150,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
|
||||||
|
|
@ -165,5 +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
|
||||||
|
------------------
|
||||||
|
|
||||||
|
If you want to run scripts outside of your project you need to add
|
||||||
|
these lines on top of your file:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import configurations
|
||||||
|
configurations.setup()
|
||||||
|
|
|
||||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Sphinx>4
|
||||||
|
furo
|
||||||
|
docutils
|
||||||
124
docs/values.rst
124
docs/values.rst
|
|
@ -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'``.
|
||||||
|
|
@ -120,6 +122,23 @@ instead of using the name of the :class:`~Value` instance.::
|
||||||
class Dev(Configuration):
|
class Dev(Configuration):
|
||||||
TIME_ZONE = values.Value('UTC', environ_name='MYSITE_TZ')
|
TIME_ZONE = values.Value('UTC', environ_name='MYSITE_TZ')
|
||||||
|
|
||||||
|
Allow final value to be used outside the configuration context
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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
|
||||||
|
context:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> type(values.Value([]))
|
||||||
|
<class 'configurations.values.Value'>
|
||||||
|
>>> type(values.Value([], environ_name="FOOBAR"))
|
||||||
|
<class 'list'>
|
||||||
|
|
||||||
|
This can also be achieved when using ``environ=False`` and providing a
|
||||||
|
default value.
|
||||||
|
|
||||||
Custom environment variable prefixes
|
Custom environment variable prefixes
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -139,17 +158,19 @@ the prefix.
|
||||||
``Value`` class
|
``Value`` class
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. class:: Value(default, [environ=True, environ_name=None, environ_prefix='DJANGO'])
|
.. class:: Value(default, [environ=True, environ_name=None, environ_prefix='DJANGO', environ_required=False])
|
||||||
|
|
||||||
The ``Value`` class takes one required and several optional parameters.
|
The ``Value`` class takes one required and several optional parameters.
|
||||||
|
|
||||||
: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: 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
|
||||||
|
|
||||||
The ``default`` parameter is effectively the value the setting has
|
The ``default`` parameter is effectively the value the setting has
|
||||||
right now in your ``settings.py``.
|
right now in your ``settings.py``.
|
||||||
|
|
@ -213,6 +234,16 @@ Type values
|
||||||
|
|
||||||
MYSITE_CACHE_TIMEOUT = values.IntegerValue(3600)
|
MYSITE_CACHE_TIMEOUT = values.IntegerValue(3600)
|
||||||
|
|
||||||
|
.. class:: PositiveIntegerValue
|
||||||
|
|
||||||
|
A :class:`~Value` subclass that handles positive integer values.
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
MYSITE_WORKER_POOL = values.PositiveIntegerValue(8)
|
||||||
|
|
||||||
.. class:: FloatValue
|
.. class:: FloatValue
|
||||||
|
|
||||||
A :class:`~Value` subclass that handles float values.
|
A :class:`~Value` subclass that handles float values.
|
||||||
|
|
@ -229,9 +260,13 @@ 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:`~Value` subclass that handles list values.
|
A :class:`~SequenceValue` subclass that handles list values.
|
||||||
|
|
||||||
:param separator: the separator to split environment variables with
|
:param separator: the separator to split environment variables with
|
||||||
:param converter: the optional converter callable to apply for each list
|
:param converter: the optional converter callable to apply for each list
|
||||||
|
|
@ -252,21 +287,25 @@ 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
|
||||||
|
|
||||||
A :class:`~Value` subclass that handles tuple values.
|
A :class:`~SequenceValue` subclass that handles tuple values.
|
||||||
|
|
||||||
:param separator: the separator to split environment variables with
|
:param separator: the separator to split environment variables with
|
||||||
:param converter: the optional converter callable to apply for each tuple
|
:param converter: the optional converter callable to apply for each tuple
|
||||||
|
|
@ -274,6 +313,43 @@ 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])
|
||||||
|
|
||||||
|
A :class:`~SingleNestedSequenceValue` subclass that handles single nested tuple values,
|
||||||
|
e.g. ``((a, b), (c, d))``.
|
||||||
|
|
||||||
|
:param seq_separator: the separator to split each tuple with
|
||||||
|
:param separator: the separator to split the inner tuple contents with
|
||||||
|
:param converter: the optional converter callable to apply for each inner
|
||||||
|
tuple item
|
||||||
|
|
||||||
|
Useful for ADMINS, MANAGERS, and the like. For example::
|
||||||
|
|
||||||
|
ADMINS = SingleNestedTupleValue((
|
||||||
|
('John', 'jcleese@site.com'),
|
||||||
|
('Eric', 'eidle@site.com'),
|
||||||
|
))
|
||||||
|
|
||||||
|
Override using environment variables like this::
|
||||||
|
|
||||||
|
DJANGO_ADMINS=Terry,tjones@site.com;Graham,gchapman@site.com
|
||||||
|
|
||||||
|
.. class:: SingleNestedListValue(default, [seq_separator=';', separator=',', converter=None])
|
||||||
|
|
||||||
|
A :class:`~SingleNestedSequenceValue` subclass that handles single nested list values,
|
||||||
|
e.g. ``[[a, b], [c, d]]``.
|
||||||
|
|
||||||
|
:param seq_separator: the separator to split each list with
|
||||||
|
:param separator: the separator to split the inner list contents with
|
||||||
|
:param converter: the optional converter callable to apply for each inner
|
||||||
|
list item
|
||||||
|
|
||||||
|
See the :class:`~SingleNestedTupleValue` examples above.
|
||||||
|
|
||||||
.. class:: SetValue
|
.. class:: SetValue
|
||||||
|
|
||||||
A :class:`~Value` subclass that handles set values.
|
A :class:`~Value` subclass that handles set values.
|
||||||
|
|
@ -294,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
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -446,7 +526,7 @@ URL-based values
|
||||||
|
|
||||||
.. _`dj-email-url`: https://pypi.python.org/pypi/dj-email-url/
|
.. _`dj-email-url`: https://pypi.python.org/pypi/dj-email-url/
|
||||||
|
|
||||||
.. class:: SearchURLValue(default, [environ=True, environ_name='EMAIL_URL', environ_prefix=None])
|
.. class:: SearchURLValue(default, [environ=True, environ_name='SEARCH_URL', environ_prefix=None])
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
|
|
||||||
|
|
@ -487,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',
|
||||||
|
|
@ -501,10 +581,16 @@ 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
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
|
||||||
|
This value class has the ``environ_required`` parameter turned to
|
||||||
|
``True``.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
SECRET_KEY = values.SecretValue()
|
SECRET_KEY = values.SecretValue()
|
||||||
|
|
@ -518,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::
|
||||||
|
|
||||||
|
|
@ -539,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::
|
||||||
|
|
||||||
|
|
|
||||||
11
manage.py
11
manage.py
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings.main')
|
|
||||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'Test')
|
|
||||||
|
|
||||||
from configurations.management import execute_from_command_line
|
|
||||||
|
|
||||||
execute_from_command_line(sys.argv)
|
|
||||||
12
setup.cfg
12
setup.cfg
|
|
@ -1,2 +1,10 @@
|
||||||
[wheel]
|
[coverage:run]
|
||||||
universal = 1
|
source = .
|
||||||
|
branch = 1
|
||||||
|
parallel = 1
|
||||||
|
[coverage:report]
|
||||||
|
include = configurations/*,tests/*
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
exclude = .tox,docs/*,.eggs
|
||||||
|
ignore = E501,E127,E128,E124,W503
|
||||||
|
|
|
||||||
66
setup.py
66
setup.py
|
|
@ -1,52 +1,70 @@
|
||||||
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"},
|
||||||
url='http://django-configurations.readthedocs.org/',
|
setup_requires=["setuptools_scm"],
|
||||||
|
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'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'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 :: 4 - Beta',
|
'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.6',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Programming Language :: Python :: 3.2',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.3',
|
'Programming Language :: Python :: 3.10',
|
||||||
|
'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,
|
||||||
|
|
|
||||||
1
test_project/.env
Normal file
1
test_project/.env
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DJANGO_DOTENV_VALUE='is set'
|
||||||
|
|
@ -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',
|
||||||
|
|
@ -160,20 +158,6 @@ class Base(Configuration):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
# Uncomment the next line to enable the admin:
|
|
||||||
# 'django.contrib.admin',
|
|
||||||
# Uncomment the next line to enable admin documentation:
|
|
||||||
# 'django.contrib.admindocs',
|
|
||||||
'configurations',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Debug(Base):
|
class Debug(Base):
|
||||||
YEAH = True
|
YEAH = True
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
11
tests/settings/dot_env.py
Normal file
11
tests/settings/dot_env.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from configurations import Configuration, values
|
||||||
|
|
||||||
|
|
||||||
|
class DotEnvConfiguration(Configuration):
|
||||||
|
|
||||||
|
DOTENV = 'test_project/.env'
|
||||||
|
|
||||||
|
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,11 +1,14 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import django
|
|
||||||
|
|
||||||
from configurations import Configuration, pristinemethod
|
from configurations import Configuration, pristinemethod
|
||||||
|
|
||||||
|
|
||||||
class Test(Configuration):
|
class Test(Configuration):
|
||||||
|
BASE_DIR = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(
|
||||||
|
os.path.abspath(__file__)), os.pardir))
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
@ -24,19 +27,16 @@ 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):
|
@property
|
||||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
def ALLOWED_HOSTS(self):
|
||||||
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
allowed_hosts.append('base')
|
||||||
return Configuration.TEMPLATE_CONTEXT_PROCESSORS + (
|
return allowed_hosts
|
||||||
'tests.settings.base.test_callback',
|
|
||||||
)
|
|
||||||
|
|
||||||
ATTRIBUTE_SETTING = True
|
ATTRIBUTE_SETTING = True
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ class Test(Configuration):
|
||||||
def METHOD_SETTING(self):
|
def METHOD_SETTING(self):
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
LAMBDA_SETTING = lambda self: 3
|
LAMBDA_SETTING = lambda self: 3 # noqa: E731
|
||||||
|
|
||||||
PRISTINE_LAMBDA_SETTING = pristinemethod(lambda: 4)
|
PRISTINE_LAMBDA_SETTING = pristinemethod(lambda: 4)
|
||||||
|
|
||||||
|
|
@ -64,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,25 +1,26 @@
|
||||||
from configurations import Configuration
|
from configurations import Configuration
|
||||||
|
|
||||||
|
|
||||||
class Mixin1(object):
|
class Mixin1:
|
||||||
|
@property
|
||||||
|
def ALLOWED_HOSTS(self):
|
||||||
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
|
allowed_hosts.append('test1')
|
||||||
|
return allowed_hosts
|
||||||
|
|
||||||
|
|
||||||
|
class Mixin2:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
return super(Mixin1, self).TEMPLATE_CONTEXT_PROCESSORS + (
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
'some_app.context_processors.processor1',)
|
allowed_hosts.append('test2')
|
||||||
|
return allowed_hosts
|
||||||
|
|
||||||
class Mixin2(object):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
|
||||||
return super(Mixin2, self).TEMPLATE_CONTEXT_PROCESSORS + (
|
|
||||||
'some_app.context_processors.processor2',)
|
|
||||||
|
|
||||||
|
|
||||||
class Inheritance(Mixin2, Mixin1, Configuration):
|
class Inheritance(Mixin2, Mixin1, Configuration):
|
||||||
|
|
||||||
@property
|
def ALLOWED_HOSTS(self):
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
return super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS + (
|
allowed_hosts.append('test3')
|
||||||
'some_app.context_processors.processorbase',)
|
return allowed_hosts
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from .main import Test
|
from .single_inheritance import Inheritance as BaseInheritance
|
||||||
|
|
||||||
|
|
||||||
class Inheritance(Test):
|
class Inheritance(BaseInheritance):
|
||||||
|
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
def ALLOWED_HOSTS(self):
|
||||||
return super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS() + (
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
'tests.settings.base.test_callback',)
|
allowed_hosts.append('test-test')
|
||||||
|
return allowed_hosts
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from .base import Base
|
||||||
|
|
||||||
class Inheritance(Base):
|
class Inheritance(Base):
|
||||||
|
|
||||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
@property
|
||||||
return super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS + (
|
def ALLOWED_HOSTS(self):
|
||||||
'tests.settings.base.test_callback',)
|
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||||
|
allowed_hosts.append('test')
|
||||||
|
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')
|
||||||
15
tests/test_env.py
Normal file
15
tests/test_env.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import os
|
||||||
|
from django.test import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class DotEnvLoadingTests(TestCase):
|
||||||
|
|
||||||
|
@patch.dict(os.environ, clear=True,
|
||||||
|
DJANGO_CONFIGURATION='DotEnvConfiguration',
|
||||||
|
DJANGO_SETTINGS_MODULE='tests.settings.dot_env')
|
||||||
|
def test_env_loaded(self):
|
||||||
|
from tests.settings import dot_env
|
||||||
|
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)
|
||||||
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 ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.conf import global_settings
|
|
||||||
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):
|
||||||
|
|
@ -13,30 +12,27 @@ class InheritanceTests(TestCase):
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.single_inheritance')
|
DJANGO_SETTINGS_MODULE='tests.settings.single_inheritance')
|
||||||
def test_inherited(self):
|
def test_inherited(self):
|
||||||
from tests.settings import single_inheritance
|
from tests.settings import single_inheritance
|
||||||
self.assertEqual(single_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
self.assertEqual(
|
||||||
global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
single_inheritance.ALLOWED_HOSTS,
|
||||||
'tests.settings.base.test_callback',
|
['test']
|
||||||
))
|
)
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_CONFIGURATION='Inheritance',
|
DJANGO_CONFIGURATION='Inheritance',
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.multiple_inheritance')
|
DJANGO_SETTINGS_MODULE='tests.settings.multiple_inheritance')
|
||||||
def test_inherited2(self):
|
def test_inherited2(self):
|
||||||
from tests.settings import multiple_inheritance
|
from tests.settings import multiple_inheritance
|
||||||
self.assertEqual(multiple_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
self.assertEqual(
|
||||||
global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
multiple_inheritance.ALLOWED_HOSTS,
|
||||||
'tests.settings.base.test_callback',
|
['test', 'test-test']
|
||||||
'tests.settings.base.test_callback',
|
)
|
||||||
))
|
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True,
|
@patch.dict(os.environ, clear=True,
|
||||||
DJANGO_CONFIGURATION='Inheritance',
|
DJANGO_CONFIGURATION='Inheritance',
|
||||||
DJANGO_SETTINGS_MODULE='tests.settings.mixin_inheritance')
|
DJANGO_SETTINGS_MODULE='tests.settings.mixin_inheritance')
|
||||||
def test_inherited3(self):
|
def test_inherited3(self):
|
||||||
from tests.settings import mixin_inheritance
|
from tests.settings import mixin_inheritance
|
||||||
self.assertEqual(mixin_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
self.assertEqual(
|
||||||
global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
mixin_inheritance.ALLOWED_HOSTS,
|
||||||
'some_app.context_processors.processor1',
|
['test1', 'test2', 'test3']
|
||||||
'some_app.context_processors.processor2',
|
)
|
||||||
'some_app.context_processors.processorbase',
|
|
||||||
))
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.conf import global_settings
|
|
||||||
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.importer import ConfigurationImporter
|
from configurations.importer import ConfigurationFinder
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
||||||
|
|
||||||
|
|
||||||
class MainTests(TestCase):
|
class MainTests(TestCase):
|
||||||
|
|
@ -22,10 +25,7 @@ class MainTests(TestCase):
|
||||||
self.assertTrue(lambda: callable(main.PRISTINE_LAMBDA_SETTING))
|
self.assertTrue(lambda: callable(main.PRISTINE_LAMBDA_SETTING))
|
||||||
self.assertNotEqual(main.PRISTINE_FUNCTION_SETTING, 5)
|
self.assertNotEqual(main.PRISTINE_FUNCTION_SETTING, 5)
|
||||||
self.assertTrue(lambda: callable(main.PRISTINE_FUNCTION_SETTING))
|
self.assertTrue(lambda: callable(main.PRISTINE_FUNCTION_SETTING))
|
||||||
self.assertEqual(main.TEMPLATE_CONTEXT_PROCESSORS,
|
self.assertEqual(main.ALLOWED_HOSTS, ['base'])
|
||||||
global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
|
||||||
'tests.settings.base.test_callback',
|
|
||||||
))
|
|
||||||
self.assertEqual(main.PRE_SETUP_TEST_SETTING, 6)
|
self.assertEqual(main.PRE_SETUP_TEST_SETTING, 6)
|
||||||
self.assertRaises(AttributeError, lambda: main.POST_SETUP_TEST_SETTING)
|
self.assertRaises(AttributeError, lambda: main.POST_SETUP_TEST_SETTING)
|
||||||
self.assertEqual(main.Test.POST_SETUP_TEST_SETTING, 7)
|
self.assertEqual(main.Test.POST_SETUP_TEST_SETTING, 7)
|
||||||
|
|
@ -42,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
|
||||||
|
|
@ -55,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),
|
||||||
|
|
@ -64,20 +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(repr(importer),
|
self.assertEqual(
|
||||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
repr(finder),
|
||||||
|
"<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',
|
||||||
|
|
@ -86,9 +95,64 @@ 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):
|
||||||
|
"""
|
||||||
|
Verify that's configuration option has been added to managements
|
||||||
|
commands
|
||||||
|
"""
|
||||||
|
proc = subprocess.Popen(['django-cadmin', 'test', '--help'],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||||
|
proc = subprocess.Popen(['django-cadmin', 'runserver', '--help'],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||||
|
|
||||||
|
def test_configuration_argument_in_runypy_cli(self):
|
||||||
|
"""
|
||||||
|
Verify that's configuration option has been added to managements
|
||||||
|
commands when using the -m entry point
|
||||||
|
"""
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[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)
|
||||||
|
res = proc.communicate()
|
||||||
|
stdout = res[0].decode('utf-8')
|
||||||
|
|
||||||
|
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: ",))
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,22 @@ 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,
|
||||||
TupleValue, SetValue, DictValue,
|
TupleValue, SingleNestedTupleValue,
|
||||||
URLValue, EmailValue, IPValue,
|
SingleNestedListValue, SetValue,
|
||||||
|
DictValue, URLValue, EmailValue, IPValue,
|
||||||
RegexValue, PathValue, SecretValue,
|
RegexValue, PathValue, SecretValue,
|
||||||
DatabaseURLValue, EmailURLValue,
|
DatabaseURLValue, EmailURLValue,
|
||||||
CacheURLValue, BackendsValue,
|
CacheURLValue, BackendsValue,
|
||||||
CastingMixin, SearchURLValue)
|
CastingMixin, SearchURLValue,
|
||||||
|
setup_value, PositiveIntegerValue)
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|
@ -29,16 +32,46 @@ class FailingCasterValue(CastingMixin, Value):
|
||||||
|
|
||||||
class ValueTests(TestCase):
|
class ValueTests(TestCase):
|
||||||
|
|
||||||
def test_value(self):
|
def test_value_with_default(self):
|
||||||
value = Value('default', environ=False)
|
value = Value('default', environ=False)
|
||||||
self.assertEqual(value.setup('TEST'), 'default')
|
self.assertEqual(type(value), str)
|
||||||
|
self.assertEqual(value, 'default')
|
||||||
|
self.assertEqual(str(value), 'default')
|
||||||
|
|
||||||
|
def test_value_with_default_and_late_binding(self):
|
||||||
|
value = Value('default', environ=False, late_binding=True)
|
||||||
|
self.assertEqual(type(value), Value)
|
||||||
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')
|
||||||
|
self.assertEqual(type(value), str)
|
||||||
|
self.assertEqual(value, 'override')
|
||||||
|
self.assertEqual(str(value), 'override')
|
||||||
|
self.assertEqual(f'{value}', 'override')
|
||||||
|
self.assertEqual('%s' % value, 'override')
|
||||||
|
|
||||||
|
value = Value(environ_name='TEST', late_binding=True)
|
||||||
|
self.assertEqual(type(value), Value)
|
||||||
|
self.assertEqual(value.value, 'override')
|
||||||
|
self.assertEqual(str(value), 'override')
|
||||||
|
self.assertEqual(f'{value}', 'override')
|
||||||
|
self.assertEqual('%s' % value, 'override')
|
||||||
|
|
||||||
|
self.assertEqual(repr(value), repr('override'))
|
||||||
|
|
||||||
|
def test_value_truthy(self):
|
||||||
|
value = Value('default')
|
||||||
|
self.assertTrue(bool(value))
|
||||||
|
|
||||||
|
def test_value_falsey(self):
|
||||||
|
value = Value()
|
||||||
|
self.assertFalse(bool(value))
|
||||||
|
|
||||||
@patch.dict(os.environ, clear=True, DJANGO_TEST='override')
|
@patch.dict(os.environ, clear=True, DJANGO_TEST='override')
|
||||||
def test_env_var(self):
|
def test_env_var(self):
|
||||||
value = Value('default')
|
value = Value('default')
|
||||||
self.assertEqual(value.setup('TEST'), 'override')
|
self.assertEqual(value.setup('TEST'), 'override')
|
||||||
|
self.assertEqual(str(value), 'override')
|
||||||
self.assertNotEqual(value.setup('TEST'), value.default)
|
self.assertNotEqual(value.setup('TEST'), value.default)
|
||||||
self.assertEqual(value.to_python(os.environ['DJANGO_TEST']),
|
self.assertEqual(value.to_python(os.environ['DJANGO_TEST']),
|
||||||
value.setup('TEST'))
|
value.setup('TEST'))
|
||||||
|
|
@ -52,6 +85,12 @@ class ValueTests(TestCase):
|
||||||
self.assertEqual(value1.setup('TEST1'), 'override1')
|
self.assertEqual(value1.setup('TEST1'), 'override1')
|
||||||
self.assertEqual(value2.setup('TEST2'), 'override2')
|
self.assertEqual(value2.setup('TEST2'), 'override2')
|
||||||
|
|
||||||
|
def test_value_var_equal(self):
|
||||||
|
value1 = Value('default')
|
||||||
|
value2 = Value('default')
|
||||||
|
self.assertEqual(value1, value2)
|
||||||
|
self.assertTrue(value1 in ['default'])
|
||||||
|
|
||||||
def test_env_var_prefix(self):
|
def test_env_var_prefix(self):
|
||||||
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
|
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
|
||||||
value = Value('default', environ_prefix='ACME')
|
value = Value('default', environ_prefix='ACME')
|
||||||
|
|
@ -61,11 +100,15 @@ class ValueTests(TestCase):
|
||||||
value = Value('default', environ_prefix='')
|
value = Value('default', environ_prefix='')
|
||||||
self.assertEqual(value.setup('TEST'), 'override')
|
self.assertEqual(value.setup('TEST'), 'override')
|
||||||
|
|
||||||
|
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
|
||||||
|
value = Value('default', environ_prefix='ACME_')
|
||||||
|
self.assertEqual(value.setup('TEST'), 'override')
|
||||||
|
|
||||||
def test_boolean_values_true(self):
|
def test_boolean_values_true(self):
|
||||||
value = BooleanValue(False)
|
value = BooleanValue(False)
|
||||||
for truthy in value.true_values:
|
for truthy in value.true_values:
|
||||||
with env(DJANGO_TEST=truthy):
|
with env(DJANGO_TEST=truthy):
|
||||||
self.assertTrue(value.setup('TEST'))
|
self.assertTrue(bool(value.setup('TEST')))
|
||||||
|
|
||||||
def test_boolean_values_faulty(self):
|
def test_boolean_values_faulty(self):
|
||||||
self.assertRaises(ValueError, BooleanValue, 'false')
|
self.assertRaises(ValueError, BooleanValue, 'false')
|
||||||
|
|
@ -74,13 +117,19 @@ class ValueTests(TestCase):
|
||||||
value = BooleanValue(True)
|
value = BooleanValue(True)
|
||||||
for falsy in value.false_values:
|
for falsy in value.false_values:
|
||||||
with env(DJANGO_TEST=falsy):
|
with env(DJANGO_TEST=falsy):
|
||||||
self.assertFalse(value.setup('TEST'))
|
self.assertFalse(bool(value.setup('TEST')))
|
||||||
|
|
||||||
def test_boolean_values_nonboolean(self):
|
def test_boolean_values_nonboolean(self):
|
||||||
value = BooleanValue(True)
|
value = BooleanValue(True)
|
||||||
with env(DJANGO_TEST='nonboolean'):
|
with env(DJANGO_TEST='nonboolean'):
|
||||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
|
||||||
|
def test_boolean_values_assign_false_to_another_booleanvalue(self):
|
||||||
|
value1 = BooleanValue(False)
|
||||||
|
value2 = BooleanValue(value1)
|
||||||
|
self.assertFalse(value1.setup('TEST1'))
|
||||||
|
self.assertFalse(value2.setup('TEST2'))
|
||||||
|
|
||||||
def test_integer_values(self):
|
def test_integer_values(self):
|
||||||
value = IntegerValue(1)
|
value = IntegerValue(1)
|
||||||
with env(DJANGO_TEST='2'):
|
with env(DJANGO_TEST='2'):
|
||||||
|
|
@ -88,6 +137,15 @@ class ValueTests(TestCase):
|
||||||
with env(DJANGO_TEST='noninteger'):
|
with env(DJANGO_TEST='noninteger'):
|
||||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
|
||||||
|
def test_positive_integer_values(self):
|
||||||
|
value = PositiveIntegerValue(1)
|
||||||
|
with env(DJANGO_TEST='2'):
|
||||||
|
self.assertEqual(value.setup('TEST'), 2)
|
||||||
|
with env(DJANGO_TEST='noninteger'):
|
||||||
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
with env(DJANGO_TEST='-1'):
|
||||||
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
|
||||||
def test_float_values(self):
|
def test_float_values(self):
|
||||||
value = FloatValue(1.0)
|
value = FloatValue(1.0)
|
||||||
with env(DJANGO_TEST='2.0'):
|
with env(DJANGO_TEST='2.0'):
|
||||||
|
|
@ -148,12 +206,74 @@ class ValueTests(TestCase):
|
||||||
with env(DJANGO_TEST=''):
|
with env(DJANGO_TEST=''):
|
||||||
self.assertEqual(value.setup('TEST'), ())
|
self.assertEqual(value.setup('TEST'), ())
|
||||||
|
|
||||||
|
def test_single_nested_list_values_default(self):
|
||||||
|
value = SingleNestedListValue()
|
||||||
|
with env(DJANGO_TEST='2,3;4,5'):
|
||||||
|
expected = [['2', '3'], ['4', '5']]
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2;3;4;5'):
|
||||||
|
expected = [['2'], ['3'], ['4'], ['5']]
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2,3,4,5'):
|
||||||
|
expected = [['2', '3', '4', '5']]
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
|
||||||
|
expected = [['2', '3'], ['4', '5']]
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST=''):
|
||||||
|
self.assertEqual(value.setup('TEST'), [])
|
||||||
|
|
||||||
|
def test_single_nested_list_values_separator(self):
|
||||||
|
value = SingleNestedListValue(seq_separator=':')
|
||||||
|
with env(DJANGO_TEST='2,3:4,5'):
|
||||||
|
self.assertEqual(value.setup('TEST'), [['2', '3'], ['4', '5']])
|
||||||
|
|
||||||
|
def test_single_nested_list_values_converter(self):
|
||||||
|
value = SingleNestedListValue(converter=int)
|
||||||
|
with env(DJANGO_TEST='2,3;4,5'):
|
||||||
|
self.assertEqual(value.setup('TEST'), [[2, 3], [4, 5]])
|
||||||
|
|
||||||
|
def test_single_nested_list_values_converter_default(self):
|
||||||
|
value = SingleNestedListValue([['2', '3'], ['4', '5']], converter=int)
|
||||||
|
self.assertEqual(value.value, [[2, 3], [4, 5]])
|
||||||
|
|
||||||
|
def test_single_nested_tuple_values_default(self):
|
||||||
|
value = SingleNestedTupleValue()
|
||||||
|
with env(DJANGO_TEST='2,3;4,5'):
|
||||||
|
expected = (('2', '3'), ('4', '5'))
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2;3;4;5'):
|
||||||
|
expected = (('2',), ('3',), ('4',), ('5',))
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2,3,4,5'):
|
||||||
|
expected = (('2', '3', '4', '5'),)
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
|
||||||
|
expected = (('2', '3'), ('4', '5'))
|
||||||
|
self.assertEqual(value.setup('TEST'), expected)
|
||||||
|
with env(DJANGO_TEST=''):
|
||||||
|
self.assertEqual(value.setup('TEST'), ())
|
||||||
|
|
||||||
|
def test_single_nested_tuple_values_separator(self):
|
||||||
|
value = SingleNestedTupleValue(seq_separator=':')
|
||||||
|
with env(DJANGO_TEST='2,3:4,5'):
|
||||||
|
self.assertEqual(value.setup('TEST'), (('2', '3'), ('4', '5')))
|
||||||
|
|
||||||
|
def test_single_nested_tuple_values_converter(self):
|
||||||
|
value = SingleNestedTupleValue(converter=int)
|
||||||
|
with env(DJANGO_TEST='2,3;4,5'):
|
||||||
|
self.assertEqual(value.setup('TEST'), ((2, 3), (4, 5)))
|
||||||
|
|
||||||
|
def test_single_nested_tuple_values_converter_default(self):
|
||||||
|
value = SingleNestedTupleValue((('2', '3'), ('4', '5')), converter=int)
|
||||||
|
self.assertEqual(value.value, ((2, 3), (4, 5)))
|
||||||
|
|
||||||
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())
|
||||||
|
|
||||||
|
|
@ -189,6 +309,14 @@ class ValueTests(TestCase):
|
||||||
with env(DJANGO_TEST='httb://spam.eggs'):
|
with env(DJANGO_TEST='httb://spam.eggs'):
|
||||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
|
||||||
|
def test_url_values_with_no_default(self):
|
||||||
|
value = URLValue() # no default
|
||||||
|
with env(DJANGO_TEST='http://spam.eggs'):
|
||||||
|
self.assertEqual(value.setup('TEST'), 'http://spam.eggs')
|
||||||
|
|
||||||
|
def test_url_values_with_wrong_default(self):
|
||||||
|
self.assertRaises(ValueError, URLValue, 'httb://spam.eggs')
|
||||||
|
|
||||||
def test_ip_values(self):
|
def test_ip_values(self):
|
||||||
value = IPValue('0.0.0.0')
|
value = IPValue('0.0.0.0')
|
||||||
with env(DJANGO_TEST='127.0.0.1'):
|
with env(DJANGO_TEST='127.0.0.1'):
|
||||||
|
|
@ -226,6 +354,7 @@ class ValueTests(TestCase):
|
||||||
self.assertEqual(value.setup('TEST'), '/does/not/exist')
|
self.assertEqual(value.setup('TEST'), '/does/not/exist')
|
||||||
|
|
||||||
def test_secret_value(self):
|
def test_secret_value(self):
|
||||||
|
# no default allowed, only environment values are
|
||||||
self.assertRaises(ValueError, SecretValue, 'default')
|
self.assertRaises(ValueError, SecretValue, 'default')
|
||||||
|
|
||||||
value = SecretValue()
|
value = SecretValue()
|
||||||
|
|
@ -234,7 +363,8 @@ class ValueTests(TestCase):
|
||||||
self.assertEqual(value.setup('SECRET_KEY'), '123')
|
self.assertEqual(value.setup('SECRET_KEY'), '123')
|
||||||
|
|
||||||
value = SecretValue(environ_name='FACEBOOK_API_SECRET',
|
value = SecretValue(environ_name='FACEBOOK_API_SECRET',
|
||||||
environ_prefix=None)
|
environ_prefix=None,
|
||||||
|
late_binding=True)
|
||||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
with env(FACEBOOK_API_SECRET='123'):
|
with env(FACEBOOK_API_SECRET='123'):
|
||||||
self.assertEqual(value.setup('TEST'), '123')
|
self.assertEqual(value.setup('TEST'), '123')
|
||||||
|
|
@ -243,20 +373,45 @@ 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,
|
||||||
|
'DISABLE_SERVER_SIDE_CURSORS': False,
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'HOST': None,
|
'HOST': '',
|
||||||
'NAME': ':memory:',
|
'NAME': ':memory:',
|
||||||
'PASSWORD': None,
|
'PASSWORD': '',
|
||||||
'PORT': None,
|
'PORT': '',
|
||||||
'USER': None,
|
'USER': '',
|
||||||
}})
|
},
|
||||||
|
settings_value['default']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_database_url_additional_args(self):
|
||||||
|
|
||||||
|
def mock_database_url_caster(self, url, engine=None):
|
||||||
|
return {'URL': url, 'ENGINE': engine}
|
||||||
|
|
||||||
|
with patch('configurations.values.DatabaseURLValue.caster',
|
||||||
|
mock_database_url_caster):
|
||||||
|
value = DatabaseURLValue(
|
||||||
|
engine='django_mysqlpool.backends.mysqlpool')
|
||||||
|
with env(DATABASE_URL='sqlite://'):
|
||||||
|
self.assertEqual(value.setup('DATABASE_URL'), {
|
||||||
|
'default': {
|
||||||
|
'URL': 'sqlite://',
|
||||||
|
'ENGINE': 'django_mysqlpool.backends.mysqlpool'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def test_email_url_value(self):
|
def test_email_url_value(self):
|
||||||
value = EmailURLValue()
|
value = EmailURLValue()
|
||||||
self.assertEqual(value.default, {})
|
self.assertEqual(value.default, {})
|
||||||
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'):
|
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'): # noqa: E501
|
||||||
self.assertEqual(value.setup('EMAIL_URL'), {
|
self.assertEqual(value.setup('EMAIL_URL'), {
|
||||||
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
||||||
'EMAIL_FILE_PATH': '',
|
'EMAIL_FILE_PATH': '',
|
||||||
|
|
@ -264,36 +419,47 @@ 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_TLS': True})
|
'EMAIL_USE_TLS': True})
|
||||||
with env(EMAIL_URL='console://'):
|
with env(EMAIL_URL='console://'):
|
||||||
self.assertEqual(value.setup('EMAIL_URL'), {
|
self.assertEqual(value.setup('EMAIL_URL'), {
|
||||||
'EMAIL_BACKEND': 'django.core.mail.backends.console.EmailBackend',
|
'EMAIL_BACKEND': 'django.core.mail.backends.console.EmailBackend', # noqa: E501
|
||||||
'EMAIL_FILE_PATH': '',
|
'EMAIL_FILE_PATH': '',
|
||||||
'EMAIL_HOST': None,
|
'EMAIL_HOST': None,
|
||||||
'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_TLS': False})
|
'EMAIL_USE_TLS': False})
|
||||||
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'):
|
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'): # noqa: E501
|
||||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||||
|
|
||||||
def test_cache_url_value(self):
|
def test_cache_url_value(self):
|
||||||
cache_setting = {
|
cache_setting = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'redis_cache.cache.RedisCache',
|
'BACKEND': 'django_redis.cache.RedisCache' if DJANGO_VERSION < (4,) else 'django.core.cache.backends.redis.RedisCache', # noqa: E501
|
||||||
'KEY_PREFIX': '',
|
'LOCATION': 'redis://host:6379/1',
|
||||||
'LOCATION': 'user@host:port:1'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache_url = 'redis://user@host:port/1'
|
cache_url = 'redis://user@host:6379/1'
|
||||||
value = CacheURLValue(cache_url)
|
value = CacheURLValue(cache_url)
|
||||||
self.assertEqual(value.default, cache_setting)
|
self.assertEqual(value.default, cache_setting)
|
||||||
value = CacheURLValue()
|
value = CacheURLValue()
|
||||||
self.assertEqual(value.default, {})
|
self.assertEqual(value.default, {})
|
||||||
with env(CACHE_URL='redis://user@host:port/1'):
|
with env(CACHE_URL='redis://user@host:6379/1'):
|
||||||
self.assertEqual(value.setup('CACHE_URL'), cache_setting)
|
self.assertEqual(value.setup('CACHE_URL'), cache_setting)
|
||||||
with env(CACHE_URL='wrong://user@host:port/1'):
|
with env(CACHE_URL='wrong://user@host:port/1'):
|
||||||
self.assertRaises(KeyError, value.setup, 'TEST')
|
with self.assertRaises(Exception) as cm:
|
||||||
|
value.setup('TEST')
|
||||||
|
self.assertEqual(cm.exception.args[0], 'Unknown backend: "wrong"')
|
||||||
|
with env(CACHE_URL='redis://user@host:port/1'):
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
value.setup('TEST')
|
||||||
|
self.assertEqual(
|
||||||
|
cm.exception.args[0],
|
||||||
|
"Cannot interpret cache URL value 'redis://user@host:port/1'")
|
||||||
|
|
||||||
def test_search_url_value(self):
|
def test_search_url_value(self):
|
||||||
value = SearchURLValue()
|
value = SearchURLValue()
|
||||||
|
|
@ -301,7 +467,7 @@ class ValueTests(TestCase):
|
||||||
with env(SEARCH_URL='elasticsearch://127.0.0.1:9200/index'):
|
with env(SEARCH_URL='elasticsearch://127.0.0.1:9200/index'):
|
||||||
self.assertEqual(value.setup('SEARCH_URL'), {
|
self.assertEqual(value.setup('SEARCH_URL'), {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
|
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine', # noqa: E501
|
||||||
'URL': 'http://127.0.0.1:9200',
|
'URL': 'http://127.0.0.1:9200',
|
||||||
'INDEX_NAME': 'index',
|
'INDEX_NAME': 'index',
|
||||||
}})
|
}})
|
||||||
|
|
@ -313,3 +479,50 @@ class ValueTests(TestCase):
|
||||||
|
|
||||||
backends = ['non.existing.Backend']
|
backends = ['non.existing.Backend']
|
||||||
self.assertRaises(ValueError, BackendsValue, backends)
|
self.assertRaises(ValueError, BackendsValue, backends)
|
||||||
|
|
||||||
|
def test_tuple_value(self):
|
||||||
|
value = TupleValue(None)
|
||||||
|
self.assertEqual(value.default, ())
|
||||||
|
self.assertEqual(value.value, ())
|
||||||
|
|
||||||
|
value = TupleValue((1, 2))
|
||||||
|
self.assertEqual(value.default, (1, 2))
|
||||||
|
self.assertEqual(value.value, (1, 2))
|
||||||
|
|
||||||
|
def test_set_value(self):
|
||||||
|
value = SetValue()
|
||||||
|
self.assertEqual(value.default, set())
|
||||||
|
self.assertEqual(value.value, set())
|
||||||
|
|
||||||
|
value = SetValue([1, 2])
|
||||||
|
self.assertEqual(value.default, {1, 2})
|
||||||
|
self.assertEqual(value.value, {1, 2})
|
||||||
|
|
||||||
|
def test_setup_value(self):
|
||||||
|
|
||||||
|
class Target:
|
||||||
|
pass
|
||||||
|
|
||||||
|
value = EmailURLValue()
|
||||||
|
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'): # noqa: E501
|
||||||
|
setup_value(Target, 'EMAIL', value)
|
||||||
|
self.assertEqual(Target.EMAIL, {
|
||||||
|
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
||||||
|
'EMAIL_FILE_PATH': '',
|
||||||
|
'EMAIL_HOST': 'smtp.example.com',
|
||||||
|
'EMAIL_HOST_PASSWORD': 'password',
|
||||||
|
'EMAIL_HOST_USER': 'user@domain.com',
|
||||||
|
'EMAIL_PORT': 587,
|
||||||
|
'EMAIL_TIMEOUT': None,
|
||||||
|
'EMAIL_USE_SSL': False,
|
||||||
|
'EMAIL_USE_TLS': True
|
||||||
|
})
|
||||||
|
self.assertEqual(
|
||||||
|
Target.EMAIL_BACKEND,
|
||||||
|
'django.core.mail.backends.smtp.EmailBackend')
|
||||||
|
self.assertEqual(Target.EMAIL_FILE_PATH, '')
|
||||||
|
self.assertEqual(Target.EMAIL_HOST, 'smtp.example.com')
|
||||||
|
self.assertEqual(Target.EMAIL_HOST_PASSWORD, 'password')
|
||||||
|
self.assertEqual(Target.EMAIL_HOST_USER, 'user@domain.com')
|
||||||
|
self.assertEqual(Target.EMAIL_PORT, 587)
|
||||||
|
self.assertEqual(Target.EMAIL_USE_TLS, True)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,2 @@
|
||||||
from django.conf.urls.defaults import include, patterns
|
urlpatterns = [
|
||||||
|
]
|
||||||
# Uncomment the next two lines to enable the admin:
|
|
||||||
from django.contrib import admin
|
|
||||||
admin.autodiscover()
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
(r'^admin/', include(admin.site.urls)),
|
|
||||||
)
|
|
||||||
|
|
|
||||||
200
tox.ini
200
tox.ini
|
|
@ -1,138 +1,72 @@
|
||||||
|
[tox]
|
||||||
|
skipsdist = true
|
||||||
|
usedevelop = true
|
||||||
|
minversion = 1.8
|
||||||
|
envlist =
|
||||||
|
py311-checkqa
|
||||||
|
docs
|
||||||
|
py{39}-dj{32,41,42}
|
||||||
|
py{310,py310}-dj{32,41,42,50,main}
|
||||||
|
py{311}-dj{41,42,50,51,main}
|
||||||
|
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]
|
||||||
skipsdist = True
|
usedevelop = true
|
||||||
usedevelop = True
|
setenv =
|
||||||
|
DJANGO_SETTINGS_MODULE = tests.settings.main
|
||||||
|
DJANGO_CONFIGURATION = Test
|
||||||
|
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||||
|
deps =
|
||||||
|
dj32: django~=3.2.9
|
||||||
|
dj41: django~=4.1.3
|
||||||
|
dj42: django~=4.2.0
|
||||||
|
dj50: django~=5.0.0
|
||||||
|
dj51: django~=5.1.0
|
||||||
|
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||||
|
py312: setuptools
|
||||||
|
py312: wheel
|
||||||
|
py313: setuptools
|
||||||
|
py313: wheel
|
||||||
|
coverage
|
||||||
|
coverage_enable_subprocess
|
||||||
|
extras = testing
|
||||||
|
commands =
|
||||||
|
python --version
|
||||||
|
{envbindir}/coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||||
|
coverage combine . tests docs
|
||||||
|
coverage report -m --skip-covered
|
||||||
|
coverage xml
|
||||||
|
|
||||||
|
[testenv:py311-checkqa]
|
||||||
|
commands =
|
||||||
|
flake8 {toxinidir}
|
||||||
|
check-manifest -v
|
||||||
|
python setup.py sdist
|
||||||
|
twine check dist/*
|
||||||
deps =
|
deps =
|
||||||
flake8
|
flake8
|
||||||
coverage
|
twine
|
||||||
django-discover-runner
|
check-manifest
|
||||||
mock
|
|
||||||
dj-database-url
|
|
||||||
dj-email-url
|
|
||||||
dj-search-url
|
|
||||||
django-cache-url>=0.6.0
|
|
||||||
six
|
|
||||||
deps14 =
|
|
||||||
https://github.com/django/django/archive/stable/1.4.x.zip#egg=django
|
|
||||||
deps15 =
|
|
||||||
https://github.com/django/django/archive/stable/1.5.x.zip#egg=django
|
|
||||||
deps16 =
|
|
||||||
https://github.com/django/django/archive/stable/1.6.x.zip#egg=django
|
|
||||||
deps17 =
|
|
||||||
https://github.com/django/django/archive/master.zip#egg=django
|
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
setenv =
|
||||||
|
deps =
|
||||||
|
-r docs/requirements.txt
|
||||||
commands =
|
commands =
|
||||||
python manage.py test -v2 {posargs:tests}
|
sphinx-build \
|
||||||
|
-b html \
|
||||||
|
-a \
|
||||||
[testenv:flake827]
|
-W \
|
||||||
basepython = python2.7
|
-n \
|
||||||
deps = flake8
|
docs \
|
||||||
commands = flake8 configurations --ignore=E501,E127,E128,E124
|
docs/_build/html
|
||||||
|
|
||||||
[testenv:flake833]
|
|
||||||
basepython = python3.3
|
|
||||||
deps = flake8
|
|
||||||
commands = flake8 configurations --ignore=E501,E127,E128,E124
|
|
||||||
|
|
||||||
|
|
||||||
[testenv:py26-1.4.x]
|
|
||||||
basepython = python2.6
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps14}
|
|
||||||
|
|
||||||
[testenv:py26-1.5.x]
|
|
||||||
basepython = python2.6
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps15}
|
|
||||||
|
|
||||||
[testenv:py26-1.6.x]
|
|
||||||
basepython = python2.6
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps16}
|
|
||||||
|
|
||||||
[testenv:py27-1.4.x]
|
|
||||||
basepython = python2.7
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps14}
|
|
||||||
|
|
||||||
[testenv:py27-1.5.x]
|
|
||||||
basepython = python2.7
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps15}
|
|
||||||
|
|
||||||
[testenv:py27-1.6.x]
|
|
||||||
basepython = python2.7
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps16}
|
|
||||||
|
|
||||||
[testenv:py27-1.7.x]
|
|
||||||
basepython = python2.7
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps17}
|
|
||||||
|
|
||||||
[testenv:py32-1.5.x]
|
|
||||||
basepython = python3.2
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps15}
|
|
||||||
|
|
||||||
[testenv:py32-1.6.x]
|
|
||||||
basepython = python3.2
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps16}
|
|
||||||
|
|
||||||
[testenv:py32-1.7.x]
|
|
||||||
basepython = python3.2
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps17}
|
|
||||||
|
|
||||||
[testenv:py33-1.5.x]
|
|
||||||
basepython = python3.3
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps15}
|
|
||||||
|
|
||||||
[testenv:py33-1.6.x]
|
|
||||||
basepython = python3.3
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps16}
|
|
||||||
|
|
||||||
[testenv:py33-1.7.x]
|
|
||||||
basepython = python3.3
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps17}
|
|
||||||
|
|
||||||
[testenv:pypy-1.4.x]
|
|
||||||
basepython = pypy
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps14}
|
|
||||||
|
|
||||||
[testenv:pypy-1.5.x]
|
|
||||||
basepython = pypy
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps15}
|
|
||||||
|
|
||||||
[testenv:pypy-1.6.x]
|
|
||||||
basepython = pypy
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps16}
|
|
||||||
|
|
||||||
[testenv:pypy-1.7.x]
|
|
||||||
basepython = pypy
|
|
||||||
deps =
|
|
||||||
{[testenv]deps}
|
|
||||||
{[testenv]deps17}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue