pax_global_header00006660000000000000000000000064147505011720014514gustar00rootroot0000000000000052 comment=22bdb011db0f57562ff14203209a08dbcb2a5194 django-constance-4.3.2/000077500000000000000000000000001475050117200147375ustar00rootroot00000000000000django-constance-4.3.2/.coveragerc000066400000000000000000000002141475050117200170550ustar00rootroot00000000000000[run] source = constance branch = 1 omit = */pytest.py */tests/* [report] omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py django-constance-4.3.2/.github/000077500000000000000000000000001475050117200162775ustar00rootroot00000000000000django-constance-4.3.2/.github/ISSUE_TEMPLATE.md000066400000000000000000000003241475050117200210030ustar00rootroot00000000000000### 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: django-constance-4.3.2/.github/dependabot.yml000066400000000000000000000011161475050117200211260ustar00rootroot00000000000000# 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 django-constance-4.3.2/.github/workflows/000077500000000000000000000000001475050117200203345ustar00rootroot00000000000000django-constance-4.3.2/.github/workflows/docs.yml000066400000000000000000000007431475050117200220130ustar00rootroot00000000000000name: Docs on: [push, pull_request] jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 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 django-constance-4.3.2/.github/workflows/release.yml000066400000000000000000000016021475050117200224760ustar00rootroot00000000000000name: Release on: push: tags: - '*' jobs: build: if: github.repository == 'jazzband/django-constance' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 - 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.12.4 with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} repository_url: https://jazzband.co/projects/django-constance/upload django-constance-4.3.2/.github/workflows/test.yml000066400000000000000000000022431475050117200220370ustar00rootroot00000000000000name: Test on: [push, pull_request] jobs: ruff-format: runs-on: ubuntu-latest timeout-minutes: 1 steps: - uses: actions/checkout@v4 - 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@v4 - 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'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade tox tox-gh-actions - name: Tox tests run: | tox -v - name: Upload coverage uses: codecov/codecov-action@v5 with: name: Python ${{ matrix.python-version }} django-constance-4.3.2/.gitignore000066400000000000000000000002011475050117200167200ustar00rootroot00000000000000.project .pydevproject *.pyc *.egg-info build/ dist/ test.db .tox .coverage coverage.xml docs/_build .idea constance/_version.py django-constance-4.3.2/.pre-commit-config.yaml000066400000000000000000000007111475050117200212170ustar00rootroot00000000000000repos: - 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: v5.0.0 hooks: - id: check-merge-conflict - id: check-yaml - repo: https://github.com/asottile/pyupgrade rev: v3.19.1 hooks: - id: pyupgrade args: [ --py38-plus ] exclude: /migrations/ ci: autoupdate_schedule: quarterly django-constance-4.3.2/.readthedocs.yaml000066400000000000000000000004341475050117200201670ustar00rootroot00000000000000# 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 django-constance-4.3.2/AUTHORS000066400000000000000000000032051475050117200160070ustar00rootroot00000000000000Ales Zoulek Alexander Frenzel Alexandr Artemyev Bouke Haarsma Camilo Nova Charlie Hornsby Curtis Maloney Dan Poirier David Burke Dmitriy Tatarkin Elisey Zanko Florian Apolloner Igor Támara Ilya Chichak Ivan Klass Jake Merdich Jannis Leidel Janusz Harkot Jiri Barton John Carter Jonas Kuba Zarzycki Leandra Finger Les Orchard Lin Xianyi Marcin Baran Mario Orlandi Mario Rosa Mariusz Felisiak Mattia Larentis Merijn Bertels Omer Katz Petr Knap Philip Neustrom Pierre-Olivier Marec Roman Krejcik Silvan Spross Sławek Ehlert Vladas Tamoshaitis Vojtech Jasny Yin Jifeng illumin-us-r3v0lution mega saw2th trbs vl <1844144@gmail.com> vl django-constance-4.3.2/CODE_OF_CONDUCT.md000066400000000000000000000045071475050117200175440ustar00rootroot00000000000000# Code of Conduct As contributors and maintainers of the Jazzband projects, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in the Jazzband a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery - Personal attacks - Trolling or insulting/derogatory comments - Public or private harassment - Publishing other's private information, such as physical or electronic addresses, without explicit permission - Other unethical or unprofessional conduct The Jazzband roadies have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, the roadies commit themselves to fairly and consistently applying these principles to every aspect of managing the jazzband projects. Roadies who do not follow or enforce the Code of Conduct may be permanently removed from the Jazzband roadies. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Roadies are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] [homepage]: https://contributor-covenant.org [version]: https://contributor-covenant.org/version/1/3/0/ django-constance-4.3.2/CONTRIBUTING.md000066400000000000000000000004621475050117200171720ustar00rootroot00000000000000[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines). django-constance-4.3.2/LICENSE000066400000000000000000000027101475050117200157440ustar00rootroot00000000000000Copyright (c) 2009-2017, Jazzband All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-constance-4.3.2/MANIFEST.in000066400000000000000000000001771475050117200165020ustar00rootroot00000000000000recursive-include constance/templates *.html recursive-include constance/locale *.po *.mo recursive-include constance/static * django-constance-4.3.2/README.rst000066400000000000000000000020161475050117200164250ustar00rootroot00000000000000Constance - Dynamic Django settings =================================== .. image:: https://jazzband.co/static/img/badge.svg :alt: Jazzband :target: https://jazzband.co/ .. image:: https://img.shields.io/readthedocs/django-constance.svg :target: https://django-constance.readthedocs.io/ :alt: Documentation .. image:: https://github.com/jazzband/django-constance/workflows/Test/badge.svg :target: https://github.com/jazzband/django-constance/actions :alt: GitHub Actions .. image:: https://codecov.io/gh/jazzband/django-constance/branch/master/graph/badge.svg :target: https://codecov.io/gh/jazzband/django-constance :alt: Coverage A Django app for storing dynamic settings in pluggable backends (Redis and Django model backend built in) with an integration with the Django admin app. For more information see the documentation at: https://django-constance.readthedocs.io/ If you have questions or have trouble using the app please file a bug report at: https://github.com/jazzband/django-constance/issues django-constance-4.3.2/constance/000077500000000000000000000000001475050117200167145ustar00rootroot00000000000000django-constance-4.3.2/constance/__init__.py000066400000000000000000000003001475050117200210160ustar00rootroot00000000000000from django.utils.functional import LazyObject class LazyConfig(LazyObject): def _setup(self): from .base import Config self._wrapped = Config() config = LazyConfig() django-constance-4.3.2/constance/admin.py000066400000000000000000000164271475050117200203700ustar00rootroot00000000000000from collections import OrderedDict from datetime import date from datetime import datetime from operator import itemgetter from django import forms from django import get_version from django.apps import apps from django.contrib import admin from django.contrib import messages from django.contrib.admin.options import csrf_protect_m from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.template.response import TemplateResponse from django.urls import path from django.utils.formats import localize from django.utils.translation import gettext_lazy as _ from . import LazyConfig from . import settings from .forms import ConstanceForm from .utils import get_values config = LazyConfig() class ConstanceAdmin(admin.ModelAdmin): change_list_template = 'admin/constance/change_list.html' change_list_form = ConstanceForm def __init__(self, model, admin_site): model._meta.concrete_model = Config super().__init__(model, admin_site) def get_urls(self): info = f'{self.model._meta.app_label}_{self.model._meta.module_name}' return [ path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_changelist'), path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_add'), ] def get_config_value(self, name, options, form, initial): default, help_text = options[0], options[1] field_type = None if len(options) == 3: field_type = options[2] # First try to load the value from the actual backend value = initial.get(name) # Then if the returned value is None, get the default if value is None: value = getattr(config, name) form_field = form[name] config_value = { 'name': name, 'default': localize(default), 'raw_default': default, 'help_text': _(help_text), 'value': localize(value), 'modified': localize(value) != localize(default), 'form_field': form_field, 'is_date': isinstance(default, date), 'is_datetime': isinstance(default, datetime), 'is_checkbox': isinstance(form_field.field.widget, forms.CheckboxInput), 'is_file': isinstance(form_field.field.widget, forms.FileInput), } if field_type and field_type in settings.ADDITIONAL_FIELDS: serialized_default = form[name].field.prepare_value(default) config_value['default'] = serialized_default config_value['raw_default'] = serialized_default config_value['value'] = form[name].field.prepare_value(value) return config_value def get_changelist_form(self, request): """Returns a Form class for use in the changelist_view.""" # Defaults to self.change_list_form in order to preserve backward # compatibility return self.change_list_form @csrf_protect_m def changelist_view(self, request, extra_context=None): if not self.has_view_or_change_permission(request): raise PermissionDenied initial = get_values() form_cls = self.get_changelist_form(request) form = form_cls(initial=initial, request=request) if request.method == 'POST' and request.user.has_perm('constance.change_config'): form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request) if form.is_valid(): form.save() messages.add_message(request, messages.SUCCESS, _('Live settings updated successfully.')) return HttpResponseRedirect('.') messages.add_message(request, messages.ERROR, _('Failed to update live settings.')) context = dict( self.admin_site.each_context(request), config_values=[], title=self.model._meta.app_config.verbose_name, app_label='constance', opts=self.model._meta, form=form, media=self.media + form.media, icon_type='svg', django_version=get_version(), ) for name, options in settings.CONFIG.items(): context['config_values'].append(self.get_config_value(name, options, form, initial)) if settings.CONFIG_FIELDSETS: if isinstance(settings.CONFIG_FIELDSETS, dict): fieldset_items = settings.CONFIG_FIELDSETS.items() else: fieldset_items = settings.CONFIG_FIELDSETS context['fieldsets'] = [] for fieldset_title, fieldset_data in fieldset_items: if isinstance(fieldset_data, dict): fields_list = fieldset_data['fields'] collapse = fieldset_data.get('collapse', False) else: fields_list = fieldset_data collapse = False absent_fields = [field for field in fields_list if field not in settings.CONFIG] if any(absent_fields): raise ValueError( 'CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}'.format( ', '.join(absent_fields) ) ) config_values = [] for name in fields_list: options = settings.CONFIG.get(name) if options: config_values.append(self.get_config_value(name, options, form, initial)) fieldset_context = {'title': fieldset_title, 'config_values': config_values} if collapse: fieldset_context['collapse'] = True context['fieldsets'].append(fieldset_context) if not isinstance(settings.CONFIG_FIELDSETS, (OrderedDict, tuple)): context['fieldsets'].sort(key=itemgetter('title')) if not isinstance(settings.CONFIG, OrderedDict): context['config_values'].sort(key=itemgetter('name')) request.current_app = self.admin_site.name return TemplateResponse(request, self.change_list_template, context) def has_add_permission(self, *args, **kwargs): return False def has_delete_permission(self, *args, **kwargs): return False def has_change_permission(self, request, obj=None): if settings.SUPERUSER_ONLY: return request.user.is_superuser return super().has_change_permission(request, obj) class Config: class Meta: app_label = 'constance' object_name = 'Config' concrete_model = None model_name = module_name = 'config' verbose_name_plural = _('config') abstract = False swapped = False is_composite_pk = False def get_ordered_objects(self): return False def get_change_permission(self): return f'change_{self.model_name}' @property def app_config(self): return apps.get_app_config(self.app_label) @property def label(self): return f'{self.app_label}.{self.object_name}' @property def label_lower(self): return f'{self.app_label}.{self.model_name}' _meta = Meta() admin.site.register([Config], ConstanceAdmin) django-constance-4.3.2/constance/apps.py000066400000000000000000000006051475050117200202320ustar00rootroot00000000000000from django.apps import AppConfig from django.core import checks from django.utils.translation import gettext_lazy as _ from constance.checks import check_fieldsets class ConstanceConfig(AppConfig): name = 'constance' verbose_name = _('Constance') default_auto_field = 'django.db.models.AutoField' def ready(self): checks.register(check_fieldsets, 'constance') django-constance-4.3.2/constance/backends/000077500000000000000000000000001475050117200204665ustar00rootroot00000000000000django-constance-4.3.2/constance/backends/__init__.py000066400000000000000000000010771475050117200226040ustar00rootroot00000000000000"""Defines the base constance backend.""" class Backend: def get(self, key): """ Get the key from the backend store and return the value. Return None if not found. """ raise NotImplementedError def mget(self, keys): """ Get the keys from the backend store and return a list of the values. Return an empty list if not found. """ raise NotImplementedError def set(self, key, value): """Add the value to the backend store given the key.""" raise NotImplementedError django-constance-4.3.2/constance/backends/database.py000066400000000000000000000111721475050117200226060ustar00rootroot00000000000000from 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 = {} autofill_values[full_cachekey] = 1 for key, value in self.mget(settings.CONFIG): 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 = {self.add_prefix(key): key for key in keys} try: stored = self._model._default_manager.filter(key__in=keys) for const in stored: yield keys[const.key], loads(const.value) except (OperationalError, ProgrammingError): pass 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).first() if match: value = loads(match.value) if self._cache: self._cache.add(key, value) return value 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) 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() django-constance-4.3.2/constance/backends/memory.py000066400000000000000000000016721475050117200223560ustar00rootroot00000000000000from 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) def mget(self, keys): if not keys: return None result = [] with self._lock: for key in keys: value = self._storage.get(key) if value is not None: result.append((key, value)) return result 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) django-constance-4.3.2/constance/backends/redisd.py000066400000000000000000000054211475050117200223140ustar00rootroot00000000000000from threading import RLock from time import monotonic from django.core.exceptions import ImproperlyConfigured from constance import config from constance import settings from constance import signals from constance import utils from constance.backends import Backend from constance.codecs import dumps from constance.codecs import loads class RedisBackend(Backend): def __init__(self): super().__init__() self._prefix = settings.REDIS_PREFIX connection_cls = settings.REDIS_CONNECTION_CLASS if connection_cls is not None: self._rd = utils.import_module_attr(connection_cls)() else: try: import redis except ImportError: raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.') from None if isinstance(settings.REDIS_CONNECTION, str): self._rd = redis.from_url(settings.REDIS_CONNECTION) else: self._rd = redis.Redis(**settings.REDIS_CONNECTION) def add_prefix(self, key): return f'{self._prefix}{key}' def get(self, key): value = self._rd.get(self.add_prefix(key)) if value: return loads(value) return None def mget(self, keys): if not keys: return prefixed_keys = [self.add_prefix(key) for key in keys] for key, value in zip(keys, self._rd.mget(prefixed_keys)): if value: yield key, loads(value) def set(self, key, value): old_value = self.get(key) self._rd.set(self.add_prefix(key), dumps(value)) signals.config_updated.send(sender=config, key=key, old_value=old_value, new_value=value) class CachingRedisBackend(RedisBackend): _sentinel = object() _lock = RLock() def __init__(self): super().__init__() self._timeout = settings.REDIS_CACHE_TIMEOUT self._cache = {} self._sentinel = object() 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] def set(self, key, value): with self._lock: super().set(key, value) self._cache_value(key, value) def mget(self, keys): if not keys: return for key in keys: value = self.get(key) if value is not None: yield key, value django-constance-4.3.2/constance/base.py000066400000000000000000000016021475050117200201770ustar00rootroot00000000000000from . import settings from . import utils class Config: """The global config wrapper that handles the backend.""" def __init__(self): super().__setattr__('_backend', utils.import_module_attr(settings.BACKEND)()) def __getattr__(self, 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 result = self._backend.get(key) if result is None: result = default setattr(self, key, default) return result return result def __setattr__(self, key, value): if key not in settings.CONFIG: raise AttributeError(key) self._backend.set(key, value) def __dir__(self): return settings.CONFIG.keys() django-constance-4.3.2/constance/checks.py000066400000000000000000000047301475050117200205320ustar00rootroot00000000000000from __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 django-constance-4.3.2/constance/codecs.py000066400000000000000000000070031475050117200205260ustar00rootroot00000000000000from __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() django-constance-4.3.2/constance/context_processors.py000066400000000000000000000005271475050117200232400ustar00rootroot00000000000000import constance def config(request): """ Simple context processor that puts the config into every RequestContext. Just make sure you have a setting like this: TEMPLATE_CONTEXT_PROCESSORS = ( # ... 'constance.context_processors.config', ) """ return {'config': constance.config} django-constance-4.3.2/constance/forms.py000066400000000000000000000131601475050117200204150ustar00rootroot00000000000000import hashlib from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from decimal import Decimal from os.path import join from django import conf from django import forms from django.contrib import messages from django.contrib.admin import widgets from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage from django.forms import fields from django.utils import timezone from django.utils.encoding import smart_bytes from django.utils.module_loading import import_string from django.utils.text import normalize_newlines from django.utils.translation import gettext_lazy as _ from . import LazyConfig from . import settings from .checks import get_inconsistent_fieldnames config = LazyConfig() NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10}) INTEGER_LIKE = (fields.IntegerField, {'widget': NUMERIC_WIDGET}) STRING_LIKE = ( fields.CharField, { 'widget': forms.Textarea(attrs={'rows': 3}), 'required': False, }, ) FIELDS = { bool: (fields.BooleanField, {'required': False}), int: INTEGER_LIKE, Decimal: (fields.DecimalField, {'widget': NUMERIC_WIDGET}), str: STRING_LIKE, datetime: (fields.SplitDateTimeField, {'widget': widgets.AdminSplitDateTime}), timedelta: (fields.DurationField, {'widget': widgets.AdminTextInputWidget}), date: (fields.DateField, {'widget': widgets.AdminDateWidget}), time: (fields.TimeField, {'widget': widgets.AdminTimeWidget}), float: (fields.FloatField, {'widget': NUMERIC_WIDGET}), } def parse_additional_fields(fields): for key in fields: field = list(fields[key]) if len(field) == 1: field.append({}) field[0] = import_string(field[0]) if 'widget' in field[1]: klass = import_string(field[1]['widget']) field[1]['widget'] = klass(**(field[1].get('widget_kwargs', {}) or {})) if 'widget_kwargs' in field[1]: del field[1]['widget_kwargs'] fields[key] = field return fields FIELDS.update(parse_additional_fields(settings.ADDITIONAL_FIELDS)) class ConstanceForm(forms.Form): version = forms.CharField(widget=forms.HiddenInput) def __init__(self, initial, request=None, *args, **kwargs): super().__init__(*args, initial=initial, **kwargs) version_hash = hashlib.sha256() only_view = request and not request.user.has_perm('constance.change_config') if only_view: messages.warning( request, _("You don't have permission to change these values"), ) for name, options in settings.CONFIG.items(): default = options[0] if len(options) == 3: config_type = options[2] if config_type not in settings.ADDITIONAL_FIELDS and not isinstance(default, config_type): raise ImproperlyConfigured( _( 'Default value type must be ' 'equal to declared config ' 'parameter type. Please fix ' 'the default value of ' "'%(name)s'." ) % {'name': name} ) else: config_type = type(default) if config_type not in FIELDS: raise ImproperlyConfigured( _( "Constance doesn't support " 'config values of the type ' '%(config_type)s. Please fix ' "the value of '%(name)s'." ) % {'config_type': config_type, 'name': name} ) field_class, kwargs = FIELDS[config_type] if only_view: kwargs['disabled'] = True self.fields[name] = field_class(label=name, **kwargs) version_hash.update(smart_bytes(initial.get(name, ''))) self.initial['version'] = version_hash.hexdigest() def save(self): for file_field in self.files: file = self.cleaned_data[file_field] self.cleaned_data[file_field] = default_storage.save(join(settings.FILE_ROOT, file.name), file) for name in settings.CONFIG: current = getattr(config, name) new = self.cleaned_data[name] if isinstance(new, str): new = normalize_newlines(new) if conf.settings.USE_TZ and isinstance(current, datetime) and not timezone.is_aware(current): current = timezone.make_aware(current) if current != new: setattr(config, name, new) def clean_version(self): value = self.cleaned_data['version'] if settings.IGNORE_ADMIN_VERSION_CHECK: return value if value != self.initial['version']: raise forms.ValidationError( _( 'The settings have been modified ' 'by someone else. Please reload the ' 'form and resubmit your changes.' ) ) return value def clean(self): cleaned_data = super().clean() if not settings.CONFIG_FIELDSETS: return cleaned_data missing_keys, extra_keys = get_inconsistent_fieldnames() if missing_keys or extra_keys: raise forms.ValidationError( _('CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.') ) return cleaned_data django-constance-4.3.2/constance/locale/000077500000000000000000000000001475050117200201535ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/ar/000077500000000000000000000000001475050117200205555ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/ar/LC_MESSAGES/000077500000000000000000000000001475050117200223425ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/ar/LC_MESSAGES/django.mo000066400000000000000000000041551475050117200241460ustar00rootroot00000000000000,R hOoW8 #5:b?  ^,.?@  /<K\   CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in CONSTANCE_CONFIG.ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Get/Set In-database config settings handled by ConstanceHomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2020-12-01 12:05+0100 Language: ar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last-Translator: Language-Team: X-Generator: Poedit 2.4.2 لا يحتوي CONSTANCE_CONFIG_FIELDSETS على حقول موجودة في CONSTANCE_CONFIG.كونستانسلا يعتمد كونستانس قيم التكوين من النوع %(config_type)s. الرجاء إصلاح قيمة '%(name)s'.افتراضييجب أن يكون نوع القيمة الافتراضي مساوياً لنوع معلمة التكوين المعلن. الرجاء إصلاح القيمة الافتراضية لـ '%(name)s'.الحصول على / تعيين إعدادات التكوين في قاعدة البيانات التي تعالجها كونستانسالصفحة الرئيسيةتم تعديلهتم تحديث الإعدادات المباشرة بنجاح.الإسمحفظتم تعديل الإعدادات بواسطة شخص آخر. الرجاء إعادة تحميل النموذج وإعادة إرسال التغييرات.القيمةالتكوينكونستانسكونستانسdjango-constance-4.3.2/constance/locale/ar/LC_MESSAGES/django.po000066400000000000000000000056361475050117200241560ustar00rootroot00000000000000# 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 , 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 "تم تعديله" django-constance-4.3.2/constance/locale/cs_CZ/000077500000000000000000000000001475050117200211545ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/cs_CZ/LC_MESSAGES/000077500000000000000000000000001475050117200227415ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/cs_CZ/LC_MESSAGES/django.mo000066400000000000000000000017331475050117200245440ustar00rootroot00000000000000 t #*NSX^ e ozYk q%     DefaultHomeIs modifiedLive settings updated successfully.NameSaveValueconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: POT-Creation-Date: 2017-06-13 19:40+0530 PO-Revision-Date: 2014-11-27 18:13+0000 Last-Translator: Jannis Leidel Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/django-constance/language/cs_CZ/) Language: cs_CZ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; Výchozí hodnotaDomůJe změněna?Nastavení bylo úspěšně uloženo.NázevUložitHodnotanastaveníkonstantakonstantydjango-constance-4.3.2/constance/locale/cs_CZ/LC_MESSAGES/django.po000066400000000000000000000045051475050117200245470ustar00rootroot00000000000000# 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: 2017-06-13 19:40+0530\n" "PO-Revision-Date: 2014-11-27 18:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Czech (Czech Republic) (http://www.transifex.com/projects/p/" "django-constance/language/cs_CZ/)\n" "Language: cs_CZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\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 "" #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" #: admin.py: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 "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "Nastavení bylo úspěšně uloženo." #: admin.py:285 msgid "config" msgstr "nastavení" #: apps.py:8 msgid "Constance" msgstr "" #: backends/database/models.py:19 msgid "constance" msgstr "konstanta" #: backends/database/models.py:20 msgid "constances" msgstr "konstanty" #: 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 "Uložit" #: templates/admin/constance/change_list.html:84 msgid "Home" msgstr "Domů" #: templates/admin/constance/includes/results_list.html:5 msgid "Name" msgstr "Název" #: templates/admin/constance/includes/results_list.html:6 msgid "Default" msgstr "Výchozí hodnota" #: templates/admin/constance/includes/results_list.html:7 msgid "Value" msgstr "Hodnota" #: templates/admin/constance/includes/results_list.html:8 msgid "Is modified" msgstr "Je změněna?" #~ msgid "Constance config" #~ msgstr "Nastavení konstant" django-constance-4.3.2/constance/locale/de/000077500000000000000000000000001475050117200205435ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/de/LC_MESSAGES/000077500000000000000000000000001475050117200223305ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/de/LC_MESSAGES/django.mo000066400000000000000000000027121475050117200241310ustar00rootroot00000000000000H IhS #bfl s } 6|@5|%       ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultHomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: POT-Creation-Date: 2017-06-13 19:40+0530 PO-Revision-Date: 2014-11-27 18:17+0000 Last-Translator: Jannis Leidel Language-Team: German (http://www.transifex.com/projects/p/django-constance/language/de/) Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); KonstanzeKonstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. Bitte den Ausgangswert von '%(name)s' ändern.VoreinstellungStartIst modifiziertDie Livekonfiguration wurde erfolgreich aktualisiert.NameSichernDie Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite neuladen und die Änderungen erneut vornehmen.WertKonfigurationKonstanzeKonstanzesdjango-constance-4.3.2/constance/locale/de/LC_MESSAGES/django.po000066400000000000000000000055471475050117200241450ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Jannis Leidel , 2014 msgid "" msgstr "" "Project-Id-Version: django-constance\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-06-13 19:40+0530\n" "PO-Revision-Date: 2014-11-27 18:17+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: German (http://www.transifex.com/projects/p/django-constance/" "language/de/)\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: admin.py:113 #, fuzzy, python-format #| msgid "" #| "Constance doesn't support config values of the type %(config_type)s. " #| "Please fix the value of '%(name)s'." msgid "" "Default value type must be equal to declared config parameter type. Please " "fix the default value of '%(name)s'." msgstr "" "Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. " "Bitte den Ausgangswert von '%(name)s' ändern." #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" "Konstanze unterstützt die Konfigurationswerte vom Typ %(config_type)s nicht. " "Bitte den Ausgangswert von '%(name)s' ändern." #: admin.py:147 msgid "" "The settings have been modified by someone else. Please reload the form and " "resubmit your changes." msgstr "" "Die Konfiguration wurde seit Öffnen dieser Seite verändert. Bitte die Seite " "neuladen und die Änderungen erneut vornehmen." #: admin.py:160 msgid "" "CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in " "CONSTANCE_CONFIG." msgstr "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "Die Livekonfiguration wurde erfolgreich aktualisiert." #: admin.py:285 msgid "config" msgstr "Konfiguration" #: apps.py:8 msgid "Constance" msgstr "Konstanze" #: backends/database/models.py:19 msgid "constance" msgstr "Konstanze" #: backends/database/models.py:20 msgid "constances" msgstr "Konstanzes" #: 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 "Sichern" #: templates/admin/constance/change_list.html:84 msgid "Home" msgstr "Start" #: templates/admin/constance/includes/results_list.html:5 msgid "Name" msgstr "Name" #: templates/admin/constance/includes/results_list.html:6 msgid "Default" msgstr "Voreinstellung" #: templates/admin/constance/includes/results_list.html:7 msgid "Value" msgstr "Wert" #: templates/admin/constance/includes/results_list.html:8 msgid "Is modified" msgstr "Ist modifiziert" #~ msgid "Constance config" #~ msgstr "Constance Konfiguration" django-constance-4.3.2/constance/locale/en/000077500000000000000000000000001475050117200205555ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/en/LC_MESSAGES/000077500000000000000000000000001475050117200223425ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/en/LC_MESSAGES/django.mo000066400000000000000000000005211475050117200241370ustar00rootroot00000000000000$,89Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit django-constance-4.3.2/constance/locale/en/LC_MESSAGES/django.po000066400000000000000000000041161475050117200241460ustar00rootroot00000000000000# 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 , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-07-19 21:00+0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\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 "" #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" #: admin.py: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 "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "" #: admin.py:267 msgid "Failed to update live settings." 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 "" django-constance-4.3.2/constance/locale/es/000077500000000000000000000000001475050117200205625ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/es/LC_MESSAGES/000077500000000000000000000000001475050117200223475ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/es/LC_MESSAGES/django.mo000066400000000000000000000027021475050117200241470ustar00rootroot00000000000000H IhS #bfl s } 7vA9 o(      ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultHomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2017-06-13 19:40+0530 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Igor Támara Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/language/de/ Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); ConstanceConstance no soporta valores de configuración de los tipos %(config_type)s. Por favor arregle el valor de '%(name)s'.PredeterminadoInicioEstá modificadoLas configuraciones en vivo se actualizaron exitosamente.NombreGuardarLa configuración ha sido modificada por alguien más. Por favor recargue el formulario y reenvíe sus cambios.Valorconfiguraciónconstanceconstancesdjango-constance-4.3.2/constance/locale/es/LC_MESSAGES/django.po000066400000000000000000000055371475050117200241630ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Igor Támara , 2015 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: Igor Támara \n" "Language-Team: Spanish (http://www.transifex.com/projects/p/django-constance/" "language/de/\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: admin.py:113 #, fuzzy, python-format #| msgid "" #| "Constance doesn't support config values of the type %(config_type)s. " #| "Please fix the value of '%(name)s'." msgid "" "Default value type must be equal to declared config parameter type. Please " "fix the default value of '%(name)s'." msgstr "" "Constance no soporta valores de configuración de los tipos %(config_type)s. " "Por favor arregle el valor de '%(name)s'." #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the 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:147 msgid "" "The settings have been modified by someone else. Please reload the form and " "resubmit your changes." msgstr "" "La configuración ha sido modificada por alguien más. Por favor recargue el " "formulario y reenvíe sus cambios." #: admin.py:160 msgid "" "CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in " "CONSTANCE_CONFIG." msgstr "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "Las configuraciones en vivo se actualizaron exitosamente." #: admin.py:285 msgid "config" msgstr "configuración" #: 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 "" #: 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" msgstr "Nombre" #: templates/admin/constance/includes/results_list.html:6 msgid "Default" msgstr "Predeterminado" #: templates/admin/constance/includes/results_list.html:7 msgid "Value" msgstr "Valor" #: templates/admin/constance/includes/results_list.html:8 msgid "Is modified" msgstr "Está modificado" #~ msgid "Constance config" #~ msgstr "Configuración de Constance" django-constance-4.3.2/constance/locale/et/000077500000000000000000000000001475050117200205635ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/et/LC_MESSAGES/000077500000000000000000000000001475050117200223505ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/et/LC_MESSAGES/django.mo000066400000000000000000000032651475050117200241550ustar00rootroot00000000000000 h ihso8T #b*0 7 AkL BL)d(     ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Get/Set In-database config settings handled by ConstanceIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2017-06-13 19:40+0530 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); SeadedConstance ei toeta seadete seda tüüpi %(config_type) seadete väärtusi. Palunsisestage korrektne väärtus väljale '%(name)s'.VaikimisiVaikimisi väärtus peab olema vastav seade parameetri tüübile. Palun sisestagekorrekne vaikimisi väärtus väljale '%(name)s'Loe/salvesta andmebaasi põhiseid seadeidMuudetudSeaded edukalt muudetud.NimiSalvestaSeadeid on vahepeal muudetud, palun esitage oma muudatused peale seda kui olete lehe uuesti laadinudVäärtuskonfiguratsioonseadedseadeddjango-constance-4.3.2/constance/locale/et/LC_MESSAGES/django.po000066400000000000000000000050731475050117200241570ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: LANGUAGE \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" django-constance-4.3.2/constance/locale/fa/000077500000000000000000000000001475050117200205415ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/fa/LC_MESSAGES/000077500000000000000000000000001475050117200223265ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/fa/LC_MESSAGES/django.mo000066400000000000000000000044051475050117200241300ustar00rootroot00000000000000<R hwo8( -#9]bsbx  Mbq*;%     CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in CONSTANCE_CONFIG.ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Get/Set In-database config settings handled by ConstanceHomeIs modifiedLive settings updated successfully.NameReset to defaultSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2020-09-24 17:33+0330 Language: fa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last-Translator: Mehdi Namaki Language-Team: X-Generator: Poedit 2.3 CONSTANCE_CONFIG_FIELDSETS شامل فیلدهای CONSTANCE_CONFIG نیست.تنظیماتتنظیمات مقادیر پیکربندی از نوع %(config_type)s را پشتیبانی نمی کند. لطفاً مقدار '%(name)s' را اصلاح کنید.پیش‌فرضنوع مقدار پیش فرض باید برابر با نوع پارامتر پیکربندی اعلام شده باشد. لطفاً مقدار پیش فرض '%(name)s' را اصلاح کنید.دریافت/تنظیم تنظیمات پیکربندی درون پایگاه داده که توسط تنظیمات بکار برده می شودخانهتغییر یافتهتنظیمات زنده با موفقیت به روز شد.نامبازنشانی به پیش‌فرضذخیرهتنظیمات توسط شخص دیگری تغییر یافته است. لطفاً فرم را بارگیری کنید و تغییرات خود را دوباره ارسال کنید.مقدارپیکربندیتنظیماتتنظیماتdjango-constance-4.3.2/constance/locale/fa/LC_MESSAGES/django.po000066400000000000000000000061341475050117200241340ustar00rootroot00000000000000# 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 , 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 \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 "بازنشانی به پیش‌فرض" django-constance-4.3.2/constance/locale/fr/000077500000000000000000000000001475050117200205625ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/fr/LC_MESSAGES/000077500000000000000000000000001475050117200223475ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/fr/LC_MESSAGES/django.mo000066400000000000000000000035201475050117200241460ustar00rootroot00000000000000x yho8d #b?E L Vla ^f\\ b%o -4 ; E   ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Get/Set In-database config settings handled by ConstanceHomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2017-07-02 19:01+0100 PO-Revision-Date: 2017-07-03 12:35+0100 Last-Translator: Bruno Alla Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); Language-Team: X-Generator: Poedit 2.0.2 ConstanceConstance ne prend pas en charge les valeurs de configuration du type %(config_type)s. Veuillez corriger la valeur de ‘%(name)s’.DéfautLe 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'.Obtenir/définir les paramètres de configuration de base de données gérées par ConstanceIndexEst modifiéParamètres mis à jour avec succès.NomEnregistrerLes paramètres ont été modifiés par quelqu'un d'autre. Veuillez rafraichir le formulaire et soumettre de nouveau vos modifications.Valeurconfigconstanceconstancesdjango-constance-4.3.2/constance/locale/fr/LC_MESSAGES/django.po000066400000000000000000000051051475050117200241520ustar00rootroot00000000000000# 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 , 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 \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é" django-constance-4.3.2/constance/locale/it/000077500000000000000000000000001475050117200205675ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/it/LC_MESSAGES/000077500000000000000000000000001475050117200223545ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/it/LC_MESSAGES/django.mo000066400000000000000000000035271475050117200241620ustar00rootroot00000000000000xRy h?oG #bY_ f p{O  ph}tc j;uj'. = J   CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in CONSTANCE_CONFIG.ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.HomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: PO-Revision-Date: 2018-03-13 15:26+0100 Last-Translator: Paolo Melchiorre Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/language/it/) Language: it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 2.0.4 CONSTANCE_CONFIG_FIELDSETS non contiene campi che esistono in CONSTANCE_CONFIG.ImpostazioniConstance non supporta valori di impostazioni di tipo %(config_type)s. Modifica il valore di '%(name)s'.DefaultIl tipo dei valori di default deve essere uguale al tipo del parametro. Modifica il valore di default di '%(name)s'.InizioModificatoLe impostazioni attive sono state aggiornate correttamente.NomeSalvaLe impostazioni sono state modificate da qualcuno. Ricarica la pagina e invia nuovamente le tue modifiche.Valoreconfigurazioniimpostazioneimpostazionidjango-constance-4.3.2/constance/locale/it/LC_MESSAGES/django.po000066400000000000000000000054101475050117200241560ustar00rootroot00000000000000# 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: 2017-06-13 19:40+0530\n" "PO-Revision-Date: 2018-03-13 15:26+0100\n" "Last-Translator: Paolo Melchiorre \n" "Language-Team: Italian (http://www.transifex.com/projects/p/django-constance/" "language/it/)\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.0.4\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 "" "Il tipo dei valori di default deve essere uguale al tipo del parametro. " "Modifica il valore di default di '%(name)s'." #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" "Constance non supporta valori di impostazioni di tipo %(config_type)s. " "Modifica il valore di '%(name)s'." #: admin.py:147 msgid "" "The settings have been modified by someone else. Please reload the form and " "resubmit your changes." msgstr "" "Le impostazioni sono state modificate da qualcuno. Ricarica la pagina e " "invia nuovamente le tue modifiche." #: admin.py:160 msgid "" "CONSTANCE_CONFIG_FIELDSETS does not contain fields that exist in " "CONSTANCE_CONFIG." msgstr "" "CONSTANCE_CONFIG_FIELDSETS non contiene campi che esistono in " "CONSTANCE_CONFIG." #: admin.py:224 msgid "Live settings updated successfully." msgstr "Le impostazioni attive sono state aggiornate correttamente." #: admin.py:285 msgid "config" msgstr "configurazioni" #: apps.py:8 msgid "Constance" msgstr "Impostazioni" #: backends/database/models.py:19 msgid "constance" msgstr "impostazione" #: backends/database/models.py:20 msgid "constances" msgstr "impostazioni" #: 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 "Salva" #: templates/admin/constance/change_list.html:84 msgid "Home" msgstr "Inizio" #: templates/admin/constance/includes/results_list.html:5 msgid "Name" msgstr "Nome" #: templates/admin/constance/includes/results_list.html:6 msgid "Default" msgstr "Default" #: templates/admin/constance/includes/results_list.html:7 msgid "Value" msgstr "Valore" #: templates/admin/constance/includes/results_list.html:8 msgid "Is modified" msgstr "Modificato" #~ msgid "Constance config" #~ msgstr "Configurazione Impostazioni" django-constance-4.3.2/constance/locale/pl/000077500000000000000000000000001475050117200205665ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/pl/LC_MESSAGES/000077500000000000000000000000001475050117200223535ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/pl/LC_MESSAGES/django.mo000066400000000000000000000017621475050117200241600ustar00rootroot00000000000000 t"* /#;_di o yl   !     Constance configDefaultHomeIs modifiedLive settings updated successfully.NameSaveValueconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: POT-Creation-Date: 2014-11-27 19:05+0100 PO-Revision-Date: 2014-11-27 18:13+0000 Last-Translator: Jannis Leidel Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/language/pl/) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language: pl Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); Konfiguracja ConstanceDomyślniePoczątekZmodyfikowanaParametry zostały zaktualizowaneNazwaZapiszWartośćparametrparametrydjango-constance-4.3.2/constance/locale/pl/LC_MESSAGES/django.po000066400000000000000000000045021475050117200241560ustar00rootroot00000000000000# 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: 2017-06-13 19:40+0530\n" "PO-Revision-Date: 2014-11-27 18:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Polish (http://www.transifex.com/projects/p/django-constance/" "language/pl/)\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "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 #, python-format msgid "" "Default value type must be equal to declared config parameter type. Please " "fix the default value of '%(name)s'." msgstr "" #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" #: admin.py: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 "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "Parametry zostały zaktualizowane" #: admin.py:285 msgid "config" msgstr "" #: apps.py:8 msgid "Constance" msgstr "" #: backends/database/models.py:19 msgid "constance" msgstr "parametr" #: backends/database/models.py:20 msgid "constances" msgstr "parametry" #: 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 "Zapisz" #: templates/admin/constance/change_list.html:84 msgid "Home" msgstr "Początek" #: templates/admin/constance/includes/results_list.html:5 msgid "Name" msgstr "Nazwa" #: templates/admin/constance/includes/results_list.html:6 msgid "Default" msgstr "Domyślnie" #: templates/admin/constance/includes/results_list.html:7 msgid "Value" msgstr "Wartość" #: templates/admin/constance/includes/results_list.html:8 msgid "Is modified" msgstr "Zmodyfikowana" #~ msgid "Constance config" #~ msgstr "Konfiguracja Constance" django-constance-4.3.2/constance/locale/ru/000077500000000000000000000000001475050117200206015ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/ru/LC_MESSAGES/000077500000000000000000000000001475050117200223665ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/ru/LC_MESSAGES/django.mo000066400000000000000000000022511475050117200241650ustar00rootroot00000000000000 | !)I N#Z~  z:5,=Pp   DefaultFailed to update live settings.HomeIs modifiedLive settings updated successfully.NameSaveValueconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: PO-Revision-Date: 2014-11-27 18:13+0000 Last-Translator: Jannis Leidel Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/language/ru/) Language: ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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); По умолчаниюНе удалось сохранить настройки.ГлавнаяБыло измененоНастройки успешно сохранены.НазваниеСохранитьТекущее значениенастройкинастройкинастройкиdjango-constance-4.3.2/constance/locale/ru/LC_MESSAGES/django.po000066400000000000000000000051201475050117200241660ustar00rootroot00000000000000# 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 20:59+0500\n" "PO-Revision-Date: 2014-11-27 18:13+0000\n" "Last-Translator: Jannis Leidel \n" "Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/" "language/ru/)\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "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 #, python-format msgid "" "Default value type must be equal to declared config parameter type. Please " "fix the default value of '%(name)s'." msgstr "" #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "" #: admin.py: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 "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "Настройки успешно сохранены." #: admin.py:267 msgid "Failed to update live settings." 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 "Было изменено" #~ msgid "Constance config" #~ msgstr "Настройки" django-constance-4.3.2/constance/locale/tr/000077500000000000000000000000001475050117200206005ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/tr/LC_MESSAGES/000077500000000000000000000000001475050117200223655ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/tr/LC_MESSAGES/django.mo000066400000000000000000000035241475050117200241700ustar00rootroot00000000000000,Oh BOoW #bz  HVw<  [e(uu3: ? I   CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.Constance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.Current fileDefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.HomeIs modifiedLive settings updated successfully.NameReset to defaultSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Ozcan Yarimdunya Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); CONSTANCE_CONFIG içinde mevcut olan alan(lar) için CONSTANCE_CONFIG_FIELDSETS eksik.Constance %(config_type)s tipinin yapılandırma değerlerini desteklemiyor. Lütfen '%(name)s' in değerini düzeltin.Geçerli dosyaVarsayılanVarsayı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.Ana SayfaDeğiştiril miCanlı ayarlar başarıyla güncellendi.İsimVarsayılana dönKaydetAyarlar başkası tarafından değiştirildi. Lütfen formu tekrar yükleyin ve değişikliklerinizi tekrar kaydedin.Değerayarconstanceconstancesdjango-constance-4.3.2/constance/locale/tr/LC_MESSAGES/django.po000066400000000000000000000061331475050117200241720ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: LANGUAGE \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" django-constance-4.3.2/constance/locale/uk/000077500000000000000000000000001475050117200205725ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/uk/LC_MESSAGES/000077500000000000000000000000001475050117200223575ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/uk/LC_MESSAGES/django.mo000066400000000000000000000055761475050117200241730ustar00rootroot00000000000000\Oh! o8/h m#yb0!R Y cni3Mg>tO ^ ;v D    E 3 L e     CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.Constance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.Current fileDefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Failed to update live settings.Get/Set In-database config settings handled by ConstanceHomeIs modifiedLive settings updated successfully.NameReset to defaultSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueYou don't have permission to change these valuesconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: PO-Revision-Date: 2014-11-27 18:13+0000 Last-Translator: Vasyl Dizhak Language-Team: (http://www.transifex.com/projects/p/django-constance/language/uk/) Language: uk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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); Одне чи кілька полів з CONSTANCE_CONFIG відсутні в CONSTANCE_CONFIG_FIELDSETS.Constance не підтрумує значення наступного типу %(config_type)s. Будь ласка, змініть тип для значення '%(name)s'Поточний файлЗа замовчуваннямТип значення за замовчуванням повинен співпадати із вказаним типом параметра конфігурації. Будь ласка, виправте значення за замовчуванням для '%(name)s'.Не вдалося зберегти налаштування.Отримати/встановити налашування в базі даних, якими керує ConstanceГоловнаБуло зміненоНалаштування успішно збережені.НазваСкинути до значення за замовчуваннямЗберегтиНалаштування було змінено кимось іншим. Буд ласка, перезавантажте форму та повторно збережіть зміни.Поточне значенняУ вас немає прав для зміни цих значеньналаштуванняналаштуванняналаштуванняdjango-constance-4.3.2/constance/locale/uk/LC_MESSAGES/django.po000066400000000000000000000076111475050117200241660ustar00rootroot00000000000000# 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 \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 "Настройки" django-constance-4.3.2/constance/locale/zh_CN/000077500000000000000000000000001475050117200211545ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/zh_CN/LC_MESSAGES/000077500000000000000000000000001475050117200227415ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/zh_CN/LC_MESSAGES/django.mo000066400000000000000000000025301475050117200245400ustar00rootroot00000000000000H IhS #bfl s }2SB N-18H    ConstanceConstance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.DefaultHomeIs modifiedLive settings updated successfully.NameSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: django-constance Report-Msgid-Bugs-To: POT-Creation-Date: 2017-06-13 19:40+0530 PO-Revision-Date: 2015-03-15 18:40+0000 Last-Translator: Yifu Yu Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-constance/language/zh_CN/) Language: zh_CN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; Constance模块Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。默认值首页是否修改过成功更新实时配置名称保存设置已经被他人修改过,请刷新页面并重新提交您的更改。值配置Constance模块Constance模块django-constance-4.3.2/constance/locale/zh_CN/LC_MESSAGES/django.po000066400000000000000000000052611475050117200245470ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Yifu Yu , 2015 msgid "" msgstr "" "Project-Id-Version: django-constance\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-06-13 19:40+0530\n" "PO-Revision-Date: 2015-03-15 18:40+0000\n" "Last-Translator: Yifu Yu \n" "Language-Team: Chinese (China) (http://www.transifex.com/jezdez/django-" "constance/language/zh_CN/)\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: admin.py:113 #, fuzzy, python-format #| msgid "" #| "Constance doesn't support config values of the type %(config_type)s. " #| "Please fix the value of '%(name)s'." msgid "" "Default value type must be equal to declared config parameter type. Please " "fix the default value of '%(name)s'." msgstr "Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。" #: admin.py:123 #, python-format msgid "" "Constance doesn't support config values of the type %(config_type)s. Please " "fix the value of '%(name)s'." msgstr "Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。" #: admin.py: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 "" #: admin.py:224 msgid "Live settings updated successfully." msgstr "成功更新实时配置" #: admin.py:285 msgid "config" msgstr "配置" #: apps.py:8 msgid "Constance" msgstr "Constance模块" #: 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 "" #: 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 "是否修改过" #~ msgid "Constance config" #~ msgstr "Constance 配置页面" django-constance-4.3.2/constance/locale/zh_Hans/000077500000000000000000000000001475050117200215455ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/zh_Hans/LC_MESSAGES/000077500000000000000000000000001475050117200233325ustar00rootroot00000000000000django-constance-4.3.2/constance/locale/zh_Hans/LC_MESSAGES/django.mo000066400000000000000000000035651475050117200251420ustar00rootroot00000000000000<Oh jwo8( -#9]bsbx  dKbS  P9jN DNUe   CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.Constance doesn't support config values of the type %(config_type)s. Please fix the value of '%(name)s'.Current fileDefaultDefault value type must be equal to declared config parameter type. Please fix the default value of '%(name)s'.Get/Set In-database config settings handled by ConstanceHomeIs modifiedLive settings updated successfully.NameReset to defaultSaveThe settings have been modified by someone else. Please reload the form and resubmit your changes.ValueconfigconstanceconstancesProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2018-04-19 11:31+0800 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; CONSTANCE_CONFIG_FIELDSETS中缺少在CONSTANCE_CONFIG中声明的字段。Constance不支持保存类型为%(config_type)s的值,请修正%(name)s的值。当前文件默认值默认值的类型必须与参数声明类型相同,请修正%(name)s的值。获取或设置由Constance模块处理的数据库配置首页是否修改过实时配置更新成功名称重置至默认值保存设置已经被他人修改过,请刷新页面并重新提交您的更改。当前值配置Constance模块Constance模块django-constance-4.3.2/constance/locale/zh_Hans/LC_MESSAGES/django.po000066400000000000000000000053411475050117200251370ustar00rootroot00000000000000# 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 , 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 \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 "重置至默认值" django-constance-4.3.2/constance/management/000077500000000000000000000000001475050117200210305ustar00rootroot00000000000000django-constance-4.3.2/constance/management/__init__.py000066400000000000000000000000001475050117200231270ustar00rootroot00000000000000django-constance-4.3.2/constance/management/commands/000077500000000000000000000000001475050117200226315ustar00rootroot00000000000000django-constance-4.3.2/constance/management/commands/__init__.py000066400000000000000000000000001475050117200247300ustar00rootroot00000000000000django-constance-4.3.2/constance/management/commands/constance.py000066400000000000000000000065711475050117200251710ustar00rootroot00000000000000from 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: actual_keys = settings.CONSTANCE_CONFIG.keys() 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') django-constance-4.3.2/constance/migrations/000077500000000000000000000000001475050117200210705ustar00rootroot00000000000000django-constance-4.3.2/constance/migrations/0001_initial.py000066400000000000000000000014331475050117200235340ustar00rootroot00000000000000from 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')], }, ), ] django-constance-4.3.2/constance/migrations/0002_migrate_from_old_table.py000066400000000000000000000025721475050117200265710ustar00rootroot00000000000000from 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), ] django-constance-4.3.2/constance/migrations/0003_drop_pickle.py000066400000000000000000000042541475050117200244040ustar00rootroot00000000000000import 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), ] django-constance-4.3.2/constance/migrations/__init__.py000066400000000000000000000000001475050117200231670ustar00rootroot00000000000000django-constance-4.3.2/constance/models.py000066400000000000000000000010141475050117200205450ustar00rootroot00000000000000from 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 django-constance-4.3.2/constance/settings.py000066400000000000000000000021441475050117200211270ustar00rootroot00000000000000from django.conf import settings BACKEND = getattr(settings, 'CONSTANCE_BACKEND', 'constance.backends.redisd.RedisBackend') CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {}) CONFIG_FIELDSETS = getattr(settings, 'CONSTANCE_CONFIG_FIELDSETS', {}) ADDITIONAL_FIELDS = getattr(settings, 'CONSTANCE_ADDITIONAL_FIELDS', {}) FILE_ROOT = getattr(settings, 'CONSTANCE_FILE_ROOT', '') DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND', None) DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings, 'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT', 60 * 60 * 24) DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '') REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:') REDIS_CACHE_TIMEOUT = getattr(settings, 'CONSTANCE_REDIS_CACHE_TIMEOUT', 60) REDIS_CONNECTION_CLASS = getattr(settings, 'CONSTANCE_REDIS_CONNECTION_CLASS', None) REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', {}) SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True) IGNORE_ADMIN_VERSION_CHECK = getattr(settings, 'CONSTANCE_IGNORE_ADMIN_VERSION_CHECK', False) django-constance-4.3.2/constance/signals.py000066400000000000000000000001021475050117200207170ustar00rootroot00000000000000import django.dispatch config_updated = django.dispatch.Signal() django-constance-4.3.2/constance/static/000077500000000000000000000000001475050117200202035ustar00rootroot00000000000000django-constance-4.3.2/constance/static/admin/000077500000000000000000000000001475050117200212735ustar00rootroot00000000000000django-constance-4.3.2/constance/static/admin/css/000077500000000000000000000000001475050117200220635ustar00rootroot00000000000000django-constance-4.3.2/constance/static/admin/css/constance.css000066400000000000000000000010711475050117200245510ustar00rootroot00000000000000#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; } django-constance-4.3.2/constance/static/admin/js/000077500000000000000000000000001475050117200217075ustar00rootroot00000000000000django-constance-4.3.2/constance/static/admin/js/constance.js000066400000000000000000000022211475050117200242170ustar00rootroot00000000000000(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 === '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); django-constance-4.3.2/constance/templates/000077500000000000000000000000001475050117200207125ustar00rootroot00000000000000django-constance-4.3.2/constance/templates/admin/000077500000000000000000000000001475050117200220025ustar00rootroot00000000000000django-constance-4.3.2/constance/templates/admin/constance/000077500000000000000000000000001475050117200237575ustar00rootroot00000000000000django-constance-4.3.2/constance/templates/admin/constance/change_list.html000066400000000000000000000053741475050117200271360ustar00rootroot00000000000000{% extends "admin/base_site.html" %} {% load admin_list static i18n %} {% block extrastyle %} {{ block.super }} {{ media.css }} {% endblock %} {% block extrahead %} {% url 'admin:jsi18n' as jsi18nurl %} {{ block.super }} {{ media.js }} {% if django_version < "5.1" %} {% endif %} {% endblock %} {% block bodyclass %}{{ block.super }} change-list{% endblock %} {% block content %}
{% csrf_token %} {% if form.non_field_errors %}
    {% for error in form.non_field_errors %}
  • {{ error }}
  • {% endfor %}
{% endif %} {% if form.errors %}
    {% endif %} {% for field in form.hidden_fields %} {% for error in field.errors %}
  • {{ error }}
  • {% endfor %} {{ field }} {% endfor %} {% if form.errors %}
{% endif %} {% if fieldsets %} {% for fieldset in fieldsets %}

{{ fieldset.title }}

{% with config_values=fieldset.config_values %} {% include "admin/constance/includes/results_list.html" %} {% endwith %}
{% endfor %} {% else %} {% include "admin/constance/includes/results_list.html" %} {% endif %}
{% endblock %} {% block breadcrumbs %} {% endblock %} django-constance-4.3.2/constance/templates/admin/constance/includes/000077500000000000000000000000001475050117200255655ustar00rootroot00000000000000django-constance-4.3.2/constance/templates/admin/constance/includes/results_list.html000066400000000000000000000051371475050117200312150ustar00rootroot00000000000000{% load admin_list static i18n %}
{% for item in config_values %} {% endfor %}
{% trans "Name" %}
{% trans "Default" %}
{% trans "Value" %}
{% trans "Is modified" %}
{{ item.name }}
{{ item.help_text|linebreaksbr }}
{{ item.default|linebreaks }} {{ item.form_field.errors }} {% if item.is_file %}{% trans "Current file" %}: {{ item.value }}{% endif %} {{ item.form_field }} {% if not item.is_file %}
{% trans "Reset to default" %} {% endif %}
{% if item.modified %} {{ item.modified }} {% else %} {{ item.modified }} {% endif %}
django-constance-4.3.2/constance/test/000077500000000000000000000000001475050117200176735ustar00rootroot00000000000000django-constance-4.3.2/constance/test/__init__.py000066400000000000000000000001311475050117200217770ustar00rootroot00000000000000from .unittest import override_config # pragma: no cover __all__ = ['override_config'] django-constance-4.3.2/constance/test/pytest.py000066400000000000000000000035531475050117200216030ustar00rootroot00000000000000""" 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 django-constance-4.3.2/constance/test/unittest.py000066400000000000000000000053141475050117200221270ustar00rootroot00000000000000from functools import wraps from django import VERSION as DJANGO_VERSION from django.test import SimpleTestCase from django.test.utils import override_settings from constance import config __all__ = ('override_config',) class override_config(override_settings): """ Decorator to modify constance setting for TestCase. Based on django.test.utils.override_settings. """ def __init__(self, **kwargs): super().__init__(**kwargs) self.original_values = {} def __call__(self, test_func): """Modify the decorated function to override config values.""" if isinstance(test_func, type): if not issubclass(test_func, SimpleTestCase): raise Exception('Only subclasses of Django SimpleTestCase can be decorated with override_config') return self.modify_test_case(test_func) @wraps(test_func) def inner(*args, **kwargs): with self: return test_func(*args, **kwargs) return inner def modify_test_case(self, test_case): """ Override the config by modifying TestCase methods. This method follows the Django <= 1.6 method of overriding the _pre_setup and _post_teardown hooks rather than modifying the TestCase itself. """ original_pre_setup = test_case._pre_setup original_post_teardown = test_case._post_teardown if DJANGO_VERSION < (5, 2): def _pre_setup(inner_self): self.enable() original_pre_setup(inner_self) else: @classmethod def _pre_setup(cls): # NOTE: Django 5.2 turned this as a classmethod # https://github.com/django/django/pull/18514/files self.enable() original_pre_setup() def _post_teardown(inner_self): original_post_teardown(inner_self) self.disable() test_case._pre_setup = _pre_setup test_case._post_teardown = _post_teardown return test_case def enable(self): """Store original config values and set overridden values.""" # Store the original values to an instance variable for config_key in self.options: self.original_values[config_key] = getattr(config, config_key) # Update config with the overridden values self.unpack_values(self.options) def disable(self): """Set original values to the config.""" self.unpack_values(self.original_values) @staticmethod def unpack_values(options): """Unpack values from the given dict to config.""" for name, value in options.items(): setattr(config, name, value) django-constance-4.3.2/constance/utils.py000066400000000000000000000030471475050117200204320ustar00rootroot00000000000000from importlib import import_module from . import LazyConfig from . import settings config = LazyConfig() def import_module_attr(path): package, module = path.rsplit('.', 1) return getattr(import_module(package), module) def get_values(): """ Get dictionary of values from the backend :return: """ # First load a mapping between config name and default value default_initial = ((name, options[0]) for name, options in settings.CONFIG.items()) # Then update the mapping with actually values from the backend return dict(default_initial, **dict(config._backend.mget(settings.CONFIG))) 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, **dict(config._backend.mget(keys))) django-constance-4.3.2/docs/000077500000000000000000000000001475050117200156675ustar00rootroot00000000000000django-constance-4.3.2/docs/Makefile000066400000000000000000000011721475050117200173300ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) django-constance-4.3.2/docs/_static/000077500000000000000000000000001475050117200173155ustar00rootroot00000000000000django-constance-4.3.2/docs/_static/screenshot1.png000066400000000000000000000641341475050117200222710ustar00rootroot00000000000000PNG  IHDRY\=sRGBbKGD pHYs a @tIME /):}tEXtCommentCreated with GIMPW IDATxy`řV-Y-Y, c !@k؄,$a !@@e7!GB}d˲eْu_3澻~H%K0=駪Z߷ c A.@AAA-AAK@AAA-AAK@AAA-AAK@AAA-AAK@AAAd*@V  F  Z [2OT_~} 0j٢rmկl9CZ )+*/RTsብI?uahKBX}ˬ4-onUmwh~/_?JϚ+zBcwa U0``c,\#rZp'Y@NYx׊xzA0X=|+!E{S:?C%*\_n/}2)KmG?tPwxs|"CDx>nKXz&Kˆޑ=f8]{v,u{lxrR}30˜Y}q0p?ج"q[y{ϚGspW<~R2Y(RkjBo*wݔN4/Ys/G8NVq]1V{`,v|zC(-jd+54{W;E7>S̛V䘜vm?Ew\x3W *uͺ΂H҃\@?y/@zG}-V?E>bnden1ϝ WZ{sض[a\ﶕW9bEP..U;z\{Vf34]-嫏M R|h؁cȱwŘeY"r9E4D cʑF<&15,Сl)ʇץֳ I$s)8h d>t1<_Z>2cڽ~HhL,ϧTq |ZgYE/>E.Iibx?9[ & 8}96s4ƵĸAK.JL iBea_lݜw{Yxt@wHf!";L-C! 1<4 0^ ./&=CZ%C-SPQ¹ B77f?x_diM vNy, %(L$pF٬U?+v7hW&~2t-r/\ar 6(tucC;}wT񳁱 \YEJrZ-( zN){FJ|.˰-u_dl wI(nke\iʍ!;`)$W3L.!˜ V75dJMqDQf8}q4Y@ds^s ,, A_FL](x¸u!G/W~i2#, a) @ƒ2纳\_W͇'q~ ܳڽj{ԿxSvip>y/lnڽvR _[vqFݪ_ݫI.{~ٖ e>`ҡ'4 #5~Yg⿟U> Ly& UڳOyOpI?|#}p7 rñy8ɗNBK;ȣ \8&q'Jgy2 2Ǽp}Nˋ9UӨ^7.gD n_*]WZrͲoftU/>+ AB2Ar߽ %  h  Z  %  h  Z 2Qc {A(AAK@AAA-AAK@AAA-AAK@AAA!J}m#)v_V&S˦\`K`޶~@6&$e,YsL9Ceg,!yRIrB!#Am {3d[[.âaKPG<Ӧ*jef5p3a^Ps?E2ڷoXhzz,O=:&8mjNa9̯'x+t/>Bӿ??Z`2`ʕ20RypD,WӭkNw9CrCj^5k֏cXkWO'𚸌ҥW/_qӧ^{>(l}]'zʄ7ucco|r)* W~"#lŵ[1qZuw|}t-U˂=՟s8)!%mZ57.J݇CoG{O>Χas zhl6~Z~[0M=A.'h-%yu p ė,1rvx7~|U 4Mt{6t#5?ϛsռ8^sɣό6?Y*VvȖO_|QJGg5-9ZMƎOUXx~ni ^x^w~~}5B?u )tS-L]KPW @ϡ^:oH+(lł~ta?|;>H;r4ֽhgZ|F,~ѿ'+G,3_.Fk6h aAo?>\L\~`}m˦Aw>8:gλ ._] nmݿ{Vޘ2vo}oZiؼj=Bփ% =*j;W(Ϩzo(XvɌxw@ؘ}uG;7|jK4ßco{yGc&A%}7ׄή]u6ᑇobSJC|Ԝ:6l.e]]+ל ygeI#7,IdG]vm8#ҕv᷿jO~u3j~kU4Ӿ}kmoy*i߹6 0K|w/0r3^70Gx1>1%=-V''OYV?|j)6f_)_~zij}Sj PP8#jmYWW-Lp7riݡCGNjhhã{5 +:{p! ܙ5 0%}_ɿXj~N*Hr#DZGnLbA8^ޒ0#I "D6J^kcF*2W\X 896~ZNe+^>'Nv}5#M$Ti<Al *p鍅K7O4'4KsΝǸ9k./~lbL% ;k&ZeDU⋊ۺ-=v?6|'|&`UFwjS%t Scu´jNXbi'MǴBƒJxCz zohJp0}ݿY` _=,dL1±~ ,@ge*?cJ.ks6fч#G_X8OJs7vɌt\5iˊgd'{jM@AR@Ǿ/ՙ:13ݨ]Y=~=oU7,/J3BG{ 3GXphm R=ΡȓJK̟oxJmK3mϻ=S}~?I~Q^VjIO;"}8O_M|Y%8y4"%rN1OGg|s/$Td*o{;ڸ` `@?L%(A JP2%NA;GHLz3JPdJ&%P $l+ ʔH$}Lڏ0%(AL>JTIX$v` P%(A JP2$` R|@_VPmi_0>ьՙ$kt tX0T Kbb!PYo#I(P)R,m%(A JP2dr 20( g`e d0i16$EalZP%cIF'm#^olG$Bm4 $ td3R6"!¤Ȉ1 C%(ds MD&%@R Zmj6ə12z=0ʘHPv_|:#+.oxQFT3"tlѣ t)S6a(A JPqJFsO?TUU:C);|٫ þE JPL)I1'RJ#7E4 8,|OZ$*A JPLY$GC:D" a!r\ԝP%SS2GC,h<2xaLZ@ JPLMɤ,Ad; "|Rd{pEvC JPLIɤGSH؎Xikd$%(ATL6)Arjv  %  h  Z  لNX 7ƛ n[. ȗǪWF?o~pla㕩Oν?_[>7ٱzg;ZB ȗ/?ڷBo_F:`}Uqlb:e_q!nYT=,tJbS'OSӯnIЊZVfƞ^-A.8Ajuz:D%I5{FElA5q2~xgsGԳg-W,9?NAd;n/vZ9ОMOQ^Qv<`qmGm}c{Z; .*SRk퉗wXAJkι1C>WJ~P8}6 ]KT\sa7Yp{"*S ݇[[oYZaߟl %dd>=.;^ *A.|Qs0Q8ή Pw[%K~X{?;3`kf,x v>B+('닙5_=`XrSC.\E# ]o4494z~ꩆ``.*p=ek\/t<64n{W_{4?P9+ I9Y :XuYy{8X4p 7czYcU7X~Pܲ2WC _2Tg @`08͘.#|nF {b89wW+e$|a: j%ֽ܃L3+SjvS)vࣖ.[^gȹSbN2onv8eC3e_|,-X48Ej=V'B. 4Z=@ OXh @uNo@J_(tڛ[|}R|2C҇3|>ks˙#`)P5@PZ1&2";た#nh|@ܭn@zwsGݗO ,zZ7i=sÊ s5˺(f_[PTst͏~:'לu"{~Ȟ?fH.6M 5]=ugo=`ڂ @ꋕԤd \&Dg V!8k8syWݡGvt-! g)}v(B#m<?XrS{^۸xcd?^t'캫xY0e+3(MP1b?GBPk`LaIT=yS[II_u x[ÎS#G)>va޴40\8! n҂]jTںֿ7** IDAT֗%' 7wG{G]oɴKg}gND=}t[F΄WJ\ 겲5whW:(5~)ÍqBH0sќoo'Td긄kIUs/mXLL՝˧85ٱ9A#< O{WY@]zCIj~TUU]_3֧"r1nU&-gGrv*"/>r'zkh;^W#2Rw,ox3W}x©L׭OQ rm`! h  Z  q8lS h f{ AG Z  %  h  Z 2~&$ԳYOF0="W>9A_<oonqv }Uqlb:YԞ[6N\#_e{^XuY8i w+ wi%Л`|x'RmՍ=bvv?")y kgW&"QΚ%'(gh썃$]ޭ JTRCt[|fѨ-7Ǹ`z߰8% 7}m.xT+SUi] #+,;tuz o{lO?S+yCEeJj=5Ht97f'.0}z\vJrATέijGF]{tGk(04xp*"8J9\1"B3 g}` 5ndOKuoUXggs,PaMÌ/'_he$|}1s& ޻ri%4430w6TT/3pi髧 K díC*>i[ro }:_M|P@A$ JCF1LPrC=ė{$r@Xeӱ!yԗbV F0|~? Fhr3žBk y4`q~n6Zua]2ͬLfSMإZnAtx>6ʞ!N:d29`b.'$qj#87GA;,V8*-ִ6_T)5pܣmò⹈@pZF+W4!ȕ3 g|3G^S>j`9]T'cLdDF#nh|@ܭn@zwsG q^?1xcMfjQdL`D +R0}ָ/ZyB 7^3G{׶%MoBIr+IEj'1]hܾn(MT2Gou]4C @t`Ջ+}>_([Z3?'pztkGrm{ٿpk5%w=絍:FK~Qͮ%{[vo)&(grlUrm{5p簋$srB 줤uhK5&'+A,$?88^aK;+!r* ޝ]&;ܢKԄH' #1Hw@EK/h1,Q?Tݹ|ӮXy>>Go{DХg=4&Yѩ*++_JA. SVk~{$Wk 29_{*/'y6U7 S)u֎W9sW?@'tݺ p1Z %1Uh %  h  Z r.5lٰAA.{ƾU ABA-AAK@AAs1*&Aˌ? ծHCl0'%7+3 (`Lz0_%(A Jd‰##0uB@""Tڈ%(’ G  F!>#I(# %(A JP2E%"UDcXh(E JPL1, NKѦJ@E}㢃3% KI@x= X4Βp%1P 2`K4JP\Y@ @@dzC hCBhR pd|Z.Ox!X$:asHD#'%(A(Ig∅'uz=Ǝl*&fQZ/#Zln|Nl,rޜT^ ULLܔҰQ(i8B_xI1T);~N,r cny1/ln eNU %d4lFSm%W%P \3$Q =a,mzLP{_IS)0%(I$< uXEBι;]x u}/zCW[]܆%(AɔLvQx?'1cZ]:;^}GPAfxgϮ򢴄nkg?8#{rx]ɜxEo}x'RmՍ="Z\V q0$8wղrS?߻t`AJJ}no 0~QϞݶ_ ; ѻﴻ>j@{*6m B+('닙5_=`XrSCS.͢䑆f@w7JyfsTB= ?}TCai00luȞ@Ň5 y S{:mz~OvW竉(Ȝĸ>Kce 7Z|Z<8ĸm)ͼK꘡^Ab ىgu"斕 _nyOܘ? L B~Vnts367W3AȹSr^DcrTXSL&#@[/F,)Nj\t1&3~9SsrG?@|NVn 爓U@W+}8ionno_ooMkI% A./=$}Q869U q瘟:c"#39Ɖ dwkܗuM<ٹS`mjoPdNSG8tu}[TuՏlaI⋣_=3CG{.b55(Y-`4 قU/|@Zni3|dUwѭ]gdȵ&fYJ_&q #ڥKL?f]d=-;\x,YΜ-sJ-4zTvDޓw.1UNha@t\QLsTȆHbf}Ce'\?N}q3sVLS[tH rI@sUpa! h  Z  qM/l6)A&)A˞oUCAp.AAK@AAA\⨪ A2cOB+ұA.L F y2F%ߒɤ%(b 'JX#|f,Pi#JPd K&%P $l+ "H$}Lڏ0%(AL>JTIX$v` P%(A JP2$` R|8&/Eb+ fΖP)R:+bTl /KKhAz]$%(A J<,Ad a Q 2|^YD!O4)TdQ8C2DS[уQBd],,d%(Aɕ'ힴq&Xx:\k(hf/OK3)<\=.1Nxnl=bbRBm}"cĘS^\ 7' p"*&Iz]δxy@c9sٹA%(%,i1 uMR4!edif xa)`cqe1fH|VQRkM4Lt YL41/khu X{=bx<%UkJfcT'+BjLA9 3j~)\L|,}B0>ܡ/d1P䒒L*J)BAt{!YfZ,#@BrLaH GYd+ SjΓE)Q*.ye'7'p(DBT$Lj$)%#v5Y0z,$"v]C%(A J.9ɤ,1BH[jntdrjnS*cc=ݡ@S?MW5s=:l;Fe,:3A#.EQ*ҊtxS&RJh@:R q:6 G0bd-Q!ܘq庠h&pɉ. uطBRE91ƀ ?|gPRLz.AAU,SV9PaHCm:&Qlx) cbx.QD*JunAI:mchg22+>W}eӧdgWH&g11R"5&7+I7;GT8Tl-J0 Rʢyz)wGN4u%d~fUUU8tb×J9,2[%(Aɔ*4r\dMC =ZcpJPdJ&80a%Y ;%(ɮ8 /a'(E[ctJPdjJ&e "yÖ"݃s,JPdJJ&8v2*E&/2'I-(A JP(%lXS BUUՈ9A-AAK@AAA-A7 +CvG[뷟^be DMF:6?"f|:ceJni0ELK֤I+U7Al~b:_"}ۏQ̉A͞6dwV'l~Gx„AxF !@wҳnNe_q!nYT=,tJ",:!/JKX|q32xĮ}V(K%)u%6}u%sVUD~gn[ ˯ Yr~pwv_xJr=U09ZyG<1K rx( Z4p 7cz7@^Av`'~ _ Vn{8#h#fVFf)ZnAtx>6ʞ!N:d29`bd ML*M6?wٍݾ?|Ej=V'B. 4Z=@JMAfqmD?&O=**Hlo~b`֛ԺMɘV$`(ܭq/&4Eɪy<8=?=k;V~?9kVihܰ(Gyo_r'Jm`] N?Pj̼kU" _1[;:.@誯~O8uI`j҈A/v\ %  h  Z r.ƵfaO!%455aO!\#I(# %(A JP2E%"UDcXh(E JPL1, Y)jP9p_djaIL,*R5::,^PRq&$SvZ\  1FP6:r?3o P%,#Adv+c\j# :=dEg(Q"˼ PeX\PtE:GiZgĸ|(t)= ꥀ9CL3h1J&FFBEFFqcJP䲒"$|47ٜ"}q) 8JPJeڒ2y!FH(J1-38/պ\[z\bBF2{̠G"e3o1%(A%spm644ΜaGQ%JRN"iEdZ*Me 6 %(A JPh% M u; ѕs%(Y8;Ģ;(MG>D&tJPdqJd 2n=R4=fP%RlbBU) SӜ";iL%(A Jd!mK!  sX5 Z  %  h  Z r(穓Smo}hccfg=%+Ml]$2 0WAO/o|}?]k zIaηrށ7k?n)Zg˶}ٙJF 2g%u7, Wwh>} ?IT&:r 漚[Ne7 eҫ7sc~~Lq(i>nue:ʱARiݫT-ܪLޑc5T=/+o,ކnɬ֍_U률Ii9 ް1)nԂGTƯܸ؄/C(~TuYL5K JPwɞfoE*ؿko/ ,k}յqԱp},fuA\Aƀm]+JTyyp|_Wl{`ڙJ`=wo*bU}~{C(zKy:r<t'% %]z:Fx+.'g3]gycrCcWTr{R@QEQyD3D~Λyg<a~ƻcP(#mVX/ VgD?WSe䭷{$̰Tn5(Jtq{ 64_K{=͗I)+7xNA/HS:tR[bymޛE=T"{Ş~ow5M_I)JrCrtE~a~(v\v~z Jo1(Q^笅@/?r+rȳ/$믵+ozjLw5癌IoI)~īu%PWr3:@ FgrD@d-7g?8;Gl_W}/Fc<\Bl/o Ba! MB ޲DZ۫*})Xͯ-҄HsOWwT߲>|d%aPgϱ16G,_f43zR ӽЈ+̢>~{Tv&;%%(co[gΎdfoٔ[aU*ӽwh~|ZQEF*pѝ_SIJe绚=: (jر ȢfQX.:wrӚ|_^g!Z 51~೽n{ %  h  Z r)(xAA{.T AA\  %  h  ȥ&AUg`!\7DG,r`(A JPE*Y(!z1 '@a(A JPE&Y%#"(u%:pxɨ(Sb @# gEM. JP䳐,dԹ6c}t1j=2 @8EՐ5g$TǔAi&ji>Qqc(A JP+̵& Gl"q=1S|4y ]媄Mia Per5zFGM_P*AS4k#_U!{3@JsMiZ  Sefr51а6"2Y$f~6%(A JnF tq"vCA,Ϻ>xy_ gJ@{%-Q \JVҪ.;3"kroVȇυL&Pk֮`eJDJTMUW4, 𡥱ƜP/1sb%xhbYl0 U>jI Z]Z U_/HĄx`2Jft ERF$ & "WYPz,h SYk$tv uvRoTbj@).aɔt" 2e dFe0J)(Y8)rPFRy{\h"D1Li4OL{3? &1PƐ,#Adv+c\j# :=dR^P2R ܸ"FiZzetf3@=6jc@L)k'r88 KEeiɜvDKѳR8:=72x",2b ȌBD@ JPD\Bd0D&U񁱏;<0BZ,]-1:͇tT8qXjҐk3dFidxn̔$^3w*Rj%. 6QIJeGG"PB|(R&G'&4G JP@2fCCC8=O1iqH8!֤;f? 1%($qJ).iu D֘tU%jxwY_W*(A JPrH8w$,:+&JdG-L%7d3"XtXdfD%(A JP8% EW@D7l)}i(A JPE)YPQ6~yb!M^iN4&JP%QKضA녆Y9A-AAK@AAA-Ab|ө7> Kޒ&~ ~TEM A_8,;>mxƃ'cה6@ks@^A>s;~606W q/5U?|ȶ=CEz.F h@,PMIa`]M9:>[K𹺖j>zIaηrd9vUKkG^vWsށ7k?n)Zg˶}ٙJ*!uu6i}:<}%G/+0oǻp_hOI"\9VS#OV'.__Vg%ߪ %%ܸ=u|^4|3˓cѕYI6쭈Swp~Pz6n:Ŭ43+(pkEI3//UCkmL;S)lMEjoo@xC|)OGB߲?V$W6[c^OGH=s8BPř"t˂33[iL  e#n%7&w?4fxI%7'E_).0 A^řGA.y_ =ah&xs[ JdFl9k|$ (crኜc8$Kkʛj2/uy&c#*n[R q je 8EhhNP$l.Q@`pqZG}t^`0hC.xڒQi~,*ԓ=^LKYa)Ex~f2[pCPXEP==m{̪k;Y"Mh8g-~_|uGZ~ -M?{oMV&u_0ks*y$eF{<+<+]2pI,'MEkwkSR>Q2uV숽 MfnMf.5K\ZOz8>jUH%?P)pY9ӗmwwUjҭwd`nA"~p!Lh$.J 6 `C [lg:3f+WyŪo?8 .1yӦٻ^C:%p)d589Y" ʚ?{ ) j24ιihh(++JARMx[WN%IV(UBj~2|7aǾ4 Eb8hyVNk}y)Dh  Xł  %  h  ȥBAK0XS =8BAAA-AAK@AA+g|ө7> Kޒvշv?[ ?~ '!ȍ{VO۵%0=G5eM%)$?U*g߹>݊OLCJ?}w^[\Mp8[|ZZpd۞њbIDAT"z]d{Y Ew?Q&$GǰJ1SSJ2<(r9jNA 6ۡ> yJ>WZ'Q/)[@;{~?Plٶ~5;7Gluy^;Jl^4V`֏wІ$*\GEjjs^$2kYGN\$(/<ϪnsqqFBwM/ޟN:UX7kxeտ@W7(R6ڴ94,% ȵxŅS$a [}_NU5%Zn9M'$FK<.\Tn]zu*vDOu͉ Di&5:xCk+ի5> eҫ7sc~~Lq(i>nue:ʱARiݫT-LLޑc5T=/+o,8}LM1Os/ⱢKArDc1Ás}aK!}c:Sҥ٩$Y_1ES !_χxQΩ|==gz&}犩O~ bд x̴hߘИ{$ܴ-~D|(yQ/vrJL?]ͼ3TnݱjP(yy}+azk?yOSe䭷{$̰Tn5(Jtq{ zZ漤aﻧ5)eƲr`O׉ 2 ,h/U bM M84-x@kn7z3/DrK=n?kzRtȋPU;D#0AƛbP2&3$3Y ;> ^@~ W!@"!y^]yS[Mf:9dLbDxKJ!`$^.,Uսz\ ։1 %]jf{ڞ:.? /5Yv.^75~pwO'  6:?ؾ <^LKYa)Ex ق_ B.i3.eYcmW9+3UBS_[ {=fdOWwT߲>|d%aPgϱ1>6JI|Ѭb%J/4O kC#> \2kIS*ݚ씔{mU:;bosBeSn7ퟌ bKWXGC3kR_|KwWthaBzgtg:|i9V ﶻ;*5{;r20 ,?&4yW%G;|mtb0T-3?~Dh#nyqWAP7mʘ_?>X )NaKVcQ!Pj󿎽'`Ȱ=&]bʮ_U{kETmSz/V>SIJe绚=: (jر ȵAZ T}qgc; iM_//3ؐs%  vAAK@AAAR\ь#ǃ5 Յ5 rsI AA-AAK@A.y]"5IENDB`django-constance-4.3.2/docs/_static/screenshot2.png000066400000000000000000001766631475050117200223050ustar00rootroot00000000000000PNG  IHDRRBE oFFs9p, pHYs  ~ vpAg@wIDATxg`U7%m{H%$!" ࣈ""M""w{ly?H !$Qf633ѢuxP]}]}]k 2ʕ_]^~qr9kÇ'Nyן~JQboV'%iZDKoѢ5|6dͻ{W4`KKŢ(k5k[uUt4-Kυ<*CރR ػTjfFtk\nqJ$Y[بREE)<s>Р 4Ȩ=z4-N p{R >p\IJrccbZ H sq!$#}--92$S,-MIONMe|OnݤRùxFkMWW*]MLhOLIa2bcc{R(lrMM5IǓ>Əd>ر,L!C<=d6};^b4L6oGEhw'Nhs*V^ >VO2g^uS*++x3gRRX*>_. J}|h4:nР޽R6x'1|}p[[-&&1 Vo($V;n\u*Ban.wؽ{\YY,VNN}=I뛙)RÇNfDBAQ$Y]]SC r;{LF}Nz:^VF76>yXZr E~~~ TEG?x$$32uujuG[EGttƹY5 uh..Cn޽X|dJLqw8~|B>?-,>^sM,Vx1>IVV=[RR="gp~svn߮(,Y+"<\_?'ƍB==-2eРYƎ/0p??ss(5U*oRCGCC/͛%g32"P/7 ! ܯ\^TTYY_֧[H\^WdDPQXzPK*ӛ5kz˟w;}:/߆ol:ɉ /$> Μٶp߾rCLNf2? JyHBruG( ph+W34즣sIIFUWA<9| H2!)Co}N#>˽rn({rgϾpl`̎E9;h11]xNSSnjZ>IG.]ڱhϞ5efIm}44o~ LfS_H.73Nᇀư0##.w,,B1`EDqW_:ֶvH\S$9Դ-({6lx6Vtse,O.. IrXʊQ*i4[[wwX,:D*Ul>OΝ?B!޽YYgL|}ib0tT*%IOA֮2Dڱ?YJ(MGKKgNGCC$EYc2XԫWٕ0ADDXPxvZZAA[/%اV+Mc0(nkT;e)$IɊ|˝U*&3#Fk)(PR]XQ)J&c0)+#p;;ؘ mPPBB\\j{2BQ4ZBNUWcURT!p66VZ/dVVW0yymŽ6j5A44xG>B(޽[jOF]g;{Y4e~ne%A `k`>`4I}^^NR宮A/_׺iZP& 27g/\(-^~H;|ϣ]:\x5 H$eeyy<.I44J11"р::::7oJRBаM.$9Çwt=ޞd2i4jks**Ztt M;q++MRB$Ξ?رzϦN HkkwϞռE{nb[8ݝ75T;wNξtw|||KK]]֭SS91Q& vs`ARIQ2YϏ $S>}zӓH^}wΜvoOGF[Y%';gUUj$+ر XS>>6uBqǏsBPTnmW{XL&VWW:uٳwJ饵xq^qqޭ l33_%Kf֍$E##??C;V<|86V*դtCCG꽠9wBl&mKSR *UqΝw?dE&em/I2~EXZRQFřϝ{HQQcc,. 0az{wٿHqv~}Ӧ]~$&N>=:xO  ccO>:W@ (`׮|٪*z.,LH<=uu℄ӧ-x$MMrƌ^||FkE\ޠA"ѣ%%$b%wkW;$U|?>MFqzzGI%%uqq_јLb4R*IR&kNWIe2}ַJ|j"fd#d2$:uNWlltt ʖCo E}= D=U*;;MX,+kmpBAgdDhRihHDۃ}+1M:59-)a2SYWdVTA<;اPDtXrKگ &X3ڞm/ZϦP0?%Ι{}$0LfbbNsrJ UZڙ3x|8:υS]EI$"Qllm-ӻ5qd~^` аeKnn]&&LfuuU>[RQAuDLf^GddCR\NQ$i`~VV7? 7~{oHi4$!CƏ77s'1qʔ۷e2S`/Ǐ75}`ֲ2[Y,.ѱ*#cܟHt;;']fM_>ɴwrI]\>P*ۗ =:p=&w71qݺB&R,._zU( U*6_?cٳ'MH冇yHEPXYP'LPxb۟`X,{'?4:Ų ۾}Ҥ@ԫW 29ҥO>4!5ήOMƎvm[9j>U*]݈c{щIbQG$۫уddDGCM޿'ozg2,-548x Ǝuwsg֔ e0,-gΌ04<~]kkC4a>'(kK$gΤjP 458"Ύj "QttrD}MMEE]]YL_*jB EEE]]Eca h娳}B=[Tt 4) JJ֭Q24 66o*ym#"\\X\^QӖa^-'++*^ZGO|][Ug{ >30P&d+*6(oo JJ,ٿ˭zr`+/ώJEDѽRib`p&S__01iyB!A }2\nUӿ0*Ki{&ڜ* RN  S˿J%0'&jjR{CúuK3fT^z$x} wytsL$nj12 RxtX"Q)jP77T.s'?_hK4R)رeKbɓuuϺ+pZZJQmlۚEQ E1.jA5x\-lb0BqZtÇgVWs8OloddcqʲǽBQWP_ۣ9uÇGQj%OQl*y&b==kk:p`FZG}Æ u{K55Lfuv8z:嚡AQ::::#\ T>==3ɓ=N卍nUUq8l6A$iniffiYYYSYiolŋ?5 v;u'lcAVUW7c'dl&eORן?bIϨQ}1MK::..#Gv릫hG!e2zL"I>0]kkܩ׏ФF33tqiOټ<]ퟬw.Z_ܜ戚==-,=c`xvvQ__VVXxqq6;P]maacca)鶯I ENNjj]=zٕ66޾]Vfh8mڀFFEE~sryMͭ[UUOҲ2/mgb~ukW5caabQWWYڗKR33K* nݺu{T*DִٳOHtuJM Jgg4(ܜB"a_ ۺwp!3F M( Tӧsr T<<<=%N B~Y=ʺ~]W/&Oor߯% gg$2̌FӤD%I997n^QW>|xM͊VV<^llYIRȑ Y_n۞yx;H2; ]7CC'Nrݫ!KKmNE"P_UXrE}V__Pܿ_X`h|RH4ŋ ƓI,?N(bpͥe2FKJJOgobh;:+\&%UUt g\Jp23kjonP}ٳlR5S %%'OFGy?/__}wp +(?_.o 9rn3fLo߀"Q~Ç奥R)uFQ?nh8lXXد~E$RQTS_tiqq}&; P(JJ÷mP Pwό m=5$d6iҠA7͞LQbq||tŋ2&<޶559/(-/r--kkSS$srtuy7 Rm[U:u{Ԕ]aֶ3Q /\8|ޚ4i߾0(??'X5fo뫧WSPPОҥ_}A*UMMrE6ݸܵ]=v,r߾3gW1㭷[$ ssJJѣ2YEѣ 4ڗ_Ν;ӧDEEmѢuuV\ۻ5JN/..+c22283g՛⒞.̘qI&IXJ 'ff@BAx;vܸAwsO߿z22LQ~~UV~|;O^8cctƌ(("^b)F+(rwz0a֬z;ewt>_.6l@hpL.Դ4oϞ˗y[2ڣ3J 4˥iӢD(GGi]W0c|A}}K2}䊊KYʜut믩SeCLL/NOoyQt*,,,L$pq]\jG$ʊs_9vC:T:] hl\㏅B۷ ?…=ͻ4Ο(:Rs꯿&;l).<:0^Ձh4[&  Ft);=wZtR9foVUq8鍍tJ&DD٩Ivåʂ}LR9aBRI߻sg~7@VhYYyy FHOco4V(d2oݺqCWw׮9Jt鵼: E5 E E E E E E EEQx1Po: "t:N t Fa>.≞}2YE՛7QlvSssq@*%($| 4t@L|]T*ػw{`0 \ MH˫ (J"I.յGnLMή5JKJ/_y3/O"X,cc?0Wײ7 17g2q)E54dgYs 6f̸q!!s$I%߾]_Td Tbql%%<^@9GQRia͛ݻG 8bDzzPer> kj$gn݂rs b:'<< ୷<<8MC^t)#B\Pj566,*-$HkZX]-1FFzzSVK$7o޸Q[dh--;ٲy<& (J(MK[ĉgwqvlwLVP{Z~߀\n듔(RifPeaf?obeRZjjW\RYXך{xxz:9T55G^.;GDTبVX ޽!ED~PP@@Pk[7/{w&SH22邂=gtun{t,..::9}ssǏx<ԤJb2i4*)ךZ-7689s8ϯ5(;bwa&Lpw⌌gOy37**]GGS>*PX_/ 4lXPJU^~ֱc11uuzGES[{@jjnFEEjBuryA3hH$ξu+)I.1bD77>_,}"eҷfM`0LL eMޙL@c++''[[cc__[[>3*STZ^^P_WEJUY)UU)amTT wrCx#IGSryU՛Pk*U]]QQ}F$cgꪧw~^^]J'NWWS4%/I$itu]\lmnkblK$I)X2Mf-&NoڳTk˛7BB{0EeeJIEKkX|TRYR?=xM}2iR:o„0_ߊڌ7ۻ76<̬T(>yH_5kX,M,..͛(zV*ݷͪ*SS@8L42Bx\^R\ޭ[dd<޳|~>egfdܽ;igv[ \\:LjF333336 2336ё޵R77'I:˥TFRN(mlT*t==& I099??/O.׆>F-,$=bq\\Nu+.ѣ7Q\^SQNPhbnc# مbf mAAvP(0$IJaA_{ޚ PST$;9xbqaavvu{*QK'?y}Gpw>|_'r'N;DS*[skG|z9ٳg}s))+VlܘV4קZZEl HkΚK~~STV֞=^oLF)2HRGp:Ng2;LB\9v,1݆b0t( qwg5 ý(jۯ\9~721㭷Gۗ[?._|c,-YG8QOHJZ_j|gg_/22z]ۇBߺuwZSI37t654gKNڿqz8MWWSk*Ut==W׾}.\8z<KK]]y05ZV(MR(T*iZ,ͽt3CG࿌BJeMN]TR)r9fb3`_!EQMA]}]}]#>>>>>]tttttttttttt @WV YYݻŋ EE"Zh௿^lۼ9+KG[`/rx3ub7._~=>>--'R,V(( 53qr ۷o_kk$+Pݼy͛ee TCB 17g2$#P{ƍǎ%%d(NQXs7;.B!&$hz)n:u\UNN!%ݽ<̧0i[oYYq8 f &-\`XGVVl6JM`BQRrk;Q`A:РЀ''33]]:].((|۷>x(J.ga5_f[Y9;(J3ȶꚚ7~+;[p||f>Ԕhm[P43ק,??.s]?##?B,V* t>- _##|SSH$k>VٳW;V(< 23+*$b0##'M8qGG>)IQryEEB¹s'N\pNZZ~~e6MAq8vvݻ:bD^ffLf)yߞV%'GG>|Rrr~~IIEEUU}DRiy[7=TQBQUzʙ3/޺SRР⬧ge6tС}89:0LRoli]M Z:zkS*kj+W$MW& <|#LLN曭[@{յII׮5$ƍ?Ν֮e232(J(LJ~ز޽ƖKC*++R32bckk[[*/}žֱ{˽@PP?i͛uuJ(,,wȑζx /S(*+˛/ 00xRY^~폇Qt+QϞ=v,6H$01qϞr/̚|:U`nR]SRu;!!CXX(|С'⢯O"QuuIɣGYY*U}?DoƍǏk?9'Mrvּ1ɷhzznneh zz|>b1$IQryccuu^^l#))b1A]xȃΟ߫׳EʄP4d+"bȐ޽LMl 59Ԅ)1=mFQTUUjKG\#D{y…#Oc_] zݙ3̢쬧WQWݹ<*pFctt4!$TL"HjheϢT|x^FF\.ARiV?ltE=/e^_MMi7@>74eݻ5t99ƭXZZTP3FFRQY((( y)>dA ($|AJ%fe;jڵWUAD 2vPVӛʣ@JU__Rx*GQL:4(H;|Ҭ85꫰픔޽ll4SlTThx={ΝbŻjrrLf2[ܹmmj 5 KJd=4횢ҢԲ2\N^h4g)SFvyH&Ƅ].]<1qРЀ''ss:]. ++ 23n߾y3+hkrMM{ԩÇ5oFw׿ r℄gMLlƦoߞ=?Ck[H$ɛ7\yIy{:8XYX4TZYx@P4碢7oHHry<53g0t]] 򢣏tT.+KJp[c%4Tff㏿VQ1dH^&&lBQWWT~~NQ|ee㹸 秤hu ΰaaa B!֖䤥%&&&VVzz~ҥ&۷m㎦hi2k, ̙5àE7wܵdܸ}mm)*??&;wZ^LKˈO??_ھߎOO B,-}o}kCf aSfg^II"A({ll:73{YJ+* ** _APPXӽX,KK??޽tMƤÇ-)I+W.Yfdd؄ hgsH~>EEߺumn5~IIV֓aǗpZ/MJ:{ᅮΞ}| #G`$Is„4hjJss];t(7ڵ֎l`jSoo/ic9eJHH\܆ 9yzzzIUʴlx=hI..C̝rw͙3n\X xxN}|5j֬]\qe2[##y,i41 2/?̬m}^im=`ȑ}XYxOmh4G 4i%K&Mrski0*IGDxx<0*aa>w矏dg|65,쭷,^ei^_3xp` X_M⢧Ixxx~:~|~ݺY[<:d͝CCGzY"#8Xj"I/S-裨(//33K|U@ȑ͝eebxFRt-\#HR'?H㫯֭}[3$ zٲQ߼o.<3yyGl|,E۽%ݻw޽Ma> 3=w77GU8<EI$EEׯ'&^uLGǑ#g|Qxg u߿rP|JqFF66..>>z 4xp߾nnxg@ACt ttt/8gf|||||<e lVWH'77777Ho|w:xܹDvy7gei،w_~ifP4֭[חE@U Ir=z 4bDTT`M9"EIo۶eˉ55J%[+W.Xb`@>yPT}?嗻veeff#F|7 ja`hhh{wݺ/ vҤ+/ظW YO_m ug"_~yyw,玈ARч'&ᇞ8U<2wG#4==6…[Μپ=!!-bɒ a_2Ç'Oj1c@ww>caDxs^n{x0YYo߻WQ9|%AQBazkYY*Ac~~~4xlѣ?2?o?wLL1z#Oo~̙ d9<۾:(gt:Ad9p{(Rnܾ}C+*U*{11qwg3Ft$1?߲_ OLkhь}| hh=>66; &NwL;LVVv =^wu ֍NMMH};.l#--EsrjBCIRmɣ|x7>7V.'`2mm{7MCΎܹsѷogfjl##Wa&M3&8ҲidZАru|ff}=ohؼyJssݴwO>܁'Ϫ{CC `fF55WfedlssI}gΜ:52͍*+/\ؽчeJR}}wqaʪ'N-,,4TcΜ쇢ⴴ{i˗=22w„ӧO0a 'm߾Ɨ[-`kgGAQII/^lQ{/2\s5U/~}qwgϞ={ΜS/IΡ<ADUU\\\\iLV_~&dzxzzin_wh%|xFF7n [y[;J[Rx ˗3њ)'Ϳi4&N:T幣}=i~}Wӷ ?|Z]㙥(R( t}}3333!#CQ}<Q^d9mc%%WV޼y!!..&?xgj:~GEEYYwch}WܤN+*n߾xƍJ:qԨ 58X/rj:ܴ[:10 kl,,zܹWB-%[j1<E!ܹsփ4޽H e^AAFFW?_]]W饧׷OgݡJ;4xwZ-奦k&''$\ϚLko]׷SJVvj9k^߷] ?q#ث5Ғ RR =:>:u(;G<ؾO{پ4ק(>"bH[3=]xSGǡCG)*:|8)):z۶;mmYDm yG 8zܹ;wbckBicc'=}|xN"{fw7t: +Y:+-----E!h"Y C>SS"@]]EE^o+ۖ"""""/sbqL…C~ybӟs7;;x$: EN{hٽϔ)vvLfݻ_?##u6:z˖m[ff6e޽;vLZRWᘘxy [ ~YxdtL^^f(- 08x!C,-kעoɹxq͚_~wQ,,-GNxQ?wgtvn =I,zuݺ]0i}myf/ thL*)WSRuY[%>;~X$F, ML\  + o6m>F޿˖>eMcưa(OxOO'N|?PJǸqÆhw^=S?l?먽9(խ[lٳ…JR.W|{!C{oc͢)?<;@۪tkWܸq'ǿ'f{x_Q8w^mhؽ{n<999 7n8<<|(+ 98 {s熄xuu+WB!)Ri^ޙ3gVTAxrGEEGGO>wD+W/ߺ5&iҤ_=qb$ՠ)_O☘WB..,ƍ;Ξ-.^vXkkF% =uJGgȦ`ףǢE[֧Z]Sto`|KjX\Xxg׫NN.Zt*{[/h;7l(/_nݺ?HnݺuKWGgܸ[oG+ =TƦA [YY=H)ff9sJe%A~g_`+Ν;8;{xp8OeĈ~jjNv5k֯8pӦ秳#j<b<}ȑOoCQAQrymmNNL̅ ffoj JeIɕ+}>zT]-rgˢEC2!\~/S\,(j59r…~;i^@{~~{\Kh-ĉ?_pTŢ)${xݕ+2D HHXEӇ rnfZrȐӧٿyO7~i'j$I}}/ >gkzرkeeUX^PP[+xOO[RYRXx_%%yy͟oߠA0$|>}~5K⼼ίy/Ӯ]ׯdr\Pp8փ-^lٜ9zhӖB[?biYXVXXS#1cƼuutٳEEB!xvv,fc#ӽyIINNaaMZ2~eWO驣I틴ի^llJJ23KJj7y֬?N_r~zpU7mZ| CCmhFF-]|Rx{s8mK_E&+*JI0UWmάM;ֻR6%%aѣcX[3%WTdgŚo"7sRcc|՚nkx5eB,]_miI[̚{nn[n[~_GffDҥޚ}$co?a?^]˟>gκuǏm@*MHh^`zx8;hkhai~s2ԩ$"03ݽ)׹uGr E2YbvO>|yyNNNNryfff&oahlA=ZU%WW76TAQ :uH]_(LKw'#DG ~~-а_?=n駻weŷm;;"0p0#sgܺ57dϞ6o=Y࿊F#IԆ(T*40 ]j׮K{d 17uG:;?ޛH&KMݹs &O^,X~]3hԼЀ6jfJ-ei٭@P\|VfPؿp n}T( SÇӑ#w$$%$79quMHߖڏ$5eYU*R&I:]j]o"5@ګ;}v۶mܸre cf&AjB}ݻ'AOaùsw?իV vαcff|ټy^^m6ߚݸСw[]vr1㫯M()ٿoIKklM>43 ӇNjӛJe}ÇW/I),ӻvI$MTbqu\ndӳ1Vee%%UV*2 ŵOZq..;vxJH Bazk)ڗ=ng1mgbڵ?p\nX͗Z-޹_;w>]Pc6WRi^7ns :e'̝;ɦ%%ɕ jK/E)R\ d.=Z^2h&LZkbfWTK$7VU%'߻WV&RR~!4XXkBڥ?RQqxAǏ׿ܴ?޿&Pna11 Mͧ/4#?V㏌ ]PxҌ={ΘqvޜRD}EÆY[[[[Z7cO?]^]Ru ,2j-{~/OJj< /N쿚5Y,7777k|g&8;~8wndA^rx\a 8x ;;6~xlن ?XUE\.bq66!!~7>><A0Ֆξ22s޼cwlh jK?dٲe. k3 tIGA~l/ŋcb/,фE׭7/#ʕ#֯?~զPGm$gS?rs]S'X&O~],8p С3-b2W$US6mڱ#+ZySbtuv~Uwܽ 6d9!?""h'LBuQ/Y' Eh۶˓'w6dȾ}%%zz斖::::AA?tĴi۶l<*Uukk̚7VU$I|ƍ۶-\دT>|" 蘑qNVVyy}ӖԺϮUw0 !aժZBl2Ç{Ο;K<(I$r96eo;/hזW2SC#22n.+c>䫯,)ٽ{ǎKrrkk9UN޻wl77G >#">| ;T`}}MHc}aÜ>Sw_ʕ~c0&Mrtl#AKCfGr+1wXO$ZW~^SGgѣe~ &jړh]@U(01?rr /\=KW B&KN^jҤ~[rs KJ22~u[*1&9/O>Yg˗,پ=+A 1XVVYYVVRRZ1_}7"ATԖ-7hygg92y哽s겲Çyg<=-989<<ז«Wgpw0^"I Ivw/78&fڴ+a45[\\\|ڴi˗CSA$g'N$'痕Ym?4t͚SBCQlCxxh= (9:YZ66nPST*O<AYل 4\^_RS^ ٵWnb2dw}wED,]zر'>BQVvŠgoz{2YccyyUU@@g:[O>ٺW/@W>S[[{ C֟W>/+}}Pmw.qo+yw "#{/2r__==MꬶԖawj$,A[&VVzzׯGF|X|zj X,k^58p$'O?ݻ7;["?ٙ$_uX66{wnn_HeJd4M|g0t.h4Zj$MMǍ;p +k߾uu;z }+b&oLJsK$qqŋ=z=zXY)Ni-} NGgo^>/+Cff'NܻWW7eʐ!ٵ)gdޝ>ܹ ._y˗׬1bثW? :-Ijʤ RR)T4SgbEgX0ޗ@MM=}C쉎>sƍ~R=JKzZ"??&СSJJ q[zbGE w_opf :!ĉcDz;)I .]g\+W޺|RPR"FFZYjuE7o6KxLfMMvvEN;yr|uuo=zLH=)wW׷^GW?`;%VŹgάYxΝ HmⴴÇ ;lٲe<[۷dՖ^6bL3{D?۷O(e. Ɔ-*y3'G,c^T{G2T]}N(/OOȐܢN:4*Om`ߋo|mt-={^4mI[//trz~0AG ۻeKk`[dg? Zpk]\F矒'&ݺYZZXXXXZzzΚ4vDŽ mm:jtir7MbSS+]]ί;Eii;v˰a'N~O?맫ۙm su>GXZ:8N*՛8{8ƫ vhl,eePUU_/)LddϞNN4A"QVֹs׮%'WW4Tg@͚}޸A UU ,TSPP^.X..in\(J"),LIuΝ†T"I{A9gũ"QN΃7nŕ464gkݧO޶::UUwի99B!xe[Z>y T>>үIb7ozzBaUU}D"3O^sTgf^rVjjYRIt:kcӷo>ݻذJۖm!Gܸ`XXDRZmhҷox1[lٳ@G}_ ^lёW>"I.>,g޽utΝ bbjjBC ˵ pqr p}ҥ˗㫪 05 gxy fgd\N=xFƥK/xy鍍w>x@Q^^ÇGE*Uuݻ.ݼhc3dHPAkQH;bC:;H$99g\rvvz$' >l%j3Z__<9;=BBZ.N(+KMMM]]##Mst F"y|me@$`鹻GD2Y~ /޽ak㣣CCTz>:]WTӗ BǦURS^%ťԈDJJPH$2Rټ!X4E KX,@RYSSTT^^QQYYUuPaZPPTBZ8L-)յ|kkWW>?117Al `x4gdݼ)XY0յ-=++72}9ss''{E" >xSyJmȑ( x5^lXLIH<=#"BCCBLL8:??&YK4s[1VVݻeeӿxt=<|h#GJJ n߾|93wo}AKRU*jm/hٿZ-K$*kllaadjLVSS^i+ CC++}ʦ9򆆒|/?BOO(/.LVEEYYbCt:ErH$H^;;$ ||||wՕjkۓEJ6JP(8:3^K@M/ 0ffݺgٓfx::FFc$|;^BCE۷\9w.3S.LNdzy=k::vv=zܸqDttbZM\f WWz1..NNt͛Oٓm`ٻw~&&*w!WW\|J%A$cb=pm/JUUz))r\NQ::}8:>ohRgx6lǏ''L0{`6^Bg=">4xL;q==)>>>>>Е rz0@@@@@`tdcT(P*Ho|wCgPO "@mmmmm-o+й0@@@@`kG9@kQе`Haa <}E{~8iR&&#ۄ &QTYfg7sٖ#,0pګWn %mki{:Ǯ,VgIGQ(**no?|դF LM==Ǝ]h //y:="zx<|rО{r+Z~~O:~vЁ! 0|`hد߄ QQVV*Uuuv[߻wĺu׮%'Xwʕtzӧ&'Ξϟ>A7vȐwߝ6C.l]Pp׮h0df AOmӚZtqFFce򬣨uu˗O6wnttM Ax{i`Pgd]~ީS..X0acGJ|Y[99/;vvFƙ3W/_ӷoa2l/Mޔ+nn??;M `A|1۶9xq@>|)SN|VSkjbc/…G)J:"4t#l …ի.\ʕr03 ?- 73HdG͛5I޼R*nܹs_=~<=AҤ#8xƌŋ-9RG̙?3gǎǿjmKn cbV~tP&h4Ǎe{gO^7g:5lؖ-}׳'bmGqGNܻ79{ʔ@Wxʕ+ w-''''SBP^^^^::ƍ3z(Wsp9>L{T~_=eʒ%.\߽$J*>q̙2 >Z&-**.͝9s\wȑ}4o"QLLz6$njfgɳ"߼yЅ 55A FϞ_|+W 2R✜a:^Z+yy dIb"A2RIQ/V^tf9A8-xx5+u7kgacs 'OΘ}I,E}m'X$)eem>mGmܘ0hК5AA\P~ Æmvĉ͞ǿbEHȋƻw.4isJJI^O,.>r//ON߿f{ ޽b$$թ$INhW_͘Rn6uΛw?$AT;N?kh4c@v E4k.=z&DF~5I4Uhץ*}}/IDAThd@o_ḶG2YHw<^˖8q…ڞb-'^ki9bĠAg0g̘(ׯoذd*fԔ-_~-c)%%gϮ^G7'&UV \nQ{V\*>^(;6*۶##MLhX 5ӄ xDzlh֬Yz -Ze lILLLLkaD:fM:u*5$a2 򆆦#G.]232'0P(OOܹӧ++/^<~<#O:7RSsځ{<<0ƌ =(::ݺ͜pqqgk׾fHhɷ䘘}##-,^fȏyrk[tNv7|ܸ//Ef+, 32B=T*ĉpF&R*kkE"p4?1 LbmWdee+V[;xy!!ھ2uuYYޮ˗&`7th@JRiWϞML,,Kl4?FjDNNNNN3Z$@k4vۻPgϞ=׼`BQ[ )fR$JLϯG?y?Yī: nftCݻO{ŋII w޻@"ѽ{6ZulTkn7/RǓcc׮}>;rԩ5ko5_o{mE-2Ӷ)V2HG h4wٳǏ߾}Μ[o0 {<''''<333_C*Ue?A hޘtׯ7Dn޼v449(ѫnת $-mރ8o \]CC'LДҍ_|1lؼy/76T*j5Aċ=th{{?61qw26&#G**jXc;<쀮ɴ;vΜݧLٰgifE-=`$IjCk&5eJ֭_\VMq+|1;9uX,s=73' d3 }}KK]]Q&==MR>BO凭IͶ 7oÆ55.}Je߾sJ,,`s8)it݈gϦM榩?3f޼G U*&fԨ_~9zt ll/HxsIuutɓ))Ekc[Ç;97bG;p )8˛b">+.]S*t(p|| 9ߟ˭~+JJ~qDss*UmmnevUW9{vq?k'+ ib/jummNN\ŋqqr9AdGxAQ:tDBab2iǎ}W5Ν%K 3yƍC}z|o5k\\Wirʔ~r TWwYƎu3_գϒ?'Ԭ##!>~Æ!C@wzիf8r$, /\Urs_{->eˬ˷l ""tu_4Euu;vzrφ 3gkiLVPw?;嗯bfC]]'NܸQXe|>#UU_eKyyϫA|NWd~wo~~a!..ۛ_V\jlCeennޏ~^yWz$^k\.'L&d۷o۶mGߣᥫTYuvvvFlbGb?[YFFFE-Zd̙C(jnNINIo92,LKkDڹs/IIѩBQctϷm6336{ب=x/>>SΛ"]uʷϟ;oh_dK|>.&SO9<|ɒ+?("!ꂂ[~_,(;˗=}Z9'ۃo}|abŔ)k[o; ih_~YUW_}!$A0::QQ_~aaw@<}IZZw#GvDr֭hQI9sĉ}EQ"QACk| EQ,Ype_~9k{o9ndL@6w$#:WWX1}߿D׿*9t3~CeHsZ rjj<᯼aoM榩`DWWA?tG9;^A+efn۶cGFFcvEņ aCoݻׯ6q=s|ԏ?=;9yժ͛uu-,JdzKիgμvg߸QKҲS}Ϝry{ b7{*C]A_?==6匌&wR)EٹA̹H{DnϜJ_311 w569rϞ,h̘ѣz^_iժ˗GڸqWWeo`0dر={YWT,VuTSU?KsA"/)aVJyyǏ gK,)g [ÆbdmmUUcN] 8:lVUU^.yyD[[RRUFHȤIʳbnkcbN,+̬::rU$) LmmcInڻQ$Snp^{MG|tS;y &DD:GD/}B\~#{A͛7zC=4%URr֭?֭ǎtu0C*-.޶mIN׋HH:; {ᇙ3++p? ]DMϦM"Qq۶wߥ븸FBhiIJ%K-zo'ׯ˗{ z;ϊ99t)56J$$IQ T*VzBcAAAAǎ w?+dg].orP(+卍))_}ᡭ=fLL̝o3vɰa7JoXaIDʽGpKIK[WOd,8x(“'ǎ54 nn^9::0pؓ'~?DRS6$&I$>[W7^N=#k7nv-+^W";:y׮<{RRR=RiMMn/_lفխw~UϽm۶m۶Aە:{ BioB\e_?%%~Ŋ+з/GI0CJ+*z/;/$2244N]cܦ>P \ 6l̔J9vw=-_[[qׯK$yyqq11b1AO>t>IQ-- Ap%%}3l:]tfFFN6knܸ[_rZaTZQq떖֤I66n{055wUO?}睹sܾ׷uqkjխ쳼Sw>t޾ Dޚ2ll6m|yLSS [6m7n„N9rew54||:5y_}5(hƌ<8!ᣏibm bɓ--AA!66[YѽFQZZ"QrriD2lX $\MAGGOá'2pkcmΝrCC.42z<Fj{+F :W_{SShkۿ~4;;;d2usss"###c/'xm41>}ݻq\/뇆nxxh(]mٳ;w#gqzzӦM2}zhV#a&M HJڹ3)iϞm˽\\llBBFϏq45%Kuu.\C.\8qbϞL`|gϦml|}`hh88DEmQQ6)'Sd]]LVTcDGۯ]6bD EwƄ dk֜?wܹVV`uu]26dʧsG55+i,-ygB9[[]]3\OŒjjD">F_EPW:ۻĉ7V{z7C[fҩS̔2ZZ&fg:to?kjJ$=cP.W_Q44~a۶sNPS3667zK*55;QF~ |tGMG{2}} Eo44 d.X~`hiFE[^]djk;;x񧟎tuǁ$ d2Ys~?}{޼C&O!3g>dƖ N5ޱ##΍ں2y$cΞ5jooK,.(޹Ɔ lIkkĉ--Ozի]\\=㢦fb'V^442e֬)SEuu33>z=-bYZɍ7nH$..#GZXc_N&{ϟ˗~{P_ŋ/] /]Zuذ;;\7LL,,,-?`Cbcׯ ~ᣏ.]:V?+Wjk+*^sq?ܹy9޾QTGGV֭f&&VVvY[oذ;89с%i7899z@`n>jΝ..6|]XJ Һ'[ZI՟f[G|t޽|ZtG|$I$Ix}ݑ䤫?kz1\B<,2Y]RӧϛFJajinWoyp%xxGoݚʲAA;w549 oﯿ˫r::Ső䣏_QxwC]{\Aڵ^ñ={ǎٳq=9\M`tu}阏rUY'嵵׮%'WWKmmjkI%&:VW'D]]lDDp HTQvҵkEE--]]r92ztX?Œ˛sr.^<>#V*%KKvԨGRWS= E[[aab ))坝$djiY[:b#O\Vv} lv[[cckHDQ^^\.hjq <#kk%cɱ JϞ=p 'cΜmlnabMMbWXEw33Wv__CG yFjrWrBa}}kX,r8=__zzv'%%66!\.y'N,g7n|EFN6mopxbamb?lYqqNGTTw!KihxpZtٳ AA&&,HTRAޓ'Ϟmj&74=zbf݌ÆUY&֦edtvzzFFΟ榥%66==LfGGaa|knn'ϝutܺu񅅦&x9vlxDR\|#yySzy訫EDmoߍ ;D]n>w~$1rp3f̘_ÃeD˗\7n//}}6[,.*G<}GEEEEEE|O3MMyr̮6nssffnnCKQRPxvM iffmM\(g 2555pjk}[$"IOtfY[궷VT;;kiut)+*ryyynnl÷Ʌ}콿7UύVb蘛xmm%%"L&65OGzzzzz:^NO4p//ܹÇww;C =Rѭqq+RE$khjjjm(J*~=6ҥ rTW75 51pʯ{1aĈP;;M;Ti{eۣ2r.:wի\T`X>2ܟ^f fF^_vNuE2IX`TZW6u;;;;;;<[/ 5} {=J\"(ҥcjk z|A553(h|OBB[G rml&N\}ag6{6>o۷oWeE-Z=rB\cCBf̠ !LJ~R(‚zIWz:}|+sI--GF/!55[ȷ^dɒ ǎ51Q>cb2v…K,YۑtPs<+\._Wߙ 96Ub>O_T(1c̙3++32Ο'I"IѣMS}?x!x 1|{yg|{{vv|>=|WWiѣ[omzhiiWA(+χMk,Yx_}ŋ'N',]xe_SZ*t6FgڵKo孷VҚ{YYbLVQqžffŇUVd$la55++$) 7W"{iN8t(>~ٲMΟ?~Cb􄄷>xN$mkX/]:q$ [pRRD'5!I*$\PL6mڴiUUYYEEEEEE l1!ILS NѡJ/^pL*%wcDz'L3gI._޽7;;GEQB }jUnk߾]WWYyҫ޼jG G%glmg47olĤ$c7a<L…˖M:d\uĞ=;vҢ#hi=[$9[[]]]9\VS#66t׷5ݝ&A?uС7nZν_uuKNjf{jiYXK\&ML@jn#s[ZrO~zȐQV*O CG}ϟ?.5/ꡤQ@QEy2_;*I67<9cQRT$RTWWa?im=sSʪ)Ujj Z[u|----=_ٶm۶mۮ^olLHkl믿/H$lkkkkkknniiiihhhhh9y̙'ӫ |_pGi1%`酇o7GN6zuR)ea1u?=R}I׬  ۷^}5&s E-Zh۷oww=z4sCCcbbbk-254*-ĉ*ˀAg~]-=====PRRRRRbgggggge"wT5m]o!=.b> {=OCT>I|/3}/ ypLt>PrBom>a`x :ZZZZZZЉ4\+?qy8]]t"ADK  ł6^a`@0@/[[g!C/"}/)~xq=Ѱ3>'LX2+~w޺5*Ԕ"k֯2O:: ||>ԩ> znq-r22Y}}Z?˾}11uu"Aahhg;};X1sLvwto(J*IH8th;Nz57 յpw1bܸ7x33W(*ڵf>p~zݻkkk> ѣ c\.FχM*-+;}eG+/?{…r}ti_~|$J++{Iƌy뭫W;;'7uKe. ko1b`tyC/̞mgۗ4g_9{w~>E==ŷn8qbK A=d{}5zz  Ivv^kkKW=zJK AFD)VVt-EO?iW1=,YS[drm_I")-=s 45ml,,Ҳ۷kjΝ;u|wup_]GG\ܗ_n6hЪU=EBaRw}EttK b s?m޼yϊIIII<ތII,g}-;Oޮݍ B"IL㏳gss\9pĉ:064 x<;u&L;wѢ˿_v:>+K(451"8¢vu_Ajm91{xDE͟jnw*9@!DD,_Fѷ՝={lqRn'Mlkŋ}eKZZkk1P(pϞc֬u.{ z~rKK1߽::옘{/YX3waA'%I$\ܹǛ=͝|ĉ[ bcnߟ:u+v_}BبX_JOq׺Dn)+B  uw7npmm Sz-`z閖'~۶c@_;ϝ||O? 11A,mϞ={V{}^6O1c2 [ $DW  tu#"f6L_g[o!%{G#IB(\ 22 ^vǎcw]p@__kk:"غ_bc?+ BqXGGn`N,~}u>KJ# GROoĈŋW $hn矒ޖ~=Riak~;{fK"װ0--'88&}tuee)'ⴵ-...Ri~~~>ze'1#*j`}}mmCCyܴzcML1SiiRRnnϛPI-7 {mmSS>W"uȑ|^Cq%K{oݺ~۾9RCC%S*Otfk;|ݶ#GHNnms$YP_/VWRT[[FɓIIS榥]zJzz[wOyj=cѣW\ϏnmtLP\\WؘysΘ1~]ёq/^|`YTBQ_3fl\_fOWOW5.zΜ?8xpΜG;;qݝ]\&N?ك++ϟ_j,{{''}}hl,)).(670a uuzII۶\vYXxx{{ "QIɕ+ b1A3yr`-޽ǏWTttw쉉x z ==%K,qwOKۼ3g׋F02˫]+)9p`#U::iitgh+?8;+NzL?vرvW26 yW_ۜ{Gaz o"ii G\x㝝~8k_EFʪag:;326n1U? W Ӥ6hРA"Q\\\\i*kq轗 yom7߼fD1U__\W\\[`oϛ6ko?nܬYnn/8qxt?\- >o?LHW2YEEl޽EGwGܜgܾV]~Ν|+#G:9q8.9s??dh8dȔ)Ӧ nlᨲcbRRSWwذPk;45† HRSk 6lʔM7*)``++f>u*,l;εdBςy=EǠ(4 鶶CR]]ϖ*W}|||||z{^"qgϮX&6?L11aZ[cbml6o~イ)Stt ~xTkر㧟-[l<^ddd$nݦMу~[Tf]Ҫ̊K}|.% ɎjuOϠիϜ9{7.0<G[K%d\dϜ{9!;VS|=+Q,H$Ælczռ$aLV^}duqd--7o>}tt}N9xSRSϞt^~zB!+E{ d::SŵyxC2Y]ݭ[yyRqski6{17;ɯ׋$Z^[P:::9ikXxȞhG=嚛mm]]2#x{;tI/8eĉffNz>N6X,@K˵I(*HHHIHԴ9r++32Ξ=p,8x̘a}zs8rykk~~\\R͛r9AXFki&5sr+*Z[e2b045MM}|Ǝ15ml|εH˗H$"MM/1c||z3]}>o%߸X^N\.dDr9>ގL.olHJjhJۅ'pxGi%%G<]b)_#GlSSm펎V$G su54rNO CGڵ33 l]]WS˻pԩ["4422";//6…fcc{->9<< @ PSꪨp!6655/"<ˋc0DԌ 4i8@MMhjJMz5+rx]]6Y,zÇ  5(<9ڵNI&Lpp⢢3g⒓ njqq1402vt4;{2}`2Ym͛7ovv:9;oL,3}oggeer+MMffǏ7f>)6774&am訡f3/76Z[(ۯEQG?n%ݾ}ZbbSEDĄ ,VWWqq\n\Rsz=63M&+/#Vuuk@?{?FQпK.Ԥ`| US*/?…Լ<+cx<&F6SX,mm@ i x:e0Ҏ.$ulކ. Dzz= !9c\]== .^AY>0 --\^, *-Ͽ~ǧbycRKñ 1anx~`@0@   GɄBt" WEx~bnx\)C'\^, n  }>a~:Jkj;MHɩ 45MM==G-7o.^dYo$W_ݿȑ˝\r9s"# k3gv4Ȉ>t„:s2/ΜqԮ\Y8(x6DGmh!I|K۶Dff` ,[ƃ['ڪ38-JOOOo3||||||#ԩs粲YnJCC7ZέAC[/&L&AI VMqqʏx߆I3ĉg_$xQQGLz&tuݼq) ̚}[[!WToU\retuChj>Y yO|VZZZ\[XSd9bȑCy9=Ѱڮ]۲pԟ>xp͚:x#|5kٲc~˖kׂf>6(KCȑKRd ؼ<ޛDPV5lɭ[׮UVϘ+X}/q^nUvʔK{?f::^=Ҙ_|_)-J{_$Җs电nn``rO7;^ IR,=xIoΜiz^ j#Ǐի/5kIf\fUVF*}ܹ[xڴǏ7n؈3̶6e 0׬Yhɓ#"BBBBM[ر 啱#:{6=}ǎW^ ˗[[?ґo>pWǍx=wb}} ._.-H$[[srRRɓ.4z8 0>ms77L57ߌ0aqnj th߾~)S.MO`_;r]],ӹs.&(8xBǣG33;;32^׌~]˵ :;?lq45{@"QCCg˜9ARiQѮ]kۧv?3YY7v_˗;;[(&("Ͽ(*/YbΛ7ǎsoI[[;bĴiryA?O[ۘ1_|ﯫ{M\Kˑ# RSkjd2[ڄ3.(8~ `''pZ?rs#퍍mlLKb2_'Ioի8z'q~~;w[n]mm@w@Vo?*8;{IAٳ7oL'vv 8uurySӵk;w~ÉUUBT*47 .."Qoy;ϕ+՗/_,x{_^_pƑ#d05k^]~ӧڲ.\3j}}?atunjque2_q2z]kJa2 CB>paÒ֭KNzAS.\8gܾw履GHȁ˗{zjjb>^ma1q%ǧ$&onfDa߳@Q$P^h G~С}O\qݘL&1P=dȼyǓs<_[k+W}mx!!UU.\|r[V@y<;xpWז-Wdk…H2ٓ9j6b_~9r?3ڵ7nLKۻwŊMv5kmϧxW T=( nk_n߲W7muʕhQ\Kׯ?ܹ7\]#'g۶֮.e9սKq<ɍ3|+VE G.[6c…7~ƌ˖=H<ȑ+V,[6t(Bj AmmՙL/1cܿjU0,qW7[16o}zV|b]H~۷ggD9 />&SG' ࣏~iʻ|AQ]]MM55o 4l#yWvւ˗/]mup>W/\Xo=z}}UUa2 ÇE"AԬGppسg׮b+`''pO⒒/]*/ 6mW^z۷{V >Z響YYVU;HVRг% EggCCW!I67eeg}`0jj{TU$cbM #88&_($^x{-8<<)ÇWUuvVUwASGFZzuuuggUUttQ?\y.mf6ew׏?Vx;[Tt‘#9rBQGx%7ƍ'1nn=tq vvkX,CCQ,,xl矣Ǐ>w37ΟC-^&&Λ>nɳgÕ}G}ECٹ~U95Ad%%}ݍOfk]q˫۳'(dLjeUPpXff{5KHΞv8];}32SK <<Ȱa&&t_wŋ E)g=1Kz(iiiiiiPEQG.9z7#"y̙}UrEzrBcAAFmOvvӧ=РPtt$&.[6x?uQAAǎ kijZXLaoom:kVttmÝʖt>f4OOssccSSSSssOiӾ&>Q.ކDRTҥF98XZZX 66Ç/_~L}mbqQ˖99YYYXLWW45)[af6a”)..VV::c|ERPH=Դ7o;;33}}=='?((Hb~hmdIRRg'E)55{XiӤI3f9CNkٳ3fGew\fؘZU{WPtu~qddLLcG)J*-*G??3cdǏƖζjjNϽKſ>~@`n}Gu~; KKTkڵÇ{rp` /HrrMMR^\mxtu:gOUU@Gb<,Yd'fe}\r^2bĚ5S)TӢE^TTV{ƏL$Ç_}C~-;*;\]{_?|uko|yժ߿z52颢ʪʄ~6ښˢ+;{ÆW^ٴ[>):/o'={ A ?]^^[wY\ܳh;RR*+ϟXRR:;wꎎ'P\[^.ge>0dԩSΜii{T~X,>e`0aˆ0EQLٜ9vTWZU{kjr,X=1q B,׭[gX*,АaE.MJ2銊+Wf6lڴ;jkjkkjwڴO>o~O>nO/CMLo3?.tw=ɔ Nڨ(Oݻ珖J[[#z2ZX lhHBcj:f+/_~@aXbrDi55[۰@ z|V;;33矛oZZ56nܘTQ1f̍{xXZFDhimWRRZb]LF Ks\VSS^XdrUbMjUXᄏcǭ[bEeM/:;+5rݕSq\cd[omvtZZIISSWHTW8dH{{oyIEbkckK;{9 acok_|1o^lSΟogDaEQ$yȎTi z&lNe.={ ,,1^MMx&O~-KLtir `ƌC 3Qz6rT`2l,}Jٟ _iKJ[Z>\C#$>:slj2dWWz4E$zkjr˽!]ѣ?`|bӫHL4裏ld$dr,&SRV4iժ>:zܹ={'ml6oLO9?(~*dSؽ{NHkXsB(,(,( {~(8"B Y9jm]Rrŋ55 A(--ii=Sڪbc}|͵HW++-rswZ/Ͽ_?<,xy { d׳O7oG45w_aJ$JO_& }֬Gz7>F 0335:ҥ~s:>cLMvڿŋLMMM-qcw튊25G?\` qG޲eӦѣ B]}Ȑ??u߇7WmYq%iPر~~[ѽDžñ3&$DG U 4iCsscIAXK6jʔ 45=xUuu[[N(l3;_99'32jkqSlmG?~++uu1bՂ%$(_VBCýyY+)))))CoI711-WV7ezI+*v3& :zⴴӧ15}Jo\nȐ-;{۶Q(``zk׹D{cdGGaa|knn'ϝutܺu񅅦&xFF$,zMMYYg'V\ ZZjkյjhXZpHCK!8x̘1cxsjk22:;==##wsҒZ[{zP{LUτ~;6ȑļ<{Stt0'߸uƍ۷O|A Em+/ji?'--ee--NN8>I[;5E.wqյJK[Z:;rcPsF¢LL|}盛xȊ7%KJ뫫H:J)D,y, z= L--##-˗쬬,-שׂS=$ٿ_uuzIMMss;;>?1M.,dL2e I2o **^6XVVo^?xx\KKu++KKrP(-,,+#XOޞ) znm\yPЌ(G7ۋ;ģu_X,==oɓM36.----//)IJή2e4__}}($I &qo`xEln{{aaEE{;IIZ[A Lv֍FFvv<^UUffYca\^unssffnnC2ң(T(|YT?b0l.jkS=˅ŠzC pt4hذ#<=uu%fm_)rrykkqq^^G>97O46a2y<'CbccbKI!I&SSjP''O9[XXVVt%88dKуoA7ztkk||\ܡC{JEQ$zxZ[?٣G rqaΝ;|xl}}wcºG|GMQRi}뱱.] 3LOh =교+1-ĉSRRF De@lA{@Nǜ9yx \aFSs˵8q@`@0@   }>a`@0@   }>a`@0@   }>a`@0@   }>a`@0@   }>a`` ^NaK06^a`@0@   GY"<pExg7<^ ΞщT }>a`@0@   }]}7oݺ'oڵ&=`@edc R~`APTw EEuMR.Ogz^Egfe=a$)(  eF (ݡI*܂/^@% IұIdgzֻ(4 J ~6 :\{<@*!IepuWuWu{Mtrkʭ(н%e ދgaD0eLF?} 응Yxwg VQǿ䋺g a$oyYvw8ֳҭg834뮬k/}l)"YB*Q(1h=+z zNlѳR~~Ω1zYA*{;;*gp\\OOSSMM6[EC")--,JN'g/ʥV9ޝSp<<>PB=fߝ](óoe0lwwook#z \55KKss]]33>&ɚ\YU^3I6˳TL0gdY[*H;;H27AC˥(Uv|ŤInnں2zJ;;;:rsoݪNJ(??Gq&&+۷e276;w(+WBCMdPKKOYi(wu\XT)3*g"5}T}ocuW)e0(J")- JO05utrt$ɺonlv-?(eҢzHrutFpp4H ()pK 55TWhm43y< Җɴ ⚚v04664jԨ˻of0裦RwYB*Q(1^ϱ*;GH23Hd O_!pԬmmͭݻ\ihhi(liho70wu51ry<55.˥EQXؘե奯oe!陙q8AׯWUuudL&fjb[75H== jjd2 ~YC*Q([w{wgdr8&&\DY\I,̈́ \.AZZU_H#Gak뫩)߼Y[A$IRB!DYYRA!VW74$P¤⮮;A&;{}㲲xJ*ުXF[[ki55ݼYS#LN&oio%IQS)55I2:"/-mllltr2332%˳ E"::;oܨQ(,,\OO;;vb uu۫ǬP9vu/>P]IGcݽDg r8/sѣMM<(T")--,};..'+Wn uu}C--MMffcADׯ75t H055?Ns __CCz]]7o޾-P ]vͻ4eE ,9mǝ]-\EaS[pwF]==D+Ss/^}gGQtݱWkιz^a;+otFQE ]{B*鞠*DZc0P+erٞKc0z C*sr;ovUVY{~u/>PIYYYYc#yD >۷vI*}>90<[ P0@   }>>H$ЉσG JJJJJJЉna`@0@   }~T"IENDB`django-constance-4.3.2/docs/_static/screenshot3.png000066400000000000000000000620541475050117200222720ustar00rootroot00000000000000PNG  IHDRosRGBgAMA a pHYsodcIDATx^ |աJp k.B>@E!zW҂ K,EZT"mG@+(6PM0o@%x5{rO Mٳ̞3^k4:i{E(xYbgE(xYbgE(xYbgE(xYbgE(xV; \XEZ@MqP:ʷW>٥wn$ iSCG>,?W{587(1+wK75?#/8f>0]ӹ5甬X⫚7oczLqNj uI8r7ikqo VjE p]PHo (OBC?[JԻj4l*p'bpݧ7&*a4qNꈜ͋Qfy<հs]ܡjrm][.MݳY3__֔yS-sպ݇j_#SM~_QOS) VZN%U:QɡU_f>[]&1zcwMU̦$&T3*O.=z{-xb|gd 3>؟4Mz.Nq:2D3J0sMGZR4SϞڱ"%P^nAzkΓϧ-~fRsɀ]v@ܲOBޫҎ')U_Ě+?U/^ҚU3צzv-MU^Jrޱktۖy̚Vޢ}*EY/FJ{yMg~95R;)znf~Y9>^-:u54sw).<;Y[*vBS5s\ouxT]u=KxMi]Gh`qRsU'wz:qzƏQmeS5&.HR+ڒ<[ה*WgIÝ0LJ}7QIv3qc1khZBZoڭb|P9^M@z[4ojͦA,7<qP{?4G4wM7/dQ sֺC{jиqn6]?1˽Q)'TͱjWhB…˵YB{k CE֡FNe ޞ}n_;Z%j?A)W>4y4eTUs<ؾ@vRmP_>ʖD%,}/%$p3C:e8Sb]Ze*NC?]i ōF=t+ݵZo]y硇v; @(xݧE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(^ 7kc[u!&kɚ<3Yna(.UD=KMo/~ĝW;s֯b#۵굧K\& OUO0=b2Wtͽz^ pu;6gͿBq:|mRzTSR@iRxL ,5ΓɑmO^7qq Mvz5z y {Hi3> vm~91Myf-M4Fݶ5wiԮζm8H{)pG'RnUf*4}hBu㣳tkL6yToō+֪E[uqz]By%)/,NO<+]KI}Wʡ[; ^1f6|K|Yiy: j?PАW-&4b;LѼyez>ykJ~YsM?IW˞[ =-;o(Wּ5fp8ruX\r+R;L 6?&b[fn՘ 3]mK%@l'ҦE/Ό+\O/bc=FUpT l=b;'(SQY٦QP ݧ4/4)TO(y5zUJ<7{OtG)_^5м$Qں1|i|ߤMѧ3_sf&V\MZPrr+pMQna+W;e {c8m-ࠊPt;u)f?)kP}. ]w;P lkʞOI羗r?P!}9T>S04|N*; W|J@6}i[S_ppϛ)^|yr56x;5Z_^}W}hi*tOx7n}H|G::mF}ݍmWKڵ괠*^7Ǿ~[kh !&j],]wҒіB0@[4Cյg~*pٞX̯:Ngk:˄Rl8Y>\Ok{fW5u}.>]qW(1qٰڴ766>6uSl]fmG<0WN۲LG\c"n^y>jQNGflvy&>}3j[ݟk?e{ܗpg }Y s~6u<+~&{w쏵ڼ=R VKwq~]߯/Et\z'!۩*ԝ]W?. H?v.M}8]-s 5dkE,:SI׆C(5jc@sh{#Rc? yQֆyj6we?ߕT _h؟=NY׮ahcj( QNuZ_}>ڦ,wq@_ }Zg9AҥMŪyܹGePf p\ϾvT6|a"q._Yg=!$z:ϧmϘYV4 _(ȼS-_?UYCn.SdCJP6CGj{RPpqjPi8.z(1B]|QsЩ.jS5 hgޗ@`rVل%[.'Hx/l=򷛕>3tl&ng6x)$Z۬O/ȗ^,]g}Y ΡNY_I昨઺dbV)P6q_Z*}ZLY-a7g$m݃-kƴ;@ {L`ޯNm5 w MUݬwmu6a޷Mߣ ]7gisfmp2qծ֣Z.u<9vt=;sFDigUJ-p5U>ݐN_xbȸqtH?(;_i9yQ1-j`'K]L 4$*_lY>C]oNWB}̹hl|]gLU:md /6d}PW^ؼb h?A{"ƧAANp^^D]\W5n+V}ąNXꢸUF޳!t cn5&D=Yf785jgJ/ 2n(uv׆U_;kJm3>2o'=B{!6W>U՝9cE3ͺҊ8U=d8;cuxŻN1uX"

 r{!l)En6YZlD"iiڸH ZYyLNW+|˘d=S˥z$5&.QE+Wjԩ*#ǖz38"XKk閪I&ԅ0XÆI+gW^UlPăyX# UVDR7ggh@s+3xpW_ocK5`ug)|l-w}x/{jQ ?_l.DzkVNzZ\XN t6m+p-[v>-I?N%izi{]#lm5z4Y+jD!WTj 2[G -[#&i0Fyv8XHhFm޽BntS -:X:0JrVk_'WׯO՟Ffg?w0)ɹܹj9ޖ˂qrhfC?[z) MSaOb99BnRVۢ9R!gRA!#ZQ?Z1#ui3XUcLm-l[~9~=it3⭓4_ͼB"eSH=2iehkzjjnx^Rz˹e[o-oBUF?c1: %V=CUڪ5QiJU[d36>{Vޅ:PbF+bReifjkQe-5u/-MsN[*31nCti6rc&ֱ"Y 'f~+7ݒ-bx_⢔GYtH4L(gEhj Iz)'-M6+$W?7QwejshtV+1Z&Fa-"Q)YYJImjdyu])JKE#bg$67>1( 9rhTQZ떫]2JF-Q˞C^-r9ɞnbJ.M\ 4vD|0_Lp le,59"+GF1-ڶ k>t& ֐q1J^%ƌӐZE*fdW~U+o~`P q̹Y ְZY6Pޱ:.-k\FjWF5j(>Znje>7 8Xœvyso˶*[ tYI5G4-5R9M5iڰ.>"}5~jO:"t6l:U9Iъ7ߐG+2@ {3m=rf3Ï-gG:?yD˳KjN4hѲLw\Нp]vR'O[_sI-% }ֺ%p:{{ӝi{ovڶ-9:N7x嗕"F% Pч6RQ&We>'Ek%Ct>{2=+: qj}m:B1+twpGU9D}(%yRzƞ{/\5I9@]#W!9\3j+ּ!chɌ5QrtUWoxMNIԲn飹ZYţP|^S޶mhX͕0b"M`2WeDQkѐ׵٭+܄ڜ>'^K6|L_-6kE+Ur$tڦOW-F޲\3q?TwSU|;ZIH r/]rp⻯U|LP/Z;i}{}]*8!vPu8W5<oe"3-vYm@^^59c,\N_4M~զOVO6Gj˛j]C6s-K|㊸lUƷ-k:5C_ TT0Awl[$}~qw7ۖe;}TVی-:)ZM+cA52J-| %:C{SӤ~J9o㎨Stkg}#]iyeJ}}Sw.999j۶[>Fk/wԾ[ ع\KSܶڔiW1eVDj+[6Pt0 Uo|/4i/4J,`LWx&b:iU=7_fGXM#' V7x|LY+?Ց>c'鶫j?+FJ?wz4muvR`M-{oV~\Cao*Cӌ'pN'muP6ڷekȶY])TX`qں⏚?&hÃ=q[ B"_O7'g$dm(f8@'oV|J}SrXe $7QDhSeei_H+{L,n{l$%& y +tq3D]~|GUW!ҭךh}Zvp%ZuUqaFD:+2R99iAZ]A+_lr/g+ 3 diex"T}G [#ބ6k^H}([yՙΪ*iue[*nS^.6PlCp5SUem6JMC#|!Ym5dX/uo^2S{ˮ|_[[71h!1)knX'ziHӑѾH>?DnTn)q }?mnUj_~󪡷 uBX{M4N13 s *OK1YbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(ɓ'S2bx޼yn PP nb@ F(hb)p_~\j Ķl?P ѢE 8p-+[~|4i@-JJJ}v8AAAtb:(sP6;GukaL/ZjE JbgE(xYbgE(xYbg5:í)NEwABqII;SݧE(xYbgE(xY\꡴Tu1.M4QբE .u p6ܹS͚5s&9p(..v+,,Tǎ Ⱦ}@m۶ukҵw^8qBaaan o/ /aekʣ?;5137jIzhK=kJO3U켗n}@4CjhZ:fRzf} zjax@#cnn㜁;MwաΩ:tz>krL~==ڴlZzA%\<>}Z hnݪ+b m1oԵkWυ 1ֱ/{4w*5C3QL^JH˘1N͵Y+V&-Wc^]*8!T3Z|ؖ{IL_yDw,t?=6^3-պcz1~x@oV1i`K NwJ9w5i@ 4?Gog$.BUA8FL?wJ̿J"Fi=D/j\76&~YgrY0.[ b#bУZ|kn>R3Z@E>W'8p6f+)1 H:>3#}>Ck;Mѫgj XG:~ ߤ?~]?r2/طOQQ MޖmU6 Ŏ^{ b@3z'֥@[/~7*.wN5sSiԕZyڝuT}&4!&9}K <|l,"v M:?'2%9쫻՞LcSOߩ~hJrc[xg\|V:Owu>At\}{qo~|lcx_7&IeE>PMmWvg~>Mw uTɲ7o|Ln+xu_ٿ *pы9N;Q[.W  8Xi ;qso˶*[ t6ɥjBp{pXIS^]OHT=1)Tv %Wsmzewȷ7i AuEkUm@S7WkL9[]gjϿ&&(~\Bx^%$Gq-iZ\F' w))995]PΞc~%edd(::-dC\vYl nv]I/\㛧B1 B1P KG]m @CC(.d(K2pE(xY~$'*)[کOJT6(=wmKV{ikv ޙ, ~Ww߯NZPܮu3~+ߣ'vMZ]K_{k4FmSrb?uWlP@yrnT1 w9q¤SAZGwZNcji˛J  }bRgꫝ'ޭm6e]?9Q=M; VlU`diamxտ);< KhTҶul狀mvNSW;u"P}m۶n .}999 sK.2ws۩uwsD;s+d Cwf[iY#VkWG8byG`mj$MqfiB}VTl ҀmGʳ~?S{Zk#>.>M?QWjO`5.>]u:.Yl&~.|CynP|y7wiZz澝ݎYWeemOjv ݞ]ku.sru&`PnamK~Ko%:\<L",\}:7\›]2Ӕㄢ~v4.Qn{fz/cN~1vWյoM'8]ع;wVjZP\*0} TӖΆާ5Qn;UsS}iOBqM;WuGu45&Raqk -ejuC|Ӓkowhi v:|n;EZ]ᷦfs %%,v̮M-Y7vrh@`s$'Xjٮ 4s>:vT\n'yZ-;+<;mk-n:2w~oUֶ>H@_h}ԭ wDބև\'l8l[m}Z벝rvٷձo%_}҉ݧZбcknrZOOWRGݧVG(7+}2\Yy 4.(׌3K oB LN &k 7^͋>Rn)-jGo)imxnpzvi~O'LF2IRFaqZ~JJ׎բfx? 󾗨wtٙQ^u\{,^N݅n[L6`Z*鞝#P1ѾVpIPwtcʴGznVnF' w)))K8;vL5 [.,-/##Cn O`{R F($GJ YbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgb8͛n _]زg͚5sR;}ޔSp)--Ν;-Z(((ȝBlqaa:v쨀w@w#@=`~`رcn-\:li^˖- Y2sN1"

