Refactor app and model

This commit is contained in:
Sergei Iurchenko 2023-03-10 20:38:31 +03:00 committed by GitHub
parent 8b03012918
commit 2cb2ccd063
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 81 additions and 179 deletions

1
.gitignore vendored
View file

@ -7,5 +7,6 @@ dist/
test.db
.tox
.coverage
coverage.xml
docs/_build
.idea

View file

@ -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'},
)

View file

@ -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

View file

@ -1,6 +0,0 @@
from django.apps import AppConfig
class ConstanceConfig(AppConfig):
name = 'constance.backends.database'
default_auto_field = 'django.db.models.AutoField'

View file

@ -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),
),
]

View file

@ -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()

View file

@ -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,),
),
]

View 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),
]

View file

@ -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

View file

@ -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

View file

@ -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)
~~~~~~~~~~~~~~~~~~

View file

@ -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'

View file

@ -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={

View file

@ -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/')

View file

@ -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
)

View file

@ -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

View file

@ -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)

View file

@ -8,7 +8,6 @@ envlist =
deps =
redis
coverage
mock
django-picklefield
dj22: Django>=2.2,<3.0
dj30: Django>=3.0,<3.1