mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Compare commits
253 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 |
56 changed files with 1351 additions and 1349 deletions
|
|
@ -1,6 +0,0 @@
|
|||
[run]
|
||||
source = configurations
|
||||
branch = 1
|
||||
|
||||
[report]
|
||||
omit = *tests*,*migrations*
|
||||
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 }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
.coverage
|
||||
coverage.xml
|
||||
docs/_build
|
||||
*.egg-info
|
||||
*.egg
|
||||
|
|
@ -8,3 +9,4 @@ build/
|
|||
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
|
||||
71
.travis.yml
71
.travis.yml
|
|
@ -1,71 +0,0 @@
|
|||
language: python
|
||||
python: 3.5
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- "~/.cache/pip"
|
||||
env:
|
||||
global:
|
||||
- PIP_WHEEL_DIR=$HOME/.cache/pip/wheels
|
||||
- PIP_FIND_LINKS=file://$HOME/.cache/pip/wheels
|
||||
matrix:
|
||||
- TOXENV=flake8-py27
|
||||
- TOXENV=flake8-py35
|
||||
- TOXENV=py26-dj14
|
||||
- TOXENV=py26-dj15
|
||||
- TOXENV=py26-dj16
|
||||
- TOXENV=pypy-dj14
|
||||
- TOXENV=pypy-dj15
|
||||
- TOXENV=pypy-dj16
|
||||
- TOXENV=py27-dj14
|
||||
- TOXENV=py27-dj15
|
||||
- TOXENV=py27-dj16
|
||||
- TOXENV=py27-dj17
|
||||
- TOXENV=py27-dj18
|
||||
- TOXENV=py27-dj19
|
||||
- TOXENV=py27-djmaster
|
||||
- TOXENV=py32-dj15
|
||||
- TOXENV=py32-dj16
|
||||
- TOXENV=py32-dj17
|
||||
- TOXENV=py32-dj18
|
||||
- TOXENV=py33-dj15
|
||||
- TOXENV=py33-dj16
|
||||
- TOXENV=py33-dj17
|
||||
- TOXENV=py33-dj18
|
||||
- TOXENV=py34-dj15
|
||||
- TOXENV=py34-dj16
|
||||
- TOXENV=py34-dj17
|
||||
- TOXENV=py34-dj18
|
||||
- TOXENV=py34-dj19
|
||||
- TOXENV=py35-dj18
|
||||
- TOXENV=py35-dj19
|
||||
- TOXENV=py35-djmaster
|
||||
- TOXENV=pypy-dj15
|
||||
- TOXENV=pypy-dj16
|
||||
- TOXENV=pypy-dj17
|
||||
- TOXENV=pypy-dj18
|
||||
- TOXENV=pypy-dj19
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: TOXENV=py27-djmaster
|
||||
- env: TOXENV=py34-djmaster
|
||||
- env: TOXENV=py35-djmaster
|
||||
- env: TOXENV=pypy-djmaster
|
||||
install:
|
||||
- pip wheel -r tests/requirements.txt
|
||||
- pip install tox codecov
|
||||
script: tox -v
|
||||
after_success:
|
||||
- codecov
|
||||
branches:
|
||||
except: templates/1.5.x templates/1.6.x
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: jazzband
|
||||
distributions: "sdist bdist_wheel"
|
||||
password:
|
||||
secure: Oq6xiBtbip8+0U8t1WvDcUt1tX6imQeaHXvB+bqh2eU+RYntz8UWl9UOxfQEV4ghssX1zEKeOZzpYohj+oEi5b8oJo3H8pcbJIdK6rTNJEjQpDGSSjI3I1pHlflAJCDhPlGIgZYVPlY0dWK50l86hL+Y7QzLXjgsVCqlTCCtDfA=
|
||||
on:
|
||||
tags: true
|
||||
repo: jazzband/django-configurations
|
||||
condition: "$TOXENV = py27-dj19"
|
||||
18
AUTHORS
18
AUTHORS
|
|
@ -1,6 +1,22 @@
|
|||
Arseny Sokolov
|
||||
Asif Saif Uddin
|
||||
Baptiste Mispelon
|
||||
Brian Helba
|
||||
Bruno Clermont
|
||||
Christoph Krybus
|
||||
Finn-Thorben Sell
|
||||
Gilles Fabio
|
||||
Jannis Leidel
|
||||
John Franey
|
||||
Marc Abramowitz
|
||||
Michael Käufl
|
||||
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/
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
[](https://jazzband.co/)
|
||||
|
||||
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Condut](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).
|
||||
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.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
|
|||
13
MANIFEST.in
13
MANIFEST.in
|
|
@ -1,6 +1,11 @@
|
|||
include README.rst
|
||||
include .pre-commit-config.yaml
|
||||
include .readthedocs.yaml
|
||||
include AUTHORS
|
||||
include .travis.yml
|
||||
include tasks.py
|
||||
recursive-include tests *
|
||||
include CODE_OF_CONDUCT.md
|
||||
include CONTRIBUTING.md
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include tox.ini
|
||||
recursive-include docs *
|
||||
recursive-include test_project *
|
||||
recursive-include tests *
|
||||
|
|
|
|||
84
README.rst
84
README.rst
|
|
@ -1,27 +1,44 @@
|
|||
django-configurations
|
||||
=====================
|
||||
django-configurations |latest-version|
|
||||
======================================
|
||||
|
||||
.. image:: https://travis-ci.org/jazzband/django-configurations.svg?branch=master
|
||||
:alt: Build Status
|
||||
:target: https://travis-ci.org/jazzband/django-configurations
|
||||
|
||||
.. image:: https://jazzband.co/static/img/badge.svg
|
||||
:alt: Jazzband
|
||||
:target: https://jazzband.co/
|
||||
|
||||
.. image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
|
||||
:alt: Codecov
|
||||
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
|
||||
|jazzband| |build-status| |codecov| |docs| |python-support| |django-support|
|
||||
|
||||
django-configurations eases Django project configuration by relying
|
||||
on the composability of Python classes. It extends the notion of
|
||||
Django's module based settings loading with well established
|
||||
object oriented programming patterns.
|
||||
|
||||
Check out the `documentation`__ for more complete examples.
|
||||
Check out the `documentation`_ for more complete examples.
|
||||
|
||||
.. __: http://django-configurations.readthedocs.org/en/latest/
|
||||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-configurations.svg
|
||||
:target: https://pypi.python.org/pypi/django-configurations
|
||||
:alt: Latest version on PyPI
|
||||
|
||||
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||
:target: https://jazzband.co/
|
||||
:alt: Jazzband
|
||||
|
||||
.. |build-status| image:: https://github.com/jazzband/django-configurations/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-configurations/actions
|
||||
:alt: Build Status
|
||||
|
||||
.. |codecov| image:: https://codecov.io/github/jazzband/django-configurations/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/jazzband/django-configurations?branch=master
|
||||
:alt: Test coverage status
|
||||
|
||||
.. |docs| image:: https://img.shields.io/readthedocs/django-configurations/latest.svg
|
||||
:target: https://readthedocs.org/projects/django-configurations/
|
||||
:alt: Documentation status
|
||||
|
||||
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-configurations.svg
|
||||
:target: https://pypi.python.org/pypi/django-configurations
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. |django-support| image:: https://img.shields.io/pypi/djversions/django-configurations
|
||||
:target: https://pypi.org/project/django-configurations
|
||||
:alt: Supported Django versions
|
||||
|
||||
.. _documentation: https://django-configurations.readthedocs.io/en/latest/
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
|
@ -30,7 +47,13 @@ Install django-configurations:
|
|||
|
||||
.. 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
|
||||
project's **settings.py** or any other module you're using to store the
|
||||
|
|
@ -50,28 +73,29 @@ you just created, e.g. in bash:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
export DJANGO_CONFIGURATION=Dev
|
||||
$ export DJANGO_CONFIGURATION=Dev
|
||||
|
||||
and the ``DJANGO_SETTINGS_MODULE`` environment variable to the module
|
||||
import path as usual, e.g. in bash:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
|
||||
*Alternatively* supply the ``--configuration`` option when using Django
|
||||
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
|
||||
**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
|
||||
django-configurations would look like this:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
|
@ -93,7 +117,6 @@ Notice in line 10 we don't use the common tool
|
|||
The same applies to your **wsgi.py** file, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 6
|
||||
|
||||
import os
|
||||
|
||||
|
|
@ -107,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``
|
||||
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
|
||||
WSGI enabled server.
|
||||
WSGI/ASGI enabled server.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# flake8: noqa
|
||||
from .base import Settings, Configuration
|
||||
from .decorators import pristinemethod
|
||||
from .base import Configuration # noqa
|
||||
from .decorators import pristinemethod # noqa
|
||||
from .version import __version__ # noqa
|
||||
|
||||
__version__ = '1.0'
|
||||
__all__ = ['Configuration', 'pristinemethod', 'Settings']
|
||||
|
||||
__all__ = ['Configuration', 'pristinemethod']
|
||||
|
||||
|
||||
def _setup():
|
||||
|
|
@ -11,12 +11,10 @@ def _setup():
|
|||
|
||||
importer.install()
|
||||
|
||||
# django >=1.7
|
||||
try:
|
||||
from django.apps import apps
|
||||
if not apps.ready:
|
||||
import django
|
||||
django.setup()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def load_ipython_extension(ipython):
|
||||
|
|
@ -29,10 +27,5 @@ def load_ipython_extension(ipython):
|
|||
|
||||
|
||||
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``.
|
||||
"""
|
||||
"""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,8 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from django.utils import six
|
||||
from django.conf import global_settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
|
@ -15,16 +13,13 @@ __all__ = ['Configuration']
|
|||
install_failure = ("django-configurations settings importer wasn't "
|
||||
"correctly installed. Please use one of the starter "
|
||||
"functions to install it as mentioned in the docs: "
|
||||
"http://django-configurations.readthedocs.org/")
|
||||
"https://django-configurations.readthedocs.io/")
|
||||
|
||||
|
||||
class ConfigurationBase(type):
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
# also check for "Configuration" here to handle the Settings class
|
||||
# below remove it when we deprecate the Settings class
|
||||
if (bases not in ((object,), ()) and
|
||||
bases[0].__name__ not in ('NewBase', 'Configuration')):
|
||||
if bases not in ((object,), ()) and bases[0].__name__ != 'NewBase':
|
||||
# if this is actually a subclass in a settings module
|
||||
# we better check if the importer was correctly installed
|
||||
from . import importer
|
||||
|
|
@ -36,15 +31,50 @@ class ConfigurationBase(type):
|
|||
if parents:
|
||||
for base in bases[::-1]:
|
||||
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):
|
||||
return "<Configuration '{0}.{1}'>".format(self.__module__,
|
||||
return "<Configuration '{}.{}'>".format(self.__module__,
|
||||
self.__name__)
|
||||
|
||||
|
||||
class Configuration(six.with_metaclass(ConfigurationBase)):
|
||||
class Configuration(metaclass=ConfigurationBase):
|
||||
"""
|
||||
The base configuration class to inherit from.
|
||||
|
||||
|
|
@ -77,10 +107,10 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
|||
environment variables from a .env file located in the project root
|
||||
or provided directory.
|
||||
|
||||
http://www.wellfireinteractive.com/blog/easier-12-factor-django/
|
||||
https://wellfire.co/learn/easier-12-factor-django/
|
||||
https://gist.github.com/bennylope/2999704
|
||||
"""
|
||||
# check if the class has DOTENV set wether with a path or None
|
||||
# check if the class has DOTENV set whether with a path or None
|
||||
dotenv = getattr(cls, 'DOTENV', None)
|
||||
|
||||
# if DOTENV is falsy we want to disable it
|
||||
|
|
@ -89,12 +119,12 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
|||
|
||||
# now check if we can access the file since we know we really want to
|
||||
try:
|
||||
with open(dotenv, 'r') as f:
|
||||
with open(dotenv) as f:
|
||||
content = f.read()
|
||||
except IOError as e:
|
||||
except OSError as e:
|
||||
raise ImproperlyConfigured("Couldn't read .env file "
|
||||
"with the path {}. Error: "
|
||||
"{}".format(dotenv, e))
|
||||
"{}".format(dotenv, e)) from e
|
||||
else:
|
||||
for line in content.splitlines():
|
||||
m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line)
|
||||
|
|
@ -125,13 +155,3 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
|||
for name, value in uppercase_attributes(cls).items():
|
||||
if isinstance(value, 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", DeprecationWarning)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import imp
|
||||
from importlib.machinery import PathFinder
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser, make_option
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management import base
|
||||
|
|
@ -29,34 +28,30 @@ configuration_options = (make_option(CONFIGURATION_ARGUMENT,
|
|||
def install(check_options=False):
|
||||
global installed
|
||||
if not installed:
|
||||
if DJANGO_VERSION >= (1, 8):
|
||||
orig_create_parser = base.BaseCommand.create_parser
|
||||
orig_create_parser = base.BaseCommand.create_parser
|
||||
|
||||
def create_parser(self, prog_name, subcommand):
|
||||
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
|
||||
def create_parser(self, prog_name, subcommand):
|
||||
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
|
||||
|
||||
base.BaseCommand.create_parser = create_parser
|
||||
else:
|
||||
# add the configuration option to all management commands
|
||||
base.BaseCommand.option_list += configuration_options
|
||||
importer = ConfigurationImporter(check_options=check_options)
|
||||
base.BaseCommand.create_parser = create_parser
|
||||
importer = ConfigurationFinder(check_options=check_options)
|
||||
sys.meta_path.insert(0, importer)
|
||||
installed = True
|
||||
|
||||
|
||||
class ConfigurationImporter(object):
|
||||
class ConfigurationFinder(PathFinder):
|
||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||
error_msg = ("Configuration cannot be imported, "
|
||||
|
|
@ -75,7 +70,7 @@ class ConfigurationImporter(object):
|
|||
self.announce()
|
||||
|
||||
def __repr__(self):
|
||||
return "<ConfigurationImporter for '{0}.{1}'>".format(self.module,
|
||||
return "<ConfigurationFinder for '{}.{}'>".format(self.module,
|
||||
self.name)
|
||||
|
||||
@property
|
||||
|
|
@ -87,35 +82,23 @@ class ConfigurationImporter(object):
|
|||
return os.environ.get(self.namevar)
|
||||
|
||||
def check_options(self):
|
||||
# django switched to argparse in version 1.8
|
||||
if DJANGO_VERSION >= (1, 8):
|
||||
parser = base.CommandParser(None,
|
||||
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 = base.CommandParser(
|
||||
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:
|
||||
options, args = parser.parse_known_args(self.argv[2:])
|
||||
if options.configuration:
|
||||
os.environ[self.namevar] = options.configuration
|
||||
base.handle_default_options(options)
|
||||
except base.CommandError:
|
||||
pass # Ignore any option errors at this point.
|
||||
# django < 1.7 did use optparse
|
||||
else:
|
||||
from django.core.management import LaxOptionParser
|
||||
parser = LaxOptionParser(option_list=configuration_options,
|
||||
add_help_option=False)
|
||||
try:
|
||||
options, args = parser.parse_args(self.argv)
|
||||
if options.configuration:
|
||||
os.environ[self.namevar] = options.configuration
|
||||
except:
|
||||
pass # Ignore any option errors at this point.
|
||||
parser.add_argument('args', nargs='*') # catch-all
|
||||
try:
|
||||
options, args = parser.parse_known_args(self.argv[2:])
|
||||
if options.configuration:
|
||||
os.environ[self.namevar] = options.configuration
|
||||
base.handle_default_options(options)
|
||||
except base.CommandError:
|
||||
pass # Ignore any option errors at this point.
|
||||
|
||||
def validate(self):
|
||||
if self.name is None:
|
||||
|
|
@ -127,73 +110,71 @@ class ConfigurationImporter(object):
|
|||
if len(self.argv) > 1:
|
||||
from . import __version__
|
||||
from django.utils.termcolors import colorize
|
||||
# Django >= 1.7 supports hiding the colorization in the shell
|
||||
try:
|
||||
from django.core.management.color import no_style
|
||||
except ImportError:
|
||||
no_style = None
|
||||
from django.core.management.color import no_style
|
||||
|
||||
if no_style is not None and '--no-color' in self.argv:
|
||||
if '--no-color' in self.argv:
|
||||
stylize = no_style()
|
||||
else:
|
||||
def stylize(text):
|
||||
return colorize(text, fg='green')
|
||||
|
||||
if (self.argv[1] == 'runserver' and
|
||||
os.environ.get('RUN_MAIN') == 'true'):
|
||||
if (self.argv[1] == 'runserver'
|
||||
and os.environ.get('RUN_MAIN') == 'true'):
|
||||
|
||||
message = ("django-configurations version {0}, using "
|
||||
"configuration '{1}'".format(__version__,
|
||||
self.name))
|
||||
message = ("django-configurations version {}, using "
|
||||
"configuration {}".format(__version__ or "",
|
||||
self.name))
|
||||
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:
|
||||
module = fullname.rsplit('.', 1)[-1]
|
||||
return ConfigurationLoader(self.name,
|
||||
imp.find_module(module, path))
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
spec = super().find_spec(fullname, path, target)
|
||||
if spec is not None:
|
||||
wrap_loader(spec.loader, self.name)
|
||||
return spec
|
||||
else:
|
||||
mod = imp.load_module(fullname, *self.location)
|
||||
cls_path = '{0}.{1}'.format(mod.__name__, self.name)
|
||||
return None
|
||||
|
||||
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,
|
||||
self.name))
|
||||
cls.post_setup()
|
||||
def wrap_loader(loader, class_name):
|
||||
class ConfigurationLoader(loader.__class__):
|
||||
def exec_module(self, module):
|
||||
super().exec_module(module)
|
||||
|
||||
except Exception as err:
|
||||
reraise(err, "Couldn't setup configuration '{0}'".format(cls_path))
|
||||
mod = module
|
||||
|
||||
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
|
||||
|
|
|
|||
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,12 +1,11 @@
|
|||
import inspect
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from functools import partial
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import six
|
||||
try:
|
||||
from django.utils.importlib import import_module
|
||||
except ImportError:
|
||||
from importlib import import_module
|
||||
|
||||
|
||||
def isuppercase(name):
|
||||
|
|
@ -14,8 +13,7 @@ def isuppercase(name):
|
|||
|
||||
|
||||
def uppercase_attributes(obj):
|
||||
return dict((name, getattr(obj, name))
|
||||
for name in filter(isuppercase, dir(obj)))
|
||||
return {name: getattr(obj, name) for name in dir(obj) if isuppercase(name)}
|
||||
|
||||
|
||||
def import_by_path(dotted_path, error_prefix=''):
|
||||
|
|
@ -26,25 +24,26 @@ def import_by_path(dotted_path, error_prefix=''):
|
|||
|
||||
Backported from Django 1.6.
|
||||
"""
|
||||
warnings.warn("Function utils.import_by_path is deprecated in favor of "
|
||||
"django.utils.module_loading.import_string.", DeprecationWarning)
|
||||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("{0}{1} doesn't look like "
|
||||
raise ImproperlyConfigured("{}{} doesn't look like "
|
||||
"a module path".format(error_prefix,
|
||||
dotted_path))
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
except ImportError as err:
|
||||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
||||
msg = '{}Error importing module {}: "{}"'.format(error_prefix,
|
||||
module_path,
|
||||
err)
|
||||
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
|
||||
sys.exc_info()[2])
|
||||
raise ImproperlyConfigured(msg).with_traceback(sys.exc_info()[2])
|
||||
try:
|
||||
attr = getattr(module, class_name)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
|
||||
'"{2}" attribute/class'.format(error_prefix,
|
||||
raise ImproperlyConfigured('{}Module "{}" does not define a '
|
||||
'"{}" attribute/class'.format(error_prefix,
|
||||
module_path,
|
||||
class_name))
|
||||
return attr
|
||||
|
|
@ -62,78 +61,41 @@ def reraise(exc, prefix=None, suffix=None):
|
|||
suffix = ''
|
||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||
suffix = '(' + suffix + ')'
|
||||
exc.args = ('{0} {1} {2}'.format(prefix, exc.args[0], suffix),) + args[1:]
|
||||
raise
|
||||
exc.args = (f'{prefix} {args[0]} {suffix}',) + args[1:]
|
||||
raise exc
|
||||
|
||||
|
||||
# Copied over from Sphinx
|
||||
if sys.version_info >= (3, 0):
|
||||
from functools import partial
|
||||
|
||||
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)
|
||||
|
||||
else: # 2.6, 2.7
|
||||
from functools import partial
|
||||
|
||||
def getargspec(func):
|
||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||
if inspect.ismethod(func):
|
||||
func = func.im_func
|
||||
parts = 0, ()
|
||||
if type(func) is partial:
|
||||
keywords = func.keywords
|
||||
if keywords is None:
|
||||
keywords = {}
|
||||
parts = len(func.args), keywords.keys()
|
||||
func = func.func
|
||||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
args, varargs, varkw = inspect.getargs(func.func_code)
|
||||
func_defaults = func.func_defaults
|
||||
if func_defaults is None:
|
||||
func_defaults = []
|
||||
else:
|
||||
func_defaults = list(func_defaults)
|
||||
if parts[0]:
|
||||
args = args[parts[0]:]
|
||||
if parts[1]:
|
||||
for arg in parts[1]:
|
||||
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 func_defaults[i]
|
||||
del defaults[i]
|
||||
except IndexError:
|
||||
pass
|
||||
return inspect.ArgSpec(args, varargs, varkw, func_defaults)
|
||||
except ValueError: # must be a kwonly arg
|
||||
i = kwoargs.index(arg)
|
||||
del kwoargs[i]
|
||||
del kwodefs[arg]
|
||||
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
||||
tuple(defaults), kwoargs,
|
||||
kwodefs, argspec[6])
|
||||
while hasattr(func, '__wrapped__'):
|
||||
func = func.__wrapped__
|
||||
if not inspect.isfunction(func):
|
||||
raise TypeError('%r is not a Python function' % func)
|
||||
return inspect.getfullargspec(func)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import sys
|
|||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils import six
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .utils import import_by_path, getargspec
|
||||
from .utils import getargspec
|
||||
|
||||
|
||||
def setup_value(target, name, value):
|
||||
|
|
@ -20,7 +20,7 @@ def setup_value(target, name, value):
|
|||
setattr(target, multiple_name, multiple_value)
|
||||
|
||||
|
||||
class Value(object):
|
||||
class Value:
|
||||
"""
|
||||
A single settings value that is able to interpret env variables
|
||||
and implements a simple validation scheme.
|
||||
|
|
@ -52,8 +52,8 @@ class Value(object):
|
|||
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)):
|
||||
if ((instance.environ and instance.environ_name)
|
||||
or (not instance.environ and instance.default)):
|
||||
instance = instance.setup(instance.environ_name)
|
||||
return instance
|
||||
|
||||
|
|
@ -80,13 +80,19 @@ class Value(object):
|
|||
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 = '{0}_{1}'.format(self.environ_prefix, environ_name)
|
||||
environ_name = f'{self.environ_prefix}_{environ_name}'
|
||||
return environ_name
|
||||
|
||||
def setup(self, name):
|
||||
|
|
@ -96,8 +102,8 @@ class Value(object):
|
|||
if full_environ_name in os.environ:
|
||||
value = self.to_python(os.environ[full_environ_name])
|
||||
elif self.environ_required:
|
||||
raise ValueError('Value {0!r} is required to be set as the '
|
||||
'environment variable {1!r}'
|
||||
raise ValueError('Value {!r} is required to be set as the '
|
||||
'environment variable {!r}'
|
||||
.format(name, full_environ_name))
|
||||
self.value = value
|
||||
return value
|
||||
|
|
@ -106,12 +112,12 @@ class Value(object):
|
|||
"""
|
||||
Convert the given value of a environment variable into an
|
||||
appropriate Python representation of the value.
|
||||
This should be overriden when subclassing.
|
||||
This should be overridden when subclassing.
|
||||
"""
|
||||
return value
|
||||
|
||||
|
||||
class MultipleMixin(object):
|
||||
class MultipleMixin:
|
||||
multiple = True
|
||||
|
||||
|
||||
|
|
@ -120,9 +126,9 @@ class BooleanValue(Value):
|
|||
false_values = ('no', 'n', 'false', '0', '')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BooleanValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
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))
|
||||
|
||||
def to_python(self, value):
|
||||
|
|
@ -133,28 +139,30 @@ class BooleanValue(Value):
|
|||
return False
|
||||
else:
|
||||
raise ValueError('Cannot interpret '
|
||||
'boolean value {0!r}'.format(value))
|
||||
'boolean value {!r}'.format(value))
|
||||
|
||||
|
||||
class CastingMixin(object):
|
||||
class CastingMixin:
|
||||
exception = (TypeError, ValueError)
|
||||
message = 'Cannot interpret value {0!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CastingMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.caster, six.string_types):
|
||||
self._caster = import_by_path(self.caster)
|
||||
super().__init__(*args, **kwargs)
|
||||
if isinstance(self.caster, str):
|
||||
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):
|
||||
self._caster = self.caster
|
||||
else:
|
||||
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
||||
error = 'Cannot use caster of {} ({!r})'.format(self,
|
||||
self.caster)
|
||||
raise ValueError(error)
|
||||
try:
|
||||
arg_names = getargspec(self._caster)[0]
|
||||
self._params = dict((name, kwargs[name])
|
||||
for name in arg_names
|
||||
if name in kwargs)
|
||||
self._params = {name: kwargs[name] for name in arg_names if name in kwargs}
|
||||
except TypeError:
|
||||
self._params = {}
|
||||
|
||||
|
|
@ -172,6 +180,15 @@ class IntegerValue(CastingMixin, Value):
|
|||
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):
|
||||
caster = float
|
||||
|
||||
|
|
@ -198,7 +215,7 @@ class SequenceValue(Value):
|
|||
converter = kwargs.pop('converter', None)
|
||||
if converter is not None:
|
||||
self.converter = converter
|
||||
super(SequenceValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
# make sure the default is the correct sequence type
|
||||
if self.default is None:
|
||||
self.default = self.sequence_type()
|
||||
|
|
@ -242,7 +259,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.seq_separator = kwargs.pop('seq_separator', ';')
|
||||
super(SingleNestedSequenceValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _convert(self, items):
|
||||
# This could receive either a bare or nested sequence
|
||||
|
|
@ -251,8 +268,7 @@ class SingleNestedSequenceValue(SequenceValue):
|
|||
super(SingleNestedSequenceValue, self)._convert(i) for i in items
|
||||
]
|
||||
return self.sequence_type(converted_sequences)
|
||||
return self.sequence_type(
|
||||
super(SingleNestedSequenceValue, self)._convert(items))
|
||||
return self.sequence_type(super()._convert(items))
|
||||
|
||||
def to_python(self, value):
|
||||
split_value = [
|
||||
|
|
@ -278,9 +294,9 @@ class BackendsValue(ListValue):
|
|||
|
||||
def converter(self, value):
|
||||
try:
|
||||
import_by_path(value)
|
||||
except ImproperlyConfigured as err:
|
||||
six.reraise(ValueError, ValueError(err), sys.exc_info()[2])
|
||||
import_string(value)
|
||||
except ImportError as err:
|
||||
raise ValueError(err).with_traceback(sys.exc_info()[2])
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -288,28 +304,28 @@ class SetValue(ListValue):
|
|||
message = 'Cannot interpret set item {0!r} in set {1!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SetValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = set()
|
||||
else:
|
||||
self.default = set(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
return set(super(SetValue, self).to_python(value))
|
||||
return set(super().to_python(value))
|
||||
|
||||
|
||||
class DictValue(Value):
|
||||
message = 'Cannot interpret dict value {0!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DictValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = dict(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DictValue, self).to_python(value)
|
||||
value = super().to_python(value)
|
||||
if not value:
|
||||
return {}
|
||||
try:
|
||||
|
|
@ -321,17 +337,21 @@ class DictValue(Value):
|
|||
return evaled_value
|
||||
|
||||
|
||||
class ValidationMixin(object):
|
||||
class ValidationMixin:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ValidationMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.validator, six.string_types):
|
||||
self._validator = import_by_path(self.validator)
|
||||
super().__init__(*args, **kwargs)
|
||||
if isinstance(self.validator, str):
|
||||
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):
|
||||
self._validator = self.validator
|
||||
else:
|
||||
raise ValueError('Cannot use validator of '
|
||||
'{0} ({1!r})'.format(self, self.validator))
|
||||
'{} ({!r})'.format(self, self.validator))
|
||||
if self.default:
|
||||
self.to_python(self.default)
|
||||
|
||||
|
|
@ -365,19 +385,19 @@ class RegexValue(ValidationMixin, Value):
|
|||
def __init__(self, *args, **kwargs):
|
||||
regex = kwargs.pop('regex', None)
|
||||
self.validator = validators.RegexValidator(regex=regex)
|
||||
super(RegexValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class PathValue(Value):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.check_exists = kwargs.pop('check_exists', True)
|
||||
super(PathValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def setup(self, name):
|
||||
value = super(PathValue, self).setup(name)
|
||||
value = super().setup(name)
|
||||
value = os.path.expanduser(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)
|
||||
|
||||
|
||||
|
|
@ -386,15 +406,15 @@ class SecretValue(Value):
|
|||
def __init__(self, *args, **kwargs):
|
||||
kwargs['environ'] = True
|
||||
kwargs['environ_required'] = True
|
||||
super(SecretValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is not None:
|
||||
raise ValueError('Secret values are only allowed to '
|
||||
'be set as environment variables')
|
||||
|
||||
def setup(self, name):
|
||||
value = super(SecretValue, self).setup(name)
|
||||
value = super().setup(name)
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -407,7 +427,7 @@ class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
|||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
||||
super(EmailURLValue, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
|
|
@ -422,14 +442,14 @@ class DictBackendMixin(Value):
|
|||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', self.environ_name)
|
||||
super(DictBackendMixin, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = self.to_python(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DictBackendMixin, self).to_python(value)
|
||||
value = super().to_python(value)
|
||||
return {self.alias: value}
|
||||
|
||||
|
||||
|
|
|
|||
7
configurations/version.py
Normal file
7
configurations/version.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
try:
|
||||
__version__ = version("django-configurations")
|
||||
except PackageNotFoundError:
|
||||
# package is not installed
|
||||
__version__ = None
|
||||
|
|
@ -2,13 +2,7 @@ from . import importer
|
|||
|
||||
importer.install()
|
||||
|
||||
try:
|
||||
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()
|
||||
from django.core.wsgi import get_wsgi_application # noqa: E402
|
||||
|
||||
# this is just for the crazy ones
|
||||
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."
|
||||
139
docs/changes.rst
139
docs/changes.rst
|
|
@ -3,6 +3,145 @@
|
|||
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)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
323
docs/conf.py
323
docs/conf.py
|
|
@ -1,301 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 configurations
|
||||
|
||||
import sys
|
||||
import os
|
||||
# -- Project information -----------------------------------------------------
|
||||
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,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# 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))
|
||||
release = configurations.__version__
|
||||
version = ".".join(release.split(".")[:2])
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
# -- General configuration ---------------------------------------------------
|
||||
add_function_parentheses = False
|
||||
add_module_names = False
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# 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']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# 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': '',
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/3', None),
|
||||
'sphinx': ('https://www.sphinx-doc.org/en/master', None),
|
||||
'django': ('https://docs.djangoproject.com/en/dev',
|
||||
'https://docs.djangoproject.com/en/dev/_objects/'),
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
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 HTML output -------------------------------------------------
|
||||
html_theme = 'furo'
|
||||
|
||||
# -- Options for Epub output ---------------------------------------------------
|
||||
epub_title = project
|
||||
epub_author = author
|
||||
epub_publisher = author
|
||||
epub_copyright = copyright
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'django-configurations'
|
||||
epub_author = u'Jannis Leidel'
|
||||
epub_publisher = u'Jannis Leidel'
|
||||
epub_copyright = u'2012, Jannis Leidel'
|
||||
|
||||
# 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
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
latex_documents = [
|
||||
# (source start file, target name, title, author, documentclass)
|
||||
('index', 'django-configurations.tex',
|
||||
'django-configurations Documentation', author, 'manual'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Calling a Django management command
|
|||
|
||||
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.
|
||||
equivalent of Django's :func:`~django.core.management.call_command`
|
||||
function with django-configurations, too.
|
||||
|
||||
Simply import it from ``configurations.management`` instead:
|
||||
|
||||
|
|
@ -20,6 +20,43 @@ Simply import it from ``configurations.management`` instead:
|
|||
|
||||
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
|
||||
------
|
||||
|
||||
|
|
@ -37,7 +74,7 @@ Example:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ tree mysite_env/
|
||||
$ tree --noreport mysite_env/
|
||||
mysite_env/
|
||||
├── DJANGO_SETTINGS_MODULE
|
||||
├── DJANGO_DEBUG
|
||||
|
|
@ -45,10 +82,8 @@ Example:
|
|||
├── DJANGO_CACHE_URL
|
||||
└── PYTHONSTARTUP
|
||||
|
||||
0 directories, 3 files
|
||||
$ cat mysite_env/DJANGO_CACHE_URL
|
||||
redis://user@host:port/1
|
||||
$
|
||||
|
||||
Then, to enable the ``mysite_env`` environment variables, simply use the
|
||||
``envdir`` command line tool as a prefix for your program, e.g.:
|
||||
|
|
@ -62,6 +97,42 @@ Python instead of from the command line.
|
|||
|
||||
.. _envdir: https://pypi.python.org/pypi/envdir
|
||||
|
||||
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
|
||||
|
|
@ -71,46 +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
|
||||
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:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.5.x/requirements.txt
|
||||
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||
|
||||
Then create your new Django project with the provided template:
|
||||
Or Django 1.8:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ django-admin.py startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.5.x.zip
|
||||
$ python -m django startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||
|
||||
See the repository of the template for more information:
|
||||
|
||||
https://github.com/jazzband/django-configurations/tree/templates/1.5.x
|
||||
|
||||
Django 1.6.x
|
||||
^^^^^^^^^^^^
|
||||
|
||||
First install Django 1.6.x and django-configurations:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.6.x/requirements.txt
|
||||
|
||||
Or Django 1.6:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ django-admin.py startproject mysite -v2 --template https://github.com/jazzband/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``
|
||||
Now you have a default Django 1.8.x project in the ``mysite``
|
||||
directory that uses django-configurations.
|
||||
|
||||
See the repository of the template for more information:
|
||||
|
||||
https://github.com/jazzband/django-configurations/tree/templates/1.6.x
|
||||
https://github.com/jazzband/django-configurations/tree/templates/1.8.x
|
||||
|
||||
.. _pip: http://pip-installer.org/
|
||||
|
||||
|
|
@ -128,8 +180,8 @@ probably just add the following to the **beginning** of your settings module:
|
|||
import configurations
|
||||
configurations.setup()
|
||||
|
||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities.
|
||||
This will also call ``django.setup()`` on Django >= 1.7.
|
||||
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
|
||||
This will also call ``django.setup()``.
|
||||
|
||||
>= 3.1
|
||||
^^^^^^
|
||||
|
|
@ -211,7 +263,8 @@ It also works with django-extensions's shell_plus_ management command.
|
|||
|
||||
.. _IPython: http://ipython.org/
|
||||
.. _`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
|
||||
-------
|
||||
|
|
@ -243,8 +296,6 @@ As you can see django-configurations provides a helper module
|
|||
Sphinx
|
||||
------
|
||||
|
||||
.. versionadded: 0.9
|
||||
|
||||
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
|
||||
|
|
@ -264,7 +315,36 @@ the environment variable accordingly:
|
|||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
# ...
|
||||
'configurations',
|
||||
'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,6 +93,6 @@ Bugs and feature requests
|
|||
As always your mileage may vary, so please don't hesitate to send feature
|
||||
requests and bug reports:
|
||||
|
||||
https://github.com/jazzband/django-configurations/issues
|
||||
- https://github.com/jazzband/django-configurations/issues
|
||||
|
||||
Thanks!
|
||||
190
docs/make.bat
190
docs/make.bat
|
|
@ -1,190 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-configurations.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-configurations.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
|
|
@ -3,14 +3,16 @@ Usage patterns
|
|||
|
||||
There are various configuration patterns that can be implemented with
|
||||
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.
|
||||
|
||||
Server specific settings
|
||||
------------------------
|
||||
|
||||
For example, imagine you have a base setting class in your **settings.py**
|
||||
file::
|
||||
file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
|
|
@ -19,31 +21,54 @@ file::
|
|||
|
||||
class Dev(Base):
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
class Prod(Base):
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
||||
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
|
||||
of the class names you've defined, e.g. on your production server it
|
||||
should be ``Prod``. In bash that would be::
|
||||
You can now set the ``DJANGO_CONFIGURATION`` environment variable to
|
||||
one of the class names you've defined, e.g. on your production server
|
||||
it should be ``Prod``. In Bash that would be:
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
export DJANGO_CONFIGURATION=Prod
|
||||
python manage.py runserver
|
||||
.. code-block:: console
|
||||
|
||||
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
$ export DJANGO_CONFIGURATION=Prod
|
||||
$ python -m manage runserver
|
||||
|
||||
Alternatively you can use the ``--configuration`` option when using Django
|
||||
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
|
||||
------------------------
|
||||
|
||||
Every ``configurations.Configuration`` subclass will automatically contain
|
||||
Django's global settings as class attributes, so you can refer to them when
|
||||
setting other values, e.g.::
|
||||
Every ``configurations.Configuration`` subclass will automatically
|
||||
contain Django's global settings as class attributes, so you can refer
|
||||
to them when setting other values, e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
|
|
@ -54,20 +79,24 @@ setting other values, e.g.::
|
|||
|
||||
@property
|
||||
def LANGUAGES(self):
|
||||
return Configuration.LANGUAGES + (('tlh', 'Klingon'),)
|
||||
return list(Configuration.LANGUAGES) + [('tlh', 'Klingon')]
|
||||
|
||||
Configuration mixins
|
||||
--------------------
|
||||
|
||||
You might want to apply some configuration values for each and every
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -81,8 +110,10 @@ Pristine methods
|
|||
.. versionadded:: 0.3
|
||||
|
||||
In case one of your settings itself need to be a callable, you need to
|
||||
tell that django-configurations by using the ``pristinemethod`` decorator,
|
||||
e.g.::
|
||||
tell that django-configurations by using the ``pristinemethod``
|
||||
decorator, e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
|
||||
|
|
@ -92,13 +123,18 @@ e.g.::
|
|||
def ACCESS_FUNCTION(user):
|
||||
return user.is_staff
|
||||
|
||||
Lambdas work, too::
|
||||
Lambdas work, too:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
|
||||
class Prod(Configuration):
|
||||
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
|
||||
|
||||
|
||||
.. _setup-methods:
|
||||
|
||||
Setup methods
|
||||
-------------
|
||||
|
||||
|
|
@ -107,7 +143,9 @@ Setup methods
|
|||
If there is something required to be set up before, during or after the
|
||||
settings loading happens, please override the ``pre_setup``, ``setup`` or
|
||||
``post_setup`` class methods like so (don't forget to apply the Python
|
||||
``@classmethod`` decorator)::
|
||||
``@classmethod`` decorator):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import logging
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -150,13 +190,12 @@ connecting to a database::
|
|||
import mango
|
||||
mango.connect('enterprise')
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
You could do the same by overriding the ``__init__`` method of your
|
||||
settings class but this may cause hard to debug errors because
|
||||
at the time the ``__init__`` method is called (during Django startup)
|
||||
the Django setting system isn't fully loaded yet.
|
||||
at the time the ``__init__`` method is called (during Django
|
||||
startup) the Django setting system isn't fully loaded yet.
|
||||
|
||||
So anything you do in ``__init__`` that may require
|
||||
``django.conf.settings`` or Django models there is a good chance it
|
||||
|
|
@ -165,5 +204,16 @@ connecting to a database::
|
|||
.. versionchanged:: 0.4
|
||||
|
||||
A new ``setup`` method was added to be able to handle the new
|
||||
:class:`~configurations.values.Value` classes and allow an in-between
|
||||
modification of the configuration values.
|
||||
:class:`~configurations.values.Value` classes and allow an
|
||||
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
|
||||
|
|
@ -46,7 +46,7 @@ value:
|
|||
|
||||
class Dev(Configuration):
|
||||
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.
|
||||
|
||||
|
|
@ -86,9 +86,11 @@ prefixed with ``DJANGO_``. E.g.:
|
|||
django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment
|
||||
variable when deciding which value the ``ROOT_URLCONF`` setting should have.
|
||||
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
|
||||
``'mysite.urls'``.
|
||||
|
|
@ -120,6 +122,23 @@ instead of using the name of the :class:`~Value` instance.::
|
|||
class Dev(Configuration):
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
@ -145,12 +164,12 @@ the prefix.
|
|||
|
||||
:param default: the default value of the setting
|
||||
:param environ: toggle for environment use
|
||||
:param environ_name: name of environment variable to look for
|
||||
:param environ_prefix: prefix to use when looking for environment variable
|
||||
:param environ_required: wheter or not the value is required to be set as an environment variable
|
||||
:param environ_name: capitalized name of environment variable to look for
|
||||
: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_name: capitalized string or None
|
||||
:type environ_prefix: capitalized string
|
||||
:type environ_name: str or None
|
||||
:type environ_prefix: str
|
||||
:type environ_required: bool
|
||||
|
||||
The ``default`` parameter is effectively the value the setting has
|
||||
|
|
@ -215,6 +234,16 @@ Type values
|
|||
|
||||
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
|
||||
|
||||
A :class:`~Value` subclass that handles float values.
|
||||
|
|
@ -231,6 +260,10 @@ Type values
|
|||
|
||||
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
|
||||
|
||||
.. class:: SequenceValue
|
||||
|
||||
Common base class for sequence values.
|
||||
|
||||
.. class:: ListValue(default, [separator=',', converter=None])
|
||||
|
||||
A :class:`~SequenceValue` subclass that handles list values.
|
||||
|
|
@ -254,17 +287,21 @@ Type values
|
|||
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
||||
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::
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -276,6 +313,10 @@ Type values
|
|||
|
||||
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,
|
||||
|
|
@ -283,13 +324,13 @@ Type values
|
|||
|
||||
: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
|
||||
: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'),
|
||||
('John', 'jcleese@site.com'),
|
||||
('Eric', 'eidle@site.com'),
|
||||
))
|
||||
|
||||
|
|
@ -304,7 +345,7 @@ Type values
|
|||
|
||||
: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
|
||||
:param converter: the optional converter callable to apply for each inner
|
||||
list item
|
||||
|
||||
See the :class:`~SingleNestedTupleValue` examples above.
|
||||
|
|
@ -329,6 +370,10 @@ Type values
|
|||
'it': ['Mike', 'Joe'],
|
||||
})
|
||||
|
||||
Override using environment variables like this::
|
||||
|
||||
DJANGO_DEPARTMENTS={'it':['Mike','Joe'],'hr':['Emma','Olivia']}
|
||||
|
||||
Validator values
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
@ -522,7 +567,7 @@ Other values
|
|||
|
||||
::
|
||||
|
||||
MIDDLEWARE_CLASSES = values.BackendsValue([
|
||||
MIDDLEWARE = values.BackendsValue([
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
|
@ -536,7 +581,8 @@ Other values
|
|||
A :class:`~Value` subclass that doesn't allow setting a default value
|
||||
during instantiation and force-enables the use of an environment variable
|
||||
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
|
||||
|
||||
|
|
@ -558,7 +604,7 @@ Value mixins
|
|||
requires a ``caster`` class attribute of one of the following types:
|
||||
|
||||
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
|
||||
- a callable, e.g. :func:`int`
|
||||
- a callable, e.g. :class:`int`
|
||||
|
||||
Example::
|
||||
|
||||
|
|
@ -579,7 +625,7 @@ Value mixins
|
|||
validation attempt.
|
||||
|
||||
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
|
||||
- a callable, e.g. :func:`bool`
|
||||
- a callable, e.g. :class:`bool`
|
||||
|
||||
Example::
|
||||
|
||||
|
|
|
|||
12
setup.cfg
12
setup.cfg
|
|
@ -1,2 +1,10 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
[coverage:run]
|
||||
source = .
|
||||
branch = 1
|
||||
parallel = 1
|
||||
[coverage:report]
|
||||
include = configurations/*,tests/*
|
||||
|
||||
[flake8]
|
||||
exclude = .tox,docs/*,.eggs
|
||||
ignore = E501,E127,E128,E124,W503
|
||||
|
|
|
|||
63
setup.py
63
setup.py
|
|
@ -1,57 +1,70 @@
|
|||
from __future__ import print_function
|
||||
import ast
|
||||
import os
|
||||
import codecs
|
||||
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):
|
||||
filename = os.path.join(os.path.dirname(__file__), *parts)
|
||||
with codecs.open(filename, encoding='utf-8') as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def find_version(*parts):
|
||||
finder = VersionFinder()
|
||||
finder.visit(ast.parse(read(*parts)))
|
||||
return finder.version
|
||||
|
||||
|
||||
setup(
|
||||
name="django-configurations",
|
||||
version=find_version("configurations", "__init__.py"),
|
||||
url='http://django-configurations.readthedocs.org/',
|
||||
use_scm_version={"version_scheme": "post-release", "local_scheme": "dirty-tag"},
|
||||
setup_requires=["setuptools_scm"],
|
||||
url='https://django-configurations.readthedocs.io/',
|
||||
project_urls={
|
||||
'Source': 'https://github.com/jazzband/django-configurations',
|
||||
},
|
||||
license='BSD',
|
||||
description="A helper for organizing Django settings.",
|
||||
long_description=read('README.rst'),
|
||||
long_description_content_type='text/x-rst',
|
||||
author='Jannis Leidel',
|
||||
author_email='jannis@leidel.info',
|
||||
packages=['configurations'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'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=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'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',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'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',
|
||||
],
|
||||
zip_safe=False,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ class Base(Configuration):
|
|||
# Django settings for test_project project.
|
||||
|
||||
DEBUG = values.BooleanValue(True, environ=True)
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
|
|
@ -84,7 +83,6 @@ class Base(Configuration):
|
|||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
|
|
@ -96,7 +94,7 @@ class Base(Configuration):
|
|||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
MIDDLEWARE = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# 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
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from django.core.wsgi import get_wsgi_application # noqa
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
from optparse import make_option
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
# Used by a specific test to see how unupgraded
|
||||
# management commands play with configurations.
|
||||
# See the test code for more details.
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--arg1', action='store_true'),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
pass
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
coverage
|
||||
django-discover-runner
|
||||
mock
|
||||
dj-database-url
|
||||
dj-email-url
|
||||
dj-search-url
|
||||
django-cache-url>=1.0.0
|
||||
six
|
||||
|
|
@ -6,3 +6,6 @@ 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,14 +1,13 @@
|
|||
import os
|
||||
import uuid
|
||||
import django
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
from configurations.values import BooleanValue
|
||||
|
||||
|
||||
class Test(Configuration):
|
||||
|
||||
ENV_LOADED = BooleanValue(False)
|
||||
BASE_DIR = os.path.abspath(
|
||||
os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), os.pardir))
|
||||
|
||||
DEBUG = True
|
||||
|
||||
|
|
@ -28,19 +27,16 @@ class Test(Configuration):
|
|||
'django.contrib.contenttypes',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.admin',
|
||||
'tests',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'tests.urls'
|
||||
|
||||
if django.VERSION[:2] < (1, 6):
|
||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
||||
|
||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
||||
return tuple(Configuration.TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'tests.settings.base.test_callback',
|
||||
)
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('base')
|
||||
return allowed_hosts
|
||||
|
||||
ATTRIBUTE_SETTING = True
|
||||
|
||||
|
|
@ -53,7 +49,7 @@ class Test(Configuration):
|
|||
def METHOD_SETTING(self):
|
||||
return 2
|
||||
|
||||
LAMBDA_SETTING = lambda self: 3
|
||||
LAMBDA_SETTING = lambda self: 3 # noqa: E731
|
||||
|
||||
PRISTINE_LAMBDA_SETTING = pristinemethod(lambda: 4)
|
||||
|
||||
|
|
@ -68,3 +64,7 @@ class Test(Configuration):
|
|||
@classmethod
|
||||
def post_setup(cls):
|
||||
cls.POST_SETUP_TEST_SETTING = 7
|
||||
|
||||
|
||||
class TestWithDefaultSetExplicitely(Test):
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
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
|
||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
||||
return tuple(super(Mixin1, self).TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'some_app.context_processors.processor1',)
|
||||
|
||||
|
||||
class Mixin2(object):
|
||||
|
||||
@property
|
||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
||||
return tuple(super(Mixin2, self).TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'some_app.context_processors.processor2',)
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test2')
|
||||
return allowed_hosts
|
||||
|
||||
|
||||
class Inheritance(Mixin2, Mixin1, Configuration):
|
||||
|
||||
@property
|
||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
||||
return tuple(super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'some_app.context_processors.processorbase',)
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test3')
|
||||
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):
|
||||
return tuple(super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS()) + (
|
||||
'tests.settings.base.test_callback',)
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test-test')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from .base import Base
|
|||
|
||||
class Inheritance(Base):
|
||||
|
||||
def TEMPLATE_CONTEXT_PROCESSORS(self):
|
||||
return tuple(super(Inheritance, self).TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'tests.settings.base.test_callback',)
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
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')
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
from django.test import TestCase
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class DotEnvLoadingTests(TestCase):
|
||||
|
|
@ -11,4 +11,5 @@ class DotEnvLoadingTests(TestCase):
|
|||
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
|
||||
|
||||
from django.conf import global_settings
|
||||
from django.test import TestCase
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class InheritanceTests(TestCase):
|
||||
|
|
@ -13,30 +12,27 @@ class InheritanceTests(TestCase):
|
|||
DJANGO_SETTINGS_MODULE='tests.settings.single_inheritance')
|
||||
def test_inherited(self):
|
||||
from tests.settings import single_inheritance
|
||||
self.assertEqual(single_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
||||
tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'tests.settings.base.test_callback',
|
||||
))
|
||||
self.assertEqual(
|
||||
single_inheritance.ALLOWED_HOSTS,
|
||||
['test']
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION='Inheritance',
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.multiple_inheritance')
|
||||
def test_inherited2(self):
|
||||
from tests.settings import multiple_inheritance
|
||||
self.assertEqual(multiple_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
||||
tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'tests.settings.base.test_callback',
|
||||
'tests.settings.base.test_callback',
|
||||
))
|
||||
self.assertEqual(
|
||||
multiple_inheritance.ALLOWED_HOSTS,
|
||||
['test', 'test-test']
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_CONFIGURATION='Inheritance',
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.mixin_inheritance')
|
||||
def test_inherited3(self):
|
||||
from tests.settings import mixin_inheritance
|
||||
self.assertEqual(mixin_inheritance.TEMPLATE_CONTEXT_PROCESSORS,
|
||||
tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'some_app.context_processors.processor1',
|
||||
'some_app.context_processors.processor2',
|
||||
'some_app.context_processors.processorbase',
|
||||
))
|
||||
self.assertEqual(
|
||||
mixin_inheritance.ALLOWED_HOSTS,
|
||||
['test1', 'test2', 'test3']
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,19 +2,12 @@ import os
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.conf import global_settings
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
if sys.version_info >= (2, 7):
|
||||
from unittest import skipIf
|
||||
else:
|
||||
from django.utils.unittest import skipIf
|
||||
from unittest.mock import patch
|
||||
|
||||
from 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')
|
||||
|
|
@ -32,10 +25,7 @@ class MainTests(TestCase):
|
|||
self.assertTrue(lambda: callable(main.PRISTINE_LAMBDA_SETTING))
|
||||
self.assertNotEqual(main.PRISTINE_FUNCTION_SETTING, 5)
|
||||
self.assertTrue(lambda: callable(main.PRISTINE_FUNCTION_SETTING))
|
||||
self.assertEqual(main.TEMPLATE_CONTEXT_PROCESSORS,
|
||||
tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
|
||||
'tests.settings.base.test_callback',
|
||||
))
|
||||
self.assertEqual(main.ALLOWED_HOSTS, ['base'])
|
||||
self.assertEqual(main.PRE_SETUP_TEST_SETTING, 6)
|
||||
self.assertRaises(AttributeError, lambda: main.POST_SETUP_TEST_SETTING)
|
||||
self.assertEqual(main.Test.POST_SETUP_TEST_SETTING, 7)
|
||||
|
|
@ -52,12 +42,14 @@ class MainTests(TestCase):
|
|||
|
||||
@patch.dict(os.environ, clear=True, DJANGO_CONFIGURATION='Test')
|
||||
def test_empty_module_var(self):
|
||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
ConfigurationFinder()
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.main')
|
||||
def test_empty_class_var(self):
|
||||
self.assertRaises(ImproperlyConfigured, ConfigurationImporter)
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
ConfigurationFinder()
|
||||
|
||||
def test_global_settings(self):
|
||||
from configurations.base import Configuration
|
||||
|
|
@ -65,6 +57,12 @@ class MainTests(TestCase):
|
|||
self.assertEqual(repr(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):
|
||||
from tests.settings.main import Test
|
||||
self.assertEqual(repr(Test),
|
||||
|
|
@ -74,20 +72,21 @@ class MainTests(TestCase):
|
|||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||
DJANGO_CONFIGURATION='Test')
|
||||
def test_initialization(self):
|
||||
importer = ConfigurationImporter()
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'Test')
|
||||
self.assertEqual(repr(importer),
|
||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
||||
finder = ConfigurationFinder()
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'Test')
|
||||
self.assertEqual(
|
||||
repr(finder),
|
||||
"<ConfigurationFinder for 'tests.settings.main.Test'>")
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.inheritance',
|
||||
DJANGO_CONFIGURATION='Inheritance')
|
||||
def test_initialization_inheritance(self):
|
||||
importer = ConfigurationImporter()
|
||||
self.assertEqual(importer.module,
|
||||
finder = ConfigurationFinder()
|
||||
self.assertEqual(finder.module,
|
||||
'tests.settings.inheritance')
|
||||
self.assertEqual(importer.name, 'Inheritance')
|
||||
self.assertEqual(finder.name, 'Inheritance')
|
||||
|
||||
@patch.dict(os.environ, clear=True,
|
||||
DJANGO_SETTINGS_MODULE='tests.settings.main',
|
||||
|
|
@ -96,12 +95,12 @@ class MainTests(TestCase):
|
|||
'--settings=tests.settings.main',
|
||||
'--configuration=Test'])
|
||||
def test_configuration_option(self):
|
||||
importer = ConfigurationImporter(check_options=False)
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'NonExisting')
|
||||
importer = ConfigurationImporter(check_options=True)
|
||||
self.assertEqual(importer.module, 'tests.settings.main')
|
||||
self.assertEqual(importer.name, 'Test')
|
||||
finder = ConfigurationFinder(check_options=False)
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'NonExisting')
|
||||
finder = ConfigurationFinder(check_options=True)
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'Test')
|
||||
|
||||
def test_configuration_argument_in_cli(self):
|
||||
"""
|
||||
|
|
@ -115,19 +114,45 @@ class MainTests(TestCase):
|
|||
stdout=subprocess.PIPE)
|
||||
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||
|
||||
@skipIf(DJANGO_VERSION >= (1, 10), 'only applies to Django < 1.10')
|
||||
def test_deprecated_option_list_command(self):
|
||||
def test_configuration_argument_in_runypy_cli(self):
|
||||
"""
|
||||
Verify that the configuration option is correctly added to any
|
||||
management commands which are still relying on option_list to
|
||||
add their own custom arguments
|
||||
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'))
|
||||
|
||||
Specific test for a pattern which was deprecated in Django 1.8
|
||||
and which will become completely unsupported in Django 1.10.
|
||||
https://docs.djangoproject.com/en/1.8/howto/custom-management-commands/#custom-commands-options
|
||||
"""
|
||||
proc = subprocess.Popen(['django-cadmin', 'old_optparse_command', '--help'],
|
||||
stdout=subprocess.PIPE)
|
||||
output = proc.communicate()[0].decode('utf-8')
|
||||
self.assertIn('--configuration', output)
|
||||
self.assertIn('--arg1', output)
|
||||
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,10 +2,11 @@ import decimal
|
|||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations.values import (Value, BooleanValue, IntegerValue,
|
||||
FloatValue, DecimalValue, ListValue,
|
||||
|
|
@ -16,7 +17,7 @@ from configurations.values import (Value, BooleanValue, IntegerValue,
|
|||
DatabaseURLValue, EmailURLValue,
|
||||
CacheURLValue, BackendsValue,
|
||||
CastingMixin, SearchURLValue,
|
||||
setup_value)
|
||||
setup_value, PositiveIntegerValue)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -33,7 +34,7 @@ class ValueTests(TestCase):
|
|||
|
||||
def test_value_with_default(self):
|
||||
value = Value('default', environ=False)
|
||||
self.assertEqual(type(value), type('default'))
|
||||
self.assertEqual(type(value), str)
|
||||
self.assertEqual(value, 'default')
|
||||
self.assertEqual(str(value), 'default')
|
||||
|
||||
|
|
@ -43,21 +44,29 @@ class ValueTests(TestCase):
|
|||
with env(DJANGO_TEST='override'):
|
||||
self.assertEqual(value.setup('TEST'), 'default')
|
||||
value = Value(environ_name='TEST')
|
||||
self.assertEqual(type(value), type('override'))
|
||||
self.assertEqual(type(value), str)
|
||||
self.assertEqual(value, 'override')
|
||||
self.assertEqual(str(value), 'override')
|
||||
self.assertEqual('{0}'.format(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('{0}'.format(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')
|
||||
def test_env_var(self):
|
||||
value = Value('default')
|
||||
|
|
@ -99,7 +108,7 @@ class ValueTests(TestCase):
|
|||
value = BooleanValue(False)
|
||||
for truthy in value.true_values:
|
||||
with env(DJANGO_TEST=truthy):
|
||||
self.assertTrue(value.setup('TEST'))
|
||||
self.assertTrue(bool(value.setup('TEST')))
|
||||
|
||||
def test_boolean_values_faulty(self):
|
||||
self.assertRaises(ValueError, BooleanValue, 'false')
|
||||
|
|
@ -108,7 +117,7 @@ class ValueTests(TestCase):
|
|||
value = BooleanValue(True)
|
||||
for falsy in value.false_values:
|
||||
with env(DJANGO_TEST=falsy):
|
||||
self.assertFalse(value.setup('TEST'))
|
||||
self.assertFalse(bool(value.setup('TEST')))
|
||||
|
||||
def test_boolean_values_nonboolean(self):
|
||||
value = BooleanValue(True)
|
||||
|
|
@ -128,6 +137,15 @@ class ValueTests(TestCase):
|
|||
with env(DJANGO_TEST='noninteger'):
|
||||
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):
|
||||
value = FloatValue(1.0)
|
||||
with env(DJANGO_TEST='2.0'):
|
||||
|
|
@ -253,9 +271,9 @@ class ValueTests(TestCase):
|
|||
def test_set_values_default(self):
|
||||
value = SetValue()
|
||||
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 ,'):
|
||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
||||
self.assertEqual(value.setup('TEST'), {'2', '2'})
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), set())
|
||||
|
||||
|
|
@ -355,23 +373,33 @@ class ValueTests(TestCase):
|
|||
value = DatabaseURLValue()
|
||||
self.assertEqual(value.default, {})
|
||||
with env(DATABASE_URL='sqlite://'):
|
||||
self.assertEqual(value.setup('DATABASE_URL'), {
|
||||
'default': {
|
||||
settings_value = value.setup('DATABASE_URL')
|
||||
# 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',
|
||||
'HOST': '',
|
||||
'NAME': ':memory:',
|
||||
'PASSWORD': '',
|
||||
'PORT': '',
|
||||
'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 }
|
||||
return {'URL': url, 'ENGINE': engine}
|
||||
|
||||
with patch('configurations.values.DatabaseURLValue.caster', mock_database_url_caster):
|
||||
value = DatabaseURLValue(engine='django_mysqlpool.backends.mysqlpool')
|
||||
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': {
|
||||
|
|
@ -383,7 +411,7 @@ class ValueTests(TestCase):
|
|||
def test_email_url_value(self):
|
||||
value = EmailURLValue()
|
||||
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'), {
|
||||
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
||||
'EMAIL_FILE_PATH': '',
|
||||
|
|
@ -391,24 +419,28 @@ class ValueTests(TestCase):
|
|||
'EMAIL_HOST_PASSWORD': 'password',
|
||||
'EMAIL_HOST_USER': 'user@domain.com',
|
||||
'EMAIL_PORT': 587,
|
||||
'EMAIL_TIMEOUT': None,
|
||||
'EMAIL_USE_SSL': False,
|
||||
'EMAIL_USE_TLS': True})
|
||||
with env(EMAIL_URL='console://'):
|
||||
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_HOST': None,
|
||||
'EMAIL_HOST_PASSWORD': None,
|
||||
'EMAIL_HOST_USER': None,
|
||||
'EMAIL_PORT': None,
|
||||
'EMAIL_TIMEOUT': None,
|
||||
'EMAIL_USE_SSL': 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')
|
||||
|
||||
def test_cache_url_value(self):
|
||||
cache_setting = {
|
||||
'default': {
|
||||
'BACKEND': 'django_redis.cache.RedisCache',
|
||||
'LOCATION': 'host:6379:1'
|
||||
'BACKEND': 'django_redis.cache.RedisCache' if DJANGO_VERSION < (4,) else 'django.core.cache.backends.redis.RedisCache', # noqa: E501
|
||||
'LOCATION': 'redis://host:6379/1',
|
||||
}
|
||||
}
|
||||
cache_url = 'redis://user@host:6379/1'
|
||||
|
|
@ -425,8 +457,9 @@ class ValueTests(TestCase):
|
|||
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'")
|
||||
self.assertEqual(
|
||||
cm.exception.args[0],
|
||||
"Cannot interpret cache URL value 'redis://user@host:port/1'")
|
||||
|
||||
def test_search_url_value(self):
|
||||
value = SearchURLValue()
|
||||
|
|
@ -434,7 +467,7 @@ class ValueTests(TestCase):
|
|||
with env(SEARCH_URL='elasticsearch://127.0.0.1:9200/index'):
|
||||
self.assertEqual(value.setup('SEARCH_URL'), {
|
||||
'default': {
|
||||
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
|
||||
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine', # noqa: E501
|
||||
'URL': 'http://127.0.0.1:9200',
|
||||
'INDEX_NAME': 'index',
|
||||
}})
|
||||
|
|
@ -462,16 +495,16 @@ class ValueTests(TestCase):
|
|||
self.assertEqual(value.value, set())
|
||||
|
||||
value = SetValue([1, 2])
|
||||
self.assertEqual(value.default, set([1, 2]))
|
||||
self.assertEqual(value.value, set([1, 2]))
|
||||
self.assertEqual(value.default, {1, 2})
|
||||
self.assertEqual(value.value, {1, 2})
|
||||
|
||||
def test_setup_value(self):
|
||||
|
||||
class Target(object):
|
||||
class Target:
|
||||
pass
|
||||
|
||||
value = EmailURLValue()
|
||||
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
|
||||
setup_value(Target, 'EMAIL', value)
|
||||
self.assertEqual(Target.EMAIL, {
|
||||
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
||||
|
|
@ -480,9 +513,13 @@ class ValueTests(TestCase):
|
|||
'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_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')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,2 @@
|
|||
from django.conf.urls.defaults import include, patterns
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
urlpatterns = [
|
||||
]
|
||||
|
|
|
|||
105
tox.ini
105
tox.ini
|
|
@ -1,61 +1,72 @@
|
|||
[tox]
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
skipsdist = true
|
||||
usedevelop = true
|
||||
minversion = 1.8
|
||||
envlist =
|
||||
flake8-py27,
|
||||
flake8-py35,
|
||||
py{26,py}-dj{14,15,16},
|
||||
py27-dj{14,15,16,17,18,19,master},
|
||||
py{32,33,34,py}-dj{15,16,17,18}
|
||||
py{34,35,py}-dj{19,master}
|
||||
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]
|
||||
basepython =
|
||||
py26: python2.6
|
||||
py27: python2.7
|
||||
py32: python3.2
|
||||
py33: python3.3
|
||||
py34: python3.4
|
||||
py35: python3.5
|
||||
pypy: pypy
|
||||
usedevelop = true
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE = tests.settings.main
|
||||
DJANGO_CONFIGURATION = Test
|
||||
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||
deps =
|
||||
-rtests/requirements.txt
|
||||
dj14: https://github.com/django/django/archive/stable/1.4.x.tar.gz#egg=django
|
||||
dj15: https://github.com/django/django/archive/stable/1.5.x.tar.gz#egg=django
|
||||
dj16: https://github.com/django/django/archive/stable/1.6.x.tar.gz#egg=django
|
||||
dj17: https://github.com/django/django/archive/stable/1.7.x.tar.gz#egg=django
|
||||
dj18: https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django
|
||||
dj19: https://github.com/django/django/archive/stable/1.9.x.tar.gz#egg=django
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
||||
|
||||
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
|
||||
coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
coverage report
|
||||
{envbindir}/coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
coverage combine . tests docs
|
||||
coverage report -m --skip-covered
|
||||
coverage xml
|
||||
|
||||
# Coverage supports only Python 3.3+ for Python 3.
|
||||
[testenv:py32-dj15]
|
||||
commands = {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
[testenv:py32-dj16]
|
||||
commands = {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
[testenv:py32-dj17]
|
||||
commands = {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
[testenv:py32-dj18]
|
||||
commands = {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
[testenv:py311-checkqa]
|
||||
commands =
|
||||
flake8 {toxinidir}
|
||||
check-manifest -v
|
||||
python setup.py sdist
|
||||
twine check dist/*
|
||||
deps =
|
||||
flake8
|
||||
twine
|
||||
check-manifest
|
||||
|
||||
[testenv:flake8-py27]
|
||||
commands = flake8 configurations
|
||||
deps = flake8
|
||||
|
||||
[testenv:flake8-py35]
|
||||
commands = flake8 configurations
|
||||
deps = flake8
|
||||
|
||||
[flake8]
|
||||
exclude=.tox
|
||||
ignore=E501,E127,E128,E124
|
||||
[testenv:docs]
|
||||
setenv =
|
||||
deps =
|
||||
-r docs/requirements.txt
|
||||
commands =
|
||||
sphinx-build \
|
||||
-b html \
|
||||
-a \
|
||||
-W \
|
||||
-n \
|
||||
docs \
|
||||
docs/_build/html
|
||||
|
|
|
|||
Loading…
Reference in a new issue