uo#ە`o:dicMUZ{)yoqմk֗c4n+mO^7IvL]93+)պтMRPzt@ (Un\=pS_OVcIyj8EmzWku0뼢lʄ)'~Bm|gۧО9qb_'>UO%iy5a^ES U;l)[6;ɂU>YͮRb>I//4VסMTnl= qWf'T]9b;NoR{EN u.H5~$>Ws/ߧMJz~6Fo@lk@:/U_ΟzK3 ү^y/WO[.تM~Kw<3qjykR+ ի7 5QjWv(ٷ2va~A|+SjT\#>TUZYo {&Fh]aM7El ~t:}f7&2f_+n{ƿ`/vwhVs{ndlZgb;xϥB̶^N+*5شPss\$U[)j(7tGu01WJ1i<ȷ2|?A&,C5iIΕYO@6+l:>kv׾mCrGolNO7V7Ә^Ы4Ja'3R+l25"*< 0Y+kuA/ko腇-=T}ں@נBqVSugl`ۅ՗ҫgڪ:V1خv}꥗^e$(|arkZ$[=էwl io:T*]#d*NyJe7Ru2pr5s=z0)OrmbԠB*֞]y@@R[=u@sLs~pz=zyD޸1 ں޽w.oAqnB[zc$ؤ|lk]j>޳~O{V(Vb=8|+}o9gLNCq=v}n^njPV>3Q:m^.M|F>bV s0lo(ęո}^AeSqM9(W:ևՠAOh8=T}?NqÔNzOѼ}Xk)Ϣ4h)xYbguA:p;h:.H(...v8 w4"

]>X!Nm 3>l s4l帴2i5}Gd̈́7ш'Ho}Qק/斃5}fٙvcMaV%ͼzdh}Jk۶]彍g/_*.g+s|n>e?&í +fX3}R*MX ۃEg0jt-q~}u-JߧjNk܄8=Ou\Zx2 e4g@)Ӱxjw<-Ԑ _9SӳTLU']ڣjoyc͙9@9s^##a޳Z͝aStl'g)}Sul}X#ژbħ.\8MYԀ?RXج g mSҼ8=iόSpuSfR}E9+L4zk޳V*򸴲zeE}2_OӪEWs.]Tǿ{^5oA_(\ϾvT6|a"q._Yg=DP'󹐵]9Kߪ} P˗eRV2@wpH~"[JWb:BWC>vU\DžNwCqj 勚]_uUBm4`@;~=xhE۷B&$(*u5@C`ŗ,+U[Y˙SA63 bG#.ho6?er _&v{tmSǗeg Pv*fz^O2v՝&[˦J:(}JǏUr\ZYmǢ5ӊgej=5:vP/8&lpmUr{=@a=ԣeV6}a ivrN{OnUpAu6a޷Mߣ 5Ξۅzs6n'W:= Za=zB]\nggNЈ l^^\Xݧ9 |ѧ#iҵ"wAEo 쫁̛/bw]?z31XE-H??];9 uҶ]ͽ,'T.ԕ(6?M-uԳ7hV$T"(i˫ƍ}Ūq陸Pi"K]׽({6d߲ᡵ hݵ',}'jgJ/ 2n(umvݼCkên)?S6YǷz.Y*.:Gl3qEC.$B.]̟Z.tHVgf'u7 ء=5j&k/ƃ !7HVB}y=KBmPlޜg. nzn})+ {LѪ]qZq&L׬E;aWthB¬:˔Yѥ"vɎ\|&$PmojYY+T}WPs>̍7v6ʻN;^؏OEA'WP{sq%Ϛnf-5"n]_zO%$w rKn\=Fhr\c1SDiL鳴&? ,B1"

