mirror of
https://github.com/jazzband/django-constance.git
synced 2026-04-10 02:01:02 +00:00
Compare commits
No commits in common. "master" and "1.2" have entirely different histories.
129 changed files with 1918 additions and 6182 deletions
|
|
@ -1,9 +1,6 @@
|
||||||
[run]
|
[run]
|
||||||
source = constance
|
source = constance
|
||||||
branch = 1
|
branch = 1
|
||||||
omit =
|
|
||||||
*/pytest.py
|
|
||||||
*/tests/*
|
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py
|
omit = *tests*,*migrations*
|
||||||
|
|
|
||||||
13
.github/ISSUE_TEMPLATE.md
vendored
13
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
### 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
18
.github/dependabot.yml
vendored
|
|
@ -1,18 +0,0 @@
|
||||||
# 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
24
.github/workflows/docs.yml
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
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
37
.github/workflows/release.yml
vendored
|
|
@ -1,37 +0,0 @@
|
||||||
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
54
.github/workflows/test.yml
vendored
|
|
@ -1,54 +0,0 @@
|
||||||
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@v6
|
|
||||||
with:
|
|
||||||
name: Python ${{ matrix.python-version }}
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -7,7 +7,4 @@ dist/
|
||||||
test.db
|
test.db
|
||||||
.tox
|
.tox
|
||||||
.coverage
|
.coverage
|
||||||
coverage.xml
|
|
||||||
docs/_build
|
docs/_build
|
||||||
.idea
|
|
||||||
constance/_version.py
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# 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: .
|
|
||||||
37
.travis.yml
Normal file
37
.travis.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
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"
|
||||||
8
.tx/config
Normal file
8
.tx/config
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[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
11
AUTHORS
|
|
@ -1,23 +1,17 @@
|
||||||
Ales Zoulek <ales.zoulek@gmail.com>
|
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>
|
Bouke Haarsma <bouke@webatoom.nl>
|
||||||
Camilo Nova <camilo.nova@gmail.com>
|
Camilo Nova <camilo.nova@gmail.com>
|
||||||
Charlie Hornsby <charlie.hornsby@hotmail.co.uk>
|
Charlie Hornsby <charlie.hornsby@hotmail.co.uk>
|
||||||
Curtis Maloney <curtis@tinbrain.net>
|
Curtis Maloney <curtis@tinbrain.net>
|
||||||
Dan Poirier <dpoirier@caktusgroup.com>
|
Dan Poirier <dpoirier@caktusgroup.com>
|
||||||
David Burke <dmbst32@gmail.com>
|
David Burke <dmbst32@gmail.com>
|
||||||
Dmitriy Tatarkin <mail@dtatarkin.ru>
|
|
||||||
Elisey Zanko <elisey.zanko@gmail.com>
|
|
||||||
Florian Apolloner <florian@apolloner.eu>
|
Florian Apolloner <florian@apolloner.eu>
|
||||||
Igor Támara <igor@axiacore.com>
|
Igor Támara <igor@axiacore.com>
|
||||||
Ilya Chichak <ilyachch@gmail.com>
|
|
||||||
Ivan Klass <klass.ivanklass@gmail.com>
|
|
||||||
Jake Merdich <jmerdich@users.noreply.github.com>
|
Jake Merdich <jmerdich@users.noreply.github.com>
|
||||||
Jannis Leidel <jannis@leidel.info>
|
Jannis Leidel <jannis@leidel.info>
|
||||||
Janusz Harkot <janusz.harkot@gmail.com>
|
Janusz Harkot <janusz.harkot@gmail.com>
|
||||||
Jiri Barton <jbar@hosting4u.cz>
|
Jiri Barton <jbar@hosting4u.cz>
|
||||||
John Carter <john@therefromhere.org>
|
|
||||||
Jonas <jvp@jonasundderwolf.de>
|
Jonas <jvp@jonasundderwolf.de>
|
||||||
Kuba Zarzycki <jakubzarzycki@gmail.com>
|
Kuba Zarzycki <jakubzarzycki@gmail.com>
|
||||||
Leandra Finger <leandra.finger@gmail.com>
|
Leandra Finger <leandra.finger@gmail.com>
|
||||||
|
|
@ -26,18 +20,15 @@ Lin Xianyi <iynaix@gmail.com>
|
||||||
Marcin Baran <marcin.baran@agencjawmc.pl>
|
Marcin Baran <marcin.baran@agencjawmc.pl>
|
||||||
Mario Orlandi <morlandi@brainstorm.it>
|
Mario Orlandi <morlandi@brainstorm.it>
|
||||||
Mario Rosa <mario@dwaiter.com>
|
Mario Rosa <mario@dwaiter.com>
|
||||||
Mariusz Felisiak <felisiak.mariusz@gmail.com>
|
|
||||||
Mattia Larentis <mattia@larentis.eu>
|
Mattia Larentis <mattia@larentis.eu>
|
||||||
Merijn Bertels <merijn.bertels@gmail.com>
|
Merijn Bertels <merijn.bertels@gmail.com>
|
||||||
Omer Katz <omer.drow@gmail.com>
|
Omer Katz <omer.drow@gmail.com>
|
||||||
Petr Knap <dev@petrknap.cz>
|
Petr Knap <dev@petrknap.cz>
|
||||||
Philip Neustrom <philipn@gmail.com>
|
Philip Neustrom <philipn@gmail.com>
|
||||||
Philipp Thumfart <philipp@thumfart.eu>
|
|
||||||
Pierre-Olivier Marec <pomarec@free.fr>
|
Pierre-Olivier Marec <pomarec@free.fr>
|
||||||
Roman Krejcik <farin@farin.cz>
|
Roman Krejcik <farin@farin.cz>
|
||||||
Silvan Spross <silvan.spross@gmail.com>
|
Silvan Spross <silvan.spross@gmail.com>
|
||||||
Sławek Ehlert <slafs@op.pl>
|
Sławek Ehlert <slafs@op.pl>
|
||||||
Vladas Tamoshaitis <amd.vladas@gmail.com>
|
|
||||||
Vojtech Jasny <voy@voy.cz>
|
Vojtech Jasny <voy@voy.cz>
|
||||||
Yin Jifeng <jifeng.yin@gmail.com>
|
Yin Jifeng <jifeng.yin@gmail.com>
|
||||||
illumin-us-r3v0lution <luminaries@riseup.net>
|
illumin-us-r3v0lution <luminaries@riseup.net>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# 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).
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# Contributing to Django Constance
|
[](https://jazzband.co/)
|
||||||
|
|
||||||
This is a [Django Commons](https://github.com/django-commons/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md).
|
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).
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2009-2017, Jazzband
|
Copyright (c) 2009-2015, Comoga and individual contributors
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
recursive-include constance/templates *.html
|
recursive-include constance/templates *.html
|
||||||
recursive-include constance/locale *.po *.mo
|
recursive-include constance/locale *.po *.mo
|
||||||
recursive-include constance/static *
|
|
||||||
|
|
|
||||||
18
README.rst
18
README.rst
|
|
@ -1,28 +1,20 @@
|
||||||
Constance - Dynamic Django settings
|
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
|
.. image:: https://jazzband.co/static/img/badge.svg
|
||||||
:alt: Jazzband
|
:alt: Jazzband
|
||||||
:target: https://jazzband.co/
|
: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
|
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.
|
Django model backend built in) with an integration with the Django admin app.
|
||||||
|
|
||||||
For more information see the documentation at:
|
For more information see the documentation at:
|
||||||
|
|
||||||
https://django-constance.readthedocs.io/
|
http://django-constance.readthedocs.org/
|
||||||
|
|
||||||
If you have questions or have trouble using the app please file a bug report
|
If you have questions or have trouble using the app please file a bug report
|
||||||
at:
|
at:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from django.utils.functional import LazyObject
|
from django.utils.functional import LazyObject
|
||||||
|
|
||||||
|
__version__ = '1.2'
|
||||||
|
|
||||||
|
default_app_config = 'constance.apps.ConstanceConfig'
|
||||||
|
|
||||||
|
|
||||||
class LazyConfig(LazyObject):
|
class LazyConfig(LazyObject):
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
from .base import Config
|
from .base import Config
|
||||||
|
|
||||||
self._wrapped = Config()
|
self._wrapped = Config()
|
||||||
|
|
||||||
|
|
||||||
config = LazyConfig()
|
config = LazyConfig()
|
||||||
|
|
|
||||||
|
|
@ -1,231 +1,191 @@
|
||||||
import json
|
from datetime import datetime, date, time
|
||||||
from collections import OrderedDict
|
from decimal import Decimal
|
||||||
from datetime import date
|
import hashlib
|
||||||
from datetime import datetime
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from django import forms
|
from django import forms, VERSION
|
||||||
from django import get_version
|
from django.conf.urls import url
|
||||||
from django.apps import apps
|
from django.contrib import admin, messages
|
||||||
from django.contrib import admin
|
from django.contrib.admin import widgets
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.admin.models import CHANGE
|
|
||||||
from django.contrib.admin.models import LogEntry
|
|
||||||
from django.contrib.admin.options import csrf_protect_m
|
from django.contrib.admin.options import csrf_protect_m
|
||||||
from django.contrib.admin.views.main import PAGE_VAR
|
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.forms import fields
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import path
|
from django.utils import six
|
||||||
|
from django.utils.encoding import smart_bytes
|
||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.module_loading import import_string
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
import django
|
||||||
|
|
||||||
from . import LazyConfig
|
|
||||||
from . import settings
|
from . import LazyConfig, settings
|
||||||
from .forms import ConstanceForm
|
|
||||||
from .utils import get_values
|
|
||||||
|
|
||||||
config = LazyConfig()
|
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):
|
class ConstanceAdmin(admin.ModelAdmin):
|
||||||
change_list_template = "admin/constance/change_list.html"
|
change_list_template = 'admin/constance/change_list.html'
|
||||||
change_list_form = ConstanceForm
|
change_list_form = ConstanceForm
|
||||||
|
|
||||||
def __init__(self, model, admin_site):
|
|
||||||
model._meta.concrete_model = Config
|
|
||||||
super().__init__(model, admin_site)
|
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
info = f"{self.model._meta.app_label}_{self.model._meta.module_name}"
|
info = self.model._meta.app_label, self.model._meta.module_name
|
||||||
return [
|
return [
|
||||||
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_changelist"),
|
url(r'^$',
|
||||||
path("", self.admin_site.admin_view(self.changelist_view), name=f"{info}_add"),
|
self.admin_site.admin_view(self.changelist_view),
|
||||||
# Redirect <object_id>/change/ to the changelist so that "Recent actions" links in the admin index
|
name='%s_%s_changelist' % info),
|
||||||
# point somewhere useful. The relative "../../" resolves to the constance changelist because the
|
url(r'^$',
|
||||||
# full path is <app>/<model>/<object_id>/change/ and two levels up lands on <app>/<model>/.
|
self.admin_site.admin_view(self.changelist_view),
|
||||||
path(
|
name='%s_%s_add' % info),
|
||||||
"<path:object_id>/change/",
|
|
||||||
self.admin_site.admin_view(lambda request, object_id: HttpResponseRedirect("../../")),
|
|
||||||
name=f"{info}_change",
|
|
||||||
),
|
|
||||||
path("history/", self.admin_site.admin_view(self.history_view), name=f"{info}_history"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
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
|
@csrf_protect_m
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
if not self.has_view_or_change_permission(request):
|
# First load a mapping between config name and default value
|
||||||
|
if not self.has_change_permission(request, None):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
initial = get_values()
|
default_initial = ((name, options[0])
|
||||||
form_cls = self.get_changelist_form(request)
|
for name, options in settings.CONFIG.items())
|
||||||
form = form_cls(initial=initial, request=request)
|
# Then update the mapping with actually values from the backend
|
||||||
if request.method == "POST" and request.user.has_perm("constance.change_config"):
|
initial = dict(default_initial,
|
||||||
form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request)
|
**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)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
changed_fields = form.save()
|
form.save()
|
||||||
if changed_fields:
|
# In django 1.5 this can be replaced with self.message_user
|
||||||
self._log_config_change(request, changed_fields)
|
messages.add_message(
|
||||||
messages.add_message(request, messages.SUCCESS, _("Live settings updated successfully."))
|
request,
|
||||||
return HttpResponseRedirect(".")
|
messages.SUCCESS,
|
||||||
messages.add_message(request, messages.ERROR, _("Failed to update live settings."))
|
_('Live settings updated successfully.'),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect('.')
|
||||||
context = {
|
context = {
|
||||||
**self.admin_site.each_context(request),
|
'config_values': [],
|
||||||
**(extra_context or {}),
|
'title': _('Constance config'),
|
||||||
"config_values": [],
|
'app_label': 'constance',
|
||||||
"title": self.model._meta.app_config.verbose_name,
|
'opts': Config._meta,
|
||||||
"app_label": "constance",
|
'form': form,
|
||||||
"opts": self.model._meta,
|
'media': self.media + form.media,
|
||||||
"form": form,
|
'icon_type': 'gif' if VERSION < (1, 9) else 'svg',
|
||||||
"media": self.media + form.media,
|
|
||||||
"icon_type": "svg",
|
|
||||||
"django_version": get_version(),
|
|
||||||
}
|
}
|
||||||
for name, options in settings.CONFIG.items():
|
for name, options in settings.CONFIG.items():
|
||||||
context["config_values"].append(self.get_config_value(name, options, form, initial))
|
default, help_text = options[0], options[1]
|
||||||
|
# First try to load the value from the actual backend
|
||||||
if settings.CONFIG_FIELDSETS:
|
value = initial.get(name)
|
||||||
if isinstance(settings.CONFIG_FIELDSETS, dict):
|
# Then if the returned value is None, get the default
|
||||||
fieldset_items = settings.CONFIG_FIELDSETS.items()
|
if value is None:
|
||||||
else:
|
value = getattr(config, name)
|
||||||
fieldset_items = settings.CONFIG_FIELDSETS
|
context['config_values'].append({
|
||||||
|
'name': name,
|
||||||
context["fieldsets"] = []
|
'default': localize(default),
|
||||||
for fieldset_title, fieldset_data in fieldset_items:
|
'help_text': _(help_text),
|
||||||
if isinstance(fieldset_data, dict):
|
'value': localize(value),
|
||||||
fields_list = fieldset_data["fields"]
|
'modified': value != default,
|
||||||
collapse = fieldset_data.get("collapse", False)
|
'form_field': form[name],
|
||||||
else:
|
})
|
||||||
fields_list = fieldset_data
|
context['config_values'].sort(key=itemgetter('name'))
|
||||||
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
|
request.current_app = self.admin_site.name
|
||||||
return TemplateResponse(request, self.change_list_template, context)
|
# compatibility to be removed when 1.7 is deprecated
|
||||||
|
extra = {'current_app': self.admin_site.name} if VERSION < (1, 8) else {}
|
||||||
def history_view(self, request, object_id=None, extra_context=None):
|
return TemplateResponse(request, self.change_list_template, context,
|
||||||
"""Display the change history for constance config values."""
|
**extra)
|
||||||
if not self.has_view_or_change_permission(request):
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
ct = ContentType.objects.get_for_model(self.model)
|
|
||||||
action_list = (
|
|
||||||
LogEntry.objects.filter(
|
|
||||||
content_type=ct,
|
|
||||||
object_id="Config",
|
|
||||||
)
|
|
||||||
.select_related()
|
|
||||||
.order_by("-action_time")
|
|
||||||
)
|
|
||||||
|
|
||||||
paginator = self.get_paginator(request, action_list, 100)
|
|
||||||
page_number = request.GET.get(PAGE_VAR, 1)
|
|
||||||
page_obj = paginator.get_page(page_number)
|
|
||||||
page_range = paginator.get_elided_page_range(page_obj.number)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
**self.admin_site.each_context(request),
|
|
||||||
"title": _("Change history: %s") % self.model._meta.verbose_name_plural.capitalize(),
|
|
||||||
"action_list": page_obj,
|
|
||||||
"page_range": page_range,
|
|
||||||
"page_var": PAGE_VAR,
|
|
||||||
"pagination_required": paginator.count > 100,
|
|
||||||
"opts": self.model._meta,
|
|
||||||
"app_label": "constance",
|
|
||||||
**(extra_context or {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
request.current_app = self.admin_site.name
|
|
||||||
return TemplateResponse(
|
|
||||||
request,
|
|
||||||
"admin/constance/config_history.html",
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _log_config_change(self, request, changed_fields):
|
|
||||||
"""
|
|
||||||
Create a Django admin LogEntry recording which config fields were changed.
|
|
||||||
|
|
||||||
Uses the standard Django JSON change_message format so that
|
|
||||||
LogEntry.get_change_message() can interpret it correctly.
|
|
||||||
"""
|
|
||||||
ct = ContentType.objects.get_for_model(self.model)
|
|
||||||
change_message = json.dumps([{"changed": {"fields": changed_fields}}])
|
|
||||||
LogEntry.objects.create(
|
|
||||||
user_id=request.user.pk,
|
|
||||||
content_type_id=ct.pk,
|
|
||||||
object_id="Config",
|
|
||||||
object_repr="Config",
|
|
||||||
action_flag=CHANGE,
|
|
||||||
change_message=change_message,
|
|
||||||
)
|
|
||||||
|
|
||||||
def has_add_permission(self, *args, **kwargs):
|
def has_add_permission(self, *args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
@ -233,45 +193,26 @@ class ConstanceAdmin(admin.ModelAdmin):
|
||||||
def has_delete_permission(self, *args, **kwargs):
|
def has_delete_permission(self, *args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_view_permission(self, request, obj=None):
|
|
||||||
if settings.SUPERUSER_ONLY:
|
|
||||||
return request.user.is_superuser
|
|
||||||
return super().has_view_permission(request, obj)
|
|
||||||
|
|
||||||
def has_change_permission(self, request, obj=None):
|
def has_change_permission(self, request, obj=None):
|
||||||
if settings.SUPERUSER_ONLY:
|
if settings.SUPERUSER_ONLY:
|
||||||
return request.user.is_superuser
|
return request.user.is_superuser
|
||||||
return super().has_change_permission(request, obj)
|
return super(ConstanceAdmin, self).has_change_permission(request, obj)
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config(object):
|
||||||
class Meta:
|
class Meta(object):
|
||||||
app_label = "constance"
|
app_label = 'constance'
|
||||||
object_name = "Config"
|
object_name = 'Config'
|
||||||
concrete_model = None
|
model_name = module_name = 'config'
|
||||||
model_name = module_name = "config"
|
verbose_name_plural = _('config')
|
||||||
verbose_name_plural = _("config")
|
|
||||||
abstract = False
|
abstract = False
|
||||||
swapped = False
|
swapped = False
|
||||||
is_composite_pk = False
|
|
||||||
|
|
||||||
def get_ordered_objects(self):
|
def get_ordered_objects(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_change_permission(self):
|
def get_change_permission(self):
|
||||||
return f"change_{self.model_name}"
|
return 'change_%s' % 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()
|
_meta = Meta()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,34 @@
|
||||||
|
from django.db.models import signals
|
||||||
|
from django import VERSION
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.core import checks
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from constance.checks import check_fieldsets
|
|
||||||
|
|
||||||
|
|
||||||
class ConstanceConfig(AppConfig):
|
class ConstanceConfig(AppConfig):
|
||||||
name = "constance"
|
name = 'constance'
|
||||||
verbose_name = _("Constance")
|
verbose_name = _('Constance')
|
||||||
default_auto_field = "django.db.models.AutoField"
|
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
checks.register(check_fieldsets, "constance")
|
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')
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,26 @@
|
||||||
"""Defines the base constance backend."""
|
"""
|
||||||
|
Defines the base constance backend
|
||||||
from abc import ABC
|
"""
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
|
|
||||||
class Backend(ABC):
|
class Backend(object):
|
||||||
@abstractmethod
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
"""
|
"""
|
||||||
Get the key from the backend store and return the value.
|
Get the key from the backend store and return the value.
|
||||||
Return None if not found.
|
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):
|
def mget(self, keys):
|
||||||
"""
|
"""
|
||||||
Get the keys from the backend store and return a dict mapping
|
Get the keys from the backend store and return a list of the values.
|
||||||
each found key to its value. Return an empty dict if no keys
|
Return an empty list if not found.
|
||||||
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):
|
def set(self, key, value):
|
||||||
"""Add the value to the backend store given the key."""
|
"""
|
||||||
...
|
Add the value to the backend store given the key.
|
||||||
|
"""
|
||||||
@abstractmethod
|
raise NotImplementedError
|
||||||
async def aset(self, key, value):
|
|
||||||
"""Add the value to the backend store given the key."""
|
|
||||||
...
|
|
||||||
|
|
|
||||||
|
|
@ -1,176 +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 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()
|
|
||||||
90
constance/backends/database/__init__.py
Normal file
90
constance/backends/database/__init__.py
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
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()
|
||||||
27
constance/backends/database/migrations/0001_initial.py
Normal file
27
constance/backends/database/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- 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,),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
constance/backends/database/models.py
Normal file
24
constance/backends/database/models.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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
|
||||||
33
constance/backends/database/south_migrations/0001_initial.py
Normal file
33
constance/backends/database/south_migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- 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']
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- 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']
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
@ -1,62 +1,37 @@
|
||||||
import asyncio
|
|
||||||
from threading import RLock
|
|
||||||
from time import monotonic
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.utils import six
|
||||||
|
from django.utils.six.moves import zip
|
||||||
|
|
||||||
from constance import config
|
from . import Backend
|
||||||
from constance import settings
|
from .. import settings, utils
|
||||||
from constance import signals
|
|
||||||
from constance import utils
|
try:
|
||||||
from constance.backends import Backend
|
from cPickle import loads, dumps
|
||||||
from constance.codecs import dumps
|
except ImportError:
|
||||||
from constance.codecs import loads
|
from pickle import loads, dumps
|
||||||
|
|
||||||
|
|
||||||
class RedisBackend(Backend):
|
class RedisBackend(Backend):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super(RedisBackend, self).__init__()
|
||||||
self._prefix = settings.REDIS_PREFIX
|
self._prefix = settings.REDIS_PREFIX
|
||||||
connection_cls = settings.REDIS_CONNECTION_CLASS
|
connection_cls = settings.REDIS_CONNECTION_CLASS
|
||||||
async_connection_cls = settings.REDIS_ASYNC_CONNECTION_CLASS
|
if connection_cls is not None:
|
||||||
|
|
||||||
if connection_cls:
|
|
||||||
self._rd = utils.import_module_attr(connection_cls)()
|
self._rd = utils.import_module_attr(connection_cls)()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
import redis
|
import redis
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImproperlyConfigured("The Redis backend requires redis-py to be installed.") from None
|
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)
|
||||||
else:
|
else:
|
||||||
if isinstance(settings.REDIS_CONNECTION, str):
|
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
|
||||||
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):
|
def add_prefix(self, key):
|
||||||
return f"{self._prefix}{key}"
|
return "%s%s" % (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):
|
def get(self, key):
|
||||||
value = self._rd.get(self.add_prefix(key))
|
value = self._rd.get(self.add_prefix(key))
|
||||||
|
|
@ -64,156 +39,13 @@ class RedisBackend(Backend):
|
||||||
return loads(value)
|
return loads(value)
|
||||||
return None
|
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):
|
def mget(self, keys):
|
||||||
if not keys:
|
if not keys:
|
||||||
return {}
|
return
|
||||||
prefixed_keys = [self.add_prefix(key) for key in keys]
|
prefixed_keys = [self.add_prefix(key) for key in keys]
|
||||||
return {key: loads(value) for key, value in zip(keys, self._rd.mget(prefixed_keys)) if value}
|
for key, value in zip(keys, self._rd.mget(prefixed_keys)):
|
||||||
|
if value:
|
||||||
async def amget(self, keys):
|
yield key, loads(value)
|
||||||
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):
|
def set(self, key, value):
|
||||||
old_value = self.get(key)
|
|
||||||
self._rd.set(self.add_prefix(key), dumps(value))
|
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
|
|
||||||
|
|
|
||||||
|
|
@ -1,149 +1,32 @@
|
||||||
import asyncio
|
from . import settings, utils
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncValueProxy:
|
class Config(object):
|
||||||
def __init__(self, key, config, default):
|
"""
|
||||||
self._key = key
|
The global config wrapper that handles the backend.
|
||||||
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):
|
def __init__(self):
|
||||||
super().__setattr__("_backend", utils.import_module_attr(settings.BACKEND)())
|
super(Config, self).__setattr__('_backend',
|
||||||
|
utils.import_module_attr(settings.BACKEND)())
|
||||||
|
|
||||||
def _get_sync_value(self, key, default):
|
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)
|
||||||
result = self._backend.get(key)
|
result = self._backend.get(key)
|
||||||
if result is None:
|
if result is None:
|
||||||
result = default
|
result = default
|
||||||
setattr(self, key, default)
|
setattr(self, key, default)
|
||||||
|
return result
|
||||||
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):
|
def __setattr__(self, key, value):
|
||||||
if key == "_backend":
|
|
||||||
super().__setattr__(key, value)
|
|
||||||
return
|
|
||||||
if key not in settings.CONFIG:
|
if key not in settings.CONFIG:
|
||||||
raise AttributeError(key)
|
raise AttributeError(key)
|
||||||
self._backend.set(key, value)
|
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):
|
def __dir__(self):
|
||||||
return settings.CONFIG.keys()
|
return settings.CONFIG.keys()
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
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()
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
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):
|
|
||||||
"""
|
|
||||||
Save changed config values to the backend.
|
|
||||||
|
|
||||||
Returns a list of config field names that were actually modified.
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
changed_fields = []
|
|
||||||
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)
|
|
||||||
changed_fields.append(name)
|
|
||||||
|
|
||||||
return changed_fields
|
|
||||||
|
|
||||||
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.
|
|
@ -1,100 +0,0 @@
|
||||||
# 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 "تم تعديله"
|
|
||||||
Binary file not shown.
|
|
@ -7,52 +7,42 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
||||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/"
|
"Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/django-constance/language/cs_CZ/)\n"
|
||||||
"django-constance/language/cs_CZ/)\n"
|
|
||||||
"Language: cs_CZ\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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"
|
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Nastavení bylo úspěšně uloženo."
|
msgstr "Nastavení bylo úspěšně uloženo."
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:134
|
||||||
|
msgid "Constance config"
|
||||||
|
msgstr "Nastavení konstant"
|
||||||
|
|
||||||
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr "nastavení"
|
msgstr "nastavení"
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -64,33 +54,26 @@ msgstr "konstanta"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "konstanty"
|
msgstr "konstanty"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
msgid "Get/Set In-database config settings handled by Constance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:75
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Uložit"
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:84
|
|
||||||
msgid "Home"
|
|
||||||
msgstr "Domů"
|
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:5
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Název"
|
msgstr "Název"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Výchozí hodnota"
|
msgstr "Výchozí hodnota"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Hodnota"
|
msgstr "Hodnota"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Je změněna?"
|
msgstr "Je změněna?"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Nastavení konstant"
|
msgid "Save"
|
||||||
|
msgstr "Uložit"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Domů"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,61 +8,42 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2014-11-27 18:17+0000\n"
|
"PO-Revision-Date: 2014-11-27 18:17+0000\n"
|
||||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: German (http://www.transifex.com/projects/p/django-constance/"
|
"Language-Team: German (http://www.transifex.com/projects/p/django-constance/language/de/)\n"
|
||||||
"language/de/)\n"
|
|
||||||
"Language: de\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: de\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr "Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. Bitte den Ausgangswert von '%(name)s' ändern."
|
||||||
"Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. "
|
|
||||||
"Bitte den Ausgangswert von '%(name)s' ändern."
|
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr "Die Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite neuladen und die Änderungen erneut vornehmen."
|
||||||
"Die Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite "
|
|
||||||
"neuladen und die Änderungen erneut vornehmen."
|
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Die Livekonfiguration wurde erfolgreich aktualisiert."
|
msgstr "Die Livekonfiguration wurde erfolgreich aktualisiert."
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:134
|
||||||
|
msgid "Constance config"
|
||||||
|
msgstr "Constance Konfiguration"
|
||||||
|
|
||||||
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr "Konfiguration"
|
msgstr "Konfiguration"
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr "Konstanze"
|
msgstr "Konstanze"
|
||||||
|
|
||||||
|
|
@ -74,33 +55,26 @@ msgstr "Konstanze"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "Konstanzes"
|
msgstr "Konstanzes"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
msgid "Get/Set In-database config settings handled by Constance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:75
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Sichern"
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:84
|
|
||||||
msgid "Home"
|
|
||||||
msgstr "Start"
|
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:5
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Voreinstellung"
|
msgstr "Voreinstellung"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Wert"
|
msgstr "Wert"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Ist modifiziert"
|
msgstr "Ist modifiziert"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Constance Konfiguration"
|
msgid "Save"
|
||||||
|
msgstr "Sichern"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Start"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2022-07-19 21:00+0500\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -17,45 +17,32 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:267
|
#: admin.py:134
|
||||||
msgid "Failed to update live settings."
|
msgid "Constance config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -67,30 +54,26 @@ msgstr ""
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
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"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:79
|
||||||
|
msgid "Save"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,30 +8,17 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2015-01-15 16:23-0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Igor Támara <igor@tamarapatino.org>\n"
|
"Last-Translator: Igor Támara <igor@tamarapatino.org>\n"
|
||||||
"Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/"
|
"Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/language/de/\n"
|
||||||
"language/de/\n"
|
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:71
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
|
|
@ -40,7 +27,7 @@ msgstr ""
|
||||||
"Constance no soporta valores de configuración de los tipos %(config_type)s. "
|
"Constance no soporta valores de configuración de los tipos %(config_type)s. "
|
||||||
"Por favor arregle el valor de '%(name)s'."
|
"Por favor arregle el valor de '%(name)s'."
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:90
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
|
|
@ -48,21 +35,19 @@ msgstr ""
|
||||||
"La configuración ha sido modificada por alguien más. Por favor recargue el "
|
"La configuración ha sido modificada por alguien más. Por favor recargue el "
|
||||||
"formulario y reenvíe sus cambios."
|
"formulario y reenvíe sus cambios."
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:128
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Las configuraciones en vivo se actualizaron exitosamente."
|
msgstr "Las configuraciones en vivo se actualizaron exitosamente."
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:133
|
||||||
|
msgid "Constance config"
|
||||||
|
msgstr "Configuración de Constance"
|
||||||
|
|
||||||
|
#: admin.py:176
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr "configuración"
|
msgstr "configuración"
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:7
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr "Constance"
|
msgstr "Constance"
|
||||||
|
|
||||||
|
|
@ -74,33 +59,26 @@ msgstr "constance"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "constances"
|
msgstr "constances"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:58
|
||||||
msgid "Get/Set In-database config settings handled by Constance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:75
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Guardar"
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:84
|
|
||||||
msgid "Home"
|
|
||||||
msgstr "Inicio"
|
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:5
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:59
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Predeterminado"
|
msgstr "Predeterminado"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:60
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Valor"
|
msgstr "Valor"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:61
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Está modificado"
|
msgstr "Está modificado"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:87
|
||||||
#~ msgstr "Configuración de Constance"
|
msgid "Save"
|
||||||
|
msgstr "Guardar"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:96
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Inicio"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,99 +0,0 @@
|
||||||
# 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.
|
|
@ -1,104 +0,0 @@
|
||||||
# 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.
|
|
@ -1,95 +0,0 @@
|
||||||
# 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é"
|
|
||||||
Binary file not shown.
|
|
@ -7,63 +7,44 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2018-03-13 15:26+0100\n"
|
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
||||||
"Last-Translator: Paolo Melchiorre <paolo@melchiorre.org>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/"
|
"Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/language/it/)\n"
|
||||||
"language/it/)\n"
|
|
||||||
"Language: it\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: it\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Generator: Poedit 2.0.4\n"
|
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Constance non supporta valori di impostazioni di tipo %(config_type)s. "
|
|
||||||
"Modifica il valore di '%(name)s'."
|
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Le impostazioni sono state modificate da qualcuno. Ricarica la pagina e "
|
|
||||||
"invia nuovamente le tue modifiche."
|
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
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."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Le impostazioni attive sono state aggiornate correttamente."
|
msgstr "Le impostazioni attive sono state aggiornate correttamente."
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:134
|
||||||
msgid "config"
|
msgid "Constance config"
|
||||||
msgstr "configurazioni"
|
msgstr "Configurazione Impostazioni"
|
||||||
|
|
||||||
#: apps.py:8
|
#: admin.py:177
|
||||||
|
msgid "config"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr "Impostazioni"
|
msgstr ""
|
||||||
|
|
||||||
#: backends/database/models.py:19
|
#: backends/database/models.py:19
|
||||||
msgid "constance"
|
msgid "constance"
|
||||||
|
|
@ -73,33 +54,26 @@ msgstr "impostazione"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "impostazioni"
|
msgstr "impostazioni"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
msgid "Get/Set In-database config settings handled by Constance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:75
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Salva"
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:84
|
|
||||||
msgid "Home"
|
|
||||||
msgstr "Inizio"
|
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:5
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nome"
|
msgstr "Nome"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Default"
|
msgstr "Default"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Valore"
|
msgstr "Valore"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Modificato"
|
msgstr "Modificato"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Configurazione Impostazioni"
|
msgid "Save"
|
||||||
|
msgstr "Salva"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Inizio"
|
||||||
|
|
|
||||||
|
|
@ -7,53 +7,42 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
||||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/"
|
"Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/language/pl/)\n"
|
||||||
"language/pl/)\n"
|
|
||||||
"Language: pl\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
"Language: pl\n"
|
||||||
"|| 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:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Parametry zostały zaktualizowane"
|
msgstr "Parametry zostały zaktualizowane"
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:134
|
||||||
|
msgid "Constance config"
|
||||||
|
msgstr "Konfiguracja Constance"
|
||||||
|
|
||||||
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -65,33 +54,26 @@ msgstr "parametr"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "parametry"
|
msgstr "parametry"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
msgid "Get/Set In-database config settings handled by Constance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:75
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Zapisz"
|
|
||||||
|
|
||||||
#: templates/admin/constance/change_list.html:84
|
|
||||||
msgid "Home"
|
|
||||||
msgstr "Początek"
|
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:5
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nazwa"
|
msgstr "Nazwa"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Domyślnie"
|
msgstr "Domyślnie"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Wartość"
|
msgstr "Wartość"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Zmodyfikowana"
|
msgstr "Zmodyfikowana"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Konfiguracja Constance"
|
msgid "Save"
|
||||||
|
msgstr "Zapisz"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Początek"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -7,57 +7,42 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2022-07-19 20:59+0500\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
"PO-Revision-Date: 2014-11-27 18:13+0000\n"
|
||||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/"
|
"Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/language/ru/)\n"
|
||||||
"language/ru/)\n"
|
|
||||||
"Language: ru\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
"Language: ru\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:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "Настройки успешно сохранены."
|
msgstr "Настройки успешно сохранены"
|
||||||
|
|
||||||
#: admin.py:267
|
#: admin.py:134
|
||||||
msgid "Failed to update live settings."
|
msgid "Constance config"
|
||||||
msgstr "Не удалось сохранить настройки."
|
msgstr "Настройки"
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr "настройки"
|
msgstr "настройки"
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -69,33 +54,26 @@ msgstr "настройки"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "настройки"
|
msgstr "настройки"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
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"
|
msgid "Name"
|
||||||
msgstr "Название"
|
msgstr "Название"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "По умолчанию"
|
msgstr "По умолчанию"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Текущее значение"
|
msgstr "Текущее значение"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "Было изменено"
|
msgstr "Было изменено"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Настройки"
|
msgid "Save"
|
||||||
|
msgstr "Сохранить"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Главная"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,104 +0,0 @@
|
||||||
# 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.
|
|
@ -1,121 +0,0 @@
|
||||||
# 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 "Настройки"
|
|
||||||
Binary file not shown.
|
|
@ -8,55 +8,42 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-constance\n"
|
"Project-Id-Version: django-constance\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-06-13 19:40+0530\n"
|
"POT-Creation-Date: 2014-11-27 19:05+0100\n"
|
||||||
"PO-Revision-Date: 2015-03-15 18:40+0000\n"
|
"PO-Revision-Date: 2015-03-15 18:40+0000\n"
|
||||||
"Last-Translator: Yifu Yu <root@jackyyf.com>\n"
|
"Last-Translator: Yifu Yu <root@jackyyf.com>\n"
|
||||||
"Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-"
|
"Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-constance/language/zh_CN/)\n"
|
||||||
"constance/language/zh_CN/)\n"
|
|
||||||
"Language: zh_CN\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: zh_CN\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:72
|
||||||
#, 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
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Constance doesn't support config values of the type %(config_type)s. Please "
|
"Constance doesn't support config values of the type %(config_type)s. Please "
|
||||||
"fix the value of '%(name)s'."
|
"fix the value of '%(name)s'."
|
||||||
msgstr "Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。"
|
msgstr "Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。"
|
||||||
|
|
||||||
#: admin.py:147
|
#: admin.py:91
|
||||||
msgid ""
|
msgid ""
|
||||||
"The settings have been modified by someone else. Please reload the form and "
|
"The settings have been modified by someone else. Please reload the form and "
|
||||||
"resubmit your changes."
|
"resubmit your changes."
|
||||||
msgstr "设置已经被他人修改过,请刷新页面并重新提交您的更改。"
|
msgstr "设置已经被他人修改过,请刷新页面并重新提交您的更改。"
|
||||||
|
|
||||||
#: admin.py:160
|
#: admin.py:129
|
||||||
msgid ""
|
|
||||||
"CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in "
|
|
||||||
"CONSTANCE_CONFIG."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:224
|
|
||||||
msgid "Live settings updated successfully."
|
msgid "Live settings updated successfully."
|
||||||
msgstr "成功更新实时配置"
|
msgstr "成功更新实时配置"
|
||||||
|
|
||||||
#: admin.py:285
|
#: admin.py:134
|
||||||
|
msgid "Constance config"
|
||||||
|
msgstr "Constance 配置页面"
|
||||||
|
|
||||||
|
#: admin.py:177
|
||||||
msgid "config"
|
msgid "config"
|
||||||
msgstr "配置"
|
msgstr "配置"
|
||||||
|
|
||||||
#: apps.py:8
|
#: apps.py:9
|
||||||
msgid "Constance"
|
msgid "Constance"
|
||||||
msgstr "Constance模块"
|
msgstr "Constance模块"
|
||||||
|
|
||||||
|
|
@ -68,33 +55,26 @@ msgstr "Constance模块"
|
||||||
msgid "constances"
|
msgid "constances"
|
||||||
msgstr "Constance模块"
|
msgstr "Constance模块"
|
||||||
|
|
||||||
#: management/commands/constance.py:30
|
#: templates/admin/constance/change_list.html:50
|
||||||
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"
|
msgid "Name"
|
||||||
msgstr "名称"
|
msgstr "名称"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:6
|
#: templates/admin/constance/change_list.html:51
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "默认值"
|
msgstr "默认值"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:7
|
#: templates/admin/constance/change_list.html:52
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "值"
|
msgstr "值"
|
||||||
|
|
||||||
#: templates/admin/constance/includes/results_list.html:8
|
#: templates/admin/constance/change_list.html:53
|
||||||
msgid "Is modified"
|
msgid "Is modified"
|
||||||
msgstr "是否修改过"
|
msgstr "是否修改过"
|
||||||
|
|
||||||
#~ msgid "Constance config"
|
#: templates/admin/constance/change_list.html:79
|
||||||
#~ msgstr "Constance 配置页面"
|
msgid "Save"
|
||||||
|
msgstr "保存"
|
||||||
|
|
||||||
|
#: templates/admin/constance/change_list.html:89
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "首页"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,97 +0,0 @@
|
||||||
# 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 "重置至默认值"
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
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")
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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")],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
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),
|
|
||||||
]
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
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),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
from django.conf import settings
|
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', {})
|
||||||
|
|
||||||
CONFIG_FIELDSETS = getattr(settings, "CONSTANCE_CONFIG_FIELDSETS", {})
|
ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {})
|
||||||
|
|
||||||
ADDITIONAL_FIELDS = getattr(settings, "CONSTANCE_ADDITIONAL_FIELDS", {})
|
DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND',
|
||||||
|
None)
|
||||||
|
|
||||||
FILE_ROOT = getattr(settings, "CONSTANCE_FILE_ROOT", "")
|
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings,
|
||||||
|
'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT',
|
||||||
|
60 * 60 * 24)
|
||||||
|
|
||||||
DATABASE_CACHE_BACKEND = getattr(settings, "CONSTANCE_DATABASE_CACHE_BACKEND", None)
|
DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
|
||||||
|
|
||||||
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings, "CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT", 60 * 60 * 24)
|
REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
|
||||||
|
|
||||||
DATABASE_PREFIX = getattr(settings, "CONSTANCE_DATABASE_PREFIX", "")
|
REDIS_CONNECTION_CLASS = getattr(settings, 'CONSTANCE_REDIS_CONNECTION_CLASS',
|
||||||
|
None)
|
||||||
|
|
||||||
REDIS_PREFIX = getattr(settings, "CONSTANCE_REDIS_PREFIX", "constance:")
|
REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', {})
|
||||||
|
|
||||||
REDIS_CACHE_TIMEOUT = getattr(settings, "CONSTANCE_REDIS_CACHE_TIMEOUT", 60)
|
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)
|
||||||
|
|
||||||
REDIS_CONNECTION_CLASS = getattr(settings, "CONSTANCE_REDIS_CONNECTION_CLASS", None)
|
IGNORE_ADMIN_VERSION_CHECK = getattr(settings,
|
||||||
|
'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK',
|
||||||
REDIS_ASYNC_CONNECTION_CLASS = getattr(settings, "CONSTANCE_REDIS_ASYNC_CONNECTION_CLASS", None)
|
False)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import django.dispatch
|
|
||||||
|
|
||||||
config_updated = django.dispatch.Signal()
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
(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);
|
|
||||||
|
|
@ -1,12 +1,29 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load admin_list static i18n %}
|
{% load admin_static admin_list i18n %}
|
||||||
|
|
||||||
|
|
||||||
{% block extrastyle %}
|
{% block extrastyle %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.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' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}" />
|
||||||
{{ media.css }}
|
{{ media.css }}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/constance.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>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
|
|
@ -14,28 +31,14 @@
|
||||||
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
|
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{{ media.js }}
|
{{ 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bodyclass %}{{ block.super }} change-list{% endblock %}
|
{% block bodyclass %}change-list{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<ul class="object-tools">
|
|
||||||
<li><a href="{% url 'admin:constance_config_history' %}" class="historylink">{% translate 'History' %}</a></li>
|
|
||||||
</ul>
|
|
||||||
<div id="content-main" class="constance">
|
<div id="content-main" class="constance">
|
||||||
<div class="module" id="changelist">
|
<div class="module" id="changelist">
|
||||||
<form id="changelist-form" action="" method="post" enctype="multipart/form-data">{% csrf_token %}
|
<form id="changelist-form" action="" method="post">{% 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 %}
|
{% if form.errors %}
|
||||||
<ul class="errorlist">
|
<ul class="errorlist">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -48,25 +51,39 @@
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<table cellspacing="0" id="result_list">
|
||||||
{% if fieldsets %}
|
<thead>
|
||||||
{% for fieldset in fieldsets %}
|
<tr>
|
||||||
<fieldset class="module{% if fieldset.collapse %} collapse{% endif %}">
|
<th><div class="text">{% trans "Name" %}</div></th>
|
||||||
{% if django_version >= "5.1" and fieldset.collapse %}<details><summary>{% endif %}
|
<th><div class="text">{% trans "Default" %}</div></th>
|
||||||
<h2 class="fieldset-heading">{{ fieldset.title }}</h2>
|
<th><div class="text">{% trans "Value" %}</div></th>
|
||||||
{% if django_version >= "5.1" and fieldset.collapse %}</summary>{% endif %}
|
<th><div class="text">{% trans "Is modified" %}</div></th>
|
||||||
{% with config_values=fieldset.config_values %}
|
</tr>
|
||||||
{% include "admin/constance/includes/results_list.html" %}
|
</thead>
|
||||||
{% endwith %}
|
{% for item in config_values %}
|
||||||
{% if django_version >= "5.1" and fieldset.collapse %}</details>{% endif %}
|
<tr class="{% cycle 'row1' 'row2' %}">
|
||||||
</fieldset>
|
<th>{{ item.name }}
|
||||||
{% endfor %}
|
<div class="help">{{ item.help_text|linebreaksbr }}</div>
|
||||||
{% else %}
|
</th>
|
||||||
{% include "admin/constance/includes/results_list.html" %}
|
<td>
|
||||||
{% endif %}
|
{{ item.default }}
|
||||||
|
</td>
|
||||||
<p class="paginator sticky-footer">
|
<td>
|
||||||
<input type="submit" name="_save" class="default" value="{% trans 'Save' %}">
|
{{ 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' %}"/>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -75,8 +92,8 @@
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst|escape }}</a>
|
||||||
› {{ opts.verbose_name_plural|capfirst }}
|
› {{ opts.verbose_name_plural|capfirst }}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
|
|
||||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
|
||||||
› <a href="{% url 'admin:constance_config_changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
|
||||||
› {% translate 'History' %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="content-main">
|
|
||||||
<div id="change-history" class="module">
|
|
||||||
|
|
||||||
{% if action_list %}
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">{% translate 'Date/time' %}</th>
|
|
||||||
<th scope="col">{% translate 'User' %}</th>
|
|
||||||
<th scope="col">{% translate 'Action' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for action in action_list %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{{ action.action_time|date:"DATETIME_FORMAT" }}</th>
|
|
||||||
<td>{{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
|
|
||||||
<td>{{ action.get_change_message }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p class="paginator">
|
|
||||||
{% if pagination_required %}
|
|
||||||
{% for i in page_range %}
|
|
||||||
{% if i == action_list.paginator.ELLIPSIS %}
|
|
||||||
{{ action_list.paginator.ELLIPSIS }}
|
|
||||||
{% elif i == action_list.number %}
|
|
||||||
<span class="this-page">{{ i }}</span>
|
|
||||||
{% else %}
|
|
||||||
<a href="?{{ page_var }}={{ i }}" {% if i == action_list.paginator.num_pages %} class="end" {% endif %}>{{ i }}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{{ action_list.paginator.count }} {% blocktranslate count counter=action_list.paginator.count %}entry{% plural %}entries{% endblocktranslate %}
|
|
||||||
</p>
|
|
||||||
{% else %}
|
|
||||||
<p>{% translate "This object doesn't have a change history." %}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
{% 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>
|
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
from .unittest import override_config # pragma: no cover
|
from .utils import override_config
|
||||||
|
|
||||||
__all__ = ["override_config"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from django import VERSION as DJANGO_VERSION
|
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
from constance import config
|
from .. import config
|
||||||
|
|
||||||
__all__ = ("override_config",)
|
__all__ = ('override_config',)
|
||||||
|
|
||||||
|
|
||||||
class override_config(override_settings):
|
class override_config(override_settings):
|
||||||
|
|
@ -15,23 +14,25 @@ class override_config(override_settings):
|
||||||
|
|
||||||
Based on django.test.utils.override_settings.
|
Based on django.test.utils.override_settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super(override_config, self).__init__(**kwargs)
|
||||||
self.original_values = {}
|
self.original_values = {}
|
||||||
|
|
||||||
def __call__(self, test_func):
|
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 isinstance(test_func, type):
|
||||||
if not issubclass(test_func, SimpleTestCase):
|
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)
|
return self.modify_test_case(test_func)
|
||||||
|
else:
|
||||||
@wraps(test_func)
|
@wraps(test_func)
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
||||||
with self:
|
with self:
|
||||||
return test_func(*args, **kwargs)
|
return test_func(*args, **kwargs)
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
def modify_test_case(self, test_case):
|
def modify_test_case(self, test_case):
|
||||||
|
|
@ -45,20 +46,9 @@ class override_config(override_settings):
|
||||||
original_pre_setup = test_case._pre_setup
|
original_pre_setup = test_case._pre_setup
|
||||||
original_post_teardown = test_case._post_teardown
|
original_post_teardown = test_case._post_teardown
|
||||||
|
|
||||||
if DJANGO_VERSION < (5, 2):
|
def _pre_setup(inner_self):
|
||||||
|
self.enable()
|
||||||
def _pre_setup(inner_self):
|
original_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):
|
def _post_teardown(inner_self):
|
||||||
original_post_teardown(inner_self)
|
original_post_teardown(inner_self)
|
||||||
|
|
@ -70,20 +60,26 @@ class override_config(override_settings):
|
||||||
return test_case
|
return test_case
|
||||||
|
|
||||||
def enable(self):
|
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
|
# Store the original values to an instance variable
|
||||||
for config_key in self.options:
|
for config_key in self.options.keys():
|
||||||
self.original_values[config_key] = getattr(config, config_key)
|
self.original_values[config_key] = getattr(config, config_key)
|
||||||
|
|
||||||
# Update config with the overridden values
|
# Update config with the overriden values
|
||||||
self.unpack_values(self.options)
|
self.unpack_values(self.options)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
"""Set original values to the config."""
|
"""
|
||||||
|
Set original values to the config.
|
||||||
|
"""
|
||||||
self.unpack_values(self.original_values)
|
self.unpack_values(self.original_values)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unpack_values(options):
|
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():
|
for name, value in options.items():
|
||||||
setattr(config, name, value)
|
setattr(config, name, value)
|
||||||
|
|
@ -1,76 +1,6 @@
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from . import LazyConfig
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
config = LazyConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def import_module_attr(path):
|
def import_module_attr(path):
|
||||||
package, module = path.rsplit(".", 1)
|
package, module = path.rsplit('.', 1)
|
||||||
return getattr(import_module(package), module)
|
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)
|
|
||||||
|
|
|
||||||
183
docs/Makefile
183
docs/Makefile
|
|
@ -1,20 +1,177 @@
|
||||||
# Minimal makefile for Sphinx documentation
|
# Makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line, and also
|
# You can set these variables from the command line.
|
||||||
# from the environment for the first two.
|
SPHINXOPTS =
|
||||||
SPHINXOPTS ?=
|
SPHINXBUILD = sphinx-build
|
||||||
SPHINXBUILD ?= sphinx-build
|
PAPER =
|
||||||
SOURCEDIR = .
|
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
# 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
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@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)"
|
||||||
|
|
||||||
.PHONY: help Makefile
|
clean:
|
||||||
|
rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
html:
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
%: Makefile
|
@echo
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@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."
|
||||||
|
|
|
||||||
BIN
docs/_static/screenshot3.png
vendored
BIN
docs/_static/screenshot3.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
|
|
@ -10,9 +10,6 @@ configuration values. By default it uses the Redis backend. To override
|
||||||
the default please set the :setting:`CONSTANCE_BACKEND` setting to the appropriate
|
the default please set the :setting:`CONSTANCE_BACKEND` setting to the appropriate
|
||||||
dotted path.
|
dotted path.
|
||||||
|
|
||||||
Configuration values are stored in JSON format and automatically serialized/deserialized
|
|
||||||
on access.
|
|
||||||
|
|
||||||
Redis
|
Redis
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
@ -26,15 +23,7 @@ to add it to your project settings::
|
||||||
|
|
||||||
CONSTANCE_BACKEND = 'constance.backends.redisd.RedisBackend'
|
CONSTANCE_BACKEND = 'constance.backends.redisd.RedisBackend'
|
||||||
|
|
||||||
Default redis backend retrieves values every time. There is another redis backend with local cache.
|
.. _`redis-py`: https://pypi.python.org/pypi/redis
|
||||||
`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
|
Settings
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
@ -63,11 +52,11 @@ An (optional) dotted import path to a connection to use, e.g.::
|
||||||
|
|
||||||
CONSTANCE_REDIS_CONNECTION_CLASS = 'myproject.myapp.mockup.Connection'
|
CONSTANCE_REDIS_CONNECTION_CLASS = 'myproject.myapp.mockup.Connection'
|
||||||
|
|
||||||
If you are using `django-redis <https://github.com/jazzband/django-redis>`_,
|
If you are using `django-redis <http://niwibe.github.io/django-redis/>`_,
|
||||||
feel free to use the ``CONSTANCE_REDIS_CONNECTION_CLASS`` setting to define
|
feel free to use the ``CONSTANCE_REDIS_CONNECTION_CLASS`` setting to define
|
||||||
a callable that returns a redis connection, e.g.::
|
a callable that returns a redis connection, e.g.::
|
||||||
|
|
||||||
CONSTANCE_REDIS_CONNECTION_CLASS = 'django_redis.get_redis_connection'
|
CONSTANCE_REDIS_CONNECTION_CLASS = 'redis_cache.get_redis_connection'
|
||||||
|
|
||||||
``CONSTANCE_REDIS_PREFIX``
|
``CONSTANCE_REDIS_PREFIX``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -77,25 +66,30 @@ database. Defaults to ``'constance:'``. E.g.::
|
||||||
|
|
||||||
CONSTANCE_REDIS_PREFIX = 'constance:myproject:'
|
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
|
Database
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Database backend stores configuration values in a standard Django model.
|
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]
|
||||||
|
|
||||||
You must set the ``CONSTANCE_BACKEND`` Django setting to::
|
You must set the ``CONSTANCE_BACKEND`` Django setting to::
|
||||||
|
|
||||||
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
|
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::
|
Please make sure to apply the database migrations::
|
||||||
|
|
||||||
python manage.py migrate
|
python manage.py migrate database
|
||||||
|
|
||||||
.. note:: If you're upgrading Constance to 1.0 and use Django 1.7 or higher
|
.. 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
|
please make sure to let the migration system know that you've
|
||||||
|
|
@ -105,7 +99,6 @@ Please make sure to apply the database migrations::
|
||||||
|
|
||||||
python manage.py migrate database --fake
|
python manage.py migrate database --fake
|
||||||
|
|
||||||
|
|
||||||
Just like the Redis backend you can set an optional prefix that is used during
|
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
|
database interactions (it defaults to an empty string, ``''``). To use
|
||||||
something else do this::
|
something else do this::
|
||||||
|
|
@ -134,32 +127,9 @@ configured cache backend to enable this feature, e.g. "default"::
|
||||||
cache backend included in Django because correct cache
|
cache backend included in Django because correct cache
|
||||||
invalidation can't be guaranteed.
|
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
|
.. 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
|
saving any of the config values. If you want to disable the cache
|
||||||
simply set the :setting:`CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT`
|
simply set the :setting:`CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT`
|
||||||
setting to ``None``.
|
setting to ``None``.
|
||||||
|
|
||||||
Memory
|
.. _django-picklefield: http://pypi.python.org/pypi/django-picklefield/
|
||||||
------
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
||||||
308
docs/changes.rst
308
docs/changes.rst
|
|
@ -1,311 +1,5 @@
|
||||||
Changelog
|
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)
|
v1.2 (2016/05/14)
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -366,7 +60,7 @@ v1.0 (2014/12/04)
|
||||||
|
|
||||||
* Added docs and set up Read The Docs project:
|
* Added docs and set up Read The Docs project:
|
||||||
|
|
||||||
https://django-constance.readthedocs.io/
|
http://django-constance.readthedocs.org/
|
||||||
|
|
||||||
* Set up Transifex project for easier translations:
|
* Set up Transifex project for easier translations:
|
||||||
|
|
||||||
|
|
|
||||||
314
docs/conf.py
314
docs/conf.py
|
|
@ -1,108 +1,268 @@
|
||||||
# Configuration file for the Sphinx documentation builder.
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# For the full list of built-in configuration values, see the documentation:
|
# django-constance documentation build configuration file, created by
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
# sphinx-quickstart on Tue Nov 25 19:38:51 2014.
|
||||||
|
#
|
||||||
import os
|
# 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 sys
|
||||||
from datetime import datetime
|
import os
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
|
||||||
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,
|
# 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
|
# 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.
|
# 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('extensions'))
|
||||||
sys.path.insert(0, os.path.abspath(".."))
|
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 ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
||||||
|
|
||||||
extensions = [
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
"sphinx.ext.intersphinx",
|
#needs_sphinx = '1.0'
|
||||||
"sphinx.ext.todo",
|
|
||||||
"sphinx_search.extension",
|
|
||||||
"settings",
|
|
||||||
]
|
|
||||||
|
|
||||||
templates_path = ["_templates"]
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
source_suffix = ".rst"
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
root_doc = "index"
|
# ones.
|
||||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
extensions = ['sphinx.ext.intersphinx', 'settings']
|
||||||
pygments_style = "sphinx"
|
|
||||||
html_last_updated_fmt = ""
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
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'
|
||||||
|
|
||||||
html_theme = "sphinx_rtd_theme"
|
|
||||||
html_static_path = ["_static"]
|
|
||||||
htmlhelp_basename = "django-constancedoc"
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output
|
|
||||||
|
|
||||||
latex_elements = {}
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
("index", "django-constance.tex", "django-constance Documentation", "Jazzband", "manual"),
|
('index', 'django-constance.tex', u'django-constance Documentation',
|
||||||
|
u'Comoga and individual contributors', '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 ---------------------------------------
|
# -- Options for manual page output ---------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output
|
|
||||||
|
|
||||||
man_pages = [("index", "django-constance", "django-constance Documentation", ["Jazzband"], 1)]
|
# 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
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
# -- 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 = [
|
texinfo_documents = [
|
||||||
(
|
('index', 'django-constance', u'django-constance Documentation',
|
||||||
"index",
|
u'Comoga and individual contributors', 'django-constance', 'One line description of project.',
|
||||||
"django-constance",
|
'Miscellaneous'),
|
||||||
"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.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"python": ("https://docs.python.org/3", None),
|
'http://docs.python.org/': None,
|
||||||
"django": ("https://docs.djangoproject.com/en/dev/", "https://docs.djangoproject.com/en/dev/_objects/"),
|
'django': ('http://docs.djangoproject.com/en/dev/',
|
||||||
|
'http://docs.djangoproject.com/en/dev/_objects/'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
388
docs/index.rst
388
docs/index.rst
|
|
@ -5,19 +5,27 @@ Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* Easily migrate your static settings to dynamic settings.
|
* Easily migrate your static settings to dynamic settings.
|
||||||
* Edit the dynamic settings in the Django admin interface.
|
* Admin interface to edit the dynamic settings.
|
||||||
|
|
||||||
.. image:: _static/screenshot2.png
|
.. image:: screenshot2.png
|
||||||
|
|
||||||
Quick Installation
|
Installation
|
||||||
------------------
|
------------
|
||||||
|
|
||||||
.. code-block::
|
Install from PyPI the backend specific variant of django-constance:
|
||||||
|
|
||||||
|
For the (default) Redis backend::
|
||||||
|
|
||||||
pip install "django-constance[redis]"
|
pip install "django-constance[redis]"
|
||||||
|
|
||||||
For complete installation instructions, including how to install the
|
For the database backend::
|
||||||
database backend, see :ref:`Backends <backends>`.
|
|
||||||
|
pip install "django-constance[database]"
|
||||||
|
|
||||||
|
Alternatively -- if you're sure that the dependencies are already
|
||||||
|
installed -- you can also run::
|
||||||
|
|
||||||
|
pip install django-constance
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
@ -44,12 +52,6 @@ the :setting:`CONSTANCE_CONFIG` section, like this:
|
||||||
'The Universe, and Everything'),
|
'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
|
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
|
not found in the backend. The other member of the tuple is a help text the
|
||||||
admin will show.
|
admin will show.
|
||||||
|
|
@ -60,54 +62,31 @@ finish the configuration.
|
||||||
``django-constance``'s hashes generated in different instances of the same
|
``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 :setting:`CONSTANCE_IGNORE_ADMIN_VERSION_CHECK` in order to skip hash
|
Use this option in order to skip hash verification.
|
||||||
verification.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
CONSTANCE_IGNORE_ADMIN_VERSION_CHECK = True
|
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
|
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 value can be one of the supported types or a string matching a key in your :setting:`CONSTANCE_ADDITIONAL_FIELDS`
|
||||||
|
|
||||||
The supported types are:
|
The supported types are:
|
||||||
|
|
||||||
* ``bool``
|
* `bool`
|
||||||
* ``int``
|
* `int`
|
||||||
* ``float``
|
* `float`
|
||||||
* ``Decimal``
|
* `Decimal`
|
||||||
* ``str``
|
* `long` (on python 2)
|
||||||
* ``datetime``
|
* `str`
|
||||||
* ``timedelta``
|
* `unicode` (on python 2)
|
||||||
* ``date``
|
* `datetime`
|
||||||
* ``time``
|
* `date`
|
||||||
* ``list``
|
* `time`
|
||||||
* ``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:
|
For example, to force a value to be handled as a string:
|
||||||
|
|
||||||
|
|
@ -121,17 +100,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
|
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.
|
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 `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 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
|
.. code-block:: python
|
||||||
|
|
||||||
CONSTANCE_ADDITIONAL_FIELDS = {
|
CONSTANCE_ADDITIONAL_FIELDS = {
|
||||||
'yes_no_null_select': ['django.forms.fields.ChoiceField', {
|
'yes_no_null_select': ['django.forms.fields.ChoiceField', {
|
||||||
'widget': 'django.forms.Select',
|
'widget': 'django.forms.Select',
|
||||||
'choices': ((None, "-----"), ("yes", "Yes"), ("no", "No"))
|
'choices': (("-----", None), ("yes", "Yes"), ("no", "No"))
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,141 +118,6 @@ the other values will be passed as kwargs to the field's ``__init__()``
|
||||||
'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'),
|
'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
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
@ -292,40 +136,10 @@ object and accessing the variables with attribute lookups::
|
||||||
if config.THE_ANSWER == 42:
|
if config.THE_ANSWER == 42:
|
||||||
answer_the_question()
|
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
|
Django templates
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
To access the config object from your template you can
|
To access the config object from your template you can either
|
||||||
pass the object to the template context:
|
pass the object to the template context:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
@ -336,13 +150,14 @@ pass the object to the template context:
|
||||||
def myview(request):
|
def myview(request):
|
||||||
return render(request, 'my_template.html', {'config': config})
|
return render(request, 'my_template.html', {'config': config})
|
||||||
|
|
||||||
You can also use the included context processor.
|
Or you can use the included config context processor.:
|
||||||
|
|
||||||
Insert ``'constance.context_processors.config'`` at
|
.. code-block:: python
|
||||||
the top of your ``TEMPLATES['OPTIONS']['context_processors']`` list. See the
|
|
||||||
`Django documentation`_ for details.
|
|
||||||
|
|
||||||
.. _`Django documentation`: https://docs.djangoproject.com/en/1.11/ref/templates/upgrading/#the-templates-settings
|
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||||
|
# ...
|
||||||
|
'constance.context_processors.config',
|
||||||
|
)
|
||||||
|
|
||||||
This will add the config instance to the context of any template
|
This will add the config instance to the context of any template
|
||||||
rendered with a ``RequestContext``.
|
rendered with a ``RequestContext``.
|
||||||
|
|
@ -357,112 +172,22 @@ any other variable, e.g.:
|
||||||
Woohoo! Head over <a href="/sekrit/">here</a> to use the beta.
|
Woohoo! Head over <a href="/sekrit/">here</a> to use the beta.
|
||||||
{% else %}
|
{% else %}
|
||||||
Sadly we haven't launched yet, click <a href="/newsletter/">here</a>
|
Sadly we haven't launched yet, click <a href="/newsletter/">here</a>
|
||||||
to signup for our newsletter.
|
to signup for our newletter.
|
||||||
{% endif %}
|
{% 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
|
Editing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Fire up your ``admin`` and you should see a new app called ``Constance``
|
Fire up your ``admin`` and you should see a new app called ``Constance``
|
||||||
with ``THE_ANSWER`` in the ``Config`` pseudo model.
|
with ``THE_ANSWER`` in the ``Config`` pseudo model.
|
||||||
|
|
||||||
By default, changing the settings via the admin is only allowed for superusers.
|
By default changing the settings via the admin is only allowed for super users.
|
||||||
To change this, feel free to set the :setting:`CONSTANCE_SUPERUSER_ONLY`
|
But in case you want to use the admin's ability to implement custom
|
||||||
setting to ``False`` and give users or user groups access to the
|
authorization checks, feel free to set the :setting:`CONSTANCE_SUPERUSER_ONLY`
|
||||||
|
setting to ``False`` and give the users or user groups access to the
|
||||||
``constance.change_config`` permission.
|
``constance.change_config`` permission.
|
||||||
|
|
||||||
.. figure:: _static/screenshot1.png
|
.. figure:: screenshot1.png
|
||||||
|
|
||||||
The virtual application ``Constance`` among your regular applications.
|
The virtual application ``Constance`` among your regular applications.
|
||||||
|
|
||||||
|
|
@ -478,38 +203,19 @@ settings the way you like.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from constance.admin import ConstanceAdmin, Config
|
from constance.admin import ConstanceAdmin, ConstanceForm, Config
|
||||||
from constance.forms import ConstanceForm
|
|
||||||
class CustomConfigForm(ConstanceForm):
|
class CustomConfigForm(ConstanceForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super(CustomConfigForm, self).__init__(*args, **kwargs)
|
||||||
#... do stuff to make your settings form nice ...
|
#... do stuff to make your settings form nice ...
|
||||||
|
|
||||||
class ConfigAdmin(ConstanceAdmin):
|
class ConfigAdmin(ConstanceAdmin):
|
||||||
change_list_form = CustomConfigForm
|
form = CustomConfigForm
|
||||||
change_list_template = 'admin/config/settings.html'
|
change_list_template = 'admin/config/settings.html'
|
||||||
|
|
||||||
admin.site.unregister([Config])
|
admin.site.unregister([Config])
|
||||||
admin.site.register([Config], ConfigAdmin)
|
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
|
More documentation
|
||||||
------------------
|
------------------
|
||||||
|
|
|
||||||
229
docs/make.bat
229
docs/make.bat
|
|
@ -1,16 +1,53 @@
|
||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
|
|
||||||
pushd %~dp0
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
if "%SPHINXBUILD%" == "" (
|
||||||
set SPHINXBUILD=sphinx-build
|
set SPHINXBUILD=sphinx-build
|
||||||
)
|
)
|
||||||
set SOURCEDIR=.
|
|
||||||
set BUILDDIR=_build
|
set BUILDDIR=_build
|
||||||
|
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||||
|
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||||
|
if NOT "%PAPER%" == "" (
|
||||||
|
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||||
|
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||||
|
)
|
||||||
|
|
||||||
%SPHINXBUILD% >NUL 2>NUL
|
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
|
||||||
if errorlevel 9009 (
|
if errorlevel 9009 (
|
||||||
echo.
|
echo.
|
||||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
|
@ -19,17 +56,187 @@ if errorlevel 9009 (
|
||||||
echo.may add the Sphinx directory to PATH.
|
echo.may add the Sphinx directory to PATH.
|
||||||
echo.
|
echo.
|
||||||
echo.If you don't have Sphinx installed, grab it from
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
echo.https://www.sphinx-doc.org/
|
echo.http://sphinx-doc.org/
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if "%1" == "" goto help
|
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
|
||||||
|
)
|
||||||
|
|
||||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
if "%1" == "dirhtml" (
|
||||||
goto end
|
%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
|
||||||
|
)
|
||||||
|
|
||||||
:help
|
if "%1" == "singlehtml" (
|
||||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
%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
|
||||||
|
)
|
||||||
|
|
||||||
:end
|
:end
|
||||||
popd
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
readthedocs-sphinx-search==0.3.2
|
|
||||||
sphinx==7.3.7
|
|
||||||
sphinx-rtd-theme==2.0.0
|
|
||||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
|
@ -15,7 +15,7 @@ Usage
|
||||||
|
|
||||||
It can be used as a decorator at the :class:`~django.test.TestCase` level, the
|
It can be used as a decorator at the :class:`~django.test.TestCase` level, the
|
||||||
method level and also as a
|
method level and also as a
|
||||||
`context manager <https://peps.python.org/pep-0343/>`_.
|
`context manager <https://www.python.org/dev/peps/pep-0343/>`_.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
@ -38,87 +38,3 @@ method level and also as a
|
||||||
def test_what_is_your_favourite_color(self):
|
def test_what_is_your_favourite_color(self):
|
||||||
with override_config(YOUR_FAVOURITE_COLOR="Blue?"):
|
with override_config(YOUR_FAVOURITE_COLOR="Blue?"):
|
||||||
self.assertEqual(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.
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from cheeseshop.apps.catalog.models import Brand
|
from cheeseshop.apps.catalog.models import Brand
|
||||||
|
|
||||||
admin.site.register(Brand)
|
admin.site.register(Brand)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Brand(models.Model):
|
class Brand(models.Model):
|
||||||
name = models.CharField(max_length=75)
|
name = models.CharField(max_length=75)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
from django.contrib import admin
|
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(Shelf)
|
||||||
admin.site.register(Supply)
|
admin.site.register(Supply)
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
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",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Shelf(models.Model):
|
class Shelf(models.Model):
|
||||||
name = models.CharField(max_length=75)
|
name = models.CharField(max_length=75)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = "shelves"
|
verbose_name_plural = 'shelves'
|
||||||
|
|
||||||
|
|
||||||
class Supply(models.Model):
|
class Supply(models.Model):
|
||||||
name = models.CharField(max_length=75)
|
name = models.CharField(max_length=75)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = "supplies"
|
verbose_name_plural = 'supplies'
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue