mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Compare commits
143 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 |
51 changed files with 805 additions and 1089 deletions
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Keep GitHub Actions up to date with GitHub's Dependabot...
|
||||
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
||||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*" # Group all Actions updates into a single larger pull request
|
||||
schedule:
|
||||
interval: weekly
|
||||
53
.github/workflows/release.yml
vendored
Normal file
53
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'jazzband/django-configurations'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: release-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
release-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade setuptools twine wheel
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python setup.py --version
|
||||
python setup.py sdist --format=gztar bdist_wheel
|
||||
twine check dist/*
|
||||
|
||||
- name: Upload packages to Jazzband
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: jazzband
|
||||
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||
repository-url: https://jazzband.co/projects/django-configurations/upload
|
||||
57
.github/workflows/test.yml
vendored
Normal file
57
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 6
|
||||
matrix:
|
||||
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key:
|
||||
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ matrix.python-version }}-v1-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade "tox<4" "tox-gh-actions<3"
|
||||
|
||||
- name: Tox tests
|
||||
run: |
|
||||
tox --verbose
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
name: coverage-data-${{ matrix.python-version }}
|
||||
path: ".coverage.*"
|
||||
include-hidden-files: true
|
||||
merge-multiple: true
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,5 +9,4 @@ build/
|
|||
htmlcov/
|
||||
*.pyc
|
||||
dist/
|
||||
tests/docs/_build/
|
||||
.eggs/
|
||||
|
|
|
|||
1
.pre-commit-config.yaml
Normal file
1
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
repos: []
|
||||
16
.readthedocs.yaml
Normal file
16
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
version: 2
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- method: pip
|
||||
path: .
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
formats:
|
||||
- epub
|
||||
- pdf
|
||||
37
.travis.yml
37
.travis.yml
|
|
@ -1,37 +0,0 @@
|
|||
language: python
|
||||
dist: xenial
|
||||
cache: pip
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- 'pypy3'
|
||||
install: travis_retry pip install tox-travis codecov
|
||||
script: tox -v
|
||||
after_success: codecov --required -X gcov fix pycov -f coverage.xml --flags ${TOXENV//-/ }
|
||||
branches:
|
||||
except: templates/1.5.x templates/1.6.x
|
||||
stages:
|
||||
- test
|
||||
- name: deploy
|
||||
if: repo = jazzband/django-configurations AND tag IS present
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
- stage: deploy
|
||||
install: skip
|
||||
script: skip
|
||||
python: 3.7
|
||||
env: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: jazzband
|
||||
server: https://jazzband.co/projects/django-configurations/upload
|
||||
distributions: sdist bdist_wheel
|
||||
password:
|
||||
secure: LuserSjUTGSsls9zrvck/FbfL+gFpNU/ywOQ/67ufEbbpGCeDBEgxDzgb0acfHNk8wlAkaPvaAejQBFtcUulhdNT/g0NsmaEAjd6HhCGM+FRJAnYFaj33Js6C+N2tX5wznL7uCBxqgtaaH0hf6ucqC8OXqwoCVGgdxAEnUlC/fY=
|
||||
on:
|
||||
tags: true
|
||||
repo: jazzband/django-configurations
|
||||
13
AUTHORS
13
AUTHORS
|
|
@ -1,9 +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
|
||||
Miro Hrončok
|
||||
Nicholas Dujay
|
||||
Paolo Melchiorre
|
||||
Peter Bittner
|
||||
Richard de Wit
|
||||
Thomas Grainger
|
||||
Tim Gates
|
||||
Victor Seva
|
||||
|
|
|
|||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Code of Conduct
|
||||
|
||||
As contributors and maintainers of the Jazzband projects, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in the Jazzband a harassment-free experience
|
||||
for everyone, regardless of the level of experience, gender, gender identity and
|
||||
expression, sexual orientation, disability, personal appearance, body size, race,
|
||||
ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery
|
||||
- Personal attacks
|
||||
- Trolling or insulting/derogatory comments
|
||||
- Public or private harassment
|
||||
- Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
- Other unethical or unprofessional conduct
|
||||
|
||||
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are not
|
||||
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
||||
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing the jazzband
|
||||
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
||||
removed from the Jazzband roadies.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
||||
investigated and will result in a response that is deemed necessary and appropriate to
|
||||
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
||||
reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
||||
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
||||
|
||||
[homepage]: https://contributor-covenant.org
|
||||
[version]: https://contributor-covenant.org/version/1/3/0/
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2012-2014, Jannis Leidel and other contributors.
|
||||
Copyright (c) 2012-2023, Jannis Leidel and other contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
|
|||
12
MANIFEST.in
12
MANIFEST.in
|
|
@ -1,9 +1,11 @@
|
|||
include README.rst
|
||||
include CONTRIBUTING.md
|
||||
include .pre-commit-config.yaml
|
||||
include .readthedocs.yaml
|
||||
include AUTHORS
|
||||
include .travis.yml
|
||||
include CODE_OF_CONDUCT.md
|
||||
include CONTRIBUTING.md
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include tox.ini
|
||||
recursive-include tests *
|
||||
recursive-include docs *
|
||||
recursive-include test_project *
|
||||
include LICENSE
|
||||
recursive-include tests *
|
||||
|
|
|
|||
59
README.rst
59
README.rst
|
|
@ -1,7 +1,7 @@
|
|||
django-configurations |latest-version|
|
||||
======================================
|
||||
|
||||
|build-status| |codecov| |docs| |python-support| |jazzband|
|
||||
|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
|
||||
|
|
@ -11,23 +11,33 @@ object oriented programming patterns.
|
|||
Check out the `documentation`_ for more complete examples.
|
||||
|
||||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-configurations.svg
|
||||
:alt: Latest version on PyPI
|
||||
:target: https://pypi.python.org/pypi/django-configurations
|
||||
.. |build-status| image:: https://img.shields.io/travis/jazzband/django-configurations/master.svg
|
||||
:alt: Build status
|
||||
:target: https://travis-ci.org/jazzband/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
|
||||
:alt: Codecov
|
||||
: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
|
||||
:alt: Documentation status
|
||||
: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: Python versions
|
||||
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||
:alt: Jazzband
|
||||
:target: https://jazzband.co/
|
||||
: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
|
||||
|
|
@ -37,13 +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
|
||||
|
||||
pip install django-configurations[cache,database,email,search]
|
||||
$ 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
|
||||
|
|
@ -63,14 +73,14 @@ 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``
|
||||
|
|
@ -78,10 +88,10 @@ command line option, e.g.
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
python manage.py runserver --settings=mysite.settings --configuration=Dev
|
||||
$ 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:
|
||||
|
||||
|
|
@ -120,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.
|
||||
|
|
|
|||
10
configurations/__main__.py
Normal file
10
configurations/__main__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
invokes django-cadmin when the configurations module is run as a script.
|
||||
|
||||
Example: python -m configurations check
|
||||
"""
|
||||
|
||||
from .management import execute_from_command_line
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_from_command_line()
|
||||
8
configurations/asgi.py
Normal file
8
configurations/asgi.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from . import importer
|
||||
|
||||
importer.install()
|
||||
|
||||
from django.core.asgi import get_asgi_application # noqa: E402
|
||||
|
||||
# this is just for the crazy ones
|
||||
application = get_asgi_application()
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import six
|
||||
|
||||
from django.conf import global_settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -32,19 +31,50 @@ class ConfigurationBase(type):
|
|||
if parents:
|
||||
for base in bases[::-1]:
|
||||
settings_vars.update(uppercase_attributes(base))
|
||||
attrs = dict(settings_vars, **attrs)
|
||||
# Fix ImproperlyConfigured issue introduced in Django
|
||||
|
||||
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_DAYS" in attrs and "PASSWORD_RESET_TIMEOUT" in attrs:
|
||||
attrs.pop("PASSWORD_RESET_TIMEOUT_DAYS")
|
||||
return super(ConfigurationBase, cls).__new__(cls, name, bases, attrs)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import imp
|
||||
from importlib.machinery import PathFinder
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -46,12 +46,12 @@ def install(check_options=False):
|
|||
return parser
|
||||
|
||||
base.BaseCommand.create_parser = create_parser
|
||||
importer = ConfigurationImporter(check_options=check_options)
|
||||
importer = ConfigurationFinder(check_options=check_options)
|
||||
sys.meta_path.insert(0, importer)
|
||||
installed = True
|
||||
|
||||
|
||||
class ConfigurationImporter(object):
|
||||
class ConfigurationFinder(PathFinder):
|
||||
modvar = SETTINGS_ENVIRONMENT_VARIABLE
|
||||
namevar = CONFIGURATION_ENVIRONMENT_VARIABLE
|
||||
error_msg = ("Configuration cannot be imported, "
|
||||
|
|
@ -70,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
|
||||
|
|
@ -82,16 +82,10 @@ class ConfigurationImporter(object):
|
|||
return os.environ.get(self.namevar)
|
||||
|
||||
def check_options(self):
|
||||
try:
|
||||
parser = base.CommandParser(
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False)
|
||||
except TypeError:
|
||||
# Django before 2.1 used a `cmd` argument.
|
||||
parser = base.CommandParser(
|
||||
None,
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False)
|
||||
parser = base.CommandParser(
|
||||
usage="%(prog)s subcommand [options] [args]",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument('--settings')
|
||||
parser.add_argument('--pythonpath')
|
||||
parser.add_argument(CONFIGURATION_ARGUMENT,
|
||||
|
|
@ -127,58 +121,60 @@ class ConfigurationImporter(object):
|
|||
if (self.argv[1] == 'runserver'
|
||||
and os.environ.get('RUN_MAIN') == 'true'):
|
||||
|
||||
message = ("django-configurations version {0}, using "
|
||||
"configuration {1}".format(__version__ or "",
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import inspect
|
||||
import six
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from functools import partial
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -12,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=''):
|
||||
|
|
@ -24,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
|
||||
|
|
@ -60,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, 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)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import ast
|
|||
import copy
|
||||
import decimal
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
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.
|
||||
|
|
@ -92,7 +92,7 @@ class Value(object):
|
|||
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):
|
||||
|
|
@ -102,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
|
||||
|
|
@ -112,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
|
||||
|
||||
|
||||
|
|
@ -126,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):
|
||||
|
|
@ -139,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 = {}
|
||||
|
||||
|
|
@ -181,7 +183,7 @@ class IntegerValue(CastingMixin, Value):
|
|||
class PositiveIntegerValue(IntegerValue):
|
||||
|
||||
def to_python(self, value):
|
||||
int_value = super(PositiveIntegerValue, self).to_python(value)
|
||||
int_value = super().to_python(value)
|
||||
if int_value < 0:
|
||||
raise ValueError(self.message.format(value))
|
||||
return int_value
|
||||
|
|
@ -213,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()
|
||||
|
|
@ -257,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
|
||||
|
|
@ -266,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 = [
|
||||
|
|
@ -293,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
|
||||
|
||||
|
||||
|
|
@ -303,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:
|
||||
|
|
@ -336,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)
|
||||
|
||||
|
|
@ -380,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)
|
||||
|
||||
|
||||
|
|
@ -401,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
|
||||
|
||||
|
||||
|
|
@ -422,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:
|
||||
|
|
@ -437,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}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
try:
|
||||
__version__ = get_distribution(__name__).version
|
||||
except DistributionNotFound:
|
||||
__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."
|
||||
|
|
@ -3,6 +3,92 @@
|
|||
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)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
319
docs/conf.py
319
docs/conf.py
|
|
@ -1,299 +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'
|
||||
|
||||
from pkg_resources import get_distribution
|
||||
|
||||
# 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))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# 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']
|
||||
|
||||
# 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.
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = get_distribution("django-configurations").version
|
||||
# The short X.Y version.
|
||||
release = configurations.__version__
|
||||
version = ".".join(release.split(".")[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# -- General configuration ---------------------------------------------------
|
||||
add_function_parentheses = False
|
||||
add_module_names = False
|
||||
|
||||
# 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'
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# 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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ Example:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ tree mysite_env/
|
||||
$ tree --noreport mysite_env/
|
||||
mysite_env/
|
||||
├── DJANGO_SETTINGS_MODULE
|
||||
├── DJANGO_DEBUG
|
||||
|
|
@ -82,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.:
|
||||
|
|
@ -151,13 +149,13 @@ First install Django 1.8.x and django-configurations:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||
$ python -m pip install -r https://raw.github.com/jazzband/django-configurations/templates/1.8.x/requirements.txt
|
||||
|
||||
Or Django 1.8:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ django-admin.py startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||
$ python -m django startproject mysite -v2 --template https://github.com/jazzband/django-configurations/archive/templates/1.8.x.zip
|
||||
|
||||
Now you have a default Django 1.8.x project in the ``mysite``
|
||||
directory that uses django-configurations.
|
||||
|
|
@ -182,7 +180,7 @@ 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.
|
||||
That has the same effect as using the ``manage.py``, ``wsgi.py`` or ``asgi.py`` utilities.
|
||||
This will also call ``django.setup()``.
|
||||
|
||||
>= 3.1
|
||||
|
|
@ -332,8 +330,8 @@ 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>`
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -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,7 +3,7 @@ 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
|
||||
|
|
@ -21,7 +21,6 @@ file:
|
|||
|
||||
class Dev(Base):
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
class Prod(Base):
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
|
@ -32,9 +31,9 @@ it should be ``Prod``. In Bash that would be:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
export DJANGO_CONFIGURATION=Prod
|
||||
python manage.py runserver
|
||||
$ export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
$ export DJANGO_CONFIGURATION=Prod
|
||||
$ python -m manage runserver
|
||||
|
||||
Alternatively you can use the ``--configuration`` option when using Django
|
||||
management commands along the lines of Django's default ``--settings``
|
||||
|
|
@ -42,7 +41,7 @@ command line option, e.g.
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
python manage.py runserver --settings=mysite.settings --configuration=Prod
|
||||
$ python -m manage runserver --settings=mysite.settings --configuration=Prod
|
||||
|
||||
Property settings
|
||||
-----------------
|
||||
|
|
@ -91,7 +90,7 @@ a few mixin you re-use multiple times:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
class FullPageCaching(object):
|
||||
class FullPageCaching:
|
||||
USE_ETAGS = True
|
||||
|
||||
Then import that mixin class in your site settings module and use it with
|
||||
|
|
|
|||
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'``.
|
||||
|
|
@ -125,7 +127,9 @@ Allow final value to be used outside the configuration context
|
|||
|
||||
You may use the ``environ_name`` parameter to allow a :class:`~Value` to be
|
||||
directly converted to its final value for use outside of the configuration
|
||||
context::
|
||||
context:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> type(values.Value([]))
|
||||
<class 'configurations.values.Value'>
|
||||
|
|
@ -160,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
|
||||
|
|
@ -256,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.
|
||||
|
|
@ -279,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
|
||||
|
||||
|
|
@ -301,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,
|
||||
|
|
@ -354,6 +370,10 @@ Type values
|
|||
'it': ['Mike', 'Joe'],
|
||||
})
|
||||
|
||||
Override using environment variables like this::
|
||||
|
||||
DJANGO_DEPARTMENTS={'it':['Mike','Joe'],'hr':['Emma','Olivia']}
|
||||
|
||||
Validator values
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
@ -547,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',
|
||||
|
|
@ -584,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::
|
||||
|
||||
|
|
@ -605,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::
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[coverage:run]
|
||||
source = .
|
||||
branch = 1
|
||||
|
|
|
|||
29
setup.py
29
setup.py
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import print_function
|
||||
import os
|
||||
import codecs
|
||||
from setuptools import setup
|
||||
|
|
@ -15,6 +14,9 @@ setup(
|
|||
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'),
|
||||
|
|
@ -27,34 +29,41 @@ setup(
|
|||
'django-cadmin = configurations.management:execute_from_command_line',
|
||||
],
|
||||
},
|
||||
install_requires=['six'],
|
||||
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-discover-runner',
|
||||
'mock',
|
||||
'django-cache-url>=1.0.0',
|
||||
'dj-database-url',
|
||||
'dj-email-url',
|
||||
'dj-search-url',
|
||||
'Sphinx>=1.4',
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
'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.7',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'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',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
@ -95,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,30 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
# setup Django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main")
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'Test')
|
||||
|
||||
extensions = [
|
||||
'configurations.sphinx',
|
||||
]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'django-configurations'
|
||||
copyright = '2012-2014, Jannis Leidel and other contributors'
|
||||
|
||||
|
||||
version = release = 'test'
|
||||
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
html_theme = 'default'
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Test Documentation
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
from optparse import make_option
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
# Used by a specific test to see how unupgraded
|
||||
# management commands play with configurations.
|
||||
# See the test code for more details.
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--arg1', action='store_true'),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
pass
|
||||
|
|
@ -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,9 +1,7 @@
|
|||
import os
|
||||
import uuid
|
||||
import django
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
from configurations.values import BooleanValue
|
||||
|
||||
|
||||
class Test(Configuration):
|
||||
|
|
@ -11,8 +9,6 @@ class Test(Configuration):
|
|||
os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), os.pardir))
|
||||
|
||||
ENV_LOADED = BooleanValue(False)
|
||||
|
||||
DEBUG = True
|
||||
|
||||
SITE_ID = 1
|
||||
|
|
@ -36,12 +32,9 @@ class Test(Configuration):
|
|||
|
||||
ROOT_URLCONF = 'tests.urls'
|
||||
|
||||
if django.VERSION[:2] < (1, 6):
|
||||
TEST_RUNNER = 'discover_runner.DiscoverRunner'
|
||||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Test, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('base')
|
||||
return allowed_hosts
|
||||
|
||||
|
|
@ -71,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,19 +1,19 @@
|
|||
from configurations import Configuration
|
||||
|
||||
|
||||
class Mixin1(object):
|
||||
class Mixin1:
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Mixin1, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test1')
|
||||
return allowed_hosts
|
||||
|
||||
|
||||
class Mixin2(object):
|
||||
class Mixin2:
|
||||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Mixin2, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test2')
|
||||
return allowed_hosts
|
||||
|
||||
|
|
@ -21,6 +21,6 @@ class Mixin2(object):
|
|||
class Inheritance(Mixin2, Mixin1, Configuration):
|
||||
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test3')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ from .single_inheritance import Inheritance as BaseInheritance
|
|||
class Inheritance(BaseInheritance):
|
||||
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test-test')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ class Inheritance(Base):
|
|||
|
||||
@property
|
||||
def ALLOWED_HOSTS(self):
|
||||
allowed_hosts = super(Inheritance, self).ALLOWED_HOSTS[:]
|
||||
allowed_hosts = super().ALLOWED_HOSTS[:]
|
||||
allowed_hosts.append('test')
|
||||
return allowed_hosts
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
"""Used by tests to ensure logging is kept when calling setup() twice."""
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
from mock import mock
|
||||
from unittest import mock
|
||||
|
||||
import configurations
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ",
|
||||
)
|
||||
)
|
||||
|
|
@ -2,7 +2,7 @@ import os
|
|||
|
||||
from django.test import TestCase
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class InheritanceTests(TestCase):
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import sys
|
|||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from configurations.importer import ConfigurationImporter
|
||||
from configurations.importer import ConfigurationFinder
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
TEST_PROJECT_DIR = os.path.join(ROOT_DIR, 'test_project')
|
||||
|
|
@ -42,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
|
||||
|
|
@ -55,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),
|
||||
|
|
@ -64,21 +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')
|
||||
finder = ConfigurationFinder()
|
||||
self.assertEqual(finder.module, 'tests.settings.main')
|
||||
self.assertEqual(finder.name, 'Test')
|
||||
self.assertEqual(
|
||||
repr(importer),
|
||||
"<ConfigurationImporter for 'tests.settings.main.Test'>")
|
||||
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',
|
||||
|
|
@ -87,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):
|
||||
"""
|
||||
|
|
@ -106,6 +114,22 @@ class MainTests(TestCase):
|
|||
stdout=subprocess.PIPE)
|
||||
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||
|
||||
def test_configuration_argument_in_runypy_cli(self):
|
||||
"""
|
||||
Verify that's configuration option has been added to managements
|
||||
commands when using the -m entry point
|
||||
"""
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, '-m', 'configurations', 'test', '--help'],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, '-m', 'configurations', 'runserver', '--help'],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn('--configuration', proc.communicate()[0].decode('utf-8'))
|
||||
|
||||
def test_django_setup_only_called_once(self):
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, os.path.join(os.path.dirname(__file__),
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class SphinxTests(TestCase):
|
||||
docs_dir = os.path.join(settings.BASE_DIR, 'docs')
|
||||
|
||||
def test_multiprocessing(self):
|
||||
output = subprocess.check_output([
|
||||
'sphinx-build',
|
||||
'-b',
|
||||
'html',
|
||||
'-j 2',
|
||||
'.',
|
||||
'_build/html',
|
||||
], cwd=self.docs_dir, stderr=subprocess.STDOUT)
|
||||
self.assertIn(b'build succeeded.', output)
|
||||
|
|
@ -2,10 +2,11 @@ import decimal
|
|||
import os
|
||||
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,
|
||||
|
|
@ -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,17 +44,17 @@ 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'))
|
||||
|
|
@ -270,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())
|
||||
|
||||
|
|
@ -372,16 +373,23 @@ 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):
|
||||
|
||||
|
|
@ -411,6 +419,7 @@ 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://'):
|
||||
|
|
@ -421,6 +430,7 @@ class ValueTests(TestCase):
|
|||
'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'): # noqa: E501
|
||||
|
|
@ -429,7 +439,7 @@ class ValueTests(TestCase):
|
|||
def test_cache_url_value(self):
|
||||
cache_setting = {
|
||||
'default': {
|
||||
'BACKEND': 'django_redis.cache.RedisCache',
|
||||
'BACKEND': 'django_redis.cache.RedisCache' if DJANGO_VERSION < (4,) else 'django.core.cache.backends.redis.RedisCache', # noqa: E501
|
||||
'LOCATION': 'redis://host:6379/1',
|
||||
}
|
||||
}
|
||||
|
|
@ -485,12 +495,12 @@ 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()
|
||||
|
|
@ -503,6 +513,7 @@ 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
|
||||
})
|
||||
|
|
|
|||
62
tox.ini
62
tox.ini
|
|
@ -2,23 +2,23 @@
|
|||
skipsdist = true
|
||||
usedevelop = true
|
||||
minversion = 1.8
|
||||
whitelist_externals = sphinx-build
|
||||
envlist =
|
||||
py36-checkqa,
|
||||
py{27,35,36,py}-dj111
|
||||
py{35,36,37,py3}-dj20
|
||||
py{35,36,37,py3}-dj21
|
||||
py{35,36,37,38,py3}-dj22
|
||||
py{36,37,38,py3}-dj{30,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}
|
||||
|
||||
[travis]
|
||||
[gh-actions]
|
||||
python =
|
||||
2.7: py27
|
||||
3.5: py35
|
||||
3.6: py36,flake8,readme
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
pypy3: pypy3
|
||||
3.9: py39
|
||||
3.10: py310
|
||||
3.11: py311,flake8,readme
|
||||
3.12: py312
|
||||
3.13: py313
|
||||
pypy-3.10: pypy310
|
||||
|
||||
[testenv]
|
||||
usedevelop = true
|
||||
|
|
@ -27,24 +27,27 @@ setenv =
|
|||
DJANGO_CONFIGURATION = Test
|
||||
COVERAGE_PROCESS_START = {toxinidir}/setup.cfg
|
||||
deps =
|
||||
dj111: django>=1.11,<2.0
|
||||
dj20: django>=2.0a1,<2.1
|
||||
dj21: django>=2.1a1,<2.2
|
||||
dj22: django>=2.2a1,<3.0
|
||||
dj30: django>=3.0a1,<3.1
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
||||
py27,pypy: mock
|
||||
dj32: django~=3.2.9
|
||||
dj41: django~=4.1.3
|
||||
dj42: django~=4.2.0
|
||||
dj50: django~=5.0.0
|
||||
dj51: django~=5.1.0
|
||||
djmain: https://github.com/django/django/archive/main.tar.gz
|
||||
py312: setuptools
|
||||
py312: wheel
|
||||
py313: setuptools
|
||||
py313: wheel
|
||||
coverage
|
||||
coverage_enable_subprocess
|
||||
extras = testing
|
||||
commands =
|
||||
python --version
|
||||
{envbindir}/coverage run {envbindir}/django-cadmin test -v2 {posargs:tests}
|
||||
coverage combine . tests/docs
|
||||
coverage combine . tests docs
|
||||
coverage report -m --skip-covered
|
||||
coverage xml
|
||||
|
||||
[testenv:py36-checkqa]
|
||||
[testenv:py311-checkqa]
|
||||
commands =
|
||||
flake8 {toxinidir}
|
||||
check-manifest -v
|
||||
|
|
@ -54,3 +57,16 @@ deps =
|
|||
flake8
|
||||
twine
|
||||
check-manifest
|
||||
|
||||
[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