EWuPeŶ@C`?A(nblmmZP<\XVe9ZV5oI[jjksTy=_V,iuᑿ^? =. yQ7wolύ>4M8[W`\S裏[v|xBqTZ5P11 4UOӖEj@S7$^?\ǫݢEw@(~Vl b3X?OkrQ;\FCg)wX5VZ\v?ኟL9-ijg;Ӎ۴™l{%D=sULarvRm!;1[?_>\)hDAV|2V2P0U1[i(1탭̷]}>k"P?Sp}RиMLh6'̾O~X8~\i{oel9x%cXr'UjJ{"Rp5Z1#uiM\XUcLmml`EܲKƔ?Zm)Xݢ# 4,&RMdRgD@'87Qtƕ8T\+}k[ ի3nx^Rz˹e[o-ojpݧ5Z TъغT)iZ1BcThFٺDko-Ouo/K*=W$D m-prLE&n}}"d"3gˆwK>\mhˤbتWE#Fdb4hM,Dzҗ%_ڿ.ۋ0aWTg^6nkHϭuK5REWue!mcxB9]>t&7tkA.RV%jEJq.ON}J"%XVf8s켁)cJmqKۛeOUF=\7Nθ- $-%(4fj ;{[VrSln\;BmٮuzN5Z8?y#jڏqȽZ7ԝS݆+5qZNnF4BϾ/];ݓ󄆛L^a9f_F3\9a{ӧ;ޖm-.[N\jISso222ԯ_]L-1RIb}:FinkQe-׳s*;.W'g:qϩyRRf~:U޷\-%cs]%cl;n^2C 3v׵d5wМs `C\vYl 2EÆ5"vS|w"LQ~>5)+HNf/ѨeYJܜ'%^}u2Y3Yg2s9grO+elDCA 45#knƮtV-K3A((m}@%yfMzx|g!L~^~u5$RC4jl6E5JNp\ct˴RA]6ҾF*viaZ3+Lm~kE}a͖m=@(ًa9EKgS7hrd˝.ѨFԨ6jB-We^Gu,q_69i=G2|<);ssAٸSs;֤f8.v:g:}*sm3^LJmSHdݖ%sxm++Z6%hRm!񓕒LGs*LOLY#hpk5E#5k/я ݺ:ݱʝ]iHb$ޭswMPw\ٷ9f_c(ef%ݧ'~64dj|eM^k>ևoT 4ҹ6}~oj,eUE+a]HIpK8ZWHdK2mp^_! NHy9*,To-Wh?-?"?uTe]*eMNp;}4A6V>RZmm֛],o[TDQZ9:o3Hhu6K>N[-( /( mO`'1rj)ϗ)kyFGM^ބM轏ӔꠦvS=O[xLNNڶmnHw\N_]ձl?SܶڔiW1eVDj+[6Pt0 Uo|/4i/4J,`LWx&b:iU1S] #_V,覑+H1ftUGiE=6U[ʺvǃ{m7(8;]v)ZMs ݧ!ju7}PO͹v4l+4a񓁶:CBQdd[u,VN.*Cɸhoam]G͟G:Kt`Aƞܸ]L{sL!X/S^Ƨ3z{r]ͶKbZ^t8zEZ֧6jdq{U|xVqq)9]77w;7m*Ojߦy)? MjBaVTiٙ:XF{VI-6a]+t&S.?CcŠѪʼnZu˜Z?kM\=K+3ijS8|~sIVBU\XThNrtmv{,/[zJ:GmYZ66Ud~zBy7aaڸW6R/VvD%dZ]VAʷǔw}8S~mnft;&JMC#|!Ym5dX/uo^2S{Vh }mUoǬI\Ŷbe!5OGG";`EQa{G4㻩I˕R+߷Ui;DzPf 2su 8:P s pE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbgE(xYbl#͛P p1\X@9_b-3/b~E:p[WW \F' wJIIo߮@2((ȝBlC@w5ʣGp]{Z""y~wR njwx0'D?_.R j)~:G;ڣ/KG׶Wx.#ݳ| ߠ )/׍mǵg?kηYpn}7U?o0ZbEKG=^2uWpH 'mo}Fz _on[J:]q .iռOiQnO_c52=Y-ު6#wNA(K?{O\O+mEvسҶAIǔ3߄Jc9tl6Eh@GiۦnZ=Q#nح4`_, feel free to use the ``CONSTANCE_REDIS_CONNECTION_CLASS`` setting to define a callable that returns a redis connection, e.g.:: CONSTANCE_REDIS_CONNECTION_CLASS = 'django_redis.get_redis_connection' ``CONSTANCE_REDIS_PREFIX`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ The (optional) prefix to be used for the key when storing in the Redis database. Defaults to ``'constance:'``. E.g.:: CONSTANCE_REDIS_PREFIX = 'constance:myproject:' ``CONSTANCE_REDIS_CACHE_TIMEOUT`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The (optional) ttl of values in seconds used by `CachingRedisBackend` for storing in a local cache. Defaults to `60` seconds. Database -------- Database backend stores configuration values in a standard Django model. You must set the ``CONSTANCE_BACKEND`` Django setting to:: CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' Please make sure to apply the database migrations:: python manage.py migrate .. note:: If you're upgrading Constance to 1.0 and use Django 1.7 or higher please make sure to let the migration system know that you've already created the tables for the database backend. You can do that using the ``--fake`` option of the migrate command:: python manage.py migrate database --fake Just like the Redis backend you can set an optional prefix that is used during database interactions (it defaults to an empty string, ``''``). To use something else do this:: CONSTANCE_DATABASE_PREFIX = 'constance:myproject:' Caching ^^^^^^^ The database backend has the ability to automatically cache the config values and clear them when saving. Assuming you have a :setting:`CACHES` setting set you only need to set the the :setting:`CONSTANCE_DATABASE_CACHE_BACKEND` setting to the name of the configured cache backend to enable this feature, e.g. "default":: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } CONSTANCE_DATABASE_CACHE_BACKEND = 'default' .. warning:: The cache feature won't work with a cache backend that is incompatible with cross-process caching like the local memory cache backend included in Django because correct cache invalidation can't be guaranteed. If you try this, Constance will throw an error and refuse to let your application start. You can work around this by subclassing ``constance.backends.database.DatabaseBackend`` and overriding `__init__` to remove the check. You'll want to consult the source code for that function to see exactly how. We're deliberately being vague about this, because it's dangerous; the behavior is undefined, and could even cause your app to crash. Nevertheless, there are some limited circumstances in which this could be useful, but please think carefully before going down this path. .. note:: By default Constance will autofill the cache on startup and after saving any of the config values. If you want to disable the cache simply set the :setting:`CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT` setting to ``None``. Memory ------ The configuration values are stored in a memory and do not persist between process restarts. In order to use this backend you must set the ``CONSTANCE_BACKEND`` Django setting to:: CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend' The main purpose of this one is to be used mostly for testing/developing means, so make sure you intentionally use it on production environments. django-constance-4.3.2/docs/changes.rst000066400000000000000000000260031475050117200200320ustar00rootroot00000000000000Changelog --------- 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`) * Prepare for Django 5.1 support * 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) ~~~~~~~~~~~~~~~~~ * Custom Fields were added as a new feature * Added documentation on how to use Custom settings form * Introduced ``CONSTANCE_IGNORE_ADMIN_VERSION_CHECK`` * Improved documentation for ``CONSTANCE_ADDITIONAL_FIELDS`` v1.1.2 (2016/02/08) ~~~~~~~~~~~~~~~~~~~ * Moved to Jazzband organization (https://github.com/jazzband/django-constance) * Added Custom Fields * Added Django 1.9 support to tests * Fixes icons for Django 1.9 admin v1.1.1 (2015/10/01) ~~~~~~~~~~~~~~~~~~~ * Fixed a regression in the 1.1 release that prevented the rendering of the admin view with constance values when using the context processor at the same time. v1.1 (2015/09/24) ~~~~~~~~~~~~~~~~~ * **BACKWARD INCOMPATIBLE** Dropped support for Python 2.6 The supported versions are 2.7, 3.3 (on Django < 1.9) and 3.4. * **BACKWARD INCOMPATIBLE** Dropped support for Django 1.4, 1.5 and 1.6 The supported versions are 1.7, 1.8 and the upcoming 1.9 release * Added compatibility to Django 1.8 and 1.9. * Added Spanish and Chinese (``zh_CN``) translations. * Added :class:`override_config` decorator/context manager for easy :doc:`testing `. * Added the ability to use linebreaks in config value help texts. * Various testing fixes. v1.0.1 (2015/01/07) ~~~~~~~~~~~~~~~~~~~ * Fixed issue with import time side effect on Django >= 1.7. v1.0 (2014/12/04) ~~~~~~~~~~~~~~~~~ * Added docs and set up Read The Docs project: https://django-constance.readthedocs.io/ * Set up Transifex project for easier translations: https://www.transifex.com/projects/p/django-constance * Added autofill feature for the database backend cache which is enabled by default. * Added Django>=1.7 migrations and moved South migrations to own folder. Please upgrade to South>=1.0 to use the new South migration location. For Django 1.7 users that means running the following to fake the migration:: django-admin.py migrate database --fake * Added consistency check when saving config values in the admin to prevent accidentally overwriting other users' changes. * Fixed issue with South migration that would break on MySQL. * Fix compatibility with Django 1.6 and 1.7 and current master (to be 1.8). * Fixed clearing database cache en masse by applying prefix correctly. * Fixed a few translation related issues. * Switched to tox as test script. * Fixed a few minor cosmetic frontend issues (e.g. padding in admin table header). * Deprecated a few old settings: ============================== =================================== deprecated replacement ============================== =================================== ``CONSTANCE_CONNECTION_CLASS`` ``CONSTANCE_REDIS_CONNECTION_CLASS`` ``CONSTANCE_CONNECTION`` ``CONSTANCE_REDIS_CONNECTION`` ``CONSTANCE_PREFIX`` ``CONSTANCE_REDIS_PREFIX`` ============================== =================================== * The undocumented feature to use an environment variable called ``CONSTANCE_SETTINGS_MODULE`` to define which module to load settings from has been removed. v0.6 (2013/04/12) ~~~~~~~~~~~~~~~~~ * Added Python 3 support. Supported versions: 2.6, 2.7, 3.2 and 3.3. For Python 3.x the use of Django > 1.5.x is required. * Fixed a serious issue with ordering in the admin when using the database backend. Thanks, Bouke Haarsma. * Switch to django-discover-runner as test runner to be able to run on Python 3. * Fixed an issue with refering to static files in the admin interface when using Django < 1.4. v0.5 (2013/03/02) ~~~~~~~~~~~~~~~~~ * Fixed compatibility with Django 1.5's swappable model backends. * Converted the ``key`` field of the database backend to use a ``CharField`` with uniqueness instead of just ``TextField``. For South users we provide a migration for that change. First you have to "fake" the initial migration we've also added to this release:: django-admin.py migrate database --fake 0001 After that you can run the rest of the migrations:: django-admin.py migrate database * Fixed compatibility with Django>1.4's way of refering to static files in the admin. * Added ability to add custom authorization checks via the new ``CONSTANCE_SUPERUSER_ONLY`` setting. * Added Polish translation. Thanks, Janusz Harkot. * Allow ``CONSTANCE_REDIS_CONNECTION`` being an URL instead of a dict. * Added ``CONSTANCE_DATABASE_PREFIX`` setting allow setting a key prefix. * Switched test runner to use django-nose. django-constance-4.3.2/docs/conf.py000066400000000000000000000063071475050117200171740ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html import os import re import sys from datetime import datetime def get_version(): with open('../pyproject.toml') as f: for line in f: match = re.match(r'version = "(.*)"', line) if match: return match.group(1) return '0.0.0' os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('extensions')) sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'django-constance' project_copyright = datetime.now().year.__str__() + ', Jazzband' # The full version, including alpha/beta/rc tags release = get_version() # The short X.Y version version = '.'.join(release.split('.')[:3]) # -- General configuration ------------------------------------------------ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx_search.extension', 'settings', ] templates_path = ['_templates'] source_suffix = '.rst' root_doc = 'index' exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] pygments_style = 'sphinx' html_last_updated_fmt = '' # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] htmlhelp_basename = 'django-constancedoc' # -- Options for LaTeX output --------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output latex_elements = {} latex_documents = [ ('index', 'django-constance.tex', 'django-constance Documentation', 'Jazzband', 'manual'), ] # -- 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)] # -- Options for Texinfo output ------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output texinfo_documents = [ ( 'index', 'django-constance', 'django-constance Documentation', 'Jazzband', 'django-constance', 'One line description of project.', 'Miscellaneous', ), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'), } django-constance-4.3.2/docs/extensions/000077500000000000000000000000001475050117200200665ustar00rootroot00000000000000django-constance-4.3.2/docs/extensions/__init__.py000066400000000000000000000000001475050117200221650ustar00rootroot00000000000000django-constance-4.3.2/docs/extensions/settings.py000066400000000000000000000002311475050117200222740ustar00rootroot00000000000000def setup(app): app.add_crossref_type( directivename='setting', rolename='setting', indextemplate='pair: %s; setting', ) django-constance-4.3.2/docs/index.rst000066400000000000000000000326051475050117200175360ustar00rootroot00000000000000Constance - Dynamic Django settings =================================== Features -------- * Easily migrate your static settings to dynamic settings. * Edit the dynamic settings in the Django admin interface. .. image:: _static/screenshot2.png Quick Installation ------------------ .. code-block:: pip install "django-constance[redis]" For complete installation instructions, including how to install the database backend, see :ref:`Backends `. Configuration ------------- Modify your ``settings.py``. Add ``'constance'`` to your :setting:`INSTALLED_APPS`, and move each key you want to turn dynamic into the :setting:`CONSTANCE_CONFIG` section, like this: .. code-block:: python INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.staticfiles', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', ... 'constance', ) CONSTANCE_CONFIG = { 'THE_ANSWER': (42, 'Answer to the Ultimate Question of Life, ' 'The Universe, and Everything'), } .. note:: Add constance *before* your project apps. .. note:: If you use admin extensions like `Grapelli `_, ``'constance'`` should be added in :setting:`INSTALLED_APPS` *before* those extensions. Here, ``42`` is the default value for the key ``THE_ANSWER`` if it is not found in the backend. The other member of the tuple is a help text the admin will show. See the :ref:`Backends ` section how to setup the backend and finish the configuration. ``django-constance``'s hashes generated in different instances of the same application may differ, preventing data from being saved. Use :setting:`CONSTANCE_IGNORE_ADMIN_VERSION_CHECK` in order to skip hash verification. .. code-block:: python CONSTANCE_IGNORE_ADMIN_VERSION_CHECK = True Signals ------- Each time a value is changed it will trigger a ``config_updated`` signal. .. code-block:: python from constance.signals import config_updated @receiver(config_updated) def constance_updated(sender, key, old_value, new_value, **kwargs): print(sender, key, old_value, new_value) The sender is the ``config`` object, and the ``key`` and ``new_value`` are the changed settings. Custom fields ------------- You can set the field type with the third value in the ``CONSTANCE_CONFIG`` tuple. The value can be one of the supported types or a string matching a key in your :setting:``CONSTANCE_ADDITIONAL_FIELDS`` The supported types are: * ``bool`` * ``int`` * ``float`` * ``Decimal`` * ``str`` * ``datetime`` * ``timedelta`` * ``date`` * ``time`` * ``list`` * ``dict`` For example, to force a value to be handled as a string: .. code-block:: python 'THE_ANSWER': (42, 'Answer to the Ultimate Question of Life, ' 'The Universe, and Everything', str), Custom field types are supported using the dictionary :setting:``CONSTANCE_ADDITIONAL_FIELDS``. This is a mapping between a field label and a sequence (list or tuple). The first item in the sequence is the string path of a field class, and the (optional) second item is a dictionary used to configure the field. The ``widget`` and ``widget_kwargs`` keys in the field config dictionary can be used to configure the widget used in admin, the other values will be passed as kwargs to the field's ``__init__()`` .. note:: Use later evaluated strings instead of direct classes for the field and widget classes: .. code-block:: python CONSTANCE_ADDITIONAL_FIELDS = { 'yes_no_null_select': ['django.forms.fields.ChoiceField', { 'widget': 'django.forms.Select', 'choices': ((None, "-----"), ("yes", "Yes"), ("no", "No")) }], } CONSTANCE_CONFIG = { '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 %} 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. 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 ----- Constance can be used from your Python code and from your Django templates. Python ^^^^^^ Accessing the config variables is as easy as importing the config object and accessing the variables with attribute lookups:: from constance import config # ... if config.THE_ANSWER == 42: answer_the_question() Django templates ^^^^^^^^^^^^^^^^ To access the config object from your template you can pass the object to the template context: .. code-block:: python from django.shortcuts import render from constance import config def myview(request): return render(request, 'my_template.html', {'config': config}) You can also use the included context processor. Insert ``'constance.context_processors.config'`` at 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 This will add the config instance to the context of any template rendered with a ``RequestContext``. Then, in your template you can refer to the config values just as any other variable, e.g.: .. code-block:: django

Welcome on {{ config.SITE_NAME }}

{% if config.BETA_LAUNCHED %} Woohoo! Head over here to use the beta. {% else %} Sadly we haven't launched yet, click here to signup for our newsletter. {% endif %} Command Line ^^^^^^^^^^^^ Constance settings can be get/set on the command line with the manage command :command:`constance`. Available options are: .. program:: constance .. option:: list list all Constance keys and their values .. code-block:: console $ ./manage.py constance list THE_ANSWER 42 SITE_NAME My Title .. option:: get get the value of a Constance key .. code-block:: console $ ./manage.py constance get THE_ANSWER 42 .. option:: set set the value of a Constance key .. code-block:: console $ ./manage.py constance set SITE_NAME "Another Title" If the value contains spaces it should be wrapped in quotes. .. note:: Set values are validated as per in admin, an error will be raised if validation fails: E.g., given this config as per the example app: .. code-block:: python CONSTANCE_CONFIG = { ... 'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"), } Setting an invalid date will fail as follow: .. code-block:: console $ ./manage.py constance set DATE_ESTABLISHED '1999-12-00' CommandError: Enter a valid date. .. note:: If the admin field is a :class:`MultiValueField`, then the separate field values need to be provided as separate arguments. E.g., a datetime using :class:`SplitDateTimeField`: .. code-block:: python CONSTANCE_CONFIG = { 'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24), 'time of the first commit'), } Then this works (and the quotes are optional): .. code-block:: console ./manage.py constance set DATETIME_VALUE '2011-09-24' '12:30:25' This doesn't work: .. code-block:: console ./manage.py constance set DATETIME_VALUE '2011-09-24 12:30:25' CommandError: Enter a list of values. .. option:: remove_stale_keys delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys) .. code-block:: console $ ./manage.py constance remove_stale_keys Record is considered stale if it exists in database but absent in config. Editing ------- Fire up your ``admin`` and you should see a new app called ``Constance`` with ``THE_ANSWER`` in the ``Config`` pseudo model. By default, changing the settings via the admin is only allowed for superusers. To change this, feel free to set the :setting:`CONSTANCE_SUPERUSER_ONLY` setting to ``False`` and give users or user groups access to the ``constance.change_config`` permission. .. figure:: _static/screenshot1.png The virtual application ``Constance`` among your regular applications. Custom settings form -------------------- If you aim at creating a custom settings form this is possible in the following way: You can inherit from ``ConstanceAdmin`` and set the ``form`` property on your custom admin to use your custom form. This allows you to define your own formsets and layouts, similar to defining a custom form on a standard Django ModelAdmin. This way you can fully style your settings form and group settings the way you like. .. code-block:: python from constance.admin import ConstanceAdmin, Config from constance.forms import ConstanceForm class CustomConfigForm(ConstanceForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) #... do stuff to make your settings form nice ... class ConfigAdmin(ConstanceAdmin): change_list_form = CustomConfigForm change_list_template = 'admin/config/settings.html' admin.site.unregister([Config]) admin.site.register([Config], ConfigAdmin) You can also override the ``get_changelist_form`` method which is called in ``changelist_view`` to get the actual form used to change the settings. This allows you to pick a different form according to the user that makes the request. For example: .. code-block:: python class SuperuserForm(ConstanceForm): # Do some stuff here class MyConstanceAdmin(ConstanceAdmin): def get_changelist_form(self, request): if request.user.is_superuser: return SuperuserForm: else: return super().get_changelist_form(request) Note that the default method returns ``self.change_list_form``. More documentation ------------------ .. toctree:: :maxdepth: 2 backends testing changes Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-constance-4.3.2/docs/make.bat000066400000000000000000000014401475050117200172730ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd django-constance-4.3.2/docs/requirements.txt000066400000000000000000000001071475050117200211510ustar00rootroot00000000000000readthedocs-sphinx-search==0.3.2 sphinx==7.3.7 sphinx-rtd-theme==2.0.0 django-constance-4.3.2/docs/testing.rst000066400000000000000000000067151475050117200201070ustar00rootroot00000000000000Testing ======= Testing how your app behaves with different config values is achieved with the :class:`override_config` class. This intentionally mirrors the use of Django's :class:`~django.test.override_setting`. .. py:class:: override_config(**kwargs) Replaces key-value pairs in the config. Use as decorator or context manager. Usage ~~~~~ It can be used as a decorator at the :class:`~django.test.TestCase` level, the method level and also as a `context manager `_. .. code-block:: python from constance import config from constance.test import override_config from django.test import TestCase @override_config(YOUR_NAME="Arthur of Camelot") class ExampleTestCase(TestCase): def test_what_is_your_name(self): self.assertEqual(config.YOUR_NAME, "Arthur of Camelot") @override_config(YOUR_QUEST="To find the Holy Grail") def test_what_is_your_quest(self): self.assertEqual(config.YOUR_QUEST, "To find the Holy Grail") def test_what_is_your_favourite_color(self): with override_config(YOUR_FAVOURITE_COLOR="Blue?"): self.assertEqual(config.YOUR_FAVOURITE_COLOR, "Blue?") Pytest usage ~~~~~~~~~~~~ Django-constance provides pytest plugin that adds marker :class:`@pytest.mark.override_config()`. It handles config override for module/class/function, and automatically revert any changes made to the constance config values when test is completed. .. py:function:: pytest.mark.override_config(**kwargs) Specify different config values for the marked tests in kwargs. Module scope override .. code-block:: python pytestmark = pytest.mark.override_config(API_URL="/awesome/url/") def test_api_url_is_awesome(): ... Class/function scope .. code-block:: python from constance import config @pytest.mark.override_config(API_URL="/awesome/url/") class SomeClassTest: def test_is_awesome_url(self): assert config.API_URL == "/awesome/url/" @pytest.mark.override_config(API_URL="/another/awesome/url/") def test_another_awesome_url(self): assert config.API_URL == "/another/awesome/url/" If you want to use override as a context manager or decorator, consider using .. code-block:: python from constance.test.pytest import override_config def test_override_context_manager(): with override_config(BOOL_VALUE=False): ... # or @override_config(BOOL_VALUE=False) def test_override_context_manager(): ... Pytest fixture as function or method parameter. .. note:: No import needed as fixture is available globally. .. code-block:: python def test_api_url_is_awesome(override_config): with override_config(API_URL="/awesome/url/"): ... Any scope, auto-used fixture alternative can also be implemented like this .. code-block:: python @pytest.fixture(scope='module', autouse=True) # e.g. module scope def api_url(override_config): with override_config(API_URL="/awesome/url/"): yield Memory backend ~~~~~~~~~~~~~~ If you don't want to rely on any external services such as Redis or database when running your unittests you can select :class:`MemoryBackend` for a test Django settings file .. code-block:: python CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend' It will provide simple thread-safe backend which will reset to default values after each test run. django-constance-4.3.2/example/000077500000000000000000000000001475050117200163725ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/000077500000000000000000000000001475050117200205205ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/__init__.py000066400000000000000000000000001475050117200226170ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/000077500000000000000000000000001475050117200214635ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/__init__.py000066400000000000000000000000001475050117200235620ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/catalog/000077500000000000000000000000001475050117200230755ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/catalog/__init__.py000066400000000000000000000000001475050117200251740ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/catalog/admin.py000066400000000000000000000001571475050117200245420ustar00rootroot00000000000000from django.contrib import admin from cheeseshop.apps.catalog.models import Brand admin.site.register(Brand) django-constance-4.3.2/example/cheeseshop/apps/catalog/migrations/000077500000000000000000000000001475050117200252515ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/catalog/migrations/0001_initial.py000066400000000000000000000006561475050117200277230ustar00rootroot00000000000000from 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)), ], ), ] django-constance-4.3.2/example/cheeseshop/apps/catalog/migrations/__init__.py000066400000000000000000000000001475050117200273500ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/catalog/models.py000066400000000000000000000001451475050117200247320ustar00rootroot00000000000000from django.db import models class Brand(models.Model): name = models.CharField(max_length=75) django-constance-4.3.2/example/cheeseshop/apps/storage/000077500000000000000000000000001475050117200231275ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/storage/__init__.py000066400000000000000000000000001475050117200252260ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/storage/admin.py000066400000000000000000000002751475050117200245750ustar00rootroot00000000000000from django.contrib import admin from cheeseshop.apps.storage.models import Shelf from cheeseshop.apps.storage.models import Supply admin.site.register(Shelf) admin.site.register(Supply) django-constance-4.3.2/example/cheeseshop/apps/storage/migrations/000077500000000000000000000000001475050117200253035ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/storage/migrations/0001_initial.py000066400000000000000000000015651475050117200277550ustar00rootroot00000000000000from 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', }, ), ] django-constance-4.3.2/example/cheeseshop/apps/storage/migrations/__init__.py000066400000000000000000000000001475050117200274020ustar00rootroot00000000000000django-constance-4.3.2/example/cheeseshop/apps/storage/models.py000066400000000000000000000004411475050117200247630ustar00rootroot00000000000000from django.db import models class Shelf(models.Model): name = models.CharField(max_length=75) class Meta: verbose_name_plural = 'shelves' class Supply(models.Model): name = models.CharField(max_length=75) class Meta: verbose_name_plural = 'supplies' django-constance-4.3.2/example/cheeseshop/fields.py000066400000000000000000000011101475050117200223310ustar00rootroot00000000000000import json from django.forms import fields from django.forms import widgets class JsonField(fields.CharField): widget = widgets.Textarea def __init__(self, rows: int = 5, **kwargs): self.rows = rows super().__init__(**kwargs) def widget_attrs(self, widget: widgets.Widget): attrs = super().widget_attrs(widget) attrs['rows'] = self.rows return attrs def to_python(self, value): if value: return json.loads(value) return {} def prepare_value(self, value): return json.dumps(value) django-constance-4.3.2/example/cheeseshop/settings.py000066400000000000000000000104651475050117200227400ustar00rootroot00000000000000""" Django settings for cheeseshop project. For more information on this file, see https://docs.djangoproject.com/en/4.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os from datetime import date BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ SITE_ID = 1 # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'hdx64#m+lnc_0ffoyehbk&7gk1&*9uar$pcfcm-%$km#p0$k=6' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'cheeseshop.apps.catalog', 'cheeseshop.apps.storage', 'constance', ) MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) ROOT_URLCONF = 'cheeseshop.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'cheeseshop.wsgi.application' # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': '/tmp/cheeseshop.db', } } CONSTANCE_REDIS_CONNECTION = { 'host': 'localhost', 'port': 6379, 'db': 0, } CONSTANCE_ADDITIONAL_FIELDS = { 'yes_no_null_select': [ 'django.forms.fields.ChoiceField', {'widget': 'django.forms.Select', 'choices': ((None, '-----'), ('yes', 'Yes'), ('no', 'No'))}, ], 'email': ('django.forms.fields.EmailField',), 'json_field': ['cheeseshop.fields.JsonField'], 'image_field': ['django.forms.ImageField', {}], } CONSTANCE_CONFIG = { 'BANNER': ('The National Cheese Emporium', 'name of the shop'), 'OWNER': ('Mr. Henry Wensleydale', 'owner of the shop'), 'OWNER_EMAIL': ('henry@example.com', 'contact email for owner', 'email'), 'MUSICIANS': (4, 'number of musicians inside the shop'), 'DATE_ESTABLISHED': (date(1972, 11, 30), "the shop's first opening"), 'MY_SELECT_KEY': ('yes', 'select yes or no', 'yes_no_null_select'), 'MULTILINE': ('Line one\nLine two', 'multiline string'), 'JSON_DATA': ( {'a': 1_000, 'b': 'test', 'max': 30_000_000}, 'Some test data for json', 'json_field', ), 'LOGO': ( '', 'Logo image file', 'image_field', ), } CONSTANCE_CONFIG_FIELDSETS = { 'Cheese shop general info': [ 'BANNER', 'OWNER', 'OWNER_EMAIL', 'MUSICIANS', 'DATE_ESTABLISHED', 'LOGO', ], 'Awkward test settings': ['MY_SELECT_KEY', 'MULTILINE', 'JSON_DATA'], } CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } } CONSTANCE_DATABASE_CACHE_BACKEND = 'default' # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'America/Chicago' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ STATIC_URL = '/static/' MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') CONSTANCE_FILE_ROOT = 'constance' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' django-constance-4.3.2/example/cheeseshop/urls.py000066400000000000000000000004701475050117200220600ustar00rootroot00000000000000from django.conf import settings from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import re_path admin.autodiscover() urlpatterns = [ re_path('admin/', admin.site.urls), ] if settings.DEBUG: urlpatterns += staticfiles_urlpatterns() django-constance-4.3.2/example/cheeseshop/wsgi.py000066400000000000000000000002531475050117200220430ustar00rootroot00000000000000import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cheeseshop.settings') application = get_wsgi_application() django-constance-4.3.2/example/manage.py000077500000000000000000000003751475050117200202040ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cheeseshop.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-constance-4.3.2/example/requirements.txt000066400000000000000000000000361475050117200216550ustar00rootroot00000000000000Django>=3.2 Pillow pymemcache django-constance-4.3.2/pyproject.toml000066400000000000000000000052401475050117200176540ustar00rootroot00000000000000[build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] name = "django-constance" dynamic = ["version"] description = "Django live settings with pluggable backends, including Redis." readme = "README.rst" license = { text = "BSD" } requires-python = ">=3.8" authors = [ { name = "Jannis Leidel", email = "jannis@leidel.info" }, ] keywords = ["django", "libraries", "redis", "settings"] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Utilities", ] [project.optional-dependencies] redis = [ "redis", ] [project.entry-points.pytest11] pytest-django-constance = "constance.test.pytest" [project.urls] homepage = "https://github.com/jazzband/django-constance/" documentation = "https://django-constance.readthedocs.io/en/latest/" repository = "https://github.com/jazzband/django-constance/" changelog = "https://github.com/jazzband/django-constance/releases/" [tool.setuptools] license-files = [] # see https://github.com/pypa/twine/issues/1216#issuecomment-2609745412 [tool.setuptools.packages.find] include = ["constance*"] [tool.setuptools_scm] version_file = "constance/_version.py" [tool.ruff] line-length = 120 indent-width = 4 [tool.ruff.format] quote-style = "single" indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" [tool.ruff.lint] select = [ "B", "D", "E", "ERA", "EXE", "F", "FBT", "FURB", "G", "FA", "I", "ICN", "INP", "LOG", "PGH", "RET", "RUF", "S", "SIM", "TID", "UP", "W", ] ignore = ["D1", "D203", "D205", "D415", "D212", "RUF012", "D400", "D401"] [tool.ruff.lint.per-file-ignores] "docs/*" = ["INP"] "example/*" = ["S"] "tests/*" = ["S"] [tool.ruff.lint.isort] force-single-line = true [tool.ruff.lint.flake8-boolean-trap] extend-allowed-calls = ["unittest.mock.patch", "django.db.models.Value"] django-constance-4.3.2/tests/000077500000000000000000000000001475050117200161015ustar00rootroot00000000000000django-constance-4.3.2/tests/__init__.py000066400000000000000000000000001475050117200202000ustar00rootroot00000000000000django-constance-4.3.2/tests/backends/000077500000000000000000000000001475050117200176535ustar00rootroot00000000000000django-constance-4.3.2/tests/backends/__init__.py000066400000000000000000000000001475050117200217520ustar00rootroot00000000000000django-constance-4.3.2/tests/backends/test_database.py000066400000000000000000000014121475050117200230260ustar00rootroot00000000000000from django.test import TestCase from constance import settings from tests.storage import StorageTestsMixin class TestDatabase(StorageTestsMixin, TestCase): def setUp(self): self.old_backend = settings.BACKEND settings.BACKEND = 'constance.backends.database.DatabaseBackend' super().setUp() def test_database_queries(self): # Read and set to default value with self.assertNumQueries(5): self.assertEqual(self.config.INT_VALUE, 1) # Read again with self.assertNumQueries(1): self.assertEqual(self.config.INT_VALUE, 1) # Set value with self.assertNumQueries(2): self.config.INT_VALUE = 15 def tearDown(self): settings.BACKEND = self.old_backend django-constance-4.3.2/tests/backends/test_memory.py000066400000000000000000000007271475050117200226020ustar00rootroot00000000000000from django.test import TestCase from constance import settings from tests.storage import StorageTestsMixin class TestMemory(StorageTestsMixin, TestCase): def setUp(self): self.old_backend = settings.BACKEND settings.BACKEND = 'constance.backends.memory.MemoryBackend' super().setUp() self.config._backend._storage = {} def tearDown(self): self.config._backend._storage = {} settings.BACKEND = self.old_backend django-constance-4.3.2/tests/backends/test_redis.py000066400000000000000000000011231475050117200223670ustar00rootroot00000000000000from django.test import TestCase from constance import settings from tests.storage import StorageTestsMixin class TestRedis(StorageTestsMixin, TestCase): _BACKEND = 'constance.backends.redisd.RedisBackend' def setUp(self): self.old_backend = settings.BACKEND settings.BACKEND = self._BACKEND super().setUp() self.config._backend._rd.clear() def tearDown(self): self.config._backend._rd.clear() settings.BACKEND = self.old_backend class TestCachingRedis(TestRedis): _BACKEND = 'constance.backends.redisd.CachingRedisBackend' django-constance-4.3.2/tests/redis_mockup.py000066400000000000000000000002331475050117200211350ustar00rootroot00000000000000class Connection(dict): def set(self, key, value): self[key] = value def mget(self, keys): return [self.get(key) for key in keys] django-constance-4.3.2/tests/settings.py000066400000000000000000000066731475050117200203270ustar00rootroot00000000000000from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from decimal import Decimal SECRET_KEY = 'cheese' MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) DATABASE_ENGINE = 'sqlite3' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, 'secondary': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, } INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.staticfiles', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'constance', 'constance.backends.database', ) ROOT_URLCONF = 'tests.urls' CONSTANCE_REDIS_CONNECTION_CLASS = 'tests.redis_mockup.Connection' CONSTANCE_ADDITIONAL_FIELDS = { 'yes_no_null_select': [ 'django.forms.fields.ChoiceField', {'widget': 'django.forms.Select', 'choices': ((None, '-----'), ('yes', 'Yes'), ('no', 'No'))}, ], # note this intentionally uses a tuple so that we can test immutable 'email': ('django.forms.fields.EmailField',), 'array': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}], 'json': ['django.forms.fields.CharField', {'widget': 'django.forms.Textarea'}], } USE_TZ = True CONSTANCE_CONFIG = { 'INT_VALUE': (1, 'some int'), 'BOOL_VALUE': (True, 'true or false'), 'STRING_VALUE': ('Hello world', 'greetings'), 'DECIMAL_VALUE': (Decimal('0.1'), 'the first release version'), 'DATETIME_VALUE': (datetime(2010, 8, 23, 11, 29, 24), 'time of the first commit'), 'FLOAT_VALUE': (3.1415926536, 'PI'), 'DATE_VALUE': (date(2010, 12, 24), 'Merry Chrismas'), 'TIME_VALUE': (time(23, 59, 59), 'And happy New Year'), 'TIMEDELTA_VALUE': (timedelta(days=1, hours=2, minutes=3), 'Interval'), 'CHOICE_VALUE': ('yes', 'select yes or no', 'yes_no_null_select'), 'LINEBREAK_VALUE': ('Spam spam', 'eggs\neggs'), 'EMAIL_VALUE': ('test@example.com', 'An email', 'email'), 'LIST_VALUE': ([1, '1', date(2019, 1, 1)], 'A list', 'array'), 'JSON_VALUE': ( { 'key': 'value', 'key2': 2, 'key3': [1, 2, 3], 'key4': {'key': 'value'}, 'key5': date(2019, 1, 1), 'key6': None, }, 'A JSON object', 'json', ), } DEBUG = True STATIC_ROOT = './static/' STATIC_URL = '/static/' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.request', 'django.template.context_processors.static', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'constance.context_processors.config', ], }, }, ] django-constance-4.3.2/tests/storage.py000066400000000000000000000114601475050117200201210ustar00rootroot00000000000000from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from decimal import Decimal from constance import settings from constance.base import Config class StorageTestsMixin: def setUp(self): self.config = Config() super().setUp() def test_store(self): self.assertEqual(self.config.INT_VALUE, 1) self.assertEqual(self.config.BOOL_VALUE, True) self.assertEqual(self.config.STRING_VALUE, 'Hello world') self.assertEqual(self.config.DECIMAL_VALUE, Decimal('0.1')) self.assertEqual(self.config.DATETIME_VALUE, datetime(2010, 8, 23, 11, 29, 24)) self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536) self.assertEqual(self.config.DATE_VALUE, date(2010, 12, 24)) self.assertEqual(self.config.TIME_VALUE, time(23, 59, 59)) self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=1, hours=2, minutes=3)) self.assertEqual(self.config.CHOICE_VALUE, 'yes') self.assertEqual(self.config.EMAIL_VALUE, 'test@example.com') self.assertEqual(self.config.LIST_VALUE, [1, '1', date(2019, 1, 1)]) self.assertEqual( self.config.JSON_VALUE, { 'key': 'value', 'key2': 2, 'key3': [1, 2, 3], 'key4': {'key': 'value'}, 'key5': date(2019, 1, 1), 'key6': None, }, ) # set values self.config.INT_VALUE = 100 self.config.BOOL_VALUE = False self.config.STRING_VALUE = 'Beware the weeping angel' self.config.DECIMAL_VALUE = Decimal('1.2') self.config.DATETIME_VALUE = datetime(1977, 10, 2) self.config.FLOAT_VALUE = 2.718281845905 self.config.DATE_VALUE = date(2001, 12, 20) self.config.TIME_VALUE = time(1, 59, 0) self.config.TIMEDELTA_VALUE = timedelta(days=2, hours=3, minutes=4) self.config.CHOICE_VALUE = 'no' self.config.EMAIL_VALUE = 'foo@bar.com' self.config.LIST_VALUE = [1, date(2020, 2, 2)] self.config.JSON_VALUE = {'key': 'OK'} # read again self.assertEqual(self.config.INT_VALUE, 100) self.assertEqual(self.config.BOOL_VALUE, False) self.assertEqual(self.config.STRING_VALUE, 'Beware the weeping angel') self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2')) self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2)) self.assertEqual(self.config.FLOAT_VALUE, 2.718281845905) self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20)) self.assertEqual(self.config.TIME_VALUE, time(1, 59, 0)) self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=2, hours=3, minutes=4)) self.assertEqual(self.config.CHOICE_VALUE, 'no') self.assertEqual(self.config.EMAIL_VALUE, 'foo@bar.com') self.assertEqual(self.config.LIST_VALUE, [1, date(2020, 2, 2)]) self.assertEqual(self.config.JSON_VALUE, {'key': 'OK'}) def test_nonexistent(self): self.assertRaises(AttributeError, getattr, self.config, 'NON_EXISTENT') with self.assertRaises(AttributeError): self.config.NON_EXISTENT = 1 def test_missing_values(self): # set some values and leave out others self.config.BOOL_VALUE = False self.config.DECIMAL_VALUE = Decimal('1.2') self.config.DATETIME_VALUE = datetime(1977, 10, 2) self.config.DATE_VALUE = date(2001, 12, 20) self.config.TIME_VALUE = time(1, 59, 0) self.assertEqual(self.config.INT_VALUE, 1) # this should be the default value self.assertEqual(self.config.BOOL_VALUE, False) self.assertEqual(self.config.STRING_VALUE, 'Hello world') # this should be the default value self.assertEqual(self.config.DECIMAL_VALUE, Decimal('1.2')) self.assertEqual(self.config.DATETIME_VALUE, datetime(1977, 10, 2)) self.assertEqual(self.config.FLOAT_VALUE, 3.1415926536) # this should be the default value self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20)) self.assertEqual(self.config.TIME_VALUE, time(1, 59, 0)) self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=1, hours=2, minutes=3)) def test_backend_retrieves_multiple_values(self): # Check corner cases such as falsy values self.config.INT_VALUE = 0 self.config.BOOL_VALUE = False self.config.STRING_VALUE = '' values = dict(self.config._backend.mget(settings.CONFIG)) self.assertEqual(values['INT_VALUE'], 0) self.assertEqual(values['BOOL_VALUE'], False) self.assertEqual(values['STRING_VALUE'], '') def test_backend_does_not_return_none_values(self): result = dict(self.config._backend.mget(settings.CONFIG)) self.assertEqual(result, {}) django-constance-4.3.2/tests/test_admin.py000066400000000000000000000237531475050117200206140ustar00rootroot00000000000000from datetime import datetime from unittest import mock from django.contrib import admin from django.contrib.auth.models import Permission from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.template.defaultfilters import linebreaksbr from django.test import RequestFactory from django.test import TestCase from django.utils.translation import gettext_lazy as _ from constance import settings from constance.admin import Config from constance.forms import ConstanceForm from constance.utils import get_values class TestAdmin(TestCase): model = Config def setUp(self): super().setUp() self.rf = RequestFactory() self.superuser = User.objects.create_superuser('admin', 'nimda', 'a@a.cz') self.normaluser = User.objects.create_user('normal', 'nimda', 'b@b.cz') self.normaluser.is_staff = True self.normaluser.save() self.options = admin.site._registry[self.model] def test_changelist(self): self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) self.assertEqual(response.status_code, 200) def test_custom_auth(self): settings.SUPERUSER_ONLY = False self.client.login(username='normal', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.normaluser self.assertRaises(PermissionDenied, self.options.changelist_view, request, {}) self.assertFalse(request.user.has_perm('constance.change_config')) # reload user to reset permission cache request = self.rf.get('/admin/constance/config/') request.user = User.objects.get(pk=self.normaluser.pk) request.user.user_permissions.add(Permission.objects.get(codename='change_config')) self.assertTrue(request.user.has_perm('constance.change_config')) response = self.options.changelist_view(request, {}) self.assertEqual(response.status_code, 200) def test_linebreaks(self): self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) self.assertContains(response, 'LINEBREAK_VALUE') self.assertContains(response, linebreaksbr('eggs\neggs')) @mock.patch( 'constance.settings.CONFIG_FIELDSETS', { 'Numbers': ('INT_VALUE',), 'Text': ('STRING_VALUE',), }, ) def test_fieldset_headers(self): self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) self.assertContains(response, '

Numbers

') self.assertContains(response, '

Text

') @mock.patch( 'constance.settings.CONFIG_FIELDSETS', ( ('Numbers', ('INT_VALUE',)), ('Text', ('STRING_VALUE',)), ), ) def test_fieldset_tuple(self): self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) self.assertContains(response, '

Numbers

') self.assertContains(response, '

Text

') @mock.patch( 'constance.settings.CONFIG_FIELDSETS', { 'Numbers': { 'fields': ( 'INT_VALUE', 'DECIMAL_VALUE', ), 'collapse': True, }, 'Text': { 'fields': ( 'STRING_VALUE', 'LINEBREAK_VALUE', ), 'collapse': True, }, }, ) def test_collapsed_fieldsets(self): self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) self.assertContains(response, 'module collapse') @mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('INT_VALUE',)}) @mock.patch( 'constance.settings.CONFIG', { 'INT_VALUE': (1, 'some int'), }, ) @mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True) @mock.patch('constance.forms.ConstanceForm.save', lambda _: None) @mock.patch('constance.forms.ConstanceForm.is_valid', lambda _: True) def test_submit(self): """ Test that submitting the admin page results in an http redirect when everything is in order. """ initial_value = {'INT_VALUE': settings.CONFIG['INT_VALUE'][0]} self.client.login(username='admin', password='nimda') request = self.rf.post( '/admin/constance/config/', data={ **initial_value, 'version': '123', }, ) request.user = self.superuser request._dont_enforce_csrf_checks = True with mock.patch('django.contrib.messages.add_message') as mock_message, mock.patch.object( ConstanceForm, '__init__', **initial_value, return_value=None ) as mock_form: response = self.options.changelist_view(request, {}) mock_form.assert_called_with(data=request.POST, files=request.FILES, initial=initial_value, request=request) mock_message.assert_called_with(request, 25, _('Live settings updated successfully.')) self.assertIsInstance(response, HttpResponseRedirect) @mock.patch('constance.settings.CONFIG_FIELDSETS', {'FieldSetOne': ('MULTILINE',)}) @mock.patch( 'constance.settings.CONFIG', { 'MULTILINE': ('Hello\nWorld', 'multiline value'), }, ) @mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True) def test_newlines_normalization(self): self.client.login(username='admin', password='nimda') request = self.rf.post( '/admin/constance/config/', data={ 'MULTILINE': 'Hello\r\nWorld', 'version': '123', }, ) request.user = self.superuser request._dont_enforce_csrf_checks = True with mock.patch('django.contrib.messages.add_message'): response = self.options.changelist_view(request, {}) self.assertIsInstance(response, HttpResponseRedirect) self.assertEqual(get_values()['MULTILINE'], 'Hello\nWorld') @mock.patch( 'constance.settings.CONFIG', { 'DATETIME_VALUE': (datetime(2019, 8, 7, 18, 40, 0), 'some naive datetime'), }, ) @mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True) @mock.patch('tests.redis_mockup.Connection.set', mock.MagicMock()) def test_submit_aware_datetime(self): """ Test that submitting the admin page results in an http redirect when everything is in order. """ request = self.rf.post( '/admin/constance/config/', data={ 'DATETIME_VALUE_0': '2019-08-07', 'DATETIME_VALUE_1': '19:17:01', 'version': '123', }, ) request.user = self.superuser request._dont_enforce_csrf_checks = True with mock.patch('django.contrib.messages.add_message'): response = self.options.changelist_view(request, {}) self.assertIsInstance(response, HttpResponseRedirect) @mock.patch( 'constance.settings.CONFIG_FIELDSETS', { 'Numbers': ('INT_VALUE',), 'Text': ('STRING_VALUE',), }, ) def test_inconsistent_fieldset_submit(self): """ Test that the admin page warns users if the CONFIG_FIELDSETS setting doesn't account for every field in CONFIG. """ self.client.login(username='admin', password='nimda') request = self.rf.post('/admin/constance/config/', data=None) request.user = self.superuser request._dont_enforce_csrf_checks = True with mock.patch('django.contrib.messages.add_message'): response = self.options.changelist_view(request, {}) self.assertContains(response, 'is missing field(s)') @mock.patch( 'constance.settings.CONFIG_FIELDSETS', { 'Fieldsets': ( 'STRING_VALUE', 'INT_VALUE', ), }, ) def test_fieldset_ordering_1(self): """Ordering of inner list should be preserved.""" self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) response.render() content_str = response.content.decode() self.assertGreater(content_str.find('INT_VALUE'), content_str.find('STRING_VALUE')) @mock.patch( 'constance.settings.CONFIG_FIELDSETS', { 'Fieldsets': ( 'INT_VALUE', 'STRING_VALUE', ), }, ) def test_fieldset_ordering_2(self): """Ordering of inner list should be preserved.""" self.client.login(username='admin', password='nimda') request = self.rf.get('/admin/constance/config/') request.user = self.superuser response = self.options.changelist_view(request, {}) response.render() content_str = response.content.decode() self.assertGreater(content_str.find('STRING_VALUE'), content_str.find('INT_VALUE')) def test_labels(self): self.assertEqual(type(self.model._meta.label), str) self.assertEqual(type(self.model._meta.label_lower), str) django-constance-4.3.2/tests/test_checks.py000066400000000000000000000040731475050117200207560ustar00rootroot00000000000000from unittest import mock from django.test import TestCase from constance import settings from constance.checks import check_fieldsets from constance.checks import get_inconsistent_fieldnames class ChecksTestCase(TestCase): @mock.patch('constance.settings.CONFIG_FIELDSETS', {'Set1': settings.CONFIG.keys()}) def test_get_inconsistent_fieldnames_none(self): """ Test that get_inconsistent_fieldnames returns an empty data and no checks fail if CONFIG_FIELDSETS accounts for every key in settings.CONFIG. """ missing_keys, extra_keys = get_inconsistent_fieldnames() self.assertFalse(missing_keys) self.assertFalse(extra_keys) @mock.patch( 'constance.settings.CONFIG_FIELDSETS', {'Set1': list(settings.CONFIG.keys())[:-1]}, ) def test_get_inconsistent_fieldnames_for_missing_keys(self): """ Test that get_inconsistent_fieldnames returns data and the check fails if CONFIG_FIELDSETS does not account for every key in settings.CONFIG. """ missing_keys, extra_keys = get_inconsistent_fieldnames() self.assertTrue(missing_keys) self.assertFalse(extra_keys) self.assertEqual(1, len(check_fieldsets())) @mock.patch( 'constance.settings.CONFIG_FIELDSETS', {'Set1': [*settings.CONFIG.keys(), 'FORGOTTEN_KEY']}, ) def test_get_inconsistent_fieldnames_for_extra_keys(self): """ Test that get_inconsistent_fieldnames returns data and the check fails if CONFIG_FIELDSETS contains extra key that is absent in settings.CONFIG. """ missing_keys, extra_keys = get_inconsistent_fieldnames() self.assertFalse(missing_keys) self.assertTrue(extra_keys) self.assertEqual(1, len(check_fieldsets())) @mock.patch('constance.settings.CONFIG_FIELDSETS', {}) def test_check_fieldsets(self): """check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined.""" del settings.CONFIG_FIELDSETS self.assertEqual(0, len(check_fieldsets())) django-constance-4.3.2/tests/test_cli.py000066400000000000000000000072071475050117200202670ustar00rootroot00000000000000import contextlib from datetime import datetime from io import StringIO from textwrap import dedent from django.conf import settings from django.core.management import CommandError from django.core.management import call_command from django.test import TransactionTestCase from django.utils import timezone from django.utils.encoding import smart_str from constance import config from constance.models import Constance class CliTestCase(TransactionTestCase): def setUp(self): self.out = StringIO() def test_help(self): with contextlib.suppress(SystemExit): call_command('constance', '--help') def test_list(self): call_command('constance', 'list', stdout=self.out) self.assertEqual( set(self.out.getvalue().splitlines()), set( dedent( smart_str( """ BOOL_VALUE\tTrue EMAIL_VALUE\ttest@example.com INT_VALUE\t1 LINEBREAK_VALUE\tSpam spam DATE_VALUE\t2010-12-24 TIME_VALUE\t23:59:59 TIMEDELTA_VALUE\t1 day, 2:03:00 STRING_VALUE\tHello world CHOICE_VALUE\tyes DECIMAL_VALUE\t0.1 DATETIME_VALUE\t2010-08-23 11:29:24 FLOAT_VALUE\t3.1415926536 JSON_VALUE\t{'key': 'value', 'key2': 2, 'key3': [1, 2, 3], 'key4': {'key': 'value'}, 'key5': datetime.date(2019, 1, 1), 'key6': None} LIST_VALUE\t[1, '1', datetime.date(2019, 1, 1)] """ # noqa: E501 ) ).splitlines() ), ) def test_get(self): call_command('constance', *('get EMAIL_VALUE'.split()), stdout=self.out) self.assertEqual(self.out.getvalue().strip(), 'test@example.com') def test_set(self): call_command('constance', *('set EMAIL_VALUE blah@example.com'.split()), stdout=self.out) self.assertEqual(config.EMAIL_VALUE, 'blah@example.com') call_command('constance', *('set', 'DATETIME_VALUE', '2011-09-24', '12:30:25'), stdout=self.out) expected = datetime(2011, 9, 24, 12, 30, 25) if settings.USE_TZ: expected = timezone.make_aware(expected) self.assertEqual(config.DATETIME_VALUE, expected) def test_get_invalid_name(self): self.assertRaisesMessage( CommandError, 'NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG', call_command, 'constance', 'get', 'NOT_A_REAL_CONFIG', ) def test_set_invalid_name(self): self.assertRaisesMessage( CommandError, 'NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG', call_command, 'constance', 'set', 'NOT_A_REAL_CONFIG', 'foo', ) def test_set_invalid_value(self): self.assertRaisesMessage( CommandError, 'Enter a valid email address.', call_command, 'constance', 'set', 'EMAIL_VALUE', 'not a valid email', ) def test_set_invalid_multi_value(self): self.assertRaisesMessage( CommandError, 'Enter a list of values.', call_command, 'constance', 'set', 'DATETIME_VALUE', '2011-09-24 12:30:25', ) def test_delete_stale_records(self): initial_count = Constance.objects.count() Constance.objects.create(key='STALE_KEY', value=None) call_command('constance', 'remove_stale_keys', stdout=self.out) self.assertEqual(Constance.objects.count(), initial_count, msg=self.out) django-constance-4.3.2/tests/test_codecs.py000066400000000000000000000117351475050117200207610ustar00rootroot00000000000000import uuid from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from decimal import Decimal from unittest import TestCase from constance.codecs import dumps from constance.codecs import loads from constance.codecs import register_type class TestJSONSerialization(TestCase): def setUp(self): self.datetime = datetime(2023, 10, 5, 15, 30, 0) self.date = date(2023, 10, 5) self.time = time(15, 30, 0) self.decimal = Decimal('10.5') self.uuid = uuid.UUID('12345678123456781234567812345678') self.string = 'test' self.integer = 42 self.float = 3.14 self.boolean = True self.none = None self.timedelta = timedelta(days=1, hours=2, minutes=3) self.list = [1, 2, self.date] self.dict = {'key': self.date, 'key2': 1} def test_serializes_and_deserializes_default_types(self): self.assertEqual(dumps(self.datetime), '{"__type__": "datetime", "__value__": "2023-10-05T15:30:00"}') self.assertEqual(dumps(self.date), '{"__type__": "date", "__value__": "2023-10-05"}') self.assertEqual(dumps(self.time), '{"__type__": "time", "__value__": "15:30:00"}') self.assertEqual(dumps(self.decimal), '{"__type__": "decimal", "__value__": "10.5"}') self.assertEqual(dumps(self.uuid), '{"__type__": "uuid", "__value__": "12345678123456781234567812345678"}') self.assertEqual(dumps(self.string), '{"__type__": "default", "__value__": "test"}') self.assertEqual(dumps(self.integer), '{"__type__": "default", "__value__": 42}') self.assertEqual(dumps(self.float), '{"__type__": "default", "__value__": 3.14}') self.assertEqual(dumps(self.boolean), '{"__type__": "default", "__value__": true}') self.assertEqual(dumps(self.none), '{"__type__": "default", "__value__": null}') self.assertEqual(dumps(self.timedelta), '{"__type__": "timedelta", "__value__": 93780.0}') self.assertEqual( dumps(self.list), '{"__type__": "default", "__value__": [1, 2, {"__type__": "date", "__value__": "2023-10-05"}]}', ) self.assertEqual( dumps(self.dict), '{"__type__": "default", "__value__": {"key": {"__type__": "date", "__value__": "2023-10-05"}, "key2": 1}}', ) for t in ( self.datetime, self.date, self.time, self.decimal, self.uuid, self.string, self.integer, self.float, self.boolean, self.none, self.timedelta, self.dict, self.list, ): self.assertEqual(t, loads(dumps(t))) def test_invalid_deserialization(self): with self.assertRaisesRegex(ValueError, 'Expecting value'): loads('THIS_IS_NOT_RIGHT') with self.assertRaisesRegex(ValueError, 'Invalid object'): loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test", "THIS_IS_NOT_RIGHT": "THIS_IS_NOT_RIGHT"}') with self.assertRaisesRegex(ValueError, 'Unsupported type'): loads('{"__type__": "THIS_IS_NOT_RIGHT", "__value__": "test"}') def test_handles_unknown_type(self): class UnknownType: pass with self.assertRaisesRegex(TypeError, 'Object of type UnknownType is not JSON serializable'): dumps(UnknownType()) def test_custom_type_serialization(self): class CustomType: def __init__(self, value): self.value = value register_type(CustomType, 'custom', lambda o: o.value, lambda o: CustomType(o)) custom_data = CustomType('test') json_data = dumps(custom_data) self.assertEqual(json_data, '{"__type__": "custom", "__value__": "test"}') deserialized_data = loads(json_data) self.assertTrue(isinstance(deserialized_data, CustomType)) self.assertEqual(deserialized_data.value, 'test') def test_register_known_type(self): with self.assertRaisesRegex(ValueError, 'Discriminator must be specified'): register_type(int, '', lambda o: o.value, lambda o: int(o)) with self.assertRaisesRegex(ValueError, 'Type with discriminator default is already registered'): register_type(int, 'default', lambda o: o.value, lambda o: int(o)) register_type(int, 'new_custom_type', lambda o: o.value, lambda o: int(o)) with self.assertRaisesRegex(ValueError, 'Type with discriminator new_custom_type is already registered'): register_type(int, 'new_custom_type', lambda o: o.value, lambda o: int(o)) def test_nested_collections(self): data = {'key': [[[[{'key': self.date}]]]]} self.assertEqual( dumps(data), ( '{"__type__": "default", ' '"__value__": {"key": [[[[{"key": {"__type__": "date", "__value__": "2023-10-05"}}]]]]}}' ), ) self.assertEqual(data, loads(dumps(data))) django-constance-4.3.2/tests/test_form.py000066400000000000000000000020731475050117200204570ustar00rootroot00000000000000from django.forms import fields from django.test import TestCase from constance.forms import ConstanceForm class TestForm(TestCase): def test_form_field_types(self): f = ConstanceForm({}) self.assertIsInstance(f.fields['INT_VALUE'], fields.IntegerField) self.assertIsInstance(f.fields['BOOL_VALUE'], fields.BooleanField) self.assertIsInstance(f.fields['STRING_VALUE'], fields.CharField) self.assertIsInstance(f.fields['DECIMAL_VALUE'], fields.DecimalField) self.assertIsInstance(f.fields['DATETIME_VALUE'], fields.SplitDateTimeField) self.assertIsInstance(f.fields['TIMEDELTA_VALUE'], fields.DurationField) self.assertIsInstance(f.fields['FLOAT_VALUE'], fields.FloatField) self.assertIsInstance(f.fields['DATE_VALUE'], fields.DateField) self.assertIsInstance(f.fields['TIME_VALUE'], fields.TimeField) # from CONSTANCE_ADDITIONAL_FIELDS self.assertIsInstance(f.fields['CHOICE_VALUE'], fields.ChoiceField) self.assertIsInstance(f.fields['EMAIL_VALUE'], fields.EmailField) django-constance-4.3.2/tests/test_pytest_overrides.py000066400000000000000000000051351475050117200231300ustar00rootroot00000000000000import unittest try: import pytest from constance import config from constance.test.pytest import override_config class TestPytestOverrideConfigFunctionDecorator: """ Test that the override_config decorator works correctly for Pytest classes. Test usage of override_config on test method and as context manager. """ def test_default_value_is_true(self): """Assert that the default value of config.BOOL_VALUE is True.""" assert config.BOOL_VALUE @pytest.mark.override_config(BOOL_VALUE=False) def test_override_config_on_method_changes_config_value(self): """Assert that the pytest mark decorator changes config.BOOL_VALUE.""" assert not config.BOOL_VALUE def test_override_config_as_context_manager_changes_config_value(self): """Assert that the context manager changes config.BOOL_VALUE.""" with override_config(BOOL_VALUE=False): assert not config.BOOL_VALUE assert config.BOOL_VALUE @override_config(BOOL_VALUE=False) def test_method_decorator(self): """Ensure `override_config` can be used as test method decorator.""" assert not config.BOOL_VALUE @pytest.mark.override_config(BOOL_VALUE=False) class TestPytestOverrideConfigDecorator: """Test that the override_config decorator works on classes.""" def test_override_config_on_class_changes_config_value(self): """Assert that the class decorator changes config.BOOL_VALUE.""" assert not config.BOOL_VALUE @pytest.mark.override_config(BOOL_VALUE='True') def test_override_config_on_overridden_value(self): """Ensure that method mark decorator changes already overridden value for class.""" assert config.BOOL_VALUE == 'True' def test_fixture_override_config(override_config): """ Ensure `override_config` fixture is available globally and can be used in test functions. """ with override_config(BOOL_VALUE=False): assert not config.BOOL_VALUE @override_config(BOOL_VALUE=False) def test_func_decorator(): """Ensure `override_config` can be used as test function decorator.""" assert not config.BOOL_VALUE except ImportError: pass class PytestTests(unittest.TestCase): def setUp(self): self.skipTest('Skip all pytest tests when using unittest') def test_do_not_skip_silently(self): """If no at least one test present, unittest silently skips module.""" pass django-constance-4.3.2/tests/test_test_overrides.py000066400000000000000000000024661475050117200225630ustar00rootroot00000000000000from django.test import TestCase from constance import config from constance.test import override_config class OverrideConfigFunctionDecoratorTestCase(TestCase): """ Test that the override_config decorator works correctly. Test usage of override_config on test method and as context manager. """ def test_default_value_is_true(self): """Assert that the default value of config.BOOL_VALUE is True.""" self.assertTrue(config.BOOL_VALUE) @override_config(BOOL_VALUE=False) def test_override_config_on_method_changes_config_value(self): """Assert that the method decorator changes config.BOOL_VALUE.""" self.assertFalse(config.BOOL_VALUE) def test_override_config_as_context_manager_changes_config_value(self): """Assert that the context manager changes config.BOOL_VALUE.""" with override_config(BOOL_VALUE=False): self.assertFalse(config.BOOL_VALUE) self.assertTrue(config.BOOL_VALUE) @override_config(BOOL_VALUE=False) class OverrideConfigClassDecoratorTestCase(TestCase): """Test that the override_config decorator works on classes.""" def test_override_config_on_class_changes_config_value(self): """Assert that the class decorator changes config.BOOL_VALUE.""" self.assertFalse(config.BOOL_VALUE) django-constance-4.3.2/tests/test_utils.py000066400000000000000000000061141475050117200206540ustar00rootroot00000000000000import datetime from decimal import Decimal from django.core.exceptions import ValidationError from django.test import TestCase from constance.management.commands.constance import _set_constance_value from constance.utils import get_values from constance.utils import get_values_for_keys class UtilsTestCase(TestCase): def test_set_value_validation(self): self.assertRaisesMessage(ValidationError, 'Enter a whole number.', _set_constance_value, 'INT_VALUE', 'foo') self.assertRaisesMessage( ValidationError, 'Enter a valid email address.', _set_constance_value, 'EMAIL_VALUE', 'not a valid email' ) self.assertRaisesMessage( ValidationError, 'Enter a valid date.', _set_constance_value, 'DATETIME_VALUE', ( '2000-00-00', '99:99:99', ), ) self.assertRaisesMessage( ValidationError, 'Enter a valid time.', _set_constance_value, 'DATETIME_VALUE', ( '2016-01-01', '99:99:99', ), ) def test_get_values(self): self.assertEqual( get_values(), { 'FLOAT_VALUE': 3.1415926536, 'BOOL_VALUE': True, 'EMAIL_VALUE': 'test@example.com', 'INT_VALUE': 1, 'CHOICE_VALUE': 'yes', 'TIME_VALUE': datetime.time(23, 59, 59), 'DATE_VALUE': datetime.date(2010, 12, 24), 'TIMEDELTA_VALUE': datetime.timedelta(days=1, hours=2, minutes=3), 'LINEBREAK_VALUE': 'Spam spam', 'DECIMAL_VALUE': Decimal('0.1'), 'STRING_VALUE': 'Hello world', 'DATETIME_VALUE': datetime.datetime(2010, 8, 23, 11, 29, 24), 'LIST_VALUE': [1, '1', datetime.date(2019, 1, 1)], 'JSON_VALUE': { 'key': 'value', 'key2': 2, 'key3': [1, 2, 3], 'key4': {'key': 'value'}, 'key5': datetime.date(2019, 1, 1), 'key6': None, }, }, ) def test_get_values_for_keys(self): self.assertEqual( get_values_for_keys(['BOOL_VALUE', 'CHOICE_VALUE', 'LINEBREAK_VALUE']), { 'BOOL_VALUE': True, 'CHOICE_VALUE': 'yes', 'LINEBREAK_VALUE': 'Spam spam', }, ) def test_get_values_for_keys_empty_keys(self): result = get_values_for_keys([]) self.assertEqual(result, {}) def test_get_values_for_keys_throw_error_if_no_key(self): self.assertRaisesMessage( AttributeError, '"OLD_VALUE, BOLD_VALUE" keys not found in configuration.', get_values_for_keys, ['BOOL_VALUE', 'OLD_VALUE', 'BOLD_VALUE'], ) def test_get_values_for_keys_invalid_input_type(self): with self.assertRaises(TypeError): get_values_for_keys('key1') django-constance-4.3.2/tests/urls.py000066400000000000000000000001661475050117200174430ustar00rootroot00000000000000from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] django-constance-4.3.2/tox.ini000066400000000000000000000023611475050117200162540ustar00rootroot00000000000000[tox] isolated_build = true envlist = py{38,39,310,311,312}-dj{42}-{unittest,pytest,checkmigrations} py{310,311,312}-dj{50}-{unittest,pytest,checkmigrations} py{310,311,312,313}-dj{51}-{unittest,pytest,checkmigrations} py{310,311,312,313}-dj{52}-{unittest,pytest,checkmigrations} py{310,311,312,313}-dj{main}-{unittest,pytest,checkmigrations} skip_missing_interpreters = True [testenv] deps = redis coverage dj42: django>=4.2,<4.3 dj50: django>=5.0,<5.1 dj51: django>=5.1,<5.2 dj52: django==5.2alpha1 djmain: https://github.com/django/django/archive/main.tar.gz pytest: pytest pytest: pytest-cov pytest: pytest-django usedevelop = True ignore_outcome = djmain: True commands = unittest: coverage run {envbindir}/django-admin test -v2 unittest: coverage report unittest: coverage xml pytest: pytest --cov=. --ignore=.tox --disable-pytest-warnings --cov-report=xml --cov-append {toxinidir} checkmigrations: django-admin makemigrations --check --dry-run setenv = PYTHONPATH = {toxinidir} PYTHONDONTWRITEBYTECODE = 1 DJANGO_SETTINGS_MODULE = tests.settings [gh-actions] python = 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12: py312 3.13: py313