Compare commits

..

415 commits
1.2 ... master

Author SHA1 Message Date
Alexandr Artemyev
22f56b3064
Update Code of Conduct (#661)
Some checks are pending
Docs / docs (push) Waiting to run
Test / ruff-format (push) Waiting to run
Test / ruff-lint (push) Waiting to run
Test / build (3.10) (push) Waiting to run
Test / build (3.11) (push) Waiting to run
Test / build (3.12) (push) Waiting to run
Test / build (3.13) (push) Waiting to run
Test / build (3.14) (push) Waiting to run
Test / build (3.8) (push) Waiting to run
Test / build (3.9) (push) Waiting to run
Ref: #660
2026-03-16 09:38:32 -05:00
Philipp Thumfart
c908b05740
Added missing logic to reset multi select (#659)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled
2026-03-14 21:27:39 +05:00
Philipp Thumfart
7e75db3ebc
Fixed latent none return bug (#658)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled
2026-03-11 09:58:20 -05:00
Philipp Thumfart
4ac1e546c7
Added async support (#656)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled
* Added async logic

* Added tests and fixed async deadlock on aset

* Used abstract base class for backend to simplify code coverage

* Reordered try except block

* Added explicit thread safety

* Fixed linting error

* Worked on redis init block

* Fixed async test setup

* Added tests for redis instantiation

* Fixed linting errors
2026-03-04 16:37:37 -06:00
pre-commit-ci[bot]
675c9446cb
[pre-commit.ci] pre-commit autoupdate (#655)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled
updates:
- [github.com/asottile/pyupgrade: v3.20.0 → v3.21.2](https://github.com/asottile/pyupgrade/compare/v3.20.0...v3.21.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-30 14:53:23 -06:00
Rémy HUBSCHER
d2b8ca12d5
Merge pull request #654 from vincentdavis/fix_version_docs 2025-12-30 15:48:12 +01:00
Vincent Davis
886c00c1a2 Display correct version in docs 2025-12-30 08:25:26 -06:00
Rémy HUBSCHER
9c3bcbf247
Merge pull request #651 from jazzband/dependabot/github_actions/github-actions-76468cb07f 2025-12-03 12:04:22 +01:00
dependabot[bot]
c30403302f
chore(ci): bump actions/checkout from 5 to 6 in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 17:16:53 +00:00
Ivan
520e20844a
create autofill_values dict with full_cachekey straight away (#650) 2025-11-24 12:16:02 +05:00
Ivan
a5f01e72bb
Use only method to avoid requesting unused field (#649)
dont query key field since it is not needed
2025-11-23 22:45:16 +05:00
Rémy HUBSCHER
03fe0457ef
Merge pull request #648 from IgorCode/patch-1 2025-11-12 11:40:32 +01:00
Igor Jerosimić
1614466319
Fix collapse setting for Django 5.1+ #647 2025-11-10 11:01:24 +01:00
Rémy HUBSCHER
777bdb8442
Merge pull request #646 from justmobilize/fix-django-5-1-tests 2025-11-04 17:42:55 +01:00
Justin Myers
19889239ad Fix Django 5.1+ admin tests 2025-11-04 08:31:05 -08:00
Rémy HUBSCHER
6931c45c7b
Merge pull request #645 from jazzband/fix/tests 2025-10-21 10:29:44 +02:00
Rémy Hubscher
0f74be9035
Fix linter 2025-10-21 10:28:35 +02:00
Rémy Hubscher
27b1263119
Fix formatting 2025-10-21 10:24:49 +02:00
Rémy Hubscher
f8208af706
Fix imports 2025-10-21 10:23:38 +02:00
Rémy HUBSCHER
e002f4b187
Merge pull request #644 from jazzband/feat/add-test-matrix 2025-10-21 10:09:03 +02:00
Rémy Hubscher
0717cb8d06
Remove 3.14 from Django 5.1 2025-10-21 10:08:18 +02:00
Rémy Hubscher
ae94604872
Merge branch 'mgmt-cmd-respect-db-prefix' 2025-10-21 10:01:47 +02:00
Rémy HUBSCHER
7030cbb39f
Merge pull request #634 from christherama/mgmt-cmd-respect-db-prefix 2025-10-21 10:01:13 +02:00
Rémy HUBSCHER
889090ca7a
Merge pull request #643 from justmobilize/fix-collapse-in-django-5-1 2025-10-21 10:00:20 +02:00
Rémy Hubscher
f7c3d5f736
Add Django 5.1, 5.2 and Python 3.14 test 2025-10-21 09:56:59 +02:00
Rémy HUBSCHER
5c8df725f8
Merge branch 'master' into fix-collapse-in-django-5-1 2025-10-21 09:55:29 +02:00
Rémy HUBSCHER
6a66c636c4
Merge pull request #637 from mahdirahimi1999/feature/django-5.2-support 2025-10-21 09:54:41 +02:00
Rémy HUBSCHER
6209bbaaa8
Merge pull request #642 from dtcooper/master 2025-10-07 22:22:59 +02:00
Justin Myers
93274106a2 Fix collapse in Django 5.1+ 2025-10-07 09:04:23 -07:00
David Cooper
0f88fe83fa Use extra_context view kwarg in changelist_view()
Currently the `extra_context` kwarg is not used in `changelist_view`, it
is completely ignored. However, using it is a common idiom in a
`ModelAdmin` view method as a way to provide extra context variables to
an overridden template.

This is useful if one wants extra context variables in an overridden
`admin/constance/change_list.html` template.
2025-10-07 11:23:04 -04:00
Rémy HUBSCHER
c6b2c44671
Merge pull request #641 from jazzband/chore/run-black 2025-10-07 11:59:21 +02:00
Rémy Hubscher
6d01cffbc0
Use double quotes 2025-10-07 11:26:18 +02:00
Rémy Hubscher
6f12039ae7
Run ruff format 2025-10-07 11:22:24 +02:00
Rémy Hubscher
efe8516efb
Fix missing import 2025-10-07 11:05:48 +02:00
Rémy HUBSCHER
c89ee7c25d
Merge pull request #629 from Dacid99/docs-fixes 2025-10-07 10:57:28 +02:00
Rémy HUBSCHER
eff57684c6
Merge pull request #639 from jazzband/dependabot/github_actions/github-actions-6a14be197d 2025-10-07 10:55:28 +02:00
dependabot[bot]
461b98371f
chore(ci): bump the github-actions group across 1 directory with 2 updates
Bumps the github-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-python](https://github.com/actions/setup-python).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/setup-python` from 5 to 6
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-07 08:50:48 +00:00
Rémy HUBSCHER
781e95bbbf
Merge pull request #632 from jazzband/dependabot/github_actions/dot-github/workflows/pypa/gh-action-pypi-publish-1.13.0 2025-10-07 10:49:43 +02:00
Rémy HUBSCHER
87fe8c6471
Merge pull request #627 from jazzband/pre-commit-ci-update-config 2025-10-07 10:49:17 +02:00
pre-commit-ci[bot]
c1e2360093
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/asottile/pyupgrade: v3.19.1 → v3.20.0](https://github.com/asottile/pyupgrade/compare/v3.19.1...v3.20.0)
2025-10-06 17:43:44 +00:00
Mahdi
4044d7dad9 Add Django 5.1 and 5.2 support
- Add Django 5.1 and 5.2 classifiers to pyproject.toml
- Update changelog to reflect Django 5.1 and 5.2 support
2025-10-04 17:02:23 +03:30
Chris Ramacciotti
c21ea01528 Respects database prefix when removing stale keys 2025-09-19 22:54:40 -05:00
dependabot[bot]
9d4a1fbc25
chore(deps): bump pypa/gh-action-pypi-publish in /.github/workflows
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.4 to 1.13.0.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.4...v1.13.0)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.13.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 15:46:28 +00:00
david
8563ba5ec6 Add documentation around the usage of collection types as field types 2025-08-02 21:31:39 +02:00
david
5a6a278c70 Fix for setting rst tags 2025-08-02 21:28:29 +02:00
Alexandr Artemyev
6970708e05
Django 5.2 tests (#623) 2025-05-20 00:20:14 +05:00
Rémy HUBSCHER
22bdb011db
Merge pull request #609 from pfouque/fix_unittest
Fix override_config test decorator on Django 5.2
2025-02-04 22:11:22 +01:00
Pascal F
8193866157
Ruff format 2025-02-04 16:49:33 +01:00
Pascal F
756ff419ac
Add Django 5.2 to the test matrix 2025-02-04 16:49:33 +01:00
Pascal F
e61c1ed68c
Fix override_config on Django 5.2 2025-02-04 16:49:33 +01:00
dependabot[bot]
3a4071e622
chore(ci): bump pypa/gh-action-pypi-publish in the github-actions group (#611)
Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `pypa/gh-action-pypi-publish` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.3...v1.12.4)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 13:29:28 +05:00
Alexandr Artemyev
4b928c24f9
Update pyproject.toml 2025-01-27 14:21:22 +05:00
Pascal Fouque
2ae381e58e
Fix is_composite_pk on Django 5.2 (#608) 2025-01-27 14:12:47 +05:00
Alexandr Artemyev
f213eb4b79
Fix ruff 2025-01-14 21:40:57 +05:00
Fernando Karchiloff
745fb6f01e
Add get_values_for_keys function to utils (#607)
* Add get_values_for_keys function to utils
* Error text improvement and formatting
2025-01-14 21:31:32 +05:00
Max Lyaskovskiy
0dc1321af3
Updated supported constant types (#584)
---------

Co-authored-by: Rémy HUBSCHER <hubscher.remy@gmail.com>
2025-01-08 13:46:06 +05:00
Rémy HUBSCHER
1522245b82
Merge pull request #606 from atodorov/add-django51-to-ci
Add Django 5.1 to the testing matrix
2025-01-08 09:33:41 +01:00
Alexander Todorov
1451943b7d Add Django 5.1 to the testing matrix 2025-01-07 19:11:42 +02:00
Rotzbua
82bd56650d
feat(docs): improve command line (#605)
update option descriptions according to current code
2025-01-07 08:36:24 -06:00
Rotzbua
f044a7d0fe
fix(docs): use sphinx note function (#604) 2025-01-07 08:34:32 -06:00
Rémy HUBSCHER
d846d70ac4
Merge pull request #603 from jazzband/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2025-01-07 08:51:14 +01:00
pre-commit-ci[bot]
bc8b3bfb43
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.17.0 → v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.19.1)
2025-01-06 17:54:19 +00:00
dependabot[bot]
9c30b1e778
chore(ci): bump pypa/gh-action-pypi-publish in the github-actions group (#602)
Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `pypa/gh-action-pypi-publish` from 1.12.2 to 1.12.3
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.2...v1.12.3)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 17:21:02 +05:00
Mirat Can Bayrak
7de0840fe2
Update django.po for Turkish translations/ (#601) 2024-12-23 12:58:23 +05:00
Rotzbua
d9c9c3e81f
feat(tests): add python 3.13 to tests (#600) 2024-12-09 12:43:06 +05:00
dependabot[bot]
bb50d5e4c5
chore(ci): bump the github-actions group with 2 updates (#599)
Bumps the github-actions group with 2 updates: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `pypa/gh-action-pypi-publish` from 1.11.0 to 1.12.2
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.11.0...v1.12.2)

Updates `codecov/codecov-action` from 4 to 5
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 11:53:19 +05:00
dependabot[bot]
415d82c6c0
chore(ci): bump pypa/gh-action-pypi-publish in the github-actions group (#596)
Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `pypa/gh-action-pypi-publish` from 1.10.2 to 1.11.0
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.2...v1.11.0)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-04 08:09:45 -06:00
Alexandr Artemyev
a604a5ea9f
Fix #593 (#597) 2024-11-04 08:09:19 -06:00
pre-commit-ci[bot]
d4b41234da
[pre-commit.ci] pre-commit autoupdate (#590)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)
- [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-07 15:46:28 -05:00
dependabot[bot]
9689fd5efc
chore(ci): bump pypa/gh-action-pypi-publish in the github-actions group (#589)
Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `pypa/gh-action-pypi-publish` from 1.10.0 to 1.10.2
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.0...v1.10.2)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 16:02:52 -05:00
Alexandr Artemyev
d624ee8730
Better already migrated detection (#588) 2024-09-17 14:59:11 +05:00
Alexandr Artemyev
a68bf903cf
Fix exception in 0002 migration (#579) 2024-09-05 13:30:28 +05:00
Alexandr Artemyev
f1d0dfcb8d
Add setuptools-scm for dynamic version for scm version management (#582) 2024-09-04 15:08:08 -05:00
Alexandr Artemyev
05dfb5d3cf
Release notes are on GitHub (#580) 2024-09-04 15:07:22 -05:00
Alexandr Artemyev
70dba1ca35
Bump 4.1.0 2024-09-04 14:42:41 +05:00
Alexandr Artemyev
bb0dc4676f
Collections support (#581)
---------

Co-authored-by: Sebastian Manger <manger@netcloud.ch>
2024-09-04 14:41:53 +05:00
Alexandr Artemyev
31c9e8d043
Bump 4.0.2 2024-09-03 18:54:01 +05:00
ivan-klass
5e221a855b
More safe suppress of pickle-to-json data migration (#578) 2024-09-03 08:48:37 -05:00
dependabot[bot]
5ce88b7457
chore(ci): bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0 in the github-actions group (#577)
Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `pypa/gh-action-pypi-publish` from 1.9.0 to 1.10.0
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 11:25:46 +05:00
Felipe Gonzalez
cf838ab28a
fix: Fixed _pickle.UnpicklingError (#576)
The error indicates an issue with unpickling data during the migration process. Specifically, it raises an `_pickle.UnpicklingError` with the message "invalid load key, '{'." The data being loaded may be corrupted or not in the expected format.
2024-08-31 09:31:21 -05:00
Alexandr Artemyev
c690d5ef0f
Fix #570 (#571)
* Fix #570

* [PATCH] chore(ci): check missing migrations

---------

Co-authored-by: Sebastian Manger <manger@netcloud.ch>
2024-08-23 14:13:54 -05:00
Alexandr Artemyev
98effe6917
Ping gh-action-pypi-publish version (#568) 2024-08-23 14:13:20 -05:00
Christian Clauss
e428387620
docs/index.rst: Fix typo newsletter (#567) 2024-08-23 17:07:24 +05:00
Alexandr Artemyev
f5bf8e5adc
Add build 2024-08-21 12:12:49 +05:00
Alexandr Artemyev
46f2f7aa85
Fix release.yml 2024-08-21 12:09:04 +05:00
Alexandr Artemyev
2c827124cd
bump version (#565) 2024-08-20 16:46:25 -05:00
Alexandr Artemyev
3640eb228a
Replace pickle with JSON (#564)
* Replace pickle with JSON

Co-authored-by: Ivan Klass <klass.ivanklass@gmail.com>
2024-08-20 09:35:27 -05:00
Alexandr Artemyev
ce957ac096
Fix 426 (#563) 2024-08-16 16:02:31 -05:00
Alexandr Artemyev
8c6552fdaf
Enable more rules for ruff (#562) 2024-07-05 19:38:26 +05:00
Alexandr Artemyev
8cec9c24b0
Refactoring for constance cli command (#561) 2024-07-03 19:51:11 +05:00
Alexandr Artemyev
57083bbed2
Add ruff format & lint (isort only) (#560) 2024-07-03 19:21:33 +05:00
Alexandr Artemyev
554eedc7c2
migate from setup.py to pyproject.toml & bump tox & declare support for python 3.12 (#557)
* migate from setup.py to pyproject.toml
* bump tox
* declare support for python 3.12
2024-07-03 13:30:29 +05:00
pre-commit-ci[bot]
451386aca0
[pre-commit.ci] pre-commit autoupdate (#558)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
- [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-01 16:07:42 -05:00
Rotzbua
5f262fce9a
fix: django 5.1 removed collapse.js (#556) 2024-06-15 00:55:01 +05:00
Rotzbua
ba530b9cc0
fix(docs): update links (#555) 2024-06-14 19:59:48 +05:00
Rotzbua
6976d8a5e9
feat(docs): migrate to sphinx 7 (#553)
* feat(docs): migrate to sphinx 7

feat(docs): add instant search
feat(docs): enable reproducible build with requirements.txt
fix(docs): warnings
WARNING: The pre-Sphinx 1.0 'intersphinx_mapping' format is deprecated and will be removed in Sphinx 8. Update to the current format as described in the documentation. Hint: "intersphinx_mapping = {'<name>': ('https://docs.python.org/', None)}".https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping

* feat(docs): add test build to GH CI
2024-06-14 19:57:30 +05:00
dependabot[bot]
433e0f5be9
chore(ci): bump the github-actions group with 3 updates (#552)
Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-python](https://github.com/actions/setup-python) and [actions/cache](https://github.com/actions/cache).


Updates `actions/checkout` from 3 to 4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

Updates `actions/setup-python` from 4 to 5
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

Updates `actions/cache` from 3 to 4
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 17:52:19 +05:00
Rotzbua
4b78082240
fix(docs): add required readthedocs config (#551)
* fix(docs): add required readthedocs config
---------

Co-authored-by: Alexandr Artemyev <mogost@gmail.com>
2024-06-11 17:51:45 +05:00
Rotzbua
6eaa7a6602
chore: update supported versions (#545)
- drop eol django 3.2 4.0 4.1
- drop eol python 3.7
- drop untested PyPy support
- prepare python 3.12 test
- update GH action dependencies
- add dependabot to maintain GH action dependencies
2024-06-11 17:46:29 +05:00
Alexandr Artemyev
fd54f0a15d
Merge pull request #544 from Rotzbua/chore_migrate_fstring
chore: migrate to f-string
2024-06-10 00:40:49 +05:00
Rotzbua
ee681b592a
feat(pre-commit): add pyupgrade 2024-06-09 21:34:50 +02:00
Rotzbua
eb5aef2554
chore: migrate to f-string
fix: use str() instead of f-string

credit: tool pyupgrade
Co-Authored-By: Alexandr Artemyev <mogost@gmail.com>
2024-06-09 21:07:07 +02:00
Alexandr Artemyev
053944200e
Merge pull request #547 from Rotzbua/chore_migrate_es2015
chore: migrate JS to ES2015
2024-06-09 22:37:39 +05:00
Alexandr Artemyev
bf69baec61
Merge pull request #549 from Rotzbua/fix_remove_legacy
fix: remove legacy django <1.9 code
2024-06-09 22:37:15 +05:00
Alexandr Artemyev
ef24fcb95f
Merge pull request #548 from Rotzbua/fix_html_trailing_slash
fix: html5 does not allow self-closing tags
2024-06-09 22:37:03 +05:00
Alexandr Artemyev
38f365d719
Merge pull request #543 from Rotzbua/fix_typo
fix: typos
2024-06-09 22:36:37 +05:00
Rotzbua
7589b11cf4
fix: remove legacy django <1.9 code 2024-06-08 23:28:12 +02:00
Rotzbua
3eede068f4
fix: html5 does not allow self-closing tags
Seems like leftovers from xhtml.
2024-06-08 21:31:38 +02:00
Rotzbua
60d85d7b54
chore: migrate JS to ES2015
Use let/const instead of var.
2024-06-08 21:25:52 +02:00
Rotzbua
1b27d73586
fix: typos 2024-06-08 19:55:28 +02:00
Chris Clark
cb8ae39854
Merge pull request #538 from jazzband/dont-set-in-get
Fix issue #510
2024-02-05 00:01:09 -05:00
Chris Clark
6f0bb23102 one query 2024-02-01 14:24:48 -05:00
Chris Clark
d1c409b1ac better DB handling 2024-02-01 14:15:22 -05:00
Alexandr Artemyev
46fe20dcd7
Merge pull request #537 from arunsathiya/master
ci: Use GITHUB_OUTPUT envvar instead of set-output command
2024-01-12 15:15:02 +06:00
Arun
69fc0f9a8e
ci: Use GITHUB_OUTPUT envvar instead of set-output command 2024-01-11 17:12:17 -08:00
pre-commit-ci[bot]
e001a228f8
[pre-commit.ci] pre-commit autoupdate (#536)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-02 20:06:48 -06:00
Sergei Iurchenko
d03bea804f
fix migration on mysql (#531)
Co-authored-by: Iurchenko Sergei <empty>
2023-10-16 09:14:40 -05:00
Camilo Nova
bc9707d618 Bump version 3.1.0 2023-08-21 16:20:18 -05:00
James Tiplady
6a5052e9f4
Adding support for using a subdirectory of MEDIA_ROOT for file fields (#475)
* Adding support for using a subdirectory of MEDIA_ROOT for file fields with CONSTANCE_FILE_ROOT setting

* Improving documentation for CONSTANCE_FILE_ROOT

* Updating PR branch to work with latest master
2023-07-29 11:35:38 -05:00
Sergei Iurchenko
554dac0473
remove pypy from tox tests (#524)
Co-authored-by: Iurchenko Sergei
2023-07-20 12:19:33 -05:00
Camilo Nova
8317070890 Bump version 3.0.0 2023-06-27 15:53:30 -05:00
Rémy HUBSCHER
796f0fac5c
Merge pull request #518 from browniebroke/feat/django-4.2
Add official support for Django 4.2
2023-04-18 14:02:12 +02:00
Bruno Alla
d65b916189
Add official support for Django 4.2 2023-04-18 11:04:48 +01:00
Bruno Alla
9cbd512d75
Fix formatting in changes.rst 2023-04-18 11:02:33 +01:00
Søren Howe Gersager
0047a781af
Fix constance management command without admin installed (#506)
* refactor out ConstanceForm and get_values into forms.py and utils.py respectively

* fix tests and documentation

* correct mock import

* fix merge
2023-04-07 08:16:39 -05:00
pre-commit-ci[bot]
92e595e68b
[pre-commit.ci] pre-commit autoupdate (#516)
updates:
- [github.com/pre-commit/pygrep-hooks: v1.9.0 → v1.10.0](https://github.com/pre-commit/pygrep-hooks/compare/v1.9.0...v1.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-07 08:12:07 -05:00
Sergei Iurchenko
5ab48e1943
505-race-condition-caused-by-when-constance-registers-django-checks (#514)
Co-authored-by: Iurchenko Sergei
2023-03-13 17:45:31 -05:00
Sergei Iurchenko
b486056802
fix_ci (#512)
* fix_ci

Co-authored-by: Iurchenko Sergei
2023-03-11 08:58:13 -06:00
pre-commit-ci[bot]
79fd8af0f4
[pre-commit.ci] pre-commit autoupdate (#507)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-10 20:41:05 +03:00
Sergei Iurchenko
2cb2ccd063
Refactor app and model 2023-03-10 20:38:31 +03:00
Ani Mehta
8b03012918
Fix "and and" in backends.rst (#509) 2023-01-11 11:46:55 -06:00
Dmitry
b6f8e2c5b8
"failed to update live settings" message (#491)
* "failed to update live settings" message

* Update constance/locale/ru/LC_MESSAGES/django.po

* test fix

* fix .po/.mo

Co-authored-by: Alexandr Artemyev <mogost@gmail.com>
2022-10-13 12:16:31 -05:00
Felippe Medeiros
b7da81451e
Forward the request when saving the form (#499) 2022-10-12 19:07:49 -05:00
Ihor Sychevskyi
432ffc8f1c
update links (#502) 2022-10-11 00:08:54 +03:00
Rémy HUBSCHER
02c5fd5011
Preparing release 2.9.1 (#497) 2022-08-11 13:54:59 +02:00
pre-commit-ci[bot]
c4d0b6f693
[pre-commit.ci] pre-commit autoupdate (#486)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.3.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-08-08 08:16:58 -05:00
Yuekui
4d7b904cd2
Fix text format for MultiValueField usage (#494) 2022-08-05 09:36:29 -05:00
Mariusz Felisiak
7b59c64635
Added support for Django 4.1. (#487)
* Added Django 4.0 classifier.

* Added support for Django 4.1.
2022-07-13 10:04:47 -05:00
alexkiro
55aed5d4d4
Add support for using gettext in fieldset headers (#489)
* Support tuples for CONFIG_FIELDSETS

* Add test for tuple CONFIG_FIELDSETS

* Preserve tuple fieldset sorting

* Add i18n example in the docs
2022-07-13 10:01:25 -05:00
Alessandro Piroddi
807f98cc3b
Fix build issues (#477)
Issue #468

Co-authored-by: Alessandro Piroddi <alessandro.piroddi@inpeco.com>
2022-05-11 07:28:16 -05:00
Alexandr Artemyev
908b72bc47
Merge pull request #479 from yuekui/patch-1
Fix typo in docs
2022-05-04 13:05:41 +06:00
Yuekui
b4bc9120d0
Fix typo in docs 2022-04-28 13:23:45 -07:00
Alexandr Artemyev
c47fefaf46
Merge pull request #478 from dxillar/patch-1
Update index.rst
2022-04-25 19:00:55 +06:00
Amit Garu
4899d1ce5c
Update index.rst
Typo fix for field access in template.
2022-04-24 12:08:50 +05:45
Camilo Nova
5de44eafb1 Bump version 2.9.0 2022-03-11 14:36:39 -06:00
Asger Hautop Drewsen
ebb5c8608b
Add default_auto_field to database backend (#449)
This ensures that changing DEFAULT_AUTO_FIELD in settings.py doesn't
create migrations for constance.
2022-03-11 13:25:57 -06:00
Yurchenko Sergey
fc6d41fdb3
serialize_according_to_widget (#472)
Co-authored-by: Сергей Юрченко <s.yurchenko@softpro.com>
2022-02-11 19:49:08 -06:00
horpto
8b34b63fd0
Add caching redis backend (#466)
* Add caching redis backend

* fix mget implementation

* fix lock
2022-01-06 15:45:11 -05:00
pre-commit-ci[bot]
01a8dc54d3
[pre-commit.ci] pre-commit autoupdate (#467)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-01-05 10:11:16 -05:00
MansurAliKoroglu
dc83f99df0
Add documentation for constance_dbs config (#462)
Co-authored-by: Mansur Ali Koroglu <mansur@thorgate.eu>
2021-12-15 11:18:06 -05:00
Hugo van Kemenade
87e2f277a7
Add basic pre-commit config (#461) 2021-11-11 07:48:02 -05:00
Hugo van Kemenade
bd022205f1
CI: Replace deprecated pypy3 with pypy-3.8 (#460)
pypy3 is deprecated and is not available in newer images:
https://github.com/actions/setup-python/issues/244#issuecomment-925966022

Instead explicitly specify the version:
https://github.com/actions/setup-python#specifying-a-pypy-version

Committed via https://github.com/asottile/all-repos
2021-11-11 07:47:20 -05:00
Vasyl Dizhak
9554dbacd9
Fixes for Ukrainian locale (#458)
Co-authored-by: Vasyl Dizhak <vasyl.dizhak@moneypark.com>
2021-10-26 10:17:39 -05:00
Jair Henrique
a706e4798c
Add support to python 3.10 (#455) 2021-10-21 13:56:14 -05:00
Jacob Kaplan-Moss
b89f5ddb4e
Add a note about working around the locmem cache restriction (#448)
Per a conversation on twitter with @jezdez about this, here's a stab at some docs. I've tried to strike a balance: I want to tell people about this option, but not directly provide the code to avoid copy-paste without thinking.
2021-10-21 13:51:20 -05:00
Jazzband Bot
32577c17d8
Jazzband: Created local 'CODE_OF_CONDUCT.md' from remote 'CODE_OF_CONDUCT.md' (#457) 2021-10-21 13:49:53 -05:00
dkr13
9c95294ed5
do not detect datetime fields as date type (#456)
Co-authored-by: Daniel Klein-Ridder <daniel@kleinridder.de>
2021-10-21 13:49:26 -05:00
Alexandr Artemyev
3b419e2066
Merge pull request #451 from johnthagen/patch-1
Improve formatting of the pip install command
2021-09-09 14:40:17 +03:00
johnthagen
7306c0f703
Improve formatting of the pip install command 2021-09-09 07:08:35 -04:00
Alexandr Artemyev
b9a2adf19c
Merge pull request #446 from jazzband/django-3-2-test
Run tests on django 3.2
2021-03-17 20:22:30 +03:00
Alexandr Artemyev
45bcfc1a4a
Run tests on django 3.2 2021-03-17 19:46:41 +03:00
Jannis Leidel
a16cca3e8f
Rename Django's dev branch to main. (#445)
* Rename Django's dev branch to main.

More information: https://groups.google.com/g/django-developers/c/tctDuKUGosc/
Refs: https://github.com/django/django/pull/14048

* Don't run tests against Django main on Python < 3.8.
2021-03-09 13:12:55 +01:00
Jannis Leidel
53ac3cd45f
Add concrete_model class attribute to fake admin model. (#441)
* Add concrete_model class attribute to fake admin model.

This is related to #244 and https://github.com/django-admin-tools/django-admin-tools/issues/103.

* Set attribute during init.
2021-01-13 14:45:37 -05:00
Mohamed Ben Makhlouf
b30ccb6257
add arabic translation (#438)
* add arabic translation

* add compiled file
2020-12-02 11:53:38 -05:00
Jannis Leidel
be92e09c9a
Update badges. 2020-11-30 21:03:00 +01:00
Jannis Leidel
502c8da1ba
Remove Travis config. 2020-11-30 21:02:40 +01:00
Rotzbua
d4ab97591b
Fix build badge url (#437)
travis -> github actions
2020-11-30 21:00:13 +01:00
Jannis Leidel
8d57f02023
Migrate to GitHub Actions. (#435)
* Add GitHub Actions test workflow.

* Add version map.

* Fix name.

* Update .github/workflows/test.yml

* Add release workflow.

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2020-11-27 09:29:12 -05:00
Camilo Nova
f4732b97fb Bump version 2020-11-19 08:26:54 -05:00
Rotzbua
c6087f8580
update python django (#432)
* update python django
add django 3.1 to test
drop eol python 3.5
add python 3.9
2020-11-18 11:16:33 -05:00
Alexandr Artemyev
550eb1364c
Fix RemovedInDjango40Warning about Signal (#430) 2020-11-18 11:15:44 -05:00
Jair Henrique
08a20c1bbe
Use gettext_lazy instead of ugettext_lazy (#421)
Co-authored-by: Camilo Nova <camilo.nova@gmail.com>
2020-11-18 11:14:05 -05:00
Ekin Ertaç
dfce4f2fe5
url() is deprecated in Django 3.1 (#418)
* replace url()'s with re_path()

* convert re_path to path

* import path instead of re_path

* Convert missed re_path to path

Co-authored-by: Alexandr Artemyev <mogost@gmail.com>
2020-11-18 11:12:27 -05:00
M. Hosseyn Najafi
5d2bbb7092
Respect other classes added by admin templates (#417)
Fixes #365.
2020-11-18 11:11:35 -05:00
Mahdi Namaki
abc532ed18
Create and add fa language translations files (#420) 2020-11-18 11:11:04 -05:00
mandalay-rp
ce37f630f1
changes for get_inconsistent_fieldnames(): fields_list can be a dictionary, when a fieldset is defined as collapsible (#433)
Co-authored-by: dalay <ussria@gmail.com>
2020-11-18 11:10:31 -05:00
andrei-gypaetus
4e5573ed95
issue #404 prevent reset to default for file field (#413) 2020-07-04 16:45:48 -05:00
Camilo Nova
96e95fcfe6 Bump version 2020-06-22 17:37:59 -05:00
Alexandr Artemyev
79db604954
Fix #410 (#412) 2020-06-22 17:31:13 -05:00
Dmitry Kalinin
2eb3fcec8c Added Ukrainian locale 2020-06-15 00:04:30 +03:00
Misha Behersky
9eccfe0386
Add memory backend (#394)
* Add simple backend

* Add test case for simple backend

* Add tests for mget backend method

* Fix redis mock mget implementation

* Make sure memory backend is thread safe

* Add docs section for memory backend

* Add test usage examples to docs

* Update docs for memory backend in testing

* Share memory storage between threads
2020-06-10 19:49:42 +02:00
Pavel Torbeev
6b44a52933
Added a sticky footer (#407)
* Added a sticky footer.

* Returned the semantics to django
2020-06-10 14:21:49 +02:00
Alexandr Artemyev
788616d70c
Add anchors in admin (#402)
* Add anchors in admin

* Fix whitespace

* Add some css
2020-06-09 13:27:52 +02:00
Rotzbua
a304c4f865 add django-constance version to issue template 2020-06-08 18:57:03 +02:00
Alexandr Artemyev
9e6dc03914
Merge pull request #397 from Mogost/issue-396
Normalize newlines from form
2020-06-04 12:23:38 +03:00
utapyngo
73fa362e3f Add test_newlines_normalization 2020-05-26 19:08:17 +07:00
utapyngo
39f762c9c6 Add MULTILINE field to test #396 manually 2020-05-26 18:00:13 +07:00
Alexandr Artemyev
96bd3e1047
Fix #396 2020-05-25 19:24:54 +03:00
Erik Seglem
731514c52f
Switch md5 to sha256. (#395) 2020-05-20 08:28:50 -05:00
Sébastien Corbin
1de764a22c
Read-only form with correct perm (#393) 2020-04-29 12:02:04 -05:00
Sébastien Corbin
540ead383c
Fix #386 Translate app name (#392) 2020-04-29 12:00:47 -05:00
Sébastien Corbin
86933b6774
Update example project for Django>2 (#391) 2020-04-29 12:00:20 -05:00
Sébastien Corbin
3854665637 Put back wheel generation in travis
It was removed by 590fa02eb3
2020-04-20 18:27:41 +02:00
Vladas Tamoshaitis
bd8041c55f
Allow override_config for pytest (#338)
* provides: base override class; unittest and pytest overrides

* raise invalid config error earlier

* update AUTHORS

* avoid AttributeError

* fix comment

* add tests

* fix tests, update docstring

* update docs, improve tests

* fix docs

* fix markdown

* refactor pytest override, use hidden fixture, refactor base and unittest classes

* improve docstring and error

* refactor pytest override to use hooks

* set minimum pytest version

* revert empty lines removal

* introduce pytest test runner for package, refactoring

* WIP

* Finalize tox config, refactor docs, add global fixture

* skip py35

* pytest command: remove unnecessary ignore

* address comments

* Update constance/test/pytest.py

* address comments

* add test for checking nested markers

Co-authored-by: Camilo Nova <camilo.nova@gmail.com>
Co-authored-by: Paweł Zarębski <ppjzarebski@gmail.com>
2020-04-04 11:38:22 -05:00
Ilya Chichak
4de4114bbd
Make groups of fieldsets collapsable. Fix #350 (#351)
* made results table responsive for Django 2 admin

* make fieldsets collapsable

* updated version via feature

* fixed codestyle according to requested changes

* made results table responsive for Django 2 admin

Co-authored-by: Camilo Nova <camilo.nova@axiacore.com>
2020-03-16 11:33:19 -05:00
Camilo Nova
5e91a92431
Allow concurrent calls to set() method (#384) 2020-03-05 18:04:55 -05:00
Martey Dodoo
50287a3b2a
Simplify documentation installation section. (#378)
Make "Installation" section of documentation less fraught by only
including instructions to install django-constance with the default
Redis backend. This will reduce the chances of new users failing to
successfully install the database backend because it needs additional
configuration.

Fixes #375.
2020-02-18 09:24:37 -05:00
Martey Dodoo
12ead8fd1f
Improve grammar of documentation index file. (#377) 2020-02-12 14:23:11 -05:00
Yurchenko Sergey
fa6ae65594
delete south migrations (#371) 2020-01-30 11:56:12 -05:00
Camilo Nova
0ed37e4fce Bump version 2020-01-29 16:33:02 -05:00
Yurchenko Sergey
8f20ca3111 command-to-delete-stale-records (#355)
* command-to-delete-stale-records
2020-01-27 14:24:32 -05:00
Elisey Zanko
b62206da57 Resolve #367: Set pickle protocol version for the Redis backend (#369) 2020-01-14 15:46:27 -05:00
Alexandr Artemyev
590fa02eb3 Drop support py<3.5 django<2.2 (#359)
* Drop support py<3.5 django<2.2

* Remove admin_static
2019-12-23 16:20:41 -05:00
Camilo Nova
886f6d5235 Improve documentation. Fixes #304 2019-12-23 14:17:42 -05:00
Camilo Nova
0e94d13b2a Bump version 2019-12-23 14:07:28 -05:00
Özcan Yarımdunya
288ca7c5e4 Add Turkish language support (#353)
* Add Turkish language
2019-12-23 13:55:58 -05:00
Alexandr Artemyev
917c2790d1
Update AUTHORS
Add myself to authors
2019-11-29 17:24:39 +03:00
Alexandr Artemyev
db3f6e6e2f
Merge pull request #342 from Mogost/fix-341
Fix #341
2019-09-16 15:12:41 +03:00
Alexandr Artemyev
e069ddb91a
Fix #341 2019-09-16 11:51:18 +03:00
Dmitriy Tatarkin
299b3b1996 Fixed "can't compare offset-naive and offset-aware datetimes" (#337)
* Fixed "can't compare offset-naive and offset-aware datetimes" when USE_TZ = True

* Fixed case when `USE_TZ = False`
2019-08-12 11:00:07 -05:00
riazanovslv
98800c0671 ConstanceForm save method tweak (#333)
We need to store the actual name of the file, which the storage's save method provides. Otherwise, if we upload a new file with the same name as the already stored file has, the Constance config will keep a link to the old file.
2019-07-19 13:25:33 -05:00
Kirill Goncharov
1e751f1afa Optimizations for database backend (#329)
* Add test for database backend with query count assertions

* Rewrite set() method of database backend to reduce number of queries
2019-06-08 09:14:58 -05:00
Richard Morrison
2948dff3cc Add a Django system check... (#326)
* Add a Django system check that CONFIG_FIELDSETS accounts for all of CONFIG

* Fix test (don't actually modify values when submitting form) and restore python2 compatibility
2019-04-06 09:13:55 -05:00
Ilya Chichak
d495f1b3bf made results table responsive for Django 2 admin (#325) 2019-03-28 15:04:24 -05:00
Camilo Nova
c965db1bd9 Bump version 2019-03-16 12:25:10 -05:00
Camilo Nova
bb03e1e65a Removed Python 3.4 since is not longer supported 2019-03-16 09:53:08 -05:00
Tyler Kennedy
5e422cfd54 Allow null & blank for PickleField. (#315)
The default behaviour of PickleField was changed from null=True to
null=False. This causes issues with fields such as an image_field which
will try storing a NULL when unset.
2019-03-16 09:47:43 -05:00
Marc-Antoine Lemieux
a31d6f195f Use default_storage to save file (#319) 2019-03-13 19:11:25 -05:00
Moetaz
533c3cb594 fixed "Reset to default" button with constants whose name contains a space. closes #321 (#322) 2019-03-13 19:04:33 -05:00
John Carter
d6ed427c33 Drop Django<1.11 and 2.0, fix tests vs Django 2.2b (#320)
Also added py37, pypy3 to tox, travis
2019-03-11 17:20:08 -05:00
horida
286edca505 show not existing fields in field_list (#309) 2018-10-11 12:18:39 -05:00
Camilo Nova
ce565ecd8a Bump version 2018-09-20 07:31:31 -05:00
Erivânio Vasconcelos
a188c65281 Remove duplicated } (#302) 2018-09-19 18:49:03 -05:00
Camilo Nova
678916bcc2 Bump version 2018-09-13 15:16:17 -05:00
Jürgen Ryannel
6cc9ebfb67 Added support for django 2.1
* in django>=2.1 cmd was removed as parameter to add_parser

* add tox support for django >= 2.1

* introduce helper function to remove cmd paramter form add_parsers when
django >= 2.1

* remove unecessary lines

* fix issues when cmd may be missing in kwargs
2018-08-29 10:18:22 -05:00
Camilo Nova
83ee19c858
Merge pull request #297 from silentsokolov/fix-docs
Fixed docs signals
2018-08-29 10:10:08 -05:00
Dmitriy Sokolov
18d6a48e7e
Update index.rst
Fixed note about signals
2018-08-29 14:48:07 +03:00
Camilo Nova
7b35f51cee
Merge pull request #290 from felixxm/django-2.0
Added official Django 2.0 support.
2018-06-27 12:19:31 -05:00
Mariusz Felisiak
0fdca221af
Added official Django 2.0 support. 2018-06-23 21:44:11 +02:00
Camilo Nova
e269d423da
Merge pull request #287 from mobolic/fix-spelling
Fix spelling issues in CONTRIBUTING and changelog.
2018-05-23 17:27:16 -05:00
Camilo Nova
0c1fb79311 Drop support for Python 3.3 since wheel doesn't support it 2018-05-23 17:26:58 -05:00
Martey Dodoo
2f4618cf96 Fix spelling issues in CONTRIBUTING and changelog.
Fix misspelled words in contributing guidelines and changelog. Fix
formatting of multi-line changelog entry.
2018-05-22 22:36:05 -04:00
Camilo Nova
fd7c4e86aa
Merge pull request #283 from myii/myii-patch-1
Improve consistency of reset value handling for `date`
2018-05-12 15:09:14 -05:00
Imran Iqbal
c9916c7e26 Improve consistency of reset value handling for date
* Consistent implementation re: `datetime`
* Consistent reset using `DATE_INPUT_FORMAT`, i.e.
  - Works whether `USE_L10N` setting is active or not
2018-05-07 17:55:43 +01:00
Camilo Nova
df30bcd2ff
Merge pull request #278 from yinkanghong/master
fix bug of can't change permission chang_config's name
2018-04-20 12:35:12 -05:00
ykh
887ee92e62 fix bug of can't change permission chang_config's name 2018-04-20 17:50:20 +08:00
Camilo Nova
bb1ecde5d5
Merge pull request #276 from yinkanghong/master
update Chinese translation
2018-04-19 09:17:13 -05:00
ykh
b032f8f72b update Chinese translation 2018-04-19 11:53:33 +08:00
Camilo Nova
05ec0e9f1d
Merge pull request #274 from DmitryKaramin/master
fix(admin): date type
2018-04-16 16:20:39 -05:00
Dmitry Karamin
45da6edd28 fix(admin): date type 2018-04-16 21:41:18 +03:00
Camilo Nova
566f1e386e
Merge pull request #269 from ajinkya-bhosale/patch-1
Update index.rst
2018-04-02 18:55:51 -05:00
ajinkya bhosale
cfbc8cd8d7
Update index.rst 2018-04-02 08:01:25 +05:30
Ivan
d3e21abde4 Documented use with admin extensions 2018-04-01 09:41:13 +02:00
Camilo Nova
9a2d39a2e8
Merge pull request #271 from felixxm/br-closing-slash
Fixed TestAdmin.test_linebreaks() due to linebreaksbr() behavior change on Django 2.1.
2018-03-31 12:16:22 -05:00
Mariusz Felisiak
fb2483ce8d
Fixed TestAdmin.test_linebreaks() due to linebreaksbr() behavior change on Django 2.1.
Changed in ff05de760c.
2018-03-31 18:32:46 +02:00
ajinkya bhosale
94a6d3a7f3
Update index.rst
Added note CONSTANCE_CONFIG_FIELDSETS must contain all fields from CONSTANCE_CONFIG
2018-03-29 09:00:07 +05:30
Camilo Nova
1969579cf3
Merge pull request #266 from 0bit/master
Add zh_Hans support for Django 1.9+
2018-03-26 10:42:57 -05:00
0bit
6abc66b2d7 add zh_Hans 2018-03-26 10:12:12 +08:00
Camilo Nova
40c009d2d3 Bump version 2018-03-23 08:35:02 -05:00
Camilo Nova
61356da3fb
Merge pull request #258 from ownaginatious/constance_dbs_setting
Workaround to make `django-constance` work in multi-DB project
2018-03-15 13:49:25 -05:00
Camilo Nova
ead8c81747
Merge pull request #264 from fengsi/master
Fix `CONSTANCE_CONFIG_FIELDSETS` mismatch issue
2018-03-15 13:39:17 -05:00
Si Feng
1bec11477f Fix CONSTANCE_CONFIG_FIELDSETS mismatch issue 2018-03-13 16:56:00 -07:00
Camilo Nova
8120fc473d
Merge pull request #262 from pauloxnet/italian_update
Updated italian translations
2018-03-13 17:32:59 -05:00
Paolo Melchiorre
e390248bf0 Updated italian translations 2018-03-13 15:27:39 +01:00
Camilo Nova
a9ac3634c6
Merge pull request #260 from Sinkler/add-label
Add config labels
2018-03-06 17:47:21 -05:00
Anton Shurashov
40188c093a add config labels 2018-03-06 23:41:53 +03:00
Dillon Dixon
5763010f16 CONSTANCE_DBS setting for directing constance permissions/content_type settings to certian DBs only 2018-03-02 15:40:41 -08:00
Matthieu Cornut
2cec39d609 Fix issue https://github.com/jazzband/django-constance/issues/252 2018-02-27 16:48:41 +01:00
Matthieu Cornut
1dc4b06020 Fix bug in translation file 2018-02-27 16:48:41 +01:00
Camilo Nova
2d4b09d334
Merge pull request #256 from blablacio/fix-fieldset-validation
Fix ConstanceForm validation
2018-02-26 17:14:17 -05:00
Vladislav Manchev
b37b73bf0e
Fix ConstanceForm validation
Account for cases where CONSTANCE_CONFIG_FIELDSETS is not set at all in ConstanceForm validation.
2018-02-25 14:27:54 +01:00
Jannis Leidel
15d2cba34f
Fix copypasta. 2018-02-07 17:17:33 +01:00
Jannis Leidel
f75c7fd24e
Fix rST formatting. 2018-02-07 17:02:47 +01:00
Jannis Leidel
d57f4c7d5e
Added project release config. 2018-02-07 16:59:51 +01:00
Camilo Nova
bc9b11b8fa Bump version 2018-02-07 09:05:51 -05:00
Camilo Nova
29f493f7c1 Merge branch 'file-upload' 2017-11-08 08:42:34 -05:00
Camilo Nova
3f6b694f74
Merge pull request #243 from whs/master
Fix Reset to default for DateTime, #185
2017-11-08 08:31:44 -05:00
Manatsawin Hanmongkolchai
bbc7db2edc Merge https://github.com/jazzband/django-constance 2017-11-07 21:45:04 +07:00
Manatsawin Hanmongkolchai
6015e9c360 results_list: Move inline JavaScript to constance.js
Merge branch 'default-datetime' of https://github.com/rvernica/django-constance
2017-11-07 21:43:47 +07:00
Camilo Nova
4fae43b9a1
Merge pull request #237 from felixxm/bumped-django
Bumped Django version.
2017-11-06 11:51:46 -05:00
Camilo Nova
643e8401b6
Merge pull request #224 from founders4schools/translations/french
Add French translations
2017-11-06 11:47:57 -05:00
Camilo Nova
7da7598291
Merge pull request #222 from JoshLabs/vb_validation_fix
Fixed #201
2017-11-06 11:47:22 -05:00
Camilo Nova
2d86960ab9 Remove translation from the app name. Fixes #204 2017-11-06 11:43:08 -05:00
Camilo Nova
8e42d2102f Improve docs 2017-11-06 11:40:53 -05:00
Camilo Nova
23e8557c83 Added file uploads. Fixes #141 and #241 2017-11-06 11:31:00 -05:00
Camilo Nova
15b9e73cb8 Merge pull request #240 from joshkel/patch-2
Fix link and grammar
2017-10-21 16:01:14 -05:00
Josh Kelley
da9d504b76 Fix link and grammar 2017-10-18 22:24:03 -04:00
Camilo Nova
cb39e746bf Merge pull request #239 from joshkel/patch-1
Update information on template context processors
2017-10-18 12:00:43 -05:00
Josh Kelley
0bd88e2d49 Update information on template context processors
TEMPLATE_CONTEXT_PROCESSORS is deprecated in Django 1.8. This updates the documentation to match.
2017-10-17 21:20:20 -04:00
Camilo Nova
d33df08592 Merge pull request #238 from whs/master
Pack static files into release
2017-10-07 16:14:20 -05:00
Manatsawin Hanmongkolchai
5c8f282755 Pack static files into release 2017-10-02 22:43:45 +07:00
Mariusz Felisiak
06db1864bb
Bumped Django version. 2017-09-26 21:26:55 +02:00
Camilo Nova
03ba898db3 Merge pull request #220 from mpauly/master
Fix for #219
2017-09-06 14:10:15 -05:00
Camilo Nova
c8b7d67f4b Merge pull request #233 from whs/master
database: Allow running set while database is not created (close #229)
2017-08-21 08:56:00 -05:00
Manatsawin Hanmongkolchai
0e38ae7ce1 database: Allow running set while database is not created 2017-08-20 19:44:16 +07:00
Camilo Nova
f0defe80fa Merge pull request #231 from whs/master
Moved inline css/javascripts out to their own files
2017-08-09 18:53:05 -05:00
Manatsawin Hanmongkolchai
c8568b3f6b Moved inline css/javascripts out to their own files 2017-08-09 19:44:48 +07:00
Camilo Nova
5ec5ac7991 Merge pull request #230 from whs/master
database: Ignore operation errors (#229)
2017-08-08 09:02:38 -05:00
Manatsawin Hanmongkolchai
f8aad0c7f6 Also ignore ProgrammingError for Postgres 2017-08-08 18:39:01 +07:00
Manatsawin Hanmongkolchai
bbfa8fa7dc database: Ignore operation errors (#229) 2017-08-07 23:56:20 +07:00
Bruno Alla
717304f8c0 Add French translations 2017-07-03 12:49:07 +01:00
Varun Bargali
d80acd0dc6 updated message files 2017-06-13 19:41:38 +05:30
Varun Bargali
d3da1e14bd added validation error for forgetting variables in CONSTANCE_CONFIG_FIELDSETS 2017-06-13 19:37:39 +05:30
Martin Pauly
3f5fc73409 Use admin_site property of ModelAdmin, to make sure that the correct list of apps is used 2017-06-02 23:27:20 +02:00
Jon Dufresne
3162bd656f Add testing for all supported Python and Django versions
Document all supported versions in PyPI using trove classifiers.
Alphabetize classifiers.

Add all supported versions to tox.ini for easy testing. Tidy up tox.ini
by removing defaults for basepython.

Add all supported versions to the Travis CI configuration for CI
testing.

Use Tox-Travis to help build the test matrix as the different versions
of Django do not have complete overlap of Python support.

Update Travis configuration to use built in pip caching support.

https://docs.travis-ci.com/user/caching/#pip-cache
2017-06-02 14:40:07 +02:00
Jon Dufresne
9725bb2a94 Use dict comprehension; available since Python 2.7 (#217) 2017-06-02 10:53:08 +02:00
Jannis Leidel
8b8b6569d9
Use original path to constance settings in admin tests. 2017-06-02 10:52:42 +02:00
Bruno Alla
1cce582edc Fix #187: Preserve sorting from fieldset config (#207) 2017-06-02 10:52:16 +02:00
Jon Dufresne
31c1e7b53c Rename [wheel] section to [bdist_wheel] as the former is legacy
See:

54ddbcc9ce/wheel/bdist_wheel.py (bdist_wheel.py-119):125

http://pythonwheels.com/
2017-06-02 10:24:41 +02:00
Jon Dufresne
3a1f8a898b Remove unnecessary calls to dict.keys
iter(dict) is equivalent to iter(dict.keys()). Can simply act on the
dict instead. Provides a more concise, Pythonic syntax and avoids an
unnecessary function call.

Inspired by Lennart Regebro's presentation "Prehistoric Patterns in
Python" at PyCon 2017. Available at:

https://www.youtube.com/watch?v=V5-JH23Vk0I
2017-06-02 10:23:21 +02:00
Camilo Nova
4268ea808a Merge pull request #213 from felixxm/cleanup-unused
Removed unused variables and imports.
2017-05-25 14:38:42 -05:00
Camilo Nova
98fef54678 Merge pull request #212 from felixxm/timedelta-support
Fixed #211 -- Added datetime.timedelta support.
2017-05-25 14:38:20 -05:00
Mariusz Felisiak
3af72e167b
Removed unused variables and imports. 2017-05-25 14:02:52 +02:00
Mariusz Felisiak
fc5a32f2ac
Fixed #211 -- Added datetime.timedelta support. 2017-05-25 13:50:27 +02:00
Camilo Nova
edbca6dd93 Merge pull request #208 from founders4schools/tox/py36
Add python 3.6 to build matrix
2017-05-16 15:05:48 -05:00
Bruno Alla
737fa37ae4 Run Django 1.11 tests with Python 3.6 2017-05-16 14:36:03 +01:00
Bruno Alla
3eef317761 Add python 3.6 to Tox file
A bit more is needed to add it to travis
2017-05-16 13:44:53 +01:00
Mariusz Felisiak
9227d469eb Removed deprecated TEMPLATE_CONTEXT_PROCESSORS setting. (#206) 2017-05-08 09:10:41 +02:00
Mariusz Felisiak
8ade314f51 Removed Django 1.11 from the allowed failures. (#205) 2017-05-08 09:10:11 +02:00
Camilo Nova
1b2cbb070d Merge pull request #203 from OdifYltsaeb/master
Added Estonian translations
2017-04-13 09:53:24 -05:00
Alan Kesselmann
47b8394a43 Added .mo file too 2017-04-10 14:03:19 +03:00
Alan Kesselmann
09f2dc7d69 Added Estonian translations 2017-04-10 13:38:27 +03:00
Rares Vernica
0f7a6e8f55 Account for server timezone for Date object 2017-02-20 10:57:12 -08:00
Camilo Nova
161c1d34be Bump version 2017-02-17 08:16:34 -05:00
Camilo Nova
a946f92ab3 Happy 2007 2017-02-17 08:06:06 -05:00
Camilo Nova
6da21af5ae Merge pull request #195 from jazzband/updated_signal
Added the old value to the config_updated signal
2017-02-16 16:07:34 -05:00
Camilo Nova
484ab41132 Merge pull request #198 from Kerl13/master
Add a `get_changelist_form` hook in `ConstanceAdmin`
2017-02-15 14:57:23 -05:00
Martin Pépin
cb3df533c8 typo 2017-02-14 02:28:47 +01:00
Martin Pépin
49656c055c Updates the doc according to the last commit 2017-02-14 02:20:26 +01:00
Martin Pépin
be65f6536a Add a get_changelist_form hook in the ModelAdmin
This method is used in the `changelist_view` to get the changelist form.

You may want to override this method to get a different form depending
on the user that makes the request. For example:

  class MyConstanceAdmin(ConstanceAdmin):
      def get_changelist_form(self, request):
          if request.user.is_superuser:
              return SuperuserForm:
          else:
            return super(MyConstanceAdmin, self).get_changelist_form(request)
2017-02-14 02:15:39 +01:00
Camilo Nova
baab89f4ac Merge pull request #197 from ZenHeads/master
Fix create_perm in apps.py to use database alias given by the post_migrate signal
2017-02-07 10:03:09 -05:00
Laszlo Ratsko
3ba4780a13 Fix create_perm in apps.py to use database alias given by the post_migrate signal 2017-02-05 13:27:07 +01:00
Camilo Nova
61d32d2f9e Merge pull request #196 from felixxm/issue-django2.0-stdout-bytes
Removed unnecessary `stdout` bytes encode.
2017-02-02 07:58:42 -05:00
Mariusz Felisiak
46b7a02365
Removed unnecessary bytes encode. 2017-01-31 18:08:04 +01:00
Camilo Nova
2fde4f4d83 Added the old value to the config_updated signal 2017-01-31 10:41:55 -05:00
Camilo Nova
d008017fd0 Merge pull request #194 from felixxm/issue-django2.0-include-compatibility
Fixed Django 2.0 tests due to `django.conf.urls.include` behavior change. Removed pypy for Django 2.0 tests.
2017-01-31 08:37:30 -05:00
Mariusz Felisiak
a691e9e97c
Fixed Django 2.0 tests due to django.conf.urls.include behavior change.
Removed pypy for Django 2.0 tests.
2017-01-29 15:08:52 +01:00
Camilo Nova
e16657567f Merge pull request #193 from felixxm/issue-bumped-django
Fixed travis configuration. Bumped Django versions.
2017-01-28 16:26:56 -05:00
Mariusz Felisiak
661b78da0b
Fixed travis configuration. Bumped Django versions. 2017-01-27 22:48:41 +01:00
Rares Vernica (cessna, fedora)
d308ccf601 Fix Reset to default for DateTime, #185 2017-01-19 10:55:48 -08:00
Rares Vernica
bddb6cd2ac Fix *Reset to default* to work with boolean/checkboxes (#191)
* Fix *Reset to default* to work with boolean/checkboxes

* Add field name to each config value
* Check field name and use *checked* for checkbox and *value* otherwise

Fix #189

* Add and use raw_default and is_checkbox
2017-01-13 08:14:18 +01:00
John Carter
81b723c91b Fix handling of MultiValueField's (eg SplitDateTimeField) on the command line. Fixes #186 (#190)
* Added failing test for setting a datetime with cli

Issue #186

* Adding myself to AUTHORS

* Handle MultiValueField in manage command, resolves #186

* newline at end
2017-01-11 09:52:19 +01:00
Camilo Nova
8db90e8087 Bump version 2016-12-23 17:39:01 -05:00
Camilo Nova
563cfdb8ac Merge pull request #183 from rvernica/reset-default
Add "Reset to default" feature
2016-12-01 16:12:45 -05:00
Rares Vernica (cessna, fedora)
588ac2da71 Moved "Remove to default" unded field 2016-12-01 09:40:11 -08:00
Rares Vernica (cessna, fedora)
32c760cc1d Add "Reset to default" feature 2016-11-30 12:39:55 -08:00
Camilo Nova
7934de8ede Merge pull request #180 from jazzband/backport_cli_squashed
Backport command functonality from django-constance-cli (squashed)
2016-11-30 14:09:30 -05:00
Camilo Nova
c7c1fb3037 Merge pull request #179 from jazzband/additional_fields_fix
Additional fields fix
2016-11-30 14:08:13 -05:00
John Carter
e3ea904116 Backport command functonality from django-constance-cli (squashed) 2016-11-27 18:55:21 +13:00
John Carter
8941260617 Fix ChoiceField example - key should be None, not '-----' 2016-11-27 18:18:56 +13:00
John Carter
4599a22bfc Don't ignore additional fields when creating form
Fixes regression from 2f88a1bff2
2016-11-27 17:18:35 +13:00
John Carter
5d299f6222 Added test of form field types 2016-11-27 16:53:43 +13:00
John Carter
0e05de4f9b Added CONSTANCE_ADDITIONAL_FIELDS to example app 2016-11-27 16:44:33 +13:00
Camilo Nova
7221962b61 Merge pull request #177 from rvernica/patch-1
Preserve line breaks in default value
2016-11-24 08:59:03 -05:00
Rares Vernica
6ce7f1a308 Preserve line breaks in default value 2016-11-21 11:39:08 -08:00
Rares Vernica
8da8b4278c Add each_context to context and use dict syntax (#176) 2016-11-18 13:04:54 +01:00
Hamza Khchine
e634a624ec Fix broken link to django-redis (#175) 2016-11-17 17:22:15 +01:00
Rares Vernica
70f1043bad Facilitate renaming Constance in Admin (#173)
* Load app_config dynamically and use verbose_name

* Instead of assuming the app config is always going to be ConstanceConfig, load it dynamically in case the user overrides it
* Use verbose_name for page title instead of static strings for create_list
* Use verbose_name for create_list breadcrumbs

These changes allows the user to easily rename "Constance" to something else in their project.

* Fix use of verbose_name in title and breadcrumbs
2016-11-17 08:42:55 +01:00
Jannis Leidel
c4f350e0f3 Merge pull request #174 from rvernica/patch-1
Fix typo in code example
2016-11-16 21:21:47 +01:00
Rares Vernica
d677ac2ec3 Fix typo in code example 2016-11-16 09:01:19 -08:00
Camilo Nova
68ff061ea6 Merge pull request #172 from miguelgr/master
Add app_config property to Config.Meta
2016-11-08 17:58:23 -05:00
Miguel García
dd173cd42c Add app_config property to Config model 2016-10-27 18:27:37 +02:00
Camilo Nova
2f88a1bff2 Ignore fields in CONSTANCE_ADDITIONAL_FIELDS when casting a type 2016-10-12 16:59:56 -05:00
Camilo Nova
4fc8fdd3d0 Fix parameter ordering 2016-10-12 16:40:49 -05:00
Camilo Nova
bced16bfc7 Fixes #163 2016-10-12 16:09:18 -05:00
Camilo Nova
b0ec956419 Merge pull request #170 from felixxm/issue-169
Fixed #169. Added localize to check modified flag.
2016-10-12 11:34:21 -05:00
Mariusz Felisiak
9709b211d5 Fixed #169. Added localize to check modified flag. 2016-10-10 23:00:35 +02:00
Camilo Nova
b0276aa42e Merge pull request #168 from felixxm/issue-162
Fixed #162. Made travis configuration more readable.
2016-10-05 16:37:46 -05:00
Mariusz Felisiak
ac394b1628 Fixed #162. Made travis configuration more readable. 2016-10-05 22:04:34 +02:00
Camilo Nova
7beecc90c5 Merge pull request #166 from Lumax-Rus/master
Fixed wrong sorting for config. Added sorting for config fieldset
2016-10-05 11:30:43 -05:00
Maxim Luzin
1e40db7fda Fix config ordering. 2016-10-01 18:07:40 +07:00
Camilo Nova
08c6222e4a Merge pull request #165 from jazzband/fix_example
Updated example app to django 1.8, added migrations
2016-09-26 17:30:34 -05:00
John Carter
870e3627bf default wsgi.py 2016-09-26 20:53:36 +13:00
John Carter
d5877bf2a6 removed cruft 2016-09-26 08:58:45 +13:00
John Carter
84aa49c698 Updated example app to django 1.8, added migrations 2016-09-25 22:05:44 +13:00
Camilo Nova
2c123af788 Bump version 2016-09-17 17:27:58 -05:00
Camilo Nova
bfc09f9982 Revert "Only set the attribute if value has changed"
This reverts commit 56fb550044.
2016-09-17 17:23:07 -05:00
Camilo Nova
826c494fd1 Bump version 2016-09-17 15:31:02 -05:00
Camilo Nova
56fb550044 Only set the attribute if value has changed 2016-09-17 15:18:29 -05:00
Camilo Nova
080f72f58d Bump version 2016-09-15 14:02:35 -05:00
Camilo Nova
0f049e4677 Merge pull request #159 from jazzband/signals
Moved the signal to its own file to avoid import errors. Fixes #158
2016-09-15 13:59:18 -05:00
Camilo Nova
1d791d08cc Use double backticks 2016-09-15 13:58:30 -05:00
Camilo Nova
5eeb77f9b0 Improved naming for arguments 2016-09-15 09:25:54 -05:00
Camilo Nova
bebc279edc Send the config parameter as sender for the signal 2016-09-15 09:24:30 -05:00
Jannis Leidel
39ea4aa32f Merge pull request #160 from jazzband/admin-template
Improve html layout when using fieldsets
2016-09-15 09:04:19 +02:00
Camilo Nova
1830556c97 ☀️ Improve html layout when using fieldsets 2016-09-14 16:35:30 -05:00
Camilo Nova
1d746c080f Rename signal to config_updated 2016-09-14 14:53:52 -05:00
Camilo Nova
395efc992f ☀️ Moved the signal to its own file to avoid import errors 2016-09-14 14:50:39 -05:00
Camilo Nova
9d4d0ab334 Bump version 2016-09-14 12:44:59 -05:00
Camilo Nova
ebf1d228ef Fix 2016-09-14 12:39:00 -05:00
Camilo Nova
eaeb4475a2 Added documentation on signals 2016-09-14 12:37:19 -05:00
Camilo Nova
94fabdf7c3 Added signal on change, refs #158 2016-09-14 12:22:32 -05:00
Camilo Nova
90bf97c0a9 Improved coding styles 2016-09-14 12:21:43 -05:00
Camilo Nova
b28385f501 Improved coding styles 2016-09-14 12:21:16 -05:00
Camilo Nova
03704ee4f7 Improve coding style and remove django 1.7 compatibility 2016-09-14 11:52:58 -05:00
Camilo Nova
d0d2829dff Include pypy on testing matrix 2016-09-14 11:48:11 -05:00
Camilo Nova
3fef927349 Improve testing matrix 2016-09-14 11:45:16 -05:00
Camilo Nova
2272e37374 Update python versions to test 2016-09-14 11:39:20 -05:00
Camilo Nova
01fa669e98 Added django 1.10 tests 2016-09-14 11:35:13 -05:00
Camilo Nova
07a00c44ee Merge pull request #157 from iamkhush/master
Admin Form Ordering using OrderDict  issue #153
2016-09-10 18:24:17 -05:00
Ankush Chadda
bee1a68c51 Change to test travis build 2016-09-10 16:53:52 +05:30
Ankush Chadda
b9eb800a91 Documentation changes for Ordered fields in admin 2016-09-10 16:18:52 +05:30
Ankush Chadda
7e20386682 ordering contance fields using OrderedDict 2016-09-10 16:11:36 +05:30
Camilo Nova
0b9a00099a Bump version 2016-09-01 18:41:27 -05:00
Sobolev Nikita
2665fbdf9c Updated README.rst with svg badge 2016-08-31 14:44:58 +03:00
Camilo Nova
f3d68a2496 Merge pull request #155 from farooqaaa/master
Added support for fieldsets and fixed inheritance issue.
2016-08-22 13:52:54 -05:00
Farooq Azam
4c21274342 Added documentation for fieldsets. 2016-08-22 22:39:23 +05:00
Farooq Azam
c8a06d3246 Fieldset screenshot 2016-08-22 22:38:35 +05:00
Farooq Azam
80d5fb5954 Added support for fieldsets. 2016-08-22 02:45:57 +05:00
Farooq Azam
a39a46e699 Used self.model instead Config to fix inheritance issues.
I tried to inherit the Config model and changed the app_label but it wasn't working because Config was hard-referenced here.
2016-08-21 21:40:17 +05:00
Camilo Nova
b781af795a Merge pull request #152 from mojeto/master
autofill cache if key is missing.
2016-07-12 18:35:22 -05:00
Jan Nakladal
e95f372b2f autofill cache if key si missing. 2016-07-12 16:31:11 +12:00
Jannis Leidel
3ce2750534 Merge pull request #150 from PabloVallejo/github
Create ISSUE_TEMPLATE.md
2016-06-29 15:18:27 +02:00
Pablo Vallejo
d9de7bcd91 Create ISSUE_TEMPLATE.md 2016-06-29 08:07:43 -05:00
Jannis Leidel
997e068560 Merge pull request #149 from jubel-han/patch-1
Update connection class string for django redis
2016-06-29 15:06:49 +02:00
Jubel Han
cdc0db78b2 Update connection class string for django redis 2016-06-29 15:07:46 +08:00
Camilo Nova
a6357e4d89 Merge pull request #147 from prokaktus/fix-tests
Fix backend initialization in tests
2016-06-13 07:32:41 -05:00
prokaktus
54b6d55fd5 Fix backend initialization in tests 2016-06-13 04:00:17 +03:00
Camilo Nova
7cfe5854ae Merge pull request #146 from adamchainz/readthedocs.io
Convert readthedocs links for their .org -> .io migration for hosted projects
2016-06-11 09:42:42 -05:00
Adam Chainz
2ac918df8d Convert readthedocs links for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-06-11 10:52:12 +01:00
Camilo Nova
9f9fdc067d Merge pull request #145 from prokaktus/fix-cache
Update cache when key changed
2016-06-07 09:07:29 -05:00
Maxim Filipenko
1b6730d2c5 Invalidate cache 2016-06-03 17:40:32 +03:00
Camilo Nova
c5b51394aa Merge pull request #143 from dalang/docs
Docs: Fix wrong variable and correct it from 'form' to 'change_list_form'
2016-05-30 09:21:24 -05:00
dalang
23b6f3daa6 Docs: Fix wrong variable and correct it from 'form' to 'change_list_form' 2016-05-30 12:04:40 +08:00
Camilo Nova
c9d23c6d39 Fixes #141 2016-05-24 18:54:33 -05:00
128 changed files with 5945 additions and 1965 deletions

View file

@ -1,6 +1,9 @@
[run]
source = constance
branch = 1
omit =
*/pytest.py
*/tests/*
[report]
omit = *tests*,*migrations*
omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py

13
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,13 @@
### Describe the problem
Tell us about the problem you're having.
### Steps to reproduce
Tell us how to reproduce it.
### System configuration
* Django version:
* Python version:
* Django-Constance version:

18
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,18 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "chore(ci): "
groups:
github-actions:
patterns:
- "*"
open-pull-requests-limit: 1

24
.github/workflows/docs.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: Docs
on: [push, pull_request]
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: 'docs/requirements.txt'
- name: Install dependencies
run: pip install -r docs/requirements.txt
- name: Build docs
run: |
cd docs
make html

37
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.repository == 'jazzband/django-constance'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U build setuptools twine wheel
- name: Build package
run: |
python -m build
twine check dist/*
- name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-constance/upload

54
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: Test
on: [push, pull_request]
jobs:
ruff-format:
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- uses: actions/checkout@v6
- uses: chartboost/ruff-action@v1
with:
version: 0.5.0
args: 'format --check'
ruff-lint:
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- uses: actions/checkout@v6
- uses: chartboost/ruff-action@v1
with:
version: 0.5.0
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions
- name: Tox tests
run: |
tox -v
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
name: Python ${{ matrix.python-version }}

3
.gitignore vendored
View file

@ -7,4 +7,7 @@ dist/
test.db
.tox
.coverage
coverage.xml
docs/_build
.idea
constance/_version.py

22
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,22 @@
repos:
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-merge-conflict
- id: check-yaml
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: [ --py38-plus ]
exclude: /migrations/
ci:
autoupdate_schedule: quarterly

18
.readthedocs.yaml Normal file
View file

@ -0,0 +1,18 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-lts-latest
tools:
python: "3.12"
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .

View file

@ -1,37 +0,0 @@
sudo: false
language: python
cache:
directories:
- "$HOME/.cache/pip"
install:
- pip install tox
env:
- TOXENV=py27-django-17
- TOXENV=py27-django-18
- TOXENV=py33-django-17
- TOXENV=py33-django-18
- TOXENV=py34-django-17
- TOXENV=py34-django-18
- TOXENV=pypy-django-17
- TOXENV=pypy-django-18
- TOXENV=pypy-django-19
- TOXENV=py27-django-master
- TOXENV=pypy-django-master
script:
- tox
matrix:
include:
- python: 3.5
env: TOXENV=py35-django-19
- python: 3.5
env: TOXENV=py35-django-master
deploy:
provider: pypi
user: jazzband
distributions: "sdist bdist_wheel"
password:
secure: VD+63Tnv0VYNfFQv9f1KZ0k79HSX8veNk4dTy42Hriteci50z5uSQdZMnqqD83xQJa4VF6N7DHkxHnBVOWLCqGQZeYqR/5BuDFNUewcr6O14dk31HvxMsWDaN1KW0Qwtus8ZrztwGhZtZ/92ODA6luHI4mCTzqX0gcG0/aKd75s=
on:
tags: true
repo: jazzband/django-constance
condition: "$TOXENV = py27-django-18"

View file

@ -1,8 +0,0 @@
[main]
host = https://www.transifex.com
lang_map = sr@latin:sr_Latn
[django-constance.main]
file_filter = constance/locale/<lang>/LC_MESSAGES/django.po
source_file = constance/locale/en/LC_MESSAGES/django.po
source_lang = en

11
AUTHORS
View file

@ -1,17 +1,23 @@
Ales Zoulek <ales.zoulek@gmail.com>
Alexander frenzel <alex@relatedworks.com>
Alexander Frenzel <alex@relatedworks.com>
Alexandr Artemyev <mogost@gmail.com>
Bouke Haarsma <bouke@webatoom.nl>
Camilo Nova <camilo.nova@gmail.com>
Charlie Hornsby <charlie.hornsby@hotmail.co.uk>
Curtis Maloney <curtis@tinbrain.net>
Dan Poirier <dpoirier@caktusgroup.com>
David Burke <dmbst32@gmail.com>
Dmitriy Tatarkin <mail@dtatarkin.ru>
Elisey Zanko <elisey.zanko@gmail.com>
Florian Apolloner <florian@apolloner.eu>
Igor Támara <igor@axiacore.com>
Ilya Chichak <ilyachch@gmail.com>
Ivan Klass <klass.ivanklass@gmail.com>
Jake Merdich <jmerdich@users.noreply.github.com>
Jannis Leidel <jannis@leidel.info>
Janusz Harkot <janusz.harkot@gmail.com>
Jiri Barton <jbar@hosting4u.cz>
John Carter <john@therefromhere.org>
Jonas <jvp@jonasundderwolf.de>
Kuba Zarzycki <jakubzarzycki@gmail.com>
Leandra Finger <leandra.finger@gmail.com>
@ -20,15 +26,18 @@ Lin Xianyi <iynaix@gmail.com>
Marcin Baran <marcin.baran@agencjawmc.pl>
Mario Orlandi <morlandi@brainstorm.it>
Mario Rosa <mario@dwaiter.com>
Mariusz Felisiak <felisiak.mariusz@gmail.com>
Mattia Larentis <mattia@larentis.eu>
Merijn Bertels <merijn.bertels@gmail.com>
Omer Katz <omer.drow@gmail.com>
Petr Knap <dev@petrknap.cz>
Philip Neustrom <philipn@gmail.com>
Philipp Thumfart <philipp@thumfart.eu>
Pierre-Olivier Marec <pomarec@free.fr>
Roman Krejcik <farin@farin.cz>
Silvan Spross <silvan.spross@gmail.com>
Sławek Ehlert <slafs@op.pl>
Vladas Tamoshaitis <amd.vladas@gmail.com>
Vojtech Jasny <voy@voy.cz>
Yin Jifeng <jifeng.yin@gmail.com>
illumin-us-r3v0lution <luminaries@riseup.net>

3
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,3 @@
# Django Constance Code of Conduct
The django-constance project utilizes the [Django Commons Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md).

View file

@ -1,3 +1,3 @@
[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/)
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Condut](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).

View file

@ -1,4 +1,4 @@
Copyright (c) 2009-2015, Comoga and individual contributors
Copyright (c) 2009-2017, Jazzband
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View file

@ -1,2 +1,3 @@
recursive-include constance/templates *.html
recursive-include constance/locale *.po *.mo
recursive-include constance/static *

View file

@ -1,20 +1,28 @@
Constance - Dynamic Django settings
===================================
.. image:: https://secure.travis-ci.org/jazzband/django-constance.png
:alt: Build Status
:target: http://travis-ci.org/jazzband/django-constance
.. image:: https://jazzband.co/static/img/badge.svg
:alt: Jazzband
:target: https://jazzband.co/
.. image:: https://img.shields.io/readthedocs/django-constance.svg
:target: https://django-constance.readthedocs.io/
:alt: Documentation
.. image:: https://github.com/jazzband/django-constance/workflows/Test/badge.svg
:target: https://github.com/jazzband/django-constance/actions
:alt: GitHub Actions
.. image:: https://codecov.io/gh/jazzband/django-constance/branch/master/graph/badge.svg
:target: https://codecov.io/gh/jazzband/django-constance
:alt: Coverage
A Django app for storing dynamic settings in pluggable backends (Redis and
Django model backend built in) with an integration with the Django admin app.
For more information see the documentation at:
http://django-constance.readthedocs.org/
https://django-constance.readthedocs.io/
If you have questions or have trouble using the app please file a bug report
at:

View file

@ -1,13 +1,11 @@
from django.utils.functional import LazyObject
__version__ = '1.2'
default_app_config = 'constance.apps.ConstanceConfig'
class LazyConfig(LazyObject):
def _setup(self):
from .base import Config
self._wrapped = Config()
config = LazyConfig()

View file

@ -1,191 +1,159 @@
from datetime import datetime, date, time
from decimal import Decimal
import hashlib
import json
from collections import OrderedDict
from datetime import date
from datetime import datetime
from operator import itemgetter
from django import forms, VERSION
from django.conf.urls import url
from django.contrib import admin, messages
from django.contrib.admin import widgets
from django import forms
from django import get_version
from django.apps import apps
from django.contrib import admin
from django.contrib import messages
from django.contrib.admin.options import csrf_protect_m
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.forms import fields
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.utils import six
from django.utils.encoding import smart_bytes
from django.urls import path
from django.utils.formats import localize
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
import django
from django.utils.translation import gettext_lazy as _
from . import LazyConfig, settings
from . import LazyConfig
from . import settings
from .forms import ConstanceForm
from .utils import get_values
config = LazyConfig()
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10})
INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET})
STRING_LIKE = (fields.CharField, {
'widget': forms.Textarea(attrs={'rows': 3}),
'required': False,
})
FIELDS = {
bool: (fields.BooleanField, {'required': False}),
int: INTEGER_LIKE,
Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}),
str: STRING_LIKE,
datetime: (fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}),
date: (fields.DateField, {'widget': widgets.AdminDateWidget}),
time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}),
float: (fields.FloatField, {'widget': NUMERIC_WIDGET}),
}
def parse_additional_fields(fields):
for key in fields:
field = list(fields[key])
if len(field) == 1:
field.append({})
field[0] = import_string(field[0])
if 'widget' in field[1]:
klass = import_string(field[1]['widget'])
field[1]['widget'] = klass(**(field[1].get('widget_kwargs', {}) or {}))
if 'widget_kwargs' in field[1]:
del field[1]['widget_kwargs']
fields[key] = field
return fields
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS))
if not six.PY3:
FIELDS.update({
long: INTEGER_LIKE,
unicode: STRING_LIKE,
})
class ConstanceForm(forms.Form):
version = forms.CharField(widget=forms.HiddenInput)
def __init__(self, initial, *args, **kwargs):
super(ConstanceForm, self).__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.md5()
for name, options in settings.CONFIG.items():
default, help_text = options[0], options[1]
if len(options) == 3:
config_type = options[2]
else:
config_type = type(default)
if config_type not in FIELDS:
raise ImproperlyConfigured(_("Constance doesn't support "
"config values of the type "
"%(config_type)s. Please fix "
"the value of '%(name)s'.")
% {'config_type': config_type,
'name': name})
field_class, kwargs = FIELDS[config_type]
self.fields[name] = field_class(label=name, **kwargs)
version_hash.update(smart_bytes(initial.get(name, '')))
self.initial['version'] = version_hash.hexdigest()
def save(self):
for name in settings.CONFIG:
setattr(config, name, self.cleaned_data[name])
def clean_version(self):
value = self.cleaned_data['version']
if settings.IGNORE_ADMIN_VERSION_CHECK:
return value
if value != self.initial['version']:
raise forms.ValidationError(_('The settings have been modified '
'by someone else. Please reload the '
'form and resubmit your changes.'))
return value
class ConstanceAdmin(admin.ModelAdmin):
change_list_template = 'admin/constance/change_list.html'
change_list_template = "admin/constance/change_list.html"
change_list_form = ConstanceForm
def __init__(self, model, admin_site):
model._meta.concrete_model = Config
super().__init__(model, admin_site)
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.module_name
info = f"{self.model._meta.app_label}_{self.model._meta.module_name}"
return [
url(r'^$',
self.admin_site.admin_view(self.changelist_view),
name='%s_%s_changelist' % info),
url(r'^$',
self.admin_site.admin_view(self.changelist_view),
name='%s_%s_add' % info),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_changelist"),
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"),
]
def get_config_value(self, name, options, form, initial):
default, help_text = options[0], options[1]
field_type = None
if len(options) == 3:
field_type = options[2]
# First try to load the value from the actual backend
value = initial.get(name)
# Then if the returned value is None, get the default
if value is None:
value = getattr(config, name)
form_field = form[name]
config_value = {
"name": name,
"default": localize(default),
"raw_default": default,
"help_text": _(help_text),
"value": localize(value),
"modified": localize(value) != localize(default),
"form_field": form_field,
"is_date": isinstance(default, date),
"is_datetime": isinstance(default, datetime),
"is_checkbox": isinstance(form_field.field.widget, forms.CheckboxInput),
"is_multi_select": isinstance(
form_field.field.widget, (forms.SelectMultiple, forms.CheckboxSelectMultiple)
),
"is_file": isinstance(form_field.field.widget, forms.FileInput),
}
if config_value["is_multi_select"]:
config_value["json_default"] = json.dumps(default if isinstance(default, list) else [default])
if field_type and field_type in settings.ADDITIONAL_FIELDS:
serialized_default = form[name].field.prepare_value(default)
config_value["default"] = serialized_default
config_value["raw_default"] = serialized_default
config_value["value"] = form[name].field.prepare_value(value)
return config_value
def get_changelist_form(self, request):
"""Returns a Form class for use in the changelist_view."""
# Defaults to self.change_list_form in order to preserve backward
# compatibility
return self.change_list_form
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
# First load a mapping between config name and default value
if not self.has_change_permission(request, None):
if not self.has_view_or_change_permission(request):
raise PermissionDenied
default_initial = ((name, options[0])
for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
initial = dict(default_initial,
**dict(config._backend.mget(settings.CONFIG.keys())))
form = self.change_list_form(initial=initial)
if request.method == 'POST':
form = self.change_list_form(data=request.POST, initial=initial)
initial = get_values()
form_cls = self.get_changelist_form(request)
form = form_cls(initial=initial, request=request)
if request.method == "POST" and request.user.has_perm("constance.change_config"):
form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request)
if form.is_valid():
form.save()
# In django 1.5 this can be replaced with self.message_user
messages.add_message(
request,
messages.SUCCESS,
_('Live settings updated successfully.'),
)
return HttpResponseRedirect('.')
messages.add_message(request, messages.SUCCESS, _("Live settings updated successfully."))
return HttpResponseRedirect(".")
messages.add_message(request, messages.ERROR, _("Failed to update live settings."))
context = {
'config_values': [],
'title': _('Constance config'),
'app_label': 'constance',
'opts': Config._meta,
'form': form,
'media': self.media + form.media,
'icon_type': 'gif' if VERSION < (1, 9) else 'svg',
**self.admin_site.each_context(request),
**(extra_context or {}),
"config_values": [],
"title": self.model._meta.app_config.verbose_name,
"app_label": "constance",
"opts": self.model._meta,
"form": form,
"media": self.media + form.media,
"icon_type": "svg",
"django_version": get_version(),
}
for name, options in settings.CONFIG.items():
default, help_text = options[0], options[1]
# First try to load the value from the actual backend
value = initial.get(name)
# Then if the returned value is None, get the default
if value is None:
value = getattr(config, name)
context['config_values'].append({
'name': name,
'default': localize(default),
'help_text': _(help_text),
'value': localize(value),
'modified': value != default,
'form_field': form[name],
})
context['config_values'].sort(key=itemgetter('name'))
context["config_values"].append(self.get_config_value(name, options, form, initial))
if settings.CONFIG_FIELDSETS:
if isinstance(settings.CONFIG_FIELDSETS, dict):
fieldset_items = settings.CONFIG_FIELDSETS.items()
else:
fieldset_items = settings.CONFIG_FIELDSETS
context["fieldsets"] = []
for fieldset_title, fieldset_data in fieldset_items:
if isinstance(fieldset_data, dict):
fields_list = fieldset_data["fields"]
collapse = fieldset_data.get("collapse", False)
else:
fields_list = fieldset_data
collapse = False
absent_fields = [field for field in fields_list if field not in settings.CONFIG]
if any(absent_fields):
raise ValueError(
"CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}".format(
", ".join(absent_fields)
)
)
config_values = []
for name in fields_list:
options = settings.CONFIG.get(name)
if options:
config_values.append(self.get_config_value(name, options, form, initial))
fieldset_context = {"title": fieldset_title, "config_values": config_values}
if collapse:
fieldset_context["collapse"] = True
context["fieldsets"].append(fieldset_context)
if not isinstance(settings.CONFIG_FIELDSETS, (OrderedDict, tuple)):
context["fieldsets"].sort(key=itemgetter("title"))
if not isinstance(settings.CONFIG, OrderedDict):
context["config_values"].sort(key=itemgetter("name"))
request.current_app = self.admin_site.name
# compatibility to be removed when 1.7 is deprecated
extra = {'current_app': self.admin_site.name} if VERSION < (1, 8) else {}
return TemplateResponse(request, self.change_list_template, context,
**extra)
return TemplateResponse(request, self.change_list_template, context)
def has_add_permission(self, *args, **kwargs):
return False
@ -196,23 +164,37 @@ class ConstanceAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if settings.SUPERUSER_ONLY:
return request.user.is_superuser
return super(ConstanceAdmin, self).has_change_permission(request, obj)
return super().has_change_permission(request, obj)
class Config(object):
class Meta(object):
app_label = 'constance'
object_name = 'Config'
model_name = module_name = 'config'
verbose_name_plural = _('config')
class Config:
class Meta:
app_label = "constance"
object_name = "Config"
concrete_model = None
model_name = module_name = "config"
verbose_name_plural = _("config")
abstract = False
swapped = False
is_composite_pk = False
def get_ordered_objects(self):
return False
def get_change_permission(self):
return 'change_%s' % self.model_name
return f"change_{self.model_name}"
@property
def app_config(self):
return apps.get_app_config(self.app_label)
@property
def label(self):
return f"{self.app_label}.{self.object_name}"
@property
def label_lower(self):
return f"{self.app_label}.{self.model_name}"
_meta = Meta()

View file

@ -1,34 +1,14 @@
from django.db.models import signals
from django import VERSION
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.core import checks
from django.utils.translation import gettext_lazy as _
from constance.checks import check_fieldsets
class ConstanceConfig(AppConfig):
name = 'constance'
verbose_name = _('Constance')
name = "constance"
verbose_name = _("Constance")
default_auto_field = "django.db.models.AutoField"
def ready(self):
super(ConstanceConfig, self).ready()
signals.post_migrate.connect(self.create_perm,
dispatch_uid='constance.create_perm')
def create_perm(self, *args, **kwargs):
"""
Creates a fake content type and permission
to be able to check for permissions
"""
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
if ContentType._meta.installed and Permission._meta.installed:
extra = {} if VERSION >= (1, 8) else {'name': 'config'}
content_type, created = ContentType.objects.get_or_create(
app_label='constance',
model='config',
**extra)
permission, created = Permission.objects.get_or_create(
name='Can change config',
content_type=content_type,
codename='change_config')
checks.register(check_fieldsets, "constance")

View file

@ -1,26 +1,50 @@
"""
Defines the base constance backend
"""
"""Defines the base constance backend."""
from abc import ABC
from abc import abstractmethod
class Backend(object):
class Backend(ABC):
@abstractmethod
def get(self, key):
"""
Get the key from the backend store and return the value.
Return None if not found.
"""
raise NotImplementedError
...
@abstractmethod
async def aget(self, key):
"""
Get the key from the backend store and return the value.
Return None if not found.
"""
...
@abstractmethod
def mget(self, keys):
"""
Get the keys from the backend store and return a list of the values.
Return an empty list if not found.
Get the keys from the backend store and return a dict mapping
each found key to its value. Return an empty dict if no keys
are provided or none are found.
"""
raise NotImplementedError
...
@abstractmethod
async def amget(self, keys):
"""
Get the keys from the backend store and return a dict mapping
each found key to its value. Return an empty dict if no keys
are provided or none are found.
"""
...
@abstractmethod
def set(self, key, value):
"""
Add the value to the backend store given the key.
"""
raise NotImplementedError
"""Add the value to the backend store given the key."""
...
@abstractmethod
async def aset(self, key, value):
"""Add the value to the backend store given the key."""
...

View file

@ -0,0 +1,176 @@
from django.core.cache import caches
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ImproperlyConfigured
from django.db import IntegrityError
from django.db import OperationalError
from django.db import ProgrammingError
from django.db import transaction
from django.db.models.signals import post_save
from constance import config
from constance import settings
from constance import signals
from constance.backends import Backend
from constance.codecs import dumps
from constance.codecs import loads
class DatabaseBackend(Backend):
def __init__(self):
from constance.models import Constance
self._model = Constance
self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = "autofilled"
if self._model._meta.app_config is None:
raise ImproperlyConfigured(
"The constance.backends.database app isn't installed "
"correctly. Make sure it's in your INSTALLED_APPS setting."
)
if settings.DATABASE_CACHE_BACKEND:
self._cache = caches[settings.DATABASE_CACHE_BACKEND]
if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured(
"The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a "
f"subclass of Django's local-memory backend ({settings.DATABASE_CACHE_BACKEND!r}). Please "
"set it to a backend that supports cross-process caching."
)
else:
self._cache = None
self.autofill()
# Clear simple cache.
post_save.connect(self.clear, sender=self._model)
def add_prefix(self, key):
return f"{self._prefix}{key}"
def autofill(self):
if not self._autofill_timeout or not self._cache:
return
full_cachekey = self.add_prefix(self._autofill_cachekey)
if self._cache.get(full_cachekey):
return
autofill_values = {full_cachekey: 1}
for key, value in self.mget(settings.CONFIG).items():
autofill_values[self.add_prefix(key)] = value
self._cache.set_many(autofill_values, timeout=self._autofill_timeout)
def mget(self, keys):
result = {}
if not keys:
return result
keys = {self.add_prefix(key): key for key in keys}
try:
stored = self._model._default_manager.filter(key__in=keys)
for const in stored:
result[keys[const.key]] = loads(const.value)
except (OperationalError, ProgrammingError):
pass
return result
def get(self, key):
key = self.add_prefix(key)
value = None
if self._cache:
value = self._cache.get(key)
if value is None:
self.autofill()
value = self._cache.get(key)
if value is None:
match = self._model._default_manager.filter(key=key).only("value").first()
if match:
value = loads(match.value)
if self._cache:
self._cache.add(key, value)
return value
async def aget(self, key):
from asgiref.sync import sync_to_async
prefixed_key = self.add_prefix(key)
value = None
if self._cache:
value = await self._cache.aget(prefixed_key)
if value is None:
await sync_to_async(self.autofill, thread_sensitive=True)()
value = await self._cache.aget(prefixed_key)
if value is None:
match = await self._model._default_manager.filter(key=prefixed_key).only("value").afirst()
if match:
value = loads(match.value)
if self._cache:
await self._cache.aadd(prefixed_key, value)
return value
async def amget(self, keys):
if not keys:
return {}
prefixed_keys_map = {self.add_prefix(key): key for key in keys}
results = {}
if self._cache:
cache_results = await self._cache.aget_many(prefixed_keys_map.keys())
for prefixed_key, value in cache_results.items():
results[prefixed_keys_map[prefixed_key]] = value
missing_prefixed_keys = [k for k in prefixed_keys_map if prefixed_keys_map[k] not in results]
if missing_prefixed_keys:
try:
async for const in self._model._default_manager.filter(key__in=missing_prefixed_keys):
results[prefixed_keys_map[const.key]] = loads(const.value)
except (OperationalError, ProgrammingError):
pass
return results
def set(self, key, value):
key = self.add_prefix(key)
created = False
queryset = self._model._default_manager.all()
# Set _for_write attribute as get_or_create method does
# https://github.com/django/django/blob/2.2.11/django/db/models/query.py#L536
queryset._for_write = True
try:
constance = queryset.get(key=key)
except (OperationalError, ProgrammingError):
# database is not created, noop
return
except self._model.DoesNotExist:
try:
with transaction.atomic(using=queryset.db):
queryset.create(key=key, value=dumps(value))
created = True
except IntegrityError:
# Allow concurrent writes
constance = queryset.get(key=key)
if not created:
old_value = loads(constance.value)
constance.value = dumps(value)
constance.save(update_fields=["value"])
else:
old_value = None
if self._cache:
self._cache.set(key, value)
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
async def aset(self, key, value):
from asgiref.sync import sync_to_async
# We use sync_to_async because Django's transaction.atomic() and database connections are thread-local.
# This ensures the operation runs in the correct database thread until native async transactions are supported.
return await sync_to_async(self.set, thread_sensitive=True)(key, value)
def clear(self, sender, instance, created, **kwargs):
if self._cache and not created:
keys = [self.add_prefix(k) for k in settings.CONFIG]
keys.append(self.add_prefix(self._autofill_cachekey))
self._cache.delete_many(keys)
self.autofill()

View file

@ -1,90 +0,0 @@
from django.core.cache import caches
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ImproperlyConfigured
from django.db.models.signals import post_save
from .. import Backend
from ... import settings
class DatabaseBackend(Backend):
def __init__(self):
from .models import Constance
self._model = Constance
self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled'
if not self._model._meta.installed:
raise ImproperlyConfigured(
"The constance.backends.database app isn't installed "
"correctly. Make sure it's in your INSTALLED_APPS setting.")
if settings.DATABASE_CACHE_BACKEND:
self._cache = caches[settings.DATABASE_CACHE_BACKEND]
if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured(
"The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a "
"subclass of Django's local-memory backend (%r). Please set "
"it to a backend that supports cross-process caching."
% settings.DATABASE_CACHE_BACKEND)
else:
self._cache = None
self.autofill()
# Clear simple cache.
post_save.connect(self.clear, sender=self._model)
def add_prefix(self, key):
return "%s%s" % (self._prefix, key)
def autofill(self):
if not self._autofill_timeout or not self._cache:
return
full_cachekey = self.add_prefix(self._autofill_cachekey)
if self._cache.get(full_cachekey):
return
autofill_values = {}
autofill_values[full_cachekey] = 1
for key, value in self.mget(settings.CONFIG.keys()):
autofill_values[self.add_prefix(key)] = value
self._cache.set_many(autofill_values, timeout=self._autofill_timeout)
def mget(self, keys):
if not keys:
return
keys = dict((self.add_prefix(key), key) for key in keys)
stored = self._model._default_manager.filter(key__in=keys.keys())
for const in stored:
yield keys[const.key], const.value
def get(self, key):
key = self.add_prefix(key)
if self._cache:
value = self._cache.get(key)
else:
value = None
if value is None:
try:
value = self._model._default_manager.get(key=key).value
except self._model.DoesNotExist:
pass
else:
if self._cache:
self._cache.add(key, value)
return value
def set(self, key, value):
constance, created = self._model._default_manager.get_or_create(
key=self.add_prefix(key), defaults={'value': value}
)
if not created:
constance.value = value
constance.save()
def clear(self, sender, instance, created, **kwargs):
if self._cache and not created:
keys = [self.add_prefix(k)
for k in settings.CONFIG.keys()]
keys.append(self.add_prefix(self._autofill_cachekey))
self._cache.delete_many(keys)
self.autofill()

View file

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import picklefield.fields
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='Constance',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True,
auto_created=True, serialize=False)),
('key', models.CharField(unique=True, max_length=255)),
('value', picklefield.fields.PickledObjectField(editable=False)),
],
options={
'verbose_name': 'constance',
'verbose_name_plural': 'constances',
'db_table': 'constance_config',
},
bases=(models.Model,),
),
]

View file

@ -1,24 +0,0 @@
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
try:
from picklefield import PickledObjectField
except ImportError:
raise ImproperlyConfigured("Couldn't find the the 3rd party app "
"django-picklefield which is required for "
"the constance database backend.")
class Constance(models.Model):
key = models.CharField(max_length=255, unique=True)
value = PickledObjectField()
class Meta:
verbose_name = _('constance')
verbose_name_plural = _('constances')
db_table = 'constance_config'
def __unicode__(self):
return self.key

View file

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
from south.db import db
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Constance'
db.create_table('constance_config', (
('id', self.gf('django.db.models.fields.AutoField')(
primary_key=True)),
('key', self.gf('django.db.models.fields.TextField')()),
('value', self.gf('picklefield.fields.PickledObjectField')()),
))
db.send_create_signal('database', ['Constance'])
def backwards(self, orm):
# Deleting model 'Constance'
db.delete_table('constance_config')
models = {
'database.constance': {
'Meta': {'object_name': 'Constance',
'db_table': "'constance_config'"},
'id': ('django.db.models.fields.AutoField', [],
{'primary_key': 'True'}),
'key': ('django.db.models.fields.TextField', [], {}),
'value': ('picklefield.fields.PickledObjectField', [], {})
}
}
complete_apps = ['database']

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
from south.db import db
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Constance.key'
db.alter_column('constance_config', 'key',
self.gf('django.db.models.fields.CharField')(
max_length=255))
# Adding unique constraint on 'Constance', fields ['key']
db.create_unique('constance_config', ['key'])
def backwards(self, orm):
# Removing unique constraint on 'Constance', fields ['key']
db.delete_unique('constance_config', ['key'])
# Changing field 'Constance.key'
db.alter_column('constance_config', 'key',
self.gf('django.db.models.fields.TextField')())
models = {
'database.constance': {
'Meta': {'object_name': 'Constance',
'db_table': "'constance_config'"},
'id': ('django.db.models.fields.AutoField', [],
{'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [],
{'unique': 'True', 'max_length': '255'}),
'value': ('picklefield.fields.PickledObjectField', [], {})
}
}
complete_apps = ['database']

View file

@ -0,0 +1,46 @@
from threading import Lock
from constance import config
from constance import signals
from . import Backend
class MemoryBackend(Backend):
"""Simple in-memory backend that should be mostly used for testing purposes."""
_storage = {}
_lock = Lock()
def __init__(self):
super().__init__()
def get(self, key):
with self._lock:
return self._storage.get(key)
async def aget(self, key):
# Memory operations are fast enough that we don't need true async here
return self.get(key)
def mget(self, keys):
if not keys:
return {}
with self._lock:
return {key: self._storage[key] for key in keys if key in self._storage}
async def amget(self, keys):
if not keys:
return {}
with self._lock:
return {key: self._storage[key] for key in keys if key in self._storage}
def set(self, key, value):
with self._lock:
old_value = self._storage.get(key)
self._storage[key] = value
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
async def aset(self, key, value):
# Memory operations are fast enough that we don't need true async here
self.set(key, value)

View file

@ -1,37 +1,62 @@
import asyncio
from threading import RLock
from time import monotonic
from django.core.exceptions import ImproperlyConfigured
from django.utils import six
from django.utils.six.moves import zip
from . import Backend
from .. import settings, utils
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
from constance import config
from constance import settings
from constance import signals
from constance import utils
from constance.backends import Backend
from constance.codecs import dumps
from constance.codecs import loads
class RedisBackend(Backend):
def __init__(self):
super(RedisBackend, self).__init__()
super().__init__()
self._prefix = settings.REDIS_PREFIX
connection_cls = settings.REDIS_CONNECTION_CLASS
if connection_cls is not None:
async_connection_cls = settings.REDIS_ASYNC_CONNECTION_CLASS
if connection_cls:
self._rd = utils.import_module_attr(connection_cls)()
else:
try:
import redis
except ImportError:
raise ImproperlyConfigured(
"The Redis backend requires redis-py to be installed.")
if isinstance(settings.REDIS_CONNECTION, six.string_types):
self._rd = redis.from_url(settings.REDIS_CONNECTION)
raise ImproperlyConfigured("The Redis backend requires redis-py to be installed.") from None
else:
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
if isinstance(settings.REDIS_CONNECTION, str):
self._rd = redis.from_url(settings.REDIS_CONNECTION)
else:
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
if async_connection_cls:
self._ard = utils.import_module_attr(async_connection_cls)()
else:
try:
import redis.asyncio as aredis
except ImportError:
# We set this to none instead of raising an error to indicate that async support is not available
# without breaking existing sync usage.
self._ard = None
else:
if isinstance(settings.REDIS_CONNECTION, str):
self._ard = aredis.from_url(settings.REDIS_CONNECTION)
else:
self._ard = aredis.Redis(**settings.REDIS_CONNECTION)
def add_prefix(self, key):
return "%s%s" % (self._prefix, key)
return f"{self._prefix}{key}"
def _check_async_support(self):
if self._ard is None:
raise ImproperlyConfigured(
"Async support for the Redis backend requires redis>=4.2.0 "
"or a custom CONSTANCE_REDIS_ASYNC_CONNECTION_CLASS to be configured."
)
def get(self, key):
value = self._rd.get(self.add_prefix(key))
@ -39,13 +64,156 @@ class RedisBackend(Backend):
return loads(value)
return None
async def aget(self, key):
self._check_async_support()
value = await self._ard.get(self.add_prefix(key))
if value:
return loads(value)
return None
def mget(self, keys):
if not keys:
return
return {}
prefixed_keys = [self.add_prefix(key) for key in keys]
for key, value in zip(keys, self._rd.mget(prefixed_keys)):
if value:
yield key, loads(value)
return {key: loads(value) for key, value in zip(keys, self._rd.mget(prefixed_keys)) if value}
async def amget(self, keys):
if not keys:
return {}
self._check_async_support()
prefixed_keys = [self.add_prefix(key) for key in keys]
values = await self._ard.mget(prefixed_keys)
return {key: loads(value) for key, value in zip(keys, values) if value}
def set(self, key, value):
old_value = self.get(key)
self._rd.set(self.add_prefix(key), dumps(value))
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
async def _aset_internal(self, key, value, old_value):
"""
Internal set operation. Separated to allow subclasses to provide old_value
without going through self.aget() which may have locking behavior.
"""
self._check_async_support()
await self._ard.set(self.add_prefix(key), dumps(value))
signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value)
async def aset(self, key, value):
old_value = await self.aget(key)
await self._aset_internal(key, value, old_value)
class CachingRedisBackend(RedisBackend):
_sentinel = object()
_lock = RLock()
_async_lock = None # Lazy-initialized asyncio.Lock
def __init__(self):
super().__init__()
self._timeout = settings.REDIS_CACHE_TIMEOUT
self._cache = {}
self._sentinel = object()
def _get_async_lock(self):
# Lazily create the asyncio lock to avoid issues with event loops
if self._async_lock is None:
self._async_lock = asyncio.Lock()
return self._async_lock
def _has_expired(self, value):
return value[0] <= monotonic()
def _cache_value(self, key, new_value):
self._cache[key] = (monotonic() + self._timeout, new_value)
def get(self, key):
value = self._cache.get(key, self._sentinel)
if value is self._sentinel or self._has_expired(value):
with self._lock:
new_value = super().get(key)
self._cache_value(key, new_value)
return new_value
return value[1]
async def _aget_unlocked(self, key):
"""
Get value with cache support but without acquiring lock.
Caller must already hold the lock.
"""
value = self._cache.get(key, self._sentinel)
if value is self._sentinel or self._has_expired(value):
new_value = await super().aget(key)
self._cache_value(key, new_value)
return new_value
return value[1]
async def aget(self, key):
value = self._cache.get(key, self._sentinel)
if value is self._sentinel or self._has_expired(value):
async with self._get_async_lock():
# Double-check after acquiring lock, then delegate to unlocked version
return await self._aget_unlocked(key)
return value[1]
def set(self, key, value):
with self._lock:
super().set(key, value)
self._cache_value(key, value)
async def aset(self, key, value):
async with self._get_async_lock():
# Use unlocked version since we already hold the lock
old_value = await self._aget_unlocked(key)
# Use internal method to avoid lock recursion (super().aset calls self.aget)
await self._aset_internal(key, value, old_value)
self._cache_value(key, value)
def mget(self, keys):
if not keys:
return {}
result = {}
for key in keys:
value = self.get(key)
if value is not None:
result[key] = value
return result
async def amget(self, keys):
if not keys:
return {}
results = {}
missing_keys = []
# First, check the local cache for all keys
for key in keys:
value = self._cache.get(key, self._sentinel)
if value is not self._sentinel and not self._has_expired(value):
results[key] = value[1]
else:
missing_keys.append(key)
# Fetch missing keys from Redis
if missing_keys:
async with self._get_async_lock():
# Re-check cache for keys that might have been fetched while waiting for lock
still_missing = []
for key in missing_keys:
value = self._cache.get(key, self._sentinel)
if value is not self._sentinel and not self._has_expired(value):
results[key] = value[1]
else:
still_missing.append(key)
if still_missing:
fetched = await super().amget(still_missing)
for key, value in fetched.items():
self._cache_value(key, value)
results[key] = value
return results

View file

@ -1,32 +1,149 @@
from . import settings, utils
import asyncio
import warnings
from . import settings
from . import utils
class Config(object):
"""
The global config wrapper that handles the backend.
"""
class AsyncValueProxy:
def __init__(self, key, config, default):
self._key = key
self._config = config
self._default = default
self._value = None
self._fetched = False
def __await__(self):
return self._get_value().__await__()
async def _get_value(self):
if not self._fetched:
result = await self._config._backend.aget(self._key)
if result is None:
result = self._default
await self._config.aset(self._key, result)
self._value = result
self._fetched = True
return self._value
def _get_sync_value(self):
warnings.warn(
f"Synchronous access to Constance setting '{self._key}' inside an async loop. "
f"Use 'await config.{self._key}' instead.",
RuntimeWarning,
stacklevel=3,
)
return self._config._get_sync_value(self._key, self._default)
def __str__(self):
return str(self._get_sync_value())
def __repr__(self):
return repr(self._get_sync_value())
def __int__(self):
return int(self._get_sync_value())
def __float__(self):
return float(self._get_sync_value())
def __bool__(self):
return bool(self._get_sync_value())
def __eq__(self, other):
return self._get_sync_value() == other
def __ne__(self, other):
return self._get_sync_value() != other
def __lt__(self, other):
return self._get_sync_value() < other
def __le__(self, other):
return self._get_sync_value() <= other
def __gt__(self, other):
return self._get_sync_value() > other
def __ge__(self, other):
return self._get_sync_value() >= other
def __getitem__(self, key):
return self._get_sync_value()[key]
def __iter__(self):
return iter(self._get_sync_value())
def __len__(self):
return len(self._get_sync_value())
def __contains__(self, item):
return item in self._get_sync_value()
def __hash__(self):
return hash(self._get_sync_value())
def __add__(self, other):
return self._get_sync_value() + other
def __sub__(self, other):
return self._get_sync_value() - other
def __mul__(self, other):
return self._get_sync_value() * other
def __truediv__(self, other):
return self._get_sync_value() / other
class Config:
"""The global config wrapper that handles the backend."""
def __init__(self):
super(Config, self).__setattr__('_backend',
utils.import_module_attr(settings.BACKEND)())
super().__setattr__("_backend", utils.import_module_attr(settings.BACKEND)())
def __getattr__(self, key):
try:
if not len(settings.CONFIG[key]) in (2, 3):
raise AttributeError(key)
default = settings.CONFIG[key][0]
except KeyError:
raise AttributeError(key)
def _get_sync_value(self, key, default):
result = self._backend.get(key)
if result is None:
result = default
setattr(self, key, default)
return result
return result
def __getattr__(self, key):
if key == "_backend":
return super().__getattribute__(key)
try:
if len(settings.CONFIG[key]) not in (2, 3):
raise AttributeError(key)
default = settings.CONFIG[key][0]
except KeyError as e:
raise AttributeError(key) from e
try:
asyncio.get_running_loop()
except RuntimeError:
return self._get_sync_value(key, default)
return AsyncValueProxy(key, self, default)
def __setattr__(self, key, value):
if key == "_backend":
super().__setattr__(key, value)
return
if key not in settings.CONFIG:
raise AttributeError(key)
self._backend.set(key, value)
return
async def aset(self, key, value):
if key not in settings.CONFIG:
raise AttributeError(key)
await self._backend.aset(key, value)
async def amget(self, keys):
backend_values = await self._backend.amget(keys)
# Merge with defaults like utils.get_values_for_keys
default_initial = {name: settings.CONFIG[name][0] for name in keys if name in settings.CONFIG}
return dict(default_initial, **backend_values)
def __dir__(self):
return settings.CONFIG.keys()

64
constance/checks.py Normal file
View file

@ -0,0 +1,64 @@
from __future__ import annotations
from django.core import checks
from django.core.checks import CheckMessage
from django.utils.translation import gettext_lazy as _
def check_fieldsets(*args, **kwargs) -> list[CheckMessage]:
"""
A Django system check to make sure that, if defined,
CONFIG_FIELDSETS is consistent with settings.CONFIG.
"""
from . import settings
errors = []
if hasattr(settings, "CONFIG_FIELDSETS") and settings.CONFIG_FIELDSETS:
missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys:
check = checks.Warning(
_("CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG."),
hint=", ".join(sorted(missing_keys)),
obj="settings.CONSTANCE_CONFIG",
id="constance.E001",
)
errors.append(check)
if extra_keys:
check = checks.Warning(
_("CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG."),
hint=", ".join(sorted(extra_keys)),
obj="settings.CONSTANCE_CONFIG",
id="constance.E002",
)
errors.append(check)
return errors
def get_inconsistent_fieldnames() -> tuple[set, set]:
"""
Returns a pair of values:
1) set of keys from settings.CONFIG that are not accounted for in settings.CONFIG_FIELDSETS
2) set of keys from settings.CONFIG_FIELDSETS that are not present in settings.CONFIG
If there are no fieldnames in settings.CONFIG_FIELDSETS, returns an empty set.
"""
from . import settings
if isinstance(settings.CONFIG_FIELDSETS, dict):
fieldset_items = settings.CONFIG_FIELDSETS.items()
else:
fieldset_items = settings.CONFIG_FIELDSETS
unique_field_names = set()
for _fieldset_title, fields_list in fieldset_items:
# fields_list can be a dictionary, when a fieldset is defined as collapsible
# https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing
if isinstance(fields_list, dict) and "fields" in fields_list:
fields_list = fields_list["fields"]
unique_field_names.update(fields_list)
if not unique_field_names:
return unique_field_names, unique_field_names
config_keys = set(settings.CONFIG.keys())
missing_keys = config_keys - unique_field_names
extra_keys = unique_field_names - config_keys
return missing_keys, extra_keys

101
constance/codecs.py Normal file
View file

@ -0,0 +1,101 @@
from __future__ import annotations
import json
import logging
import uuid
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from decimal import Decimal
from typing import Any
from typing import Protocol
from typing import TypeVar
logger = logging.getLogger(__name__)
DEFAULT_DISCRIMINATOR = "default"
class JSONEncoder(json.JSONEncoder):
"""Django-constance custom json encoder."""
def default(self, o):
for discriminator, (t, _, encoder) in _codecs.items():
if isinstance(o, t):
return _as(discriminator, encoder(o))
raise TypeError(f"Object of type {o.__class__.__name__} is not JSON serializable")
def _as(discriminator: str, v: Any) -> dict[str, Any]:
return {"__type__": discriminator, "__value__": v}
def dumps(obj, _dumps=json.dumps, cls=JSONEncoder, default_kwargs=None, **kwargs):
"""Serialize object to json string."""
default_kwargs = default_kwargs or {}
is_default_type = isinstance(obj, (list, dict, str, int, bool, float, type(None)))
return _dumps(
_as(DEFAULT_DISCRIMINATOR, obj) if is_default_type else obj, cls=cls, **dict(default_kwargs, **kwargs)
)
def loads(s, _loads=json.loads, *, first_level=True, **kwargs):
"""Deserialize json string to object."""
if first_level:
return _loads(s, object_hook=object_hook, **kwargs)
if isinstance(s, dict) and "__type__" not in s and "__value__" not in s:
return {k: loads(v, first_level=False) for k, v in s.items()}
if isinstance(s, list):
return list(loads(v, first_level=False) for v in s)
return _loads(s, object_hook=object_hook, **kwargs)
def object_hook(o: dict) -> Any:
"""Hook function to perform custom deserialization."""
if o.keys() == {"__type__", "__value__"}:
if o["__type__"] == DEFAULT_DISCRIMINATOR:
return o["__value__"]
codec = _codecs.get(o["__type__"])
if not codec:
raise ValueError(f"Unsupported type: {o['__type__']}")
return codec[1](o["__value__"])
if "__type__" not in o and "__value__" not in o:
return o
logger.error("Cannot deserialize object: %s", o)
raise ValueError(f"Invalid object: {o}")
T = TypeVar("T")
class Encoder(Protocol[T]):
def __call__(self, value: T, /) -> str: ... # pragma: no cover
class Decoder(Protocol[T]):
def __call__(self, value: str, /) -> T: ... # pragma: no cover
def register_type(t: type[T], discriminator: str, encoder: Encoder[T], decoder: Decoder[T]):
if not discriminator:
raise ValueError("Discriminator must be specified")
if _codecs.get(discriminator) or discriminator == DEFAULT_DISCRIMINATOR:
raise ValueError(f"Type with discriminator {discriminator} is already registered")
_codecs[discriminator] = (t, decoder, encoder)
_codecs: dict[str, tuple[type, Decoder, Encoder]] = {}
def _register_default_types():
# NOTE: datetime should be registered before date, because datetime is also instance of date.
register_type(datetime, "datetime", datetime.isoformat, datetime.fromisoformat)
register_type(date, "date", lambda o: o.isoformat(), lambda o: datetime.fromisoformat(o).date())
register_type(time, "time", lambda o: o.isoformat(), time.fromisoformat)
register_type(Decimal, "decimal", str, Decimal)
register_type(uuid.UUID, "uuid", lambda o: o.hex, uuid.UUID)
register_type(timedelta, "timedelta", lambda o: o.total_seconds(), lambda o: timedelta(seconds=o))
_register_default_types()

168
constance/forms.py Normal file
View file

@ -0,0 +1,168 @@
import hashlib
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from decimal import Decimal
from os.path import join
from django import conf
from django import forms
from django.contrib import messages
from django.contrib.admin import widgets
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import default_storage
from django.forms import fields
from django.utils import timezone
from django.utils.encoding import smart_bytes
from django.utils.module_loading import import_string
from django.utils.text import normalize_newlines
from django.utils.translation import gettext_lazy as _
from . import LazyConfig
from . import settings
from .checks import get_inconsistent_fieldnames
config = LazyConfig()
NUMERIC_WIDGET = forms.TextInput(attrs={"size": 10})
INTEGER_LIKE = (fields.IntegerField, {"widget": NUMERIC_WIDGET})
STRING_LIKE = (
fields.CharField,
{
"widget": forms.Textarea(attrs={"rows": 3}),
"required": False,
},
)
FIELDS = {
bool: (fields.BooleanField, {"required": False}),
int: INTEGER_LIKE,
Decimal: (fields.DecimalField, {"widget": NUMERIC_WIDGET}),
str: STRING_LIKE,
datetime: (fields.SplitDateTimeField, {"widget": widgets.AdminSplitDateTime}),
timedelta: (fields.DurationField, {"widget": widgets.AdminTextInputWidget}),
date: (fields.DateField, {"widget": widgets.AdminDateWidget}),
time: (fields.TimeField, {"widget": widgets.AdminTimeWidget}),
float: (fields.FloatField, {"widget": NUMERIC_WIDGET}),
}
def parse_additional_fields(fields):
for key in fields:
field = list(fields[key])
if len(field) == 1:
field.append({})
field[0] = import_string(field[0])
if "widget" in field[1]:
klass = import_string(field[1]["widget"])
field[1]["widget"] = klass(**(field[1].get("widget_kwargs", {}) or {}))
if "widget_kwargs" in field[1]:
del field[1]["widget_kwargs"]
fields[key] = field
return fields
FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS))
class ConstanceForm(forms.Form):
version = forms.CharField(widget=forms.HiddenInput)
def __init__(self, initial, request=None, *args, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
version_hash = hashlib.sha256()
only_view = request and not request.user.has_perm("constance.change_config")
if only_view:
messages.warning(
request,
_("You don't have permission to change these values"),
)
for name, options in settings.CONFIG.items():
default = options[0]
if len(options) == 3:
config_type = options[2]
if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type):
raise ImproperlyConfigured(
_(
"Default value type must be "
"equal to declared config "
"parameter type. Please fix "
"the default value of "
"'%(name)s'."
)
% {"name": name}
)
else:
config_type = type(default)
if config_type not in FIELDS:
raise ImproperlyConfigured(
_(
"Constance doesn't support "
"config values of the type "
"%(config_type)s. Please fix "
"the value of '%(name)s'."
)
% {"config_type": config_type, "name": name}
)
field_class, kwargs = FIELDS[config_type]
if only_view:
kwargs["disabled"] = True
self.fields[name] = field_class(label=name, **kwargs)
version_hash.update(smart_bytes(initial.get(name, "")))
self.initial["version"] = version_hash.hexdigest()
def save(self):
for file_field in self.files:
file = self.cleaned_data[file_field]
self.cleaned_data[file_field] = default_storage.save(join(settings.FILE_ROOT, file.name), file)
for name in settings.CONFIG:
current = getattr(config, name)
new = self.cleaned_data[name]
if isinstance(new, str):
new = normalize_newlines(new)
if conf.settings.USE_TZ and isinstance(current, datetime) and not timezone.is_aware(current):
current = timezone.make_aware(current)
if current != new:
setattr(config, name, new)
def clean_version(self):
value = self.cleaned_data["version"]
if settings.IGNORE_ADMIN_VERSION_CHECK:
return value
if value != self.initial["version"]:
raise forms.ValidationError(
_("The settings have been modified by someone else. Please reload the form and resubmit your changes.")
)
return value
def clean(self):
cleaned_data = super().clean()
if not settings.CONFIG_FIELDSETS:
return cleaned_data
missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys or extra_keys:
raise forms.ValidationError(
_("CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.")
)
return cleaned_data

Binary file not shown.

View file

@ -0,0 +1,100 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <med.b.makhlouf@gmail.com>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2020-11-30 23:15+0100\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Generator: \n"
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"يجب أن يكون نوع القيمة الافتراضي مساوياً لنوع معلمة التكوين المعلن. الرجاء "
"إصلاح القيمة الافتراضية لـ '%(name)s'."
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"لا يعتمد كونستانس قيم التكوين من النوع %(config_type)s. الرجاء إصلاح قيمة "
"'%(name)s'."
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"تم تعديل الإعدادات بواسطة شخص آخر. الرجاء إعادة تحميل النموذج وإعادة إرسال "
"التغييرات."
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
"لا يحتوي CONSTANCE_CONFIG_FIELDSETS على حقول موجودة في CONSTANCE_CONFIG."
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "تم تحديث الإعدادات المباشرة بنجاح."
#: admin.py:285
msgid "config"
msgstr "التكوين"
#: apps.py:8
msgid "Constance"
msgstr "كونستانس"
#: backends/database/models.py:19
msgid "constance"
msgstr "كونستانس"
#: backends/database/models.py:20
msgid "constances"
msgstr "كونستانس"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
"الحصول على / تعيين إعدادات التكوين في قاعدة البيانات التي تعالجها كونستانس"
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "حفظ"
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "الصفحة الرئيسية"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "الإسم"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "افتراضي"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "القيمة"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "تم تعديله"

View file

@ -1,48 +1,58 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/django-constance/language/cs_CZ/)\n"
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/"
"django-constance/language/cs_CZ/)\n"
"Language: cs_CZ\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs_CZ\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: admin.py:72
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Nastavení bylo úspěšně uloženo."
#: admin.py:134
msgid "Constance config"
msgstr "Nastavení konstant"
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr "nastavení"
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr ""
@ -54,26 +64,33 @@ msgstr "konstanta"
msgid "constances"
msgstr "konstanty"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "Název"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "Výchozí hodnota"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "Hodnota"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "Je změněna?"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Uložit"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Domů"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Název"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Výchozí hodnota"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Hodnota"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Je změněna?"
#~ msgid "Constance config"
#~ msgstr "Nastavení konstant"

View file

@ -1,49 +1,68 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2014
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2014-11-27 18:17+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: German (http://www.transifex.com/projects/p/django-constance/language/de/)\n"
"Language-Team: German (http://www.transifex.com/projects/p/django-constance/"
"language/de/)\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin.py:72
#: admin.py:113
#, fuzzy, python-format
#| msgid ""
#| "Constance doesn't support config values of the type %(config_type)s. "
#| "Please fix the value of '%(name)s'."
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. "
"Bitte den Ausgangswert von '%(name)s' ändern."
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr "Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. Bitte den Ausgangswert von '%(name)s' ändern."
msgstr ""
"Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. "
"Bitte den Ausgangswert von '%(name)s' ändern."
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr "Die Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite neuladen und die Änderungen erneut vornehmen."
msgstr ""
"Die Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite "
"neuladen und die Änderungen erneut vornehmen."
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Die Livekonfiguration wurde erfolgreich aktualisiert."
#: admin.py:134
msgid "Constance config"
msgstr "Constance Konfiguration"
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr "Konfiguration"
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr "Konstanze"
@ -55,26 +74,33 @@ msgstr "Konstanze"
msgid "constances"
msgstr "Konstanzes"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "Name"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "Voreinstellung"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "Wert"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "Ist modifiziert"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Sichern"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Start"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Name"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Voreinstellung"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Wert"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Ist modifiziert"
#~ msgid "Constance config"
#~ msgstr "Constance Konfiguration"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2022-07-19 21:00+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,32 +17,45 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin.py:72
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr ""
#: admin.py:134
msgid "Constance config"
#: admin.py:267
msgid "Failed to update live settings."
msgstr ""
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr ""
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr ""
@ -54,26 +67,30 @@ msgstr ""
msgid "constances"
msgstr ""
#: templates/admin/constance/change_list.html:50
msgid "Name"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr ""
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr ""
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr ""
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr ""
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr ""
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr ""
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr ""
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr ""
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr ""

View file

@ -1,24 +1,37 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Igor Támara <igor@tamarapatino.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-15 16:23-0500\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Igor Támara <igor@tamarapatino.org>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/language/de/\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/"
"language/de/\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin.py:71
#: admin.py:113
#, fuzzy, python-format
#| msgid ""
#| "Constance doesn't support config values of the type %(config_type)s. "
#| "Please fix the value of '%(name)s'."
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Constance no soporta valores de configuración de los tipos %(config_type)s. "
"Por favor arregle el valor de '%(name)s'."
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
@ -27,7 +40,7 @@ msgstr ""
"Constance no soporta valores de configuración de los tipos %(config_type)s. "
"Por favor arregle el valor de '%(name)s'."
#: admin.py:90
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
@ -35,19 +48,21 @@ msgstr ""
"La configuración ha sido modificada por alguien más. Por favor recargue el "
"formulario y reenvíe sus cambios."
#: admin.py:128
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Las configuraciones en vivo se actualizaron exitosamente."
#: admin.py:133
msgid "Constance config"
msgstr "Configuración de Constance"
#: admin.py:176
#: admin.py:285
msgid "config"
msgstr "configuración"
#: apps.py:7
#: apps.py:8
msgid "Constance"
msgstr "Constance"
@ -59,26 +74,33 @@ msgstr "constance"
msgid "constances"
msgstr "constances"
#: templates/admin/constance/change_list.html:58
msgid "Name"
msgstr "Nombre"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:59
msgid "Default"
msgstr "Predeterminado"
#: templates/admin/constance/change_list.html:60
msgid "Value"
msgstr "Valor"
#: templates/admin/constance/change_list.html:61
msgid "Is modified"
msgstr "Está modificado"
#: templates/admin/constance/change_list.html:87
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Guardar"
#: templates/admin/constance/change_list.html:96
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Inicio"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Nombre"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Predeterminado"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Valor"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Está modificado"
#~ msgid "Constance config"
#~ msgstr "Configuración de Constance"

Binary file not shown.

View file

@ -0,0 +1,99 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Vaikimisi väärtus peab olema vastav seade parameetri tüübile. Palun "
"sisestagekorrekne vaikimisi väärtus väljale '%(name)s'"
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Constance ei toeta seadete seda tüüpi %(config_type) seadete väärtusi. "
"Palunsisestage korrektne väärtus väljale '%(name)s'."
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Seadeid on vahepeal muudetud, palun esitage oma muudatused peale seda kui "
"olete lehe uuesti laadinud"
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Seaded edukalt muudetud."
#: admin.py:285
msgid "config"
msgstr "konfiguratsioon"
#: apps.py:8
msgid "Constance"
msgstr "Seaded"
#: backends/database/models.py:19
msgid "constance"
msgstr "seaded"
#: backends/database/models.py:20
msgid "constances"
msgstr "seaded"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr "Loe/salvesta andmebaasi põhiseid seadeid"
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Salvesta"
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr ""
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Nimi"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Vaikimisi"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Väärtus"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Muudetud"

Binary file not shown.

View file

@ -0,0 +1,104 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2020-09-24 17:33+0330\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: Mehdi Namaki <mavenium@gmail.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.3\n"
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"نوع مقدار پیش فرض باید برابر با نوع پارامتر پیکربندی اعلام شده باشد. لطفاً "
"مقدار پیش فرض '%(name)s' را اصلاح کنید."
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"تنظیمات مقادیر پیکربندی از نوع %(config_type)s را پشتیبانی نمی کند. لطفاً "
"مقدار '%(name)s' را اصلاح کنید."
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"تنظیمات توسط شخص دیگری تغییر یافته است. لطفاً فرم را بارگیری کنید و تغییرات "
"خود را دوباره ارسال کنید."
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr "CONSTANCE_CONFIG_FIELDSETS شامل فیلدهای CONSTANCE_CONFIG نیست."
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "تنظیمات زنده با موفقیت به روز شد."
#: admin.py:285
msgid "config"
msgstr "پیکربندی"
#: apps.py:8
msgid "Constance"
msgstr "تنظیمات"
#: backends/database/models.py:19
msgid "constance"
msgstr "تنظیمات"
#: backends/database/models.py:20
msgid "constances"
msgstr "تنظیمات"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
"دریافت/تنظیم تنظیمات پیکربندی درون پایگاه داده که توسط تنظیمات بکار برده می "
"شود"
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "ذخیره"
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "خانه"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "نام"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "پیش‌فرض"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "مقدار"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "تغییر یافته"
#: templates/admin/constance/includes/results_list.html:44
msgid "Reset to default"
msgstr "بازنشانی به پیش‌فرض"

Binary file not shown.

View file

@ -0,0 +1,95 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-02 19:01+0100\n"
"PO-Revision-Date: 2017-07-03 12:35+0100\n"
"Last-Translator: Bruno Alla <alla.brunoo@gmail.com>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language-Team: \n"
"X-Generator: Poedit 2.0.2\n"
#: admin.py:111
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Le type de la valeur par défaut doit être le même que le type déclaré dans "
"la configuration. Veuillez corriger la valeur par défaut de '%(name)s'."
#: admin.py:121
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Constance ne prend pas en charge les valeurs de configuration du type "
"%(config_type)s. Veuillez corriger la valeur de %(name)s."
#: admin.py:145
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Les paramètres ont été modifiés par quelqu'un d'autre. Veuillez rafraichir "
"le formulaire et soumettre de nouveau vos modifications."
#: admin.py:209
msgid "Live settings updated successfully."
msgstr "Paramètres mis à jour avec succès."
#: admin.py:271
msgid "config"
msgstr "config"
#: apps.py:8
msgid "Constance"
msgstr "Constance"
#: backends/database/models.py:19
msgid "constance"
msgstr "constance"
#: backends/database/models.py:20
msgid "constances"
msgstr "constances"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
"Obtenir/définir les paramètres de configuration de base de données gérées "
"par Constance"
#: templates/admin/constance/change_list.html:68
msgid "Save"
msgstr "Enregistrer"
#: templates/admin/constance/change_list.html:77
msgid "Home"
msgstr "Index"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Nom"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Défaut"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Valeur"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Est modifié"

View file

@ -1,50 +1,69 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/language/it/)\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2018-03-13 15:26+0100\n"
"Last-Translator: Paolo Melchiorre <paolo@melchiorre.org>\n"
"Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/"
"language/it/)\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.0.4\n"
#: admin.py:72
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Il tipo dei valori di default deve essere uguale al tipo del parametro. "
"Modifica il valore di default di '%(name)s'."
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Constance non supporta valori di impostazioni di tipo %(config_type)s. "
"Modifica il valore di '%(name)s'."
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Le impostazioni sono state modificate da qualcuno. Ricarica la pagina e "
"invia nuovamente le tue modifiche."
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
"CONSTANCE_CONFIG_FIELDSETS non contiene campi che esistono in "
"CONSTANCE_CONFIG."
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Le impostazioni attive sono state aggiornate correttamente."
#: admin.py:134
msgid "Constance config"
msgstr "Configurazione Impostazioni"
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr ""
msgstr "configurazioni"
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr ""
msgstr "Impostazioni"
#: backends/database/models.py:19
msgid "constance"
@ -54,26 +73,33 @@ msgstr "impostazione"
msgid "constances"
msgstr "impostazioni"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "Nome"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "Default"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "Valore"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "Modificato"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Salva"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Inizio"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Nome"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Default"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Valore"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Modificato"
#~ msgid "Constance config"
#~ msgstr "Configurazione Impostazioni"

View file

@ -1,48 +1,59 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/language/pl/)\n"
"Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/"
"language/pl/)\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
#: admin.py:72
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Parametry zostały zaktualizowane"
#: admin.py:134
msgid "Constance config"
msgstr "Konfiguracja Constance"
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr ""
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr ""
@ -54,26 +65,33 @@ msgstr "parametr"
msgid "constances"
msgstr "parametry"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "Nazwa"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "Domyślnie"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "Wartość"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "Zmodyfikowana"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Zapisz"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Początek"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Nazwa"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "Domyślnie"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Wartość"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Zmodyfikowana"
#~ msgid "Constance config"
#~ msgstr "Konfiguracja Constance"

View file

@ -1,48 +1,63 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2022-07-19 20:59+0500\n"
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/language/ru/)\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/"
"language/ru/)\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: admin.py:72
#: admin.py:113
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "Настройки успешно сохранены"
msgstr "Настройки успешно сохранены."
#: admin.py:134
msgid "Constance config"
msgstr "Настройки"
#: admin.py:267
msgid "Failed to update live settings."
msgstr "Не удалось сохранить настройки."
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr "настройки"
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr ""
@ -54,26 +69,33 @@ msgstr "настройки"
msgid "constances"
msgstr "настройки"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "Название"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "По умолчанию"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "Текущее значение"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "Было изменено"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "Сохранить"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "Главная"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "Название"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "По умолчанию"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "Текущее значение"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "Было изменено"
#~ msgid "Constance config"
#~ msgstr "Настройки"

Binary file not shown.

View file

@ -0,0 +1,104 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-09 19:14+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Ozcan Yarimdunya <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: constance/admin.py:116
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Varsayılan değer tipi, tanımlanan ayarlar parametresi tipi ile aynı olmalıdır. Lütfen "
"'%(name)s' in varsayılan değerini düzeltin."
#: constance/admin.py:126
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Constance %(config_type)s tipinin yapılandırma değerlerini desteklemiyor. Lütfen "
"'%(name)s' in değerini düzeltin."
#: constance/admin.py:160
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Ayarlar başkası tarafından değiştirildi. Lütfen formu tekrar yükleyin ve "
"değişikliklerinizi tekrar kaydedin."
#: constance/admin.py:172 constance/checks.py:19
msgid ""
"CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in "
"CONSTANCE_CONFIG."
msgstr ""
"CONSTANCE_CONFIG içinde mevcut olan alan(lar) için "
"CONSTANCE_CONFIG_FIELDSETS eksik."
#: constance/admin.py:240
msgid "Live settings updated successfully."
msgstr "Canlı ayarlar başarıyla güncellendi."
#: constance/admin.py:305
msgid "config"
msgstr "ayar"
#: constance/backends/database/models.py:19
msgid "constance"
msgstr "constance"
#: constance/backends/database/models.py:20
msgid "constances"
msgstr "constances"
#: constance/management/commands/constance.py:32
msgid "Get/Set In-database config settings handled by Constance"
msgstr "Constance tarafından veritabanında barındırılan ayarları görüntüle/değiştir"
#: constance/templates/admin/constance/change_list.html:60
msgid "Save"
msgstr "Kaydet"
#: constance/templates/admin/constance/change_list.html:69
msgid "Home"
msgstr "Anasayfa"
#: constance/templates/admin/constance/includes/results_list.html:6
msgid "Name"
msgstr "İsim"
#: constance/templates/admin/constance/includes/results_list.html:7
msgid "Default"
msgstr "Varsayılan"
#: constance/templates/admin/constance/includes/results_list.html:8
msgid "Value"
msgstr "Değer"
#: constance/templates/admin/constance/includes/results_list.html:9
msgid "Is modified"
msgstr "Değiştirildi mi"
#: constance/templates/admin/constance/includes/results_list.html:22
msgid "Current file"
msgstr "Geçerli dosya"
#: constance/templates/admin/constance/includes/results_list.html:39
msgid "Reset to default"
msgstr "Varsayılana dön"

Binary file not shown.

View file

@ -0,0 +1,121 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-19 16:00+0000\n"
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
"Last-Translator: Vasyl Dizhak <vasyl@dizhak.com>\n"
"Language-Team: (http://www.transifex.com/projects/p/django-constance/"
"language/uk/)\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: admin.py:109
msgid "You don't have permission to change these values"
msgstr "У вас немає прав для зміни цих значень"
#: admin.py:117
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Тип значення за замовчуванням повинен співпадати із вказаним типом параметра "
"конфігурації. Будь ласка, виправте значення за замовчуванням для '%(name)s'."
#: admin.py:127
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Constance не підтрумує значення наступного типу %(config_type)s. Будь ласка, "
"змініть тип для значення '%(name)s'"
#: admin.py:166
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Налаштування було змінено кимось іншим. Буд ласка, перезавантажте форму та "
"повторно збережіть зміни."
#: admin.py:178 checks.py:19
msgid ""
"CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in "
"CONSTANCE_CONFIG."
msgstr ""
"Одне чи кілька полів з CONSTANCE_CONFIG відсутні в "
"CONSTANCE_CONFIG_FIELDSETS."
#: admin.py:250
msgid "Live settings updated successfully."
msgstr "Налаштування успішно збережені."
#: admin.py:267
msgid "Failed to update live settings."
msgstr "Не вдалося зберегти налаштування."
#: admin.py:326
msgid "config"
msgstr "налаштування"
#: apps.py:8
msgid "Constance"
msgstr ""
#: backends/database/models.py:19
msgid "constance"
msgstr "налаштування"
#: backends/database/models.py:20
msgid "constances"
msgstr "налаштування"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr "Отримати/встановити налашування в базі даних, якими керує Constance"
#: templates/admin/constance/change_list.html:61
msgid "Save"
msgstr "Зберегти"
#: templates/admin/constance/change_list.html:70
msgid "Home"
msgstr "Головна"
#: templates/admin/constance/includes/results_list.html:6
msgid "Name"
msgstr "Назва"
#: templates/admin/constance/includes/results_list.html:7
msgid "Default"
msgstr "За замовчуванням"
#: templates/admin/constance/includes/results_list.html:8
msgid "Value"
msgstr "Поточне значення"
#: templates/admin/constance/includes/results_list.html:9
msgid "Is modified"
msgstr "Було змінено"
#: templates/admin/constance/includes/results_list.html:26
msgid "Current file"
msgstr "Поточний файл"
#: templates/admin/constance/includes/results_list.html:44
msgid "Reset to default"
msgstr "Скинути до значення за замовчуванням"
#~ msgid "Constance config"
#~ msgstr "Настройки"

View file

@ -1,49 +1,62 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Yifu Yu <root@jackyyf.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
"PO-Revision-Date: 2015-03-15 18:40+0000\n"
"Last-Translator: Yifu Yu <root@jackyyf.com>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-constance/language/zh_CN/)\n"
"Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-"
"constance/language/zh_CN/)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: admin.py:72
#: admin.py:113
#, fuzzy, python-format
#| msgid ""
#| "Constance doesn't support config values of the type %(config_type)s. "
#| "Please fix the value of '%(name)s'."
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr "Constance不支持保存类型为%(config_type)s的值请修正%(name)s的值。"
#: admin.py:123
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr "Constance不支持保存类型为%(config_type)s的值请修正%(name)s的值。"
#: admin.py:91
#: admin.py:147
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr "设置已经被他人修改过,请刷新页面并重新提交您的更改。"
#: admin.py:129
#: admin.py:160
msgid ""
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
"CONSTANCE_CONFIG."
msgstr ""
#: admin.py:224
msgid "Live settings updated successfully."
msgstr "成功更新实时配置"
#: admin.py:134
msgid "Constance config"
msgstr "Constance 配置页面"
#: admin.py:177
#: admin.py:285
msgid "config"
msgstr "配置"
#: apps.py:9
#: apps.py:8
msgid "Constance"
msgstr "Constance模块"
@ -55,26 +68,33 @@ msgstr "Constance模块"
msgid "constances"
msgstr "Constance模块"
#: templates/admin/constance/change_list.html:50
msgid "Name"
msgstr "名称"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr ""
#: templates/admin/constance/change_list.html:51
msgid "Default"
msgstr "默认值"
#: templates/admin/constance/change_list.html:52
msgid "Value"
msgstr "值"
#: templates/admin/constance/change_list.html:53
msgid "Is modified"
msgstr "是否修改过"
#: templates/admin/constance/change_list.html:79
#: templates/admin/constance/change_list.html:75
msgid "Save"
msgstr "保存"
#: templates/admin/constance/change_list.html:89
#: templates/admin/constance/change_list.html:84
msgid "Home"
msgstr "首页"
#: templates/admin/constance/includes/results_list.html:5
msgid "Name"
msgstr "名称"
#: templates/admin/constance/includes/results_list.html:6
msgid "Default"
msgstr "默认值"
#: templates/admin/constance/includes/results_list.html:7
msgid "Value"
msgstr "值"
#: templates/admin/constance/includes/results_list.html:8
msgid "Is modified"
msgstr "是否修改过"
#~ msgid "Constance config"
#~ msgstr "Constance 配置页面"

Binary file not shown.

View file

@ -0,0 +1,97 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Yifu Yu <root@jackyyf.com>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-19 11:31+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: YinKH <614457662@qq.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: .\admin.py:115
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr "默认值的类型必须与参数声明类型相同,请修正%(name)s的值。"
#: .\admin.py:125
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr "Constance不支持保存类型为%(config_type)s的值请修正%(name)s的值。"
#: .\admin.py:157
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr "设置已经被他人修改过,请刷新页面并重新提交您的更改。"
#: .\admin.py:173
msgid ""
"CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in "
"CONSTANCE_CONFIG."
msgstr "CONSTANCE_CONFIG_FIELDSETS中缺少在CONSTANCE_CONFIG中声明的字段。"
#: .\admin.py:240
msgid "Live settings updated successfully."
msgstr "实时配置更新成功"
#: .\admin.py:301
msgid "config"
msgstr "配置"
#: .\backends\database\models.py:19
msgid "constance"
msgstr "Constance模块"
#: .\backends\database\models.py:20
msgid "constances"
msgstr "Constance模块"
#: .\management\commands\constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr "获取或设置由Constance模块处理的数据库配置"
#: .\templates\admin\constance\change_list.html:60
msgid "Save"
msgstr "保存"
#: .\templates\admin\constance\change_list.html:69
msgid "Home"
msgstr "首页"
#: .\templates\admin\constance\includes\results_list.html:5
msgid "Name"
msgstr "名称"
#: .\templates\admin\constance\includes\results_list.html:6
msgid "Default"
msgstr "默认值"
#: .\templates\admin\constance\includes\results_list.html:7
msgid "Value"
msgstr "当前值"
#: .\templates\admin\constance\includes\results_list.html:8
msgid "Is modified"
msgstr "是否修改过"
#: .\templates\admin\constance\includes\results_list.html:21
msgid "Current file"
msgstr "当前文件"
#: .\templates\admin\constance\includes\results_list.html:36
msgid "Reset to default"
msgstr "重置至默认值"

View file

@ -0,0 +1,84 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.management import BaseCommand
from django.core.management import CommandError
from django.utils.translation import gettext as _
from constance import config
from constance.forms import ConstanceForm
from constance.models import Constance
from constance.utils import get_values
def _set_constance_value(key, value):
"""
Parses and sets a Constance value from a string
:param key:
:param value:
:return:
"""
form = ConstanceForm(initial=get_values())
field = form.fields[key]
clean_value = field.clean(field.to_python(value))
setattr(config, key, clean_value)
class Command(BaseCommand):
help = _("Get/Set In-database config settings handled by Constance")
GET = "get"
SET = "set"
LIST = "list"
REMOVE_STALE_KEYS = "remove_stale_keys"
def add_arguments(self, parser):
subparsers = parser.add_subparsers(dest="command")
subparsers.add_parser(self.LIST, help="list all Constance keys and their values")
parser_get = subparsers.add_parser(self.GET, help="get the value of a Constance key")
parser_get.add_argument("key", help="name of the key to get", metavar="KEY")
parser_set = subparsers.add_parser(self.SET, help="set the value of a Constance key")
parser_set.add_argument("key", help="name of the key to set", metavar="KEY")
# use nargs='+' so that we pass a list to MultiValueField (eg SplitDateTimeField)
parser_set.add_argument("value", help="value to set", metavar="VALUE", nargs="+")
subparsers.add_parser(
self.REMOVE_STALE_KEYS,
help="delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys)",
)
def handle(self, command, key=None, value=None, *args, **options):
if command == self.GET:
try:
self.stdout.write(str(getattr(config, key)), ending="\n")
except AttributeError as e:
raise CommandError(f"{key} is not defined in settings.CONSTANCE_CONFIG") from e
elif command == self.SET:
try:
if len(value) == 1:
# assume that if a single argument was passed, the field doesn't expect a list
value = value[0]
_set_constance_value(key, value)
except KeyError as e:
raise CommandError(f"{key} is not defined in settings.CONSTANCE_CONFIG") from e
except ValidationError as e:
raise CommandError(", ".join(e)) from e
elif command == self.LIST:
for k, v in get_values().items():
self.stdout.write(f"{k}\t{v}", ending="\n")
elif command == self.REMOVE_STALE_KEYS:
prefix = getattr(settings, "CONSTANCE_DATABASE_PREFIX", "")
actual_keys = [f"{prefix}{key}" for key in settings.CONSTANCE_CONFIG]
stale_records = Constance.objects.exclude(key__in=actual_keys)
if stale_records:
self.stdout.write("The following record will be deleted:", ending="\n")
else:
self.stdout.write("There are no stale records in the database.", ending="\n")
for stale_record in stale_records:
self.stdout.write(f"{stale_record.key}\t{stale_record.value}", ending="\n")
stale_records.delete()
else:
raise CommandError("Invalid command")

View file

@ -0,0 +1,24 @@
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Constance",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("key", models.CharField(max_length=255, unique=True)),
("value", models.TextField(blank=True, editable=False, null=True)),
],
options={
"verbose_name": "constance",
"verbose_name_plural": "constances",
"permissions": [("change_config", "Can change config"), ("view_config", "Can view config")],
},
),
]

View file

@ -0,0 +1,41 @@
from logging import getLogger
from django.core.management.color import no_style
from django.db import migrations
logger = getLogger(__name__)
def _migrate_from_old_table(apps, schema_editor) -> None:
"""
Copies values from old table.
On new installations just ignore error that table does not exist.
"""
connection = schema_editor.connection
quoted_string = ", ".join([connection.ops.quote_name(item) for item in ["id", "key", "value"]])
old_table_name = "constance_config"
with connection.cursor() as cursor:
if old_table_name not in connection.introspection.table_names():
logger.info("Old table does not exist, skipping")
return
cursor.execute(
f"INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM {old_table_name}", # noqa: S608
[],
)
cursor.execute(f"DROP TABLE {old_table_name}", [])
Constance = apps.get_model("constance", "Constance")
sequence_sql = connection.ops.sequence_reset_sql(no_style(), [Constance])
with connection.cursor() as cursor:
for sql in sequence_sql:
cursor.execute(sql)
class Migration(migrations.Migration):
dependencies = [("constance", "0001_initial")]
atomic = False
operations = [
migrations.RunPython(_migrate_from_old_table, reverse_code=lambda x, y: None),
]

View file

@ -0,0 +1,68 @@
import json
import logging
import pickle
from base64 import b64decode
from importlib import import_module
from django.db import migrations
from constance import settings
from constance.codecs import dumps
logger = logging.getLogger(__name__)
def is_already_migrated(value):
try:
data = json.loads(value)
if isinstance(data, dict) and set(data.keys()) == {"__type__", "__value__"}:
return True
except (json.JSONDecodeError, TypeError, UnicodeDecodeError):
return False
return False
def import_module_attr(path):
package, module = path.rsplit(".", 1)
return getattr(import_module(package), module)
def migrate_pickled_data(apps, schema_editor) -> None: # pragma: no cover
Constance = apps.get_model("constance", "Constance")
for constance in Constance.objects.exclude(value=None):
if not is_already_migrated(constance.value):
constance.value = dumps(pickle.loads(b64decode(constance.value.encode()))) # noqa: S301
constance.save(update_fields=["value"])
if settings.BACKEND in (
"constance.backends.redisd.RedisBackend",
"constance.backends.redisd.CachingRedisBackend",
):
import redis
_prefix = settings.REDIS_PREFIX
connection_cls = settings.REDIS_CONNECTION_CLASS
if connection_cls is not None:
_rd = import_module_attr(connection_cls)()
else:
if isinstance(settings.REDIS_CONNECTION, str):
_rd = redis.from_url(settings.REDIS_CONNECTION)
else:
_rd = redis.Redis(**settings.REDIS_CONNECTION)
redis_migrated_data = {}
for key in settings.CONFIG:
prefixed_key = f"{_prefix}{key}"
value = _rd.get(prefixed_key)
if value is not None and not is_already_migrated(value):
redis_migrated_data[prefixed_key] = dumps(pickle.loads(value)) # noqa: S301
for prefixed_key, value in redis_migrated_data.items():
_rd.set(prefixed_key, value)
class Migration(migrations.Migration):
dependencies = [("constance", "0002_migrate_from_old_table")]
operations = [
migrations.RunPython(migrate_pickled_data),
]

View file

18
constance/models.py Normal file
View file

@ -0,0 +1,18 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class Constance(models.Model):
key = models.CharField(max_length=255, unique=True)
value = models.TextField(null=True, blank=True, editable=False)
class Meta:
verbose_name = _("constance")
verbose_name_plural = _("constances")
permissions = [
("change_config", "Can change config"),
("view_config", "Can view config"),
]
def __str__(self):
return self.key

View file

@ -1,30 +1,31 @@
from django.conf import settings
BACKEND = getattr(settings, 'CONSTANCE_BACKEND',
'constance.backends.redisd.RedisBackend')
BACKEND = getattr(settings, "CONSTANCE_BACKEND", "constance.backends.redisd.RedisBackend")
CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {})
CONFIG = getattr(settings, "CONSTANCE_CONFIG", {})
ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {})
CONFIG_FIELDSETS = getattr(settings, "CONSTANCE_CONFIG_FIELDSETS", {})
DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND',
None)
ADDITIONAL_FIELDS = getattr(settings, "CONSTANCE_ADDITIONAL_FIELDS", {})
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings,
'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT',
60 * 60 * 24)
FILE_ROOT = getattr(settings, "CONSTANCE_FILE_ROOT", "")
DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
DATABASE_CACHE_BACKEND = getattr(settings, "CONSTANCE_DATABASE_CACHE_BACKEND", None)
REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings, "CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT", 60 * 60 * 24)
REDIS_CONNECTION_CLASS = getattr(settings, 'CONSTANCE_REDIS_CONNECTION_CLASS',
None)
DATABASE_PREFIX = getattr(settings, "CONSTANCE_DATABASE_PREFIX", "")
REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', {})
REDIS_PREFIX = getattr(settings, "CONSTANCE_REDIS_PREFIX", "constance:")
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)
REDIS_CACHE_TIMEOUT = getattr(settings, "CONSTANCE_REDIS_CACHE_TIMEOUT", 60)
IGNORE_ADMIN_VERSION_CHECK = getattr(settings,
'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK',
False)
REDIS_CONNECTION_CLASS = getattr(settings, "CONSTANCE_REDIS_CONNECTION_CLASS", None)
REDIS_ASYNC_CONNECTION_CLASS = getattr(settings, "CONSTANCE_REDIS_ASYNC_CONNECTION_CLASS", None)
REDIS_CONNECTION = getattr(settings, "CONSTANCE_REDIS_CONNECTION", {})
SUPERUSER_ONLY = getattr(settings, "CONSTANCE_SUPERUSER_ONLY", True)
IGNORE_ADMIN_VERSION_CHECK = getattr(settings, "CONSTANCE_IGNORE_ADMIN_VERSION_CHECK", False)

3
constance/signals.py Normal file
View file

@ -0,0 +1,3 @@
import django.dispatch
config_updated = django.dispatch.Signal()

View file

@ -0,0 +1,34 @@
#result_list .changed {
background-color: #ffc;
}
#changelist table thead th .text {
padding: 2px 5px;
}
#changelist table tbody td:first-child {
text-align: left;
}
#changelist-form ul.errorlist {
margin: 0 !important;
}
.help {
font-weight: normal !important;
}
#results {
overflow-x: auto;
}
.item-anchor {
visibility: hidden;
margin-left: .1em;
}
.item-name {
white-space: nowrap;
}
.item-name:hover .item-anchor {
visibility: visible;
}
.sticky-footer {
position: sticky;
width: 100%;
left: 0;
bottom: 0;
}

View file

@ -0,0 +1,38 @@
(function($) {
'use strict';
$(function() {
$('#content-main').on('click', '.reset-link', function(e) {
e.preventDefault();
const field_selector = this.dataset.fieldId.replace(/ /g, "\\ ")
const field = $('#' + field_selector);
const fieldType = this.dataset.fieldType;
if (fieldType === 'checkbox') {
field.prop('checked', this.dataset.default === 'true');
} else if (fieldType === 'multi-select') {
const defaults = JSON.parse(this.dataset.default);
const stringDefaults = defaults.map(function(v) { return String(v); });
// CheckboxSelectMultiple: individual checkboxes inside a wrapper
field.find('input[type="checkbox"]').each(function() {
$(this).prop('checked', stringDefaults.indexOf($(this).val()) !== -1);
});
// SelectMultiple: <select multiple> element
field.find('option').each(function() {
$(this).prop('selected', stringDefaults.indexOf($(this).val()) !== -1);
});
} else if (fieldType === 'date') {
const defaultDate = new Date(this.dataset.default * 1000);
$('#' + this.dataset.fieldId).val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0]));
} else if (fieldType === 'datetime') {
const defaultDate = new Date(this.dataset.default * 1000);
$('#' + this.dataset.fieldId + '_0').val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0]));
$('#' + this.dataset.fieldId + '_1').val(defaultDate.strftime(get_format('TIME_INPUT_FORMATS')[0]));
} else {
field.val(this.dataset.default);
}
});
});
})(django.jQuery);

View file

@ -1,29 +1,12 @@
{% extends "admin/base_site.html" %}
{% load admin_static admin_list i18n %}
{% load admin_list static i18n %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}">
{{ media.css }}
<style>
#result_list .changed {
background-color: #ffc;
}
#changelist table thead th .text {
padding: 2px 5px;
}
#changelist table tbody td:first-child {
text-align: left;
}
#changelist-form ul.errorlist {
margin: 0 !important;
}
.help {
font-weight: normal !important;
}
</style>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/constance.css' %}">
{% endblock %}
{% block extrahead %}
@ -31,14 +14,25 @@
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
{{ block.super }}
{{ media.js }}
<script type="text/javascript" src="{% static 'admin/js/constance.js' %}"></script>
{% if django_version < "5.1" %}
<script type="text/javascript" src="{% static 'admin/js/collapse.js' %}"></script>
{% endif %}
{% endblock %}
{% block bodyclass %}change-list{% endblock %}
{% block bodyclass %}{{ block.super }} change-list{% endblock %}
{% block content %}
<div id="content-main" class="constance">
<div class="module" id="changelist">
<form id="changelist-form" action="" method="post">{% csrf_token %}
<form id="changelist-form" action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{% if form.non_field_errors %}
<ul class="errorlist">
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% if form.errors %}
<ul class="errorlist">
{% endif %}
@ -51,39 +45,25 @@
{% if form.errors %}
</ul>
{% endif %}
<table cellspacing="0" id="result_list">
<thead>
<tr>
<th><div class="text">{% trans "Name" %}</div></th>
<th><div class="text">{% trans "Default" %}</div></th>
<th><div class="text">{% trans "Value" %}</div></th>
<th><div class="text">{% trans "Is modified" %}</div></th>
</tr>
</thead>
{% for item in config_values %}
<tr class="{% cycle 'row1' 'row2' %}">
<th>{{ item.name }}
<div class="help">{{ item.help_text|linebreaksbr }}</div>
</th>
<td>
{{ item.default }}
</td>
<td>
{{ item.form_field.errors }}
{{ item.form_field }}
</td>
<td>
{% if item.modified %}
<img src="{% static 'admin/img/icon-yes.'|add:icon_type %}" alt="{{ item.modified }}" />
{% else %}
<img src="{% static 'admin/img/icon-no.'|add:icon_type %}" alt="{{ item.modified }}" />
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<p class="paginator">
<input type="submit" name="_save" class="default" value="{% trans 'Save' %}"/>
{% if fieldsets %}
{% for fieldset in fieldsets %}
<fieldset class="module{% if fieldset.collapse %} collapse{% endif %}">
{% if django_version >= "5.1" and fieldset.collapse %}<details><summary>{% endif %}
<h2 class="fieldset-heading">{{ fieldset.title }}</h2>
{% if django_version >= "5.1" and fieldset.collapse %}</summary>{% endif %}
{% with config_values=fieldset.config_values %}
{% include "admin/constance/includes/results_list.html" %}
{% endwith %}
{% if django_version >= "5.1" and fieldset.collapse %}</details>{% endif %}
</fieldset>
{% endfor %}
{% else %}
{% include "admin/constance/includes/results_list.html" %}
{% endif %}
<p class="paginator sticky-footer">
<input type="submit" name="_save" class="default" value="{% trans 'Save' %}">
</p>
</form>
</div>
@ -92,8 +72,8 @@
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst|escape }}</a>
&rsaquo; {{ opts.verbose_name_plural|capfirst }}
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; {{ opts.verbose_name_plural|capfirst }}
</div>
{% endblock %}

View file

@ -0,0 +1,59 @@
{% load admin_list static i18n %}
<div id="results">
<table>
<thead>
<tr>
<th><div class="text">{% trans "Name" %}</div></th>
<th><div class="text">{% trans "Default" %}</div></th>
<th><div class="text">{% trans "Value" %}</div></th>
<th><div class="text">{% trans "Is modified" %}</div></th>
</tr>
</thead>
{% for item in config_values %}
<tr class="{% cycle 'row1' 'row2' %}">
<th>
<span class="item-name" id="{{ item.name|slugify }}">
{{ item.name }}
<a class="item-anchor" href="#{{ item.name|slugify }}" title="Link to this setting"></a>
</span>
<div class="help">{{ item.help_text|linebreaksbr }}</div>
</th>
<td>
{{ item.default|linebreaks }}
</td>
<td>
{{ item.form_field.errors }}
{% if item.is_file %}{% trans "Current file" %}: <a href="{% get_media_prefix as MEDIA_URL %}{{ MEDIA_URL }}{{ item.value }}" target="_blank">{{ item.value }}</a>{% endif %}
{{ item.form_field }}
{% if not item.is_file %}
<br>
<a href="#" class="reset-link"
data-field-id="{{ item.form_field.auto_id }}"
data-field-type="{% spaceless %}
{% if item.is_checkbox %}checkbox
{% elif item.is_multi_select %}multi-select
{% elif item.is_datetime %}datetime
{% elif item.is_date %}date
{% endif %}
{% endspaceless %}"
data-default="{% spaceless %}
{% if item.is_checkbox %}{% if item.raw_default %} true {% else %} false {% endif %}
{% elif item.is_multi_select %}{{ item.json_default }}
{% elif item.is_date %}{{ item.raw_default|date:"U" }}
{% elif item.is_datetime %}{{ item.raw_default|date:"U" }}
{% else %}{{ item.default }}
{% endif %}
{% endspaceless %}">{% trans "Reset to default" %}</a>
{% endif %}
</td>
<td>
{% if item.modified %}
<img src="{% static 'admin/img/icon-yes.'|add:icon_type %}" alt="{{ item.modified }}">
{% else %}
<img src="{% static 'admin/img/icon-no.'|add:icon_type %}" alt="{{ item.modified }}">
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>

View file

@ -1 +1,3 @@
from .utils import override_config
from .unittest import override_config # pragma: no cover
__all__ = ["override_config"]

65
constance/test/pytest.py Normal file
View file

@ -0,0 +1,65 @@
"""
Pytest constance override config plugin.
Inspired by https://github.com/pytest-dev/pytest-django/.
"""
from contextlib import ContextDecorator
import pytest
from constance import config as constance_config
@pytest.hookimpl(trylast=True)
def pytest_configure(config): # pragma: no cover
"""Register override_config marker."""
config.addinivalue_line("markers", ("override_config(**kwargs): mark test to override django-constance config"))
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item): # pragma: no cover
"""Validate constance override marker params. Run test with overridden config."""
marker = item.get_closest_marker("override_config")
if marker is not None:
if marker.args:
pytest.fail("Constance override can not not accept positional args")
with override_config(**marker.kwargs):
yield
else:
yield
class override_config(ContextDecorator):
"""
Override config while running test function.
Act as context manager and decorator.
"""
def enable(self):
"""Store original config values and set overridden values."""
for key, value in self._to_override.items():
self._original_values[key] = getattr(constance_config, key)
setattr(constance_config, key, value)
def disable(self):
"""Set original values to the config."""
for key, value in self._original_values.items():
setattr(constance_config, key, value)
def __init__(self, **kwargs):
self._to_override = kwargs.copy()
self._original_values = {}
def __enter__(self):
self.enable()
def __exit__(self, exc_type, exc_val, exc_tb):
self.disable()
@pytest.fixture(name="override_config")
def _override_config():
"""Make override_config available as a function fixture."""
return override_config

View file

@ -1,11 +1,12 @@
from functools import wraps
from django import VERSION as DJANGO_VERSION
from django.test import SimpleTestCase
from django.test.utils import override_settings
from .. import config
from constance import config
__all__ = ('override_config',)
__all__ = ("override_config",)
class override_config(override_settings):
@ -14,25 +15,23 @@ class override_config(override_settings):
Based on django.test.utils.override_settings.
"""
def __init__(self, **kwargs):
super(override_config, self).__init__(**kwargs)
super().__init__(**kwargs)
self.original_values = {}
def __call__(self, test_func):
"""
Modify the decorated function to override config values.
"""
"""Modify the decorated function to override config values."""
if isinstance(test_func, type):
if not issubclass(test_func, SimpleTestCase):
raise Exception(
"Only subclasses of Django SimpleTestCase can be "
"decorated with override_config")
raise Exception("Only subclasses of Django SimpleTestCase can be decorated with override_config")
return self.modify_test_case(test_func)
else:
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
return inner
def modify_test_case(self, test_case):
@ -46,9 +45,20 @@ class override_config(override_settings):
original_pre_setup = test_case._pre_setup
original_post_teardown = test_case._post_teardown
def _pre_setup(inner_self):
self.enable()
original_pre_setup(inner_self)
if DJANGO_VERSION < (5, 2):
def _pre_setup(inner_self):
self.enable()
original_pre_setup(inner_self)
else:
@classmethod
def _pre_setup(cls):
# NOTE: Django 5.2 turned this as a classmethod
# https://github.com/django/django/pull/18514/files
self.enable()
original_pre_setup()
def _post_teardown(inner_self):
original_post_teardown(inner_self)
@ -60,26 +70,20 @@ class override_config(override_settings):
return test_case
def enable(self):
"""
Store original config values and set overridden values.
"""
"""Store original config values and set overridden values."""
# Store the original values to an instance variable
for config_key in self.options.keys():
for config_key in self.options:
self.original_values[config_key] = getattr(config, config_key)
# Update config with the overriden values
# Update config with the overridden values
self.unpack_values(self.options)
def disable(self):
"""
Set original values to the config.
"""
"""Set original values to the config."""
self.unpack_values(self.original_values)
@staticmethod
def unpack_values(options):
"""
Unpack values from the given dict to config.
"""
"""Unpack values from the given dict to config."""
for name, value in options.items():
setattr(config, name, value)

View file

@ -1,6 +1,76 @@
from importlib import import_module
from . import LazyConfig
from . import settings
config = LazyConfig()
def import_module_attr(path):
package, module = path.rsplit('.', 1)
package, module = path.rsplit(".", 1)
return getattr(import_module(package), module)
def get_values():
"""
Get dictionary of values from the backend
:return:
"""
# First load a mapping between config name and default value
default_initial = ((name, options[0]) for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
return dict(default_initial, **config._backend.mget(settings.CONFIG))
async def aget_values():
"""
Get dictionary of values from the backend asynchronously
:return:
"""
default_initial = {name: options[0] for name, options in settings.CONFIG.items()}
backend_values = await config.amget(settings.CONFIG.keys())
return dict(default_initial, **backend_values)
def get_values_for_keys(keys):
"""
Retrieve values for specified keys from the backend.
:param keys: List of keys to retrieve.
:return: Dictionary with values for the specified keys.
:raises AttributeError: If any key is not found in the configuration.
"""
if not isinstance(keys, (list, tuple, set)):
raise TypeError("keys must be a list, tuple, or set of strings")
# Prepare default initial mapping
default_initial = {name: options[0] for name, options in settings.CONFIG.items() if name in keys}
# Check if all keys are present in the default_initial mapping
missing_keys = [key for key in keys if key not in default_initial]
if missing_keys:
raise AttributeError(f'"{", ".join(missing_keys)}" keys not found in configuration.')
# Merge default values and backend values, prioritizing backend values
return dict(default_initial, **config._backend.mget(keys))
async def aget_values_for_keys(keys):
"""
Retrieve values for specified keys from the backend asynchronously.
:param keys: List of keys to retrieve.
:return: Dictionary with values for the specified keys.
:raises AttributeError: If any key is not found in the configuration.
"""
if not isinstance(keys, (list, tuple, set)):
raise TypeError("keys must be a list, tuple, or set of strings")
default_initial = {name: options[0] for name, options in settings.CONFIG.items() if name in keys}
missing_keys = [key for key in keys if key not in default_initial]
if missing_keys:
raise AttributeError(f'"{", ".join(missing_keys)}" keys not found in configuration.')
backend_values = await config.amget(keys)
return dict(default_initial, **backend_values)

View file

@ -1,177 +1,20 @@
# Makefile for Sphinx documentation
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# 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
# Put it first so that "make" without argument is like "make 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 " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@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 " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
rm -rf $(BUILDDIR)/*
.PHONY: help Makefile
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-constance.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-constance.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-constance"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-constance"
@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."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@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."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View file

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/_static/screenshot3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -10,6 +10,9 @@ configuration values. By default it uses the Redis backend. To override
the default please set the :setting:`CONSTANCE_BACKEND` setting to the appropriate
dotted path.
Configuration values are stored in JSON format and automatically serialized/deserialized
on access.
Redis
-----
@ -23,7 +26,15 @@ to add it to your project settings::
CONSTANCE_BACKEND = 'constance.backends.redisd.RedisBackend'
.. _`redis-py`: https://pypi.python.org/pypi/redis
Default redis backend retrieves values every time. There is another redis backend with local cache.
`CachingRedisBackend` stores the value from a redis to memory at first access and checks a value ttl at next.
Configuration installation is simple::
CONSTANCE_BACKEND = 'constance.backends.redisd.CachingRedisBackend'
# optionally set a value ttl
CONSTANCE_REDIS_CACHE_TIMEOUT = 60
.. _`redis-py`: https://pypi.org/project/redis/
Settings
^^^^^^^^
@ -52,11 +63,11 @@ An (optional) dotted import path to a connection to use, e.g.::
CONSTANCE_REDIS_CONNECTION_CLASS = 'myproject.myapp.mockup.Connection'
If you are using `django-redis <http://niwibe.github.io/django-redis/>`_,
If you are using `django-redis <https://github.com/jazzband/django-redis>`_,
feel free to use the ``CONSTANCE_REDIS_CONNECTION_CLASS`` setting to define
a callable that returns a redis connection, e.g.::
CONSTANCE_REDIS_CONNECTION_CLASS = 'redis_cache.get_redis_connection'
CONSTANCE_REDIS_CONNECTION_CLASS = 'django_redis.get_redis_connection'
``CONSTANCE_REDIS_PREFIX``
~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -66,30 +77,25 @@ database. Defaults to ``'constance:'``. E.g.::
CONSTANCE_REDIS_PREFIX = 'constance:myproject:'
``CONSTANCE_REDIS_CACHE_TIMEOUT``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The (optional) ttl of values in seconds used by `CachingRedisBackend` for storing in a local cache.
Defaults to `60` seconds.
Database
--------
The database backend is optional and stores the configuration values in a
standard Django model. It requires the package `django-picklefield`_ for
storing those values. Please install it like so::
pip install django-constance[database]
Database backend stores configuration values in a standard Django model.
You must set the ``CONSTANCE_BACKEND`` Django setting to::
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
Then add the database backend app to your :setting:`INSTALLED_APPS` setting to
make sure the data model is correctly created::
INSTALLED_APPS = (
# other apps
'constance.backends.database',
)
Please make sure to apply the database migrations::
python manage.py migrate database
python manage.py migrate
.. note:: If you're upgrading Constance to 1.0 and use Django 1.7 or higher
please make sure to let the migration system know that you've
@ -99,6 +105,7 @@ Please make sure to apply the database migrations::
python manage.py migrate database --fake
Just like the Redis backend you can set an optional prefix that is used during
database interactions (it defaults to an empty string, ``''``). To use
something else do this::
@ -127,9 +134,32 @@ configured cache backend to enable this feature, e.g. "default"::
cache backend included in Django because correct cache
invalidation can't be guaranteed.
If you try this, Constance will throw an error and refuse
to let your application start. You can work around this by
subclassing ``constance.backends.database.DatabaseBackend``
and overriding `__init__` to remove the check. You'll
want to consult the source code for that function to see
exactly how.
We're deliberately being vague about this, because it's
dangerous; the behavior is undefined, and could even cause
your app to crash. Nevertheless, there are some limited
circumstances in which this could be useful, but please
think carefully before going down this path.
.. note:: By default Constance will autofill the cache on startup and after
saving any of the config values. If you want to disable the cache
simply set the :setting:`CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT`
setting to ``None``.
.. _django-picklefield: http://pypi.python.org/pypi/django-picklefield/
Memory
------
The configuration values are stored in a memory and do not persist between process
restarts. In order to use this backend you must set the ``CONSTANCE_BACKEND``
Django setting to::
CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend'
The main purpose of this one is to be used mostly for testing/developing means,
so make sure you intentionally use it on production environments.

View file

@ -1,5 +1,311 @@
Changelog
---------
Starting with version 4.0.0, the changelog is maintained at the GitHub releases `GitHub releases`_
.. _GitHub releases: https://github.com/jazzband/django-constance/releases
v4.0.0 (2024/08/21)
~~~~~~~~~~~~~~~~~~~
* Replace `pickle` with JSON for the database backend
* Fix migration on MySQL
* Fix data loss using `DatabaseBackend` when the DB connection is unstable
* Fix typos in the documentation
* Fix small HTML errors
* Drop support for legacy Django versions
* Migrate JavaScript to ES2015
* Fix documentation build
* Add linters and formatters (using `ruff`)
* Add support for Django 5.1 and 5.2
* Migrate from `setup.py` to `pyproject.toml`
* Bump `tox`
* Declare support for Python 3.12
v3.1.0 (2023/08/21)
~~~~~~~~~~~~~~~~~~~
* Add support for using a subdirectory of `MEDIA_ROOT` for file fields
* Remove pypy from tox tests
v3.0.0 (2023/07/27)
~~~~~~~~~~~~~~~~~~~
* Refactor database backend
Backward incompatible changes:
remove ``'constance.backends.database'`` from ``INSTALLED_APPS``
* Dropped support for python < 3.7 and django < 3.2
* Example app now supports django 4.1
* Add support for django 4.2
* Forward the request when saving the admin changelist form
v2.9.1 (2022/08/11)
~~~~~~~~~~~~~~~~~~~
* Add support for gettext in fieldset headers
* Add support for Django 4.1
* Fix text format for MultiValueField usage
v2.9.0 (2022/03/11)
~~~~~~~~~~~~~~~~~~~
* Added arabic translation
* Add concrete_model class attribute to fake admin model
* Added tests for django 3.2
* Fix do not detect datetime fields as date type
* Added support for python 3.10
* Fixes for Ukrainian locale
* Added documentation for constance_dbs config
* Add caching redis backend
* Serialize according to widget
* Add default_auto_field to database backend
v2.8.0 (2020/11/19)
~~~~~~~~~~~~~~~~~~~
* Prevent reset to default for file field
* Fields_list can be a dictionary, when a fieldset is defined as collapsible
* Create and add fa language translations files
* Respect other classes added by admin templates
* Removed deprecated url()
* Use gettext_lazy instead of ugettext_lazy
* Updated python and django version support
v2.7.0 (2020/06/22)
~~~~~~~~~~~~~~~~~~~
* Deleted south migrations
* Improve grammar of documentation index file
* Simplify documentation installation section
* Fix IntegrityError after 2.5.0 release
(Allow concurrent calls to `DatabaseBackend.set()` method)
* Make groups of fieldsets collapsable
* Allow override_config for pytest
* Put back wheel generation in travis
* Fix wrong "is modified" in admin for multi line strings
* Switch md5 to sha256
* Fix Attempts to change config values fail silently and
appear to succeed when user does not have change permissions
* Make constance app verbose name translatable
* Update example project for Django>2
* Add anchors in admin for constance settings
* Added a sticky footer in django constance admin
* Add memory backend
* Added Ukrainian locale
* Added lazy checks for pytest
v2.6.0 (2020/01/29)
~~~~~~~~~~~~~~~~~~~
* Drop support py<3.5 django<2.2
* Set pickle protocol version for the Redis backend
* Add a command to delete stale records
v2.5.0 (2019/12/23)
~~~~~~~~~~~~~~~~~~~
* Made results table responsive for Django 2 admin
* Add a Django system check that CONFIG_FIELDSETS accounts for all of CONFIG
* Rewrite set() method of database backend to reduce number of queries
* Fixed "can't compare offset-naive and offset-aware datetimes" when USE_TZ = True
* Fixed compatibility issue with Django 3.0 due to django.utils.six
* Add Turkish language
v2.4.0 (2019/03/16)
~~~~~~~~~~~~~~~~~~~
* Show not existing fields in field_list
* Drop Django<1.11 and 2.0, fix tests vs Django 2.2b
* Fixed "Reset to default" button with constants whose name contains a space
* Use default_storage to save file
* Allow null & blank for PickleField
* Removed Python 3.4 since is not longer supported
v2.3.1 (2018/09/20)
~~~~~~~~~~~~~~~~~~~
* Fixes javascript typo.
v2.3.0 (2018/09/13)
~~~~~~~~~~~~~~~~~~~
* Added zh_Hans translation.
* Fixed TestAdmin.test_linebreaks() due to linebreaksbr() behavior change
on Django 2.1
* Improved chinese translation
* Fix bug of can't change permission chang_config's name
* Improve consistency of reset value handling for `date`
* Drop support for Python 3.3
* Added official Django 2.0 support.
* Added support for Django 2.1
v2.2.0 (2018/03/23)
~~~~~~~~~~~~~~~~~~~
* Fix ConstanceForm validation.
* `CONSTANCE_DBS` setting for directing constance permissions/content_type
settings to certain DBs only.
* Added config labels.
* Updated italian translations.
* Fix `CONSTANCE_CONFIG_FIELDSETS` mismatch issue.
v2.1.0 (2018/02/07)
~~~~~~~~~~~~~~~~~~~
* Move inline JavaScript to constance.js.
* Remove translation from the app name.
* Added file uploads.
* Update information on template context processors.
* Allow running set while database is not created.
* Moved inline css/javascripts out to their own files.
* Add French translations.
* Add testing for all supported Python and Django versions.
* Preserve sorting from fieldset config.
* Added datetime.timedelta support.
* Added Estonian translations.
* Account for server timezone for Date object.
v2.0.0 (2017/02/17)
~~~~~~~~~~~~~~~~~~~
* **BACKWARD INCOMPATIBLE** Added the old value to the config_updated signal.
* Added a `get_changelist_form` hook in the ModelAdmin.
* Fix create_perm in apps.py to use database alias given by the post_migrate
signal.
* Added tests for django 1.11.
* Fix Reset to default to work with boolean/checkboxes.
* Fix handling of MultiValueField's (eg SplitDateTimeField) on the command
line.
v1.3.4 (2016/12/23)
~~~~~~~~~~~~~~~~~~~
* Fix config ordering issue
* Added localize to check modified flag
* Allow to rename Constance in Admin
* Preserve line breaks in default value
* Added functionality from django-constance-cli
* Added "Reset to default" feature
v1.3.3 (2016/09/17)
~~~~~~~~~~~~~~~~~~~
* Revert broken release
v1.3.2 (2016/09/17)
~~~~~~~~~~~~~~~~~~~
* Fixes a bug where the signal was sent for fields without changes
v1.3.1 (2016/09/15)
~~~~~~~~~~~~~~~~~~~
* Improved the signal path to avoid import errors
* Improved the admin layout when using fieldsets
v1.3 (2016/09/14)
~~~~~~~~~~~~~~~~~
* **BACKWARD INCOMPATIBLE** Dropped support for Django < 1.8).
* Added ordering constance fields using OrderedDict
* Added a signal when updating constance fields
v1.2.1 (2016/09/1)
~~~~~~~~~~~~~~~~~~
* Added some fixes to small bugs
* Fix cache when key changes
* Upgrade django_redis connection string
* Autofill cache key if key is missing
* Added support for fieldsets
v1.2 (2016/05/14)
~~~~~~~~~~~~~~~~~
@ -60,7 +366,7 @@ v1.0 (2014/12/04)
* Added docs and set up Read The Docs project:
http://django-constance.readthedocs.org/
https://django-constance.readthedocs.io/
* Set up Transifex project for easier translations:

View file

@ -1,268 +1,108 @@
# -*- coding: utf-8 -*-
# Configuration file for the Sphinx documentation builder.
#
# django-constance documentation build configuration file, created by
# sphinx-quickstart on Tue Nov 25 19:38:51 2014.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
import os
import sys
from datetime import datetime
def get_version():
# Try to get version from installed package metadata
try:
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version
return version("django-constance")
except (ImportError, PackageNotFoundError):
pass
# Fall back to setuptools_scm generated version file
try:
from constance._version import __version__
return __version__
except ImportError:
pass
return "0.0.0"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
# 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.abspath('extensions'))
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath("extensions"))
sys.path.insert(0, os.path.abspath(".."))
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "django-constance"
project_copyright = datetime.now().year.__str__() + ", Jazzband"
# The full version, including alpha/beta/rc tags
release = get_version()
# The short X.Y version
version = ".".join(release.split(".")[:3])
# -- General configuration ------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx_search.extension",
"settings",
]
# 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.intersphinx', 'settings']
templates_path = ["_templates"]
source_suffix = ".rst"
root_doc = "index"
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
pygments_style = "sphinx"
html_last_updated_fmt = ""
# 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-constance'
copyright = u'2016, Comoga and individual contributors'
# The short X.Y version.
try:
from constance import __version__
# The short X.Y version.
version = '.'.join(__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
except ImportError:
version = release = 'dev'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- 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 = 'sphinx_rtd_theme'
# 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']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# 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-constancedoc'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
htmlhelp_basename = "django-constancedoc"
# -- Options for LaTeX output ---------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
latex_elements = {}
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'django-constance.tex', u'django-constance Documentation',
u'Comoga and individual contributors', 'manual'),
("index", "django-constance.tex", "django-constance Documentation", "Jazzband", "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 ---------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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-constance', u'django-constance Documentation',
[u'Comoga and individual contributors'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
man_pages = [("index", "django-constance", "django-constance Documentation", ["Jazzband"], 1)]
# -- Options for Texinfo output -------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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-constance', u'django-constance Documentation',
u'Comoga and individual contributors', 'django-constance', 'One line description of project.',
'Miscellaneous'),
(
"index",
"django-constance",
"django-constance Documentation",
"Jazzband",
"django-constance",
"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'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'http://docs.python.org/': None,
'django': ('http://docs.djangoproject.com/en/dev/',
'http://docs.djangoproject.com/en/dev/_objects/'),
"python": ("https://docs.python.org/3", None),
"django": ("https://docs.djangoproject.com/en/dev/", "https://docs.djangoproject.com/en/dev/_objects/"),
}

View file

@ -5,27 +5,19 @@ Features
--------
* Easily migrate your static settings to dynamic settings.
* Admin interface to edit the dynamic settings.
* Edit the dynamic settings in the Django admin interface.
.. image:: screenshot2.png
.. image:: _static/screenshot2.png
Installation
------------
Quick Installation
------------------
Install from PyPI the backend specific variant of django-constance:
For the (default) Redis backend::
.. code-block::
pip install "django-constance[redis]"
For the database backend::
pip install "django-constance[database]"
Alternatively -- if you're sure that the dependencies are already
installed -- you can also run::
pip install django-constance
For complete installation instructions, including how to install the
database backend, see :ref:`Backends <backends>`.
Configuration
-------------
@ -52,6 +44,12 @@ the :setting:`CONSTANCE_CONFIG` section, like this:
'The Universe, and Everything'),
}
.. note:: Add constance *before* your project apps.
.. note:: If you use admin extensions like
`Grapelli <https://grappelliproject.com/>`_, ``'constance'`` should be added
in :setting:`INSTALLED_APPS` *before* those extensions.
Here, ``42`` is the default value for the key ``THE_ANSWER`` if it is
not found in the backend. The other member of the tuple is a help text the
admin will show.
@ -60,33 +58,56 @@ See the :ref:`Backends <backends>` section how to setup the backend and
finish the configuration.
``django-constance``'s hashes generated in different instances of the same
application may differ, preventing data from being saved.
application may differ, preventing data from being saved.
Use this option in order to skip hash verification.
Use :setting:`CONSTANCE_IGNORE_ADMIN_VERSION_CHECK` in order to skip hash
verification.
.. code-block:: python
CONSTANCE_IGNORE_ADMIN_VERSION_CHECK = True
Signals
-------
Each time a value is changed it will trigger a ``config_updated`` signal.
.. code-block:: python
from constance.signals import config_updated
@receiver(config_updated)
def constance_updated(sender, key, old_value, new_value, **kwargs):
print(sender, key, old_value, new_value)
The sender is the ``config`` object, and the ``key`` and ``new_value``
are the changed settings.
Custom fields
-------------
You can set the field type with the third value in the `CONSTANCE_CONFIG` tuple.
You can set the field type with the third value in the ``CONSTANCE_CONFIG`` tuple.
The value can be one of the supported types or a string matching a key in your :setting:`CONSTANCE_ADDITIONAL_FIELDS`
The supported types are:
* `bool`
* `int`
* `float`
* `Decimal`
* `long` (on python 2)
* `str`
* `unicode` (on python 2)
* `datetime`
* `date`
* `time`
* ``bool``
* ``int``
* ``float``
* ``Decimal``
* ``str``
* ``datetime``
* ``timedelta``
* ``date``
* ``time``
* ``list``
* ``dict``
.. note::
To be able to use ``list`` and ``dict`` you need to set a widget and form field for these types as it is ambiguous what types shall be stored in the collection object.
You can do so with :setting:`CONSTANCE_ADDITIONAL_FIELDS` as explained below.
For example, to force a value to be handled as a string:
@ -100,17 +121,17 @@ Custom field types are supported using the dictionary :setting:`CONSTANCE_ADDITI
This is a mapping between a field label and a sequence (list or tuple). The first item in the sequence is the string
path of a field class, and the (optional) second item is a dictionary used to configure the field.
The `widget` and `widget_kwargs` keys in the field config dictionary can be used to configure the widget used in admin,
the other values will be passed as kwargs to the field's `__init__()`
The ``widget`` and ``widget_kwargs`` keys in the field config dictionary can be used to configure the widget used in admin,
the other values will be passed as kwargs to the field's ``__init__()``
Note: Use later evaluated strings instead of direct classes for the field and widget classes:
.. note:: Use later evaluated strings instead of direct classes for the field and widget classes:
.. code-block:: python
CONSTANCE_ADDITIONAL_FIELDS = {
'yes_no_null_select': ['django.forms.fields.ChoiceField', {
'widget': 'django.forms.Select',
'choices': (("-----", None), ("yes", "Yes"), ("no", "No"))
'choices': ((None, "-----"), ("yes", "Yes"), ("no", "No"))
}],
}
@ -118,6 +139,141 @@ Note: Use later evaluated strings instead of direct classes for the field and wi
'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'),
}
If you want to work with images or files you can use this configuration:
.. code-block:: python
CONSTANCE_ADDITIONAL_FIELDS = {
'image_field': ['django.forms.ImageField', {}]
}
CONSTANCE_CONFIG = {
'LOGO_IMAGE': ('default.png', 'Company logo', 'image_field'),
}
When used in a template you probably need to use:
.. code-block:: html
{% load static %}
{% get_media_prefix as MEDIA_URL %}
<img src="{{ MEDIA_URL }}{{ config.LOGO_IMAGE }}">
Images and files are uploaded to ``MEDIA_ROOT`` by default. You can specify a subdirectory of ``MEDIA_ROOT`` to use instead by adding the ``CONSTANCE_FILE_ROOT`` setting. E.g.:
.. code-block:: python
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
CONSTANCE_FILE_ROOT = 'constance'
This will result in files being placed in ``media/constance`` within your ``BASE_DIR``. You can use deeper nesting in this setting (e.g. ``constance/images``) but other relative path components (e.g. ``../``) will be rejected.
In case you want to store a list of ``int`` values in the constance config, a working setup is
.. code-block:: python
CONSTANCE_ADDITIONAL_FIELDS = {
list: ["django.forms.fields.JSONField", {"widget": "django.forms.Textarea"}],
}
CONSTANCE_CONFIG = {
'KEY': ([0, 10, 20], 'A list of integers', list),
}
Make sure to use the ``JSONField`` for this purpose as user input in the admin page may be understood and saved as ``str`` otherwise.
Ordered Fields in Django Admin
------------------------------
To sort the fields, you can use an OrderedDict:
.. code-block:: python
from collections import OrderedDict
CONSTANCE_CONFIG = OrderedDict([
('SITE_NAME', ('My Title', 'Website title')),
('SITE_DESCRIPTION', ('', 'Website description')),
('THEME', ('light-blue', 'Website theme')),
])
Fieldsets
---------
You can define fieldsets to group settings together:
.. code-block:: python
CONSTANCE_CONFIG = {
'SITE_NAME': ('My Title', 'Website title'),
'SITE_DESCRIPTION': ('', 'Website description'),
'THEME': ('light-blue', 'Website theme'),
}
CONSTANCE_CONFIG_FIELDSETS = {
'General Options': ('SITE_NAME', 'SITE_DESCRIPTION'),
'Theme Options': ('THEME',),
}
.. note:: CONSTANCE_CONFIG_FIELDSETS must contain all fields from CONSTANCE_CONFIG.
.. image:: _static/screenshot3.png
Fieldsets collapsing
--------------------
To make some fieldsets collapsing you can use new format in CONSTANCE_CONFIG_FIELDSETS. Here's an example:
.. code-block:: python
CONSTANCE_CONFIG = {
'SITE_NAME': ('My Title', 'Website title'),
'SITE_DESCRIPTION': ('', 'Website description'),
'THEME': ('light-blue', 'Website theme'),
}
CONSTANCE_CONFIG_FIELDSETS = {
'General Options': {
'fields': ('SITE_NAME', 'SITE_DESCRIPTION'),
'collapse': True
},
'Theme Options': ('THEME',),
}
Field internationalization
--------------------------
Field description and fieldset headers can be integrated into Django's
internationalization using the ``gettext_lazy`` function. Note that the
``CONSTANCE_CONFIG_FIELDSETS`` must be converted to a tuple instead of dict
as it is not possible to have lazy proxy objects as dictionary keys in the
settings file. Example:
.. code-block:: python
from django.utils.translation import gettext_lazy as _
CONSTANCE_CONFIG = {
'SITE_NAME': ('My Title', _('Website title')),
'SITE_DESCRIPTION': ('', _('Website description')),
'THEME': ('light-blue', _('Website theme')),
}
CONSTANCE_CONFIG_FIELDSETS = (
(
_('General Options'),
{
'fields': ('SITE_NAME', 'SITE_DESCRIPTION'),
'collapse': True,
},
),
(_('Theme Options'), ('THEME',)),
)
Usage
-----
@ -136,10 +292,40 @@ object and accessing the variables with attribute lookups::
if config.THE_ANSWER == 42:
answer_the_question()
Asynchronous usage
^^^^^^^^^^^^^^^^^^
If you are using Django's asynchronous features (like async views), you can ``await`` the settings directly on the standard ``config`` object::
from constance import config
async def my_async_view(request):
# Accessing settings is awaitable
if await config.THE_ANSWER == 42:
return await answer_the_question_async()
async def update_settings():
# Updating settings asynchronously
await config.aset('THE_ANSWER', 43)
# Bulk retrieval is supported as well
values = await config.amget(['THE_ANSWER', 'SITE_NAME'])
Performance and Safety
~~~~~~~~~~~~~~~~~~~~~~
While synchronous access (e.g., ``config.THE_ANSWER``) still works inside async views for some backends, it is highly discouraged:
* **Blocking:** Synchronous access blocks the event loop, reducing the performance of your entire application.
* **Safety Guards:** For the Database backend, Django's safety guards will raise a ``SynchronousOnlyOperation`` error if you attempt to access a setting synchronously from an async thread.
* **Automatic Detection:** Constance will emit a ``RuntimeWarning`` if it detects synchronous access inside an asynchronous event loop, helping you identify and fix these performance bottlenecks.
For peak performance, especially with the Redis backend, always use the ``await`` syntax which leverages native asynchronous drivers.
Django templates
^^^^^^^^^^^^^^^^
To access the config object from your template you can either
To access the config object from your template you can
pass the object to the template context:
.. code-block:: python
@ -150,14 +336,13 @@ pass the object to the template context:
def myview(request):
return render(request, 'my_template.html', {'config': config})
Or you can use the included config context processor.:
You can also use the included context processor.
.. code-block:: python
Insert ``'constance.context_processors.config'`` at
the top of your ``TEMPLATES['OPTIONS']['context_processors']`` list. See the
`Django documentation`_ for details.
TEMPLATE_CONTEXT_PROCESSORS = (
# ...
'constance.context_processors.config',
)
.. _`Django documentation`: https://docs.djangoproject.com/en/1.11/ref/templates/upgrading/#the-templates-settings
This will add the config instance to the context of any template
rendered with a ``RequestContext``.
@ -172,22 +357,112 @@ any other variable, e.g.:
Woohoo! Head over <a href="/sekrit/">here</a> to use the beta.
{% else %}
Sadly we haven't launched yet, click <a href="/newsletter/">here</a>
to signup for our newletter.
to signup for our newsletter.
{% endif %}
Command Line
^^^^^^^^^^^^
Constance settings can be get/set on the command line with the manage command :command:`constance`.
Available options are:
.. program:: constance
.. option:: list
list all Constance keys and their values
.. code-block:: console
$ ./manage.py constance list
THE_ANSWER 42
SITE_NAME My Title
.. option:: get <KEY>
get the value of a Constance key
.. code-block:: console
$ ./manage.py constance get THE_ANSWER
42
.. option:: set <KEY> <VALUE>
set the value of a Constance key
.. code-block:: console
$ ./manage.py constance set SITE_NAME "Another Title"
If the value contains spaces it should be wrapped in quotes.
.. note:: Set values are validated as per in admin, an error will be raised if validation fails:
E.g., given this config as per the example app:
.. code-block:: python
CONSTANCE_CONFIG = {
...
'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"),
}
Setting an invalid date will fail as follow:
.. code-block:: console
$ ./manage.py constance set DATE_ESTABLISHED '1999-12-00'
CommandError: Enter a valid date.
.. note:: If the admin field is a :class:`MultiValueField`, then the separate field values need to be provided as separate arguments.
E.g., a datetime using :class:`SplitDateTimeField`:
.. code-block:: python
CONSTANCE_CONFIG = {
'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24), 'time of the first commit'),
}
Then this works (and the quotes are optional):
.. code-block:: console
./manage.py constance set DATETIME_VALUE '2011-09-24' '12:30:25'
This doesn't work:
.. code-block:: console
./manage.py constance set DATETIME_VALUE '2011-09-24 12:30:25'
CommandError: Enter a list of values.
.. option:: remove_stale_keys
delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys)
.. code-block:: console
$ ./manage.py constance remove_stale_keys
Record is considered stale if it exists in database but absent in config.
Editing
-------
Fire up your ``admin`` and you should see a new app called ``Constance``
with ``THE_ANSWER`` in the ``Config`` pseudo model.
By default changing the settings via the admin is only allowed for super users.
But in case you want to use the admin's ability to implement custom
authorization checks, feel free to set the :setting:`CONSTANCE_SUPERUSER_ONLY`
setting to ``False`` and give the users or user groups access to the
By default, changing the settings via the admin is only allowed for superusers.
To change this, feel free to set the :setting:`CONSTANCE_SUPERUSER_ONLY`
setting to ``False`` and give users or user groups access to the
``constance.change_config`` permission.
.. figure:: screenshot1.png
.. figure:: _static/screenshot1.png
The virtual application ``Constance`` among your regular applications.
@ -203,19 +478,38 @@ settings the way you like.
.. code-block:: python
from constance.admin import ConstanceAdmin, ConstanceForm, Config
from constance.admin import ConstanceAdmin, Config
from constance.forms import ConstanceForm
class CustomConfigForm(ConstanceForm):
def __init__(self, *args, **kwargs):
super(CustomConfigForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
#... do stuff to make your settings form nice ...
class ConfigAdmin(ConstanceAdmin):
form = CustomConfigForm
change_list_form = CustomConfigForm
change_list_template = 'admin/config/settings.html'
admin.site.unregister([Config])
admin.site.register([Config], ConfigAdmin)
You can also override the ``get_changelist_form`` method which is called in
``changelist_view`` to get the actual form used to change the settings. This
allows you to pick a different form according to the user that makes the
request. For example:
.. code-block:: python
class SuperuserForm(ConstanceForm):
# Do some stuff here
class MyConstanceAdmin(ConstanceAdmin):
def get_changelist_form(self, request):
if request.user.is_superuser:
return SuperuserForm:
else:
return super().get_changelist_form(request)
Note that the default method returns ``self.change_list_form``.
More documentation
------------------

View file

@ -1,53 +1,16 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
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. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
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
)
%SPHINXBUILD% 2> nul
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
@ -56,187 +19,17 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
echo.https://www.sphinx-doc.org/
exit /b 1
)
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" == "" goto help
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
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
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-constance.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-constance.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" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF 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
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

3
docs/requirements.txt Normal file
View file

@ -0,0 +1,3 @@
readthedocs-sphinx-search==0.3.2
sphinx==7.3.7
sphinx-rtd-theme==2.0.0

View file

@ -15,7 +15,7 @@ Usage
It can be used as a decorator at the :class:`~django.test.TestCase` level, the
method level and also as a
`context manager <https://www.python.org/dev/peps/pep-0343/>`_.
`context manager <https://peps.python.org/pep-0343/>`_.
.. code-block:: python
@ -38,3 +38,87 @@ method level and also as a
def test_what_is_your_favourite_color(self):
with override_config(YOUR_FAVOURITE_COLOR="Blue?"):
self.assertEqual(config.YOUR_FAVOURITE_COLOR, "Blue?")
Pytest usage
~~~~~~~~~~~~
Django-constance provides pytest plugin that adds marker
:class:`@pytest.mark.override_config()`. It handles config override for
module/class/function, and automatically revert any changes made to the
constance config values when test is completed.
.. py:function:: pytest.mark.override_config(**kwargs)
Specify different config values for the marked tests in kwargs.
Module scope override
.. code-block:: python
pytestmark = pytest.mark.override_config(API_URL="/awesome/url/")
def test_api_url_is_awesome():
...
Class/function scope
.. code-block:: python
from constance import config
@pytest.mark.override_config(API_URL="/awesome/url/")
class SomeClassTest:
def test_is_awesome_url(self):
assert config.API_URL == "/awesome/url/"
@pytest.mark.override_config(API_URL="/another/awesome/url/")
def test_another_awesome_url(self):
assert config.API_URL == "/another/awesome/url/"
If you want to use override as a context manager or decorator, consider using
.. code-block:: python
from constance.test.pytest import override_config
def test_override_context_manager():
with override_config(BOOL_VALUE=False):
...
# or
@override_config(BOOL_VALUE=False)
def test_override_context_manager():
...
Pytest fixture as function or method parameter.
.. note:: No import needed as fixture is available globally.
.. code-block:: python
def test_api_url_is_awesome(override_config):
with override_config(API_URL="/awesome/url/"):
...
Any scope, auto-used fixture alternative can also be implemented like this
.. code-block:: python
@pytest.fixture(scope='module', autouse=True) # e.g. module scope
def api_url(override_config):
with override_config(API_URL="/awesome/url/"):
yield
Memory backend
~~~~~~~~~~~~~~
If you don't want to rely on any external services such as Redis or database when
running your unittests you can select :class:`MemoryBackend` for a test Django settings file
.. code-block:: python
CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend'
It will provide simple thread-safe backend which will reset to default values after each
test run.

View file

@ -1,4 +1,5 @@
from django.contrib import admin
from cheeseshop.apps.catalog.models import Brand
admin.site.register(Brand)

View file

@ -0,0 +1,16 @@
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name="Brand",
fields=[
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
),
]

View file

@ -1,5 +1,5 @@
from django.db import models
class Brand(models.Model):
name = models.CharField(max_length=75)

View file

@ -1,5 +1,7 @@
from django.contrib import admin
from cheeseshop.apps.storage.models import Shelf, Supply
from cheeseshop.apps.storage.models import Shelf
from cheeseshop.apps.storage.models import Supply
admin.site.register(Shelf)
admin.site.register(Supply)

View file

@ -0,0 +1,29 @@
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name="Shelf",
fields=[
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
options={
"verbose_name_plural": "shelves",
},
),
migrations.CreateModel(
name="Supply",
fields=[
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("name", models.CharField(max_length=75)),
],
options={
"verbose_name_plural": "supplies",
},
),
]

View file

@ -1,14 +1,15 @@
from django.db import models
class Shelf(models.Model):
name = models.CharField(max_length=75)
class Meta:
verbose_name_plural = 'shelves'
verbose_name_plural = "shelves"
class Supply(models.Model):
name = models.CharField(max_length=75)
class Meta:
verbose_name_plural = 'supplies'
verbose_name_plural = "supplies"

View file

@ -0,0 +1,25 @@
import json
from django.forms import fields
from django.forms import widgets
class JsonField(fields.CharField):
widget = widgets.Textarea
def __init__(self, rows: int = 5, **kwargs):
self.rows = rows
super().__init__(**kwargs)
def widget_attrs(self, widget: widgets.Widget):
attrs = super().widget_attrs(widget)
attrs["rows"] = self.rows
return attrs
def to_python(self, value):
if value:
return json.loads(value)
return {}
def prepare_value(self, value):
return json.dumps(value)

Some files were not shown because too many files have changed in this diff Show more