mirror of
https://github.com/jazzband/django-constance.git
synced 2026-03-16 22:40:24 +00:00
Refactor app and model
This commit is contained in:
parent
8b03012918
commit
2cb2ccd063
19 changed files with 81 additions and 179 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,5 +7,6 @@ dist/
|
|||
test.db
|
||||
.tox
|
||||
.coverage
|
||||
coverage.xml
|
||||
docs/_build
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -1,45 +1,9 @@
|
|||
from django.db.models import signals
|
||||
from django.apps import apps, AppConfig
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ConstanceConfig(AppConfig):
|
||||
name = 'constance'
|
||||
verbose_name = _('Constance')
|
||||
default_auto_field = 'django.db.models.AutoField'
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
signals.post_migrate.connect(self.create_perm,
|
||||
dispatch_uid='constance.create_perm')
|
||||
|
||||
def create_perm(self, using=None, *args, **kwargs):
|
||||
"""
|
||||
Creates a fake content type and permission
|
||||
to be able to check for permissions
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
constance_dbs = getattr(settings, 'CONSTANCE_DBS', None)
|
||||
if constance_dbs is not None and using not in constance_dbs:
|
||||
return
|
||||
if (
|
||||
apps.is_installed('django.contrib.contenttypes') and
|
||||
apps.is_installed('django.contrib.auth')
|
||||
):
|
||||
ContentType = apps.get_model('contenttypes.ContentType')
|
||||
Permission = apps.get_model('auth.Permission')
|
||||
content_type, created = ContentType.objects.using(using).get_or_create(
|
||||
app_label='constance',
|
||||
model='config',
|
||||
)
|
||||
|
||||
Permission.objects.using(using).get_or_create(
|
||||
content_type=content_type,
|
||||
codename='change_config',
|
||||
defaults={'name': 'Can change config'},
|
||||
)
|
||||
Permission.objects.using(using).get_or_create(
|
||||
content_type=content_type,
|
||||
codename='view_config',
|
||||
defaults={'name': 'Can view config'},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ from django.db import (
|
|||
)
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from .. import Backend
|
||||
from ... import settings, signals, config
|
||||
from constance.backends import Backend
|
||||
from constance import settings, signals, config
|
||||
|
||||
|
||||
class DatabaseBackend(Backend):
|
||||
def __init__(self):
|
||||
from .models import Constance
|
||||
from constance.models import Constance
|
||||
self._model = Constance
|
||||
self._prefix = settings.DATABASE_PREFIX
|
||||
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ConstanceConfig(AppConfig):
|
||||
name = 'constance.backends.database'
|
||||
default_auto_field = 'django.db.models.AutoField'
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 2.1.5 on 2019-01-30 04:04
|
||||
|
||||
from django.db import migrations
|
||||
import picklefield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('database', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='constance',
|
||||
name='value',
|
||||
field=picklefield.fields.PickledObjectField(blank=True, editable=False, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.management import BaseCommand, CommandError
|
||||
|
|
@ -8,6 +7,7 @@ from django import VERSION
|
|||
|
||||
from ... import config
|
||||
from ...admin import ConstanceForm, get_values
|
||||
from ...models import Constance
|
||||
|
||||
|
||||
def _set_constance_value(key, value):
|
||||
|
|
@ -49,7 +49,6 @@ class Command(BaseCommand):
|
|||
help='delete all Constance keys and their values if they are not in settings.CONSTANCE_CONFIG (stale keys)',
|
||||
)
|
||||
|
||||
|
||||
def _subparsers_add_parser(self, subparsers, name, **kwargs):
|
||||
# API in Django >= 2.1 changed and removed cmd parameter from add_parser
|
||||
if VERSION >= (2, 1) and 'cmd' in kwargs:
|
||||
|
|
@ -82,23 +81,16 @@ class Command(BaseCommand):
|
|||
self.stdout.write("{}\t{}".format(k, v), ending="\n")
|
||||
|
||||
elif command == 'remove_stale_keys':
|
||||
try:
|
||||
Constance = apps.get_model('database.Constance')
|
||||
except LookupError:
|
||||
Constance = None
|
||||
|
||||
if Constance:
|
||||
actual_keys = settings.CONSTANCE_CONFIG.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 database.", ending="\n")
|
||||
|
||||
for stale_record in stale_records:
|
||||
self.stdout.write("{}\t{}".format(stale_record.key, stale_record.value), ending="\n")
|
||||
|
||||
stale_records.delete()
|
||||
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("Database backend is not set. Nothing is deleted", ending="\n")
|
||||
self.stdout.write("There are no stale records in database.", ending="\n")
|
||||
|
||||
for stale_record in stale_records:
|
||||
self.stdout.write("{}\t{}".format(stale_record.key, stale_record.value), ending="\n")
|
||||
|
||||
stale_records.delete()
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
import picklefield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Constance',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True,
|
||||
auto_created=True, serialize=False)),
|
||||
('key', models.CharField(unique=True, max_length=255)),
|
||||
('value', picklefield.fields.PickledObjectField(editable=False)),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=255, unique=True)),
|
||||
('value', picklefield.fields.PickledObjectField(blank=True, editable=False, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'constance',
|
||||
'verbose_name_plural': 'constances',
|
||||
'db_table': 'constance_config',
|
||||
'permissions': [('change_config', 'Can change config'), ('view_config', 'Can view config')],
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
33
constance/migrations/0002_migrate_from_old_table.py
Normal file
33
constance/migrations/0002_migrate_from_old_table.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from django.core.management.color import no_style
|
||||
from django.db import migrations, connection, DatabaseError
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('INSERT INTO constance_constance ( id, key, value ) SELECT id, key, value FROM constance_config', [])
|
||||
cursor.execute('DROP TABLE constance_config', [])
|
||||
|
||||
except DatabaseError:
|
||||
pass
|
||||
|
||||
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),
|
||||
]
|
||||
|
|
@ -18,7 +18,10 @@ class Constance(models.Model):
|
|||
class Meta:
|
||||
verbose_name = _('constance')
|
||||
verbose_name_plural = _('constances')
|
||||
db_table = 'constance_config'
|
||||
permissions = [
|
||||
('change_config', 'Can change config'),
|
||||
('view_config', 'Can view config'),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
|
@ -94,27 +94,17 @@ Defaults to `60` seconds.
|
|||
Database
|
||||
--------
|
||||
|
||||
The database backend is optional and stores the configuration values in a
|
||||
Database backend stores configuration values in a
|
||||
standard Django model. It requires the package `django-picklefield`_ for
|
||||
storing those values. Please install it like so::
|
||||
|
||||
pip install django-constance[database]
|
||||
storing those values.
|
||||
|
||||
You must set the ``CONSTANCE_BACKEND`` Django setting to::
|
||||
|
||||
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
|
||||
|
||||
Then add the database backend app to your :setting:`INSTALLED_APPS` setting to
|
||||
make sure the data model is correctly created::
|
||||
|
||||
INSTALLED_APPS = (
|
||||
# other apps
|
||||
'constance.backends.database',
|
||||
)
|
||||
|
||||
Please make sure to apply the database migrations::
|
||||
|
||||
python manage.py migrate database
|
||||
python manage.py migrate
|
||||
|
||||
.. note:: If you're upgrading Constance to 1.0 and use Django 1.7 or higher
|
||||
please make sure to let the migration system know that you've
|
||||
|
|
@ -124,10 +114,6 @@ Please make sure to apply the database migrations::
|
|||
|
||||
python manage.py migrate database --fake
|
||||
|
||||
.. note:: If you have multiple databases you can set what databases
|
||||
will be used with ``CONSTANCE_DBS``
|
||||
|
||||
CONSTANCE_DBS = "default"
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
Changelog
|
||||
---------
|
||||
|
||||
v3.0.0 (future)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Refactor database backend
|
||||
Backward incompatible changes:
|
||||
remove 'constance.backends.database' from INSTALLED_APPS
|
||||
|
||||
v2.10.0 (unreleased)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ INSTALLED_APPS = (
|
|||
'cheeseshop.apps.catalog',
|
||||
'cheeseshop.apps.storage',
|
||||
'constance',
|
||||
'constance.backends.database',
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
|
|
@ -149,3 +148,5 @@ USE_TZ = True
|
|||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -60,8 +60,10 @@ setup(
|
|||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
python_requires='>=3.6',
|
||||
install_requires=[
|
||||
'django-picklefield',
|
||||
],
|
||||
extras_require={
|
||||
'database': ['django-picklefield'],
|
||||
'redis': ['redis'],
|
||||
},
|
||||
entry_points={
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from datetime import datetime
|
||||
|
||||
import mock
|
||||
from unittest import mock
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.defaultfilters import linebreaksbr
|
||||
|
|
@ -53,10 +52,6 @@ class TestAdmin(TestCase):
|
|||
response = self.options.changelist_view(request, {})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_str(self):
|
||||
ct = ContentType.objects.get(app_label='constance', model='config')
|
||||
self.assertEqual(str(ct), 'config')
|
||||
|
||||
def test_linebreaks(self):
|
||||
self.client.login(username='admin', password='nimda')
|
||||
request = self.rf.get('/admin/constance/config/')
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
from django.apps import apps
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import signals
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
|
||||
class TestApp(TestCase):
|
||||
databases = ["default", "secondary"]
|
||||
|
||||
def setUp(self):
|
||||
self.app_config = apps.get_app_config('constance')
|
||||
|
||||
def test_post_migrate_signal_creates_content_type_and_permission_in_default_database(self):
|
||||
self.assert_uses_correct_database('default')
|
||||
|
||||
def test_post_migrate_signal_creates_content_type_and_permission_in_secondary_database(self):
|
||||
self.assert_uses_correct_database('secondary')
|
||||
|
||||
def test_uses_default_db_even_without_giving_using_keyword(self):
|
||||
self.call_post_migrate(None)
|
||||
|
||||
self.assert_content_type_and_permission_created('default')
|
||||
|
||||
@override_settings(CONSTANCE_DBS=['default'])
|
||||
def test_only_use_databases_in_constance_dbs(self):
|
||||
Permission.objects.using('default').delete()
|
||||
Permission.objects.using('secondary').delete()
|
||||
self.assert_uses_correct_database('default')
|
||||
with self.assertRaises(AssertionError):
|
||||
self.assert_uses_correct_database('secondary')
|
||||
|
||||
def assert_uses_correct_database(self, database_name):
|
||||
self.call_post_migrate(database_name)
|
||||
|
||||
self.assert_content_type_and_permission_created(database_name)
|
||||
|
||||
def assert_content_type_and_permission_created(self, database_name):
|
||||
content_type_queryset = ContentType.objects.filter(app_label=self.app_config.name) \
|
||||
.using(database_name)
|
||||
|
||||
self.assertTrue(content_type_queryset.exists())
|
||||
|
||||
permission_queryset = Permission.objects.filter(content_type=content_type_queryset.get()) \
|
||||
.using(database_name).exists()
|
||||
|
||||
self.assertTrue(permission_queryset)
|
||||
|
||||
def call_post_migrate(self, database_name):
|
||||
signals.post_migrate.send(
|
||||
sender=self.app_config,
|
||||
app_config=self.app_config,
|
||||
verbosity=None,
|
||||
interactive=None,
|
||||
using=database_name
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
from decimal import Decimal
|
||||
import mock
|
||||
from unittest import mock
|
||||
|
||||
from constance.admin import get_values
|
||||
from constance.checks import check_fieldsets, get_inconsistent_fieldnames
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from datetime import datetime
|
||||
from textwrap import dedent
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command, CommandError
|
||||
from django.test import TransactionTestCase
|
||||
|
|
@ -10,6 +9,7 @@ from django.utils.encoding import smart_str
|
|||
from io import StringIO
|
||||
|
||||
from constance import config
|
||||
from constance.models import Constance
|
||||
|
||||
|
||||
class CliTestCase(TransactionTestCase):
|
||||
|
|
@ -74,11 +74,10 @@ class CliTestCase(TransactionTestCase):
|
|||
call_command, 'constance', 'set', 'DATETIME_VALUE', '2011-09-24 12:30:25')
|
||||
|
||||
def test_delete_stale_records(self):
|
||||
Constance = apps.get_model('database.Constance')
|
||||
|
||||
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)
|
||||
self.assertEqual(Constance.objects.count(), initial_count, msg=self.out)
|
||||
|
|
|
|||
1
tox.ini
1
tox.ini
|
|
@ -8,7 +8,6 @@ envlist =
|
|||
deps =
|
||||
redis
|
||||
coverage
|
||||
mock
|
||||
django-picklefield
|
||||
dj22: Django>=2.2,<3.0
|
||||
dj30: Django>=3.0,<3.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue