add compatibility layer for support of Django native JSONField (fixes #29193)
This commit is contained in:
parent
b03a76dfff
commit
d730dba525
|
@ -3,14 +3,61 @@ import re
|
|||
from django.apps import AppConfig
|
||||
from django.views import debug
|
||||
|
||||
from . import plugins
|
||||
from django.db import connection
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
from . import plugins, compat
|
||||
|
||||
|
||||
class Authentic2Config(AppConfig):
|
||||
name = 'authentic2'
|
||||
verbose_name = 'Authentic2'
|
||||
|
||||
def post_migrate_update_json_column(self, sender, **kwargs):
|
||||
# adapted from https://github.com/kbussell/django-jsonfield-compat/blob/4f6ac4bfaea2224559b174b6d16d846b93d125c6/jsonfield_compat/convert.py
|
||||
# MIT License, kbussel
|
||||
if connection.vendor != 'postgresql':
|
||||
return
|
||||
|
||||
if compat.has_postgresql_support():
|
||||
expected_type = 'JSONB'
|
||||
else:
|
||||
expected_type = 'TEXT'
|
||||
|
||||
|
||||
def convert_column_to_json(model, column_name):
|
||||
table_name = model._meta.db_table
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"select data_type from information_schema.columns "
|
||||
"where table_name = %s and column_name = %s;",
|
||||
[table_name, column_name])
|
||||
|
||||
current_type = cursor.fetchone()[0].upper()
|
||||
|
||||
if current_type != expected_type:
|
||||
print("{app}: Converting {col} to use native {type} field".format(
|
||||
app=model._meta.app_label, col=column_name, type=expected_type))
|
||||
|
||||
cursor.execute(
|
||||
"ALTER TABLE {table} ALTER COLUMN {col} "
|
||||
"TYPE {type} USING {col}::{type};".format(
|
||||
table=table_name, col=column_name, type=expected_type
|
||||
)
|
||||
)
|
||||
|
||||
def convert_model_json_fields(model):
|
||||
json_fields = [f for f in model._meta.fields if f.__class__ == compat.JSONField]
|
||||
for field in json_fields:
|
||||
_, column_name = field.get_attname_column()
|
||||
convert_column_to_json(model, column_name)
|
||||
|
||||
for model in list(sender.get_models()):
|
||||
convert_model_json_fields(model)
|
||||
|
||||
def ready(self):
|
||||
plugins.init()
|
||||
debug.HIDDEN_SETTINGS = re.compile(
|
||||
'API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE|LDAP')
|
||||
post_migrate.connect(self.post_migrate_update_json_column)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
from datetime import datetime
|
||||
import inspect
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
|
||||
try:
|
||||
from django.contrib.auth import get_user_model
|
||||
|
@ -14,10 +19,97 @@ try:
|
|||
except ImportError:
|
||||
from django.db.transaction import commit_on_success
|
||||
|
||||
from . import app_settings, utils
|
||||
|
||||
user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
||||
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
|
||||
default_token_generator = PasswordResetTokenGenerator()
|
||||
|
||||
|
||||
def has_postgresql_support():
|
||||
if not settings.DATABASES['default'].get('NAME'):
|
||||
return False
|
||||
return connection.vendor == 'postgresql' and connection.pg_version > 90400
|
||||
|
||||
|
||||
def use_django_native_field():
|
||||
return has_postgresql_support() and django.VERSION >= (1, 11)
|
||||
|
||||
|
||||
class JSONField(object):
|
||||
__dj11_field = None
|
||||
__jsonfield_field = None
|
||||
__name = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__args = args
|
||||
self.__kwargs = kwargs
|
||||
if django.VERSION >= (1, 11):
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
self.__dj11_field = JSONField(*args, **kwargs)
|
||||
try:
|
||||
from jsonfield.fields import JSONField
|
||||
self.__jsonfield_field = JSONField(*args, **kwargs)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def __real_field__(self):
|
||||
if use_django_native_field():
|
||||
assert self.__dj11_field
|
||||
return self.__dj11_field
|
||||
assert self.__jsonfield_field
|
||||
return self.__jsonfield_field
|
||||
|
||||
def __getattr__(self, key):
|
||||
return getattr(self.__real_field__(), key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith('_JSONField__'):
|
||||
super(JSONField, self).__setattr__(key, value)
|
||||
else:
|
||||
setattr(self.__real__field(), key, value)
|
||||
|
||||
# we need to implement contribute_to_class so that the direct
|
||||
# implementation from the two sub-fields is not used directly
|
||||
def contribute_to_class(self, cls, name, private_only=False, virtual_only=False, **kwargs):
|
||||
assert not virtual_only and not private_only, 'virtual_only / private_only are not supported'
|
||||
assert not kwargs, 'new arguments to contribute_to_class not supported'
|
||||
self.__name = name
|
||||
if self.__dj11_field:
|
||||
self.__dj11_field.set_attributes_from_name(name)
|
||||
self.__dj11_field.model = cls
|
||||
if self.__jsonfield_field:
|
||||
self.__jsonfield_field.set_attributes_from_name(name)
|
||||
self.__jsonfield_field.model = cls
|
||||
cls._meta.add_field(self)
|
||||
|
||||
# the next two methods are useful for compatibilit with the migration engine
|
||||
# inspect is used because migration autodetector cannot recognize this class
|
||||
# as a subclass of models.Field.
|
||||
def deconstruct(self):
|
||||
d = (self.__name, 'authentic2.compat.JSONField', self.__args, self.__kwargs)
|
||||
previous_frame = inspect.currentframe().f_back
|
||||
if inspect.getframeinfo(previous_frame)[2] in ('serialize', 'deep_deconstruct'):
|
||||
d = d[1:]
|
||||
return d
|
||||
|
||||
def clone(self):
|
||||
from copy import copy
|
||||
new = copy(self)
|
||||
if self.__dj11_field:
|
||||
new.__dj11_field = new.__dj11_field.clone()
|
||||
if self.__jsonfield_field:
|
||||
new.__jsonfield_field = new.__jsonfield_field.clone()
|
||||
return new
|
||||
|
||||
|
||||
try:
|
||||
from jsonfield import fields
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# prevent django-jsonfield from modifying postgresql connection when we are
|
||||
# not using it
|
||||
def configure_database_connection(connection, **kwargs):
|
||||
if django.VERSION < (1, 11):
|
||||
fields.configure_database_connection(connection, **kwargs)
|
||||
fields.connection_created.disconnect(fields.configure_database_connection)
|
||||
fields.connection_created.connect(configure_database_connection)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.17 on 2018-12-18 23:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import authentic2.compat
|
||||
import authentic2_auth_oidc.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentic2_auth_oidc', '0006_oidcprovider_claims_parameter_supported'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='oidcprovider',
|
||||
name='jwkset_json',
|
||||
field=authentic2.compat.JSONField(blank=True, null=True, validators=[authentic2_auth_oidc.models.validate_jwkset], verbose_name='JSON WebKey set'),
|
||||
),
|
||||
]
|
|
@ -6,12 +6,13 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from jsonfield import JSONField
|
||||
|
||||
from jwcrypto.jwk import JWKSet, InvalidJWKValue, JWK
|
||||
|
||||
from django_rbac.utils import get_ou_model_name
|
||||
|
||||
from authentic2 import compat
|
||||
|
||||
from . import managers
|
||||
|
||||
|
||||
|
@ -89,7 +90,7 @@ class OIDCProvider(models.Model):
|
|||
max_length=128,
|
||||
blank=True,
|
||||
verbose_name=_('scopes'))
|
||||
jwkset_json = JSONField(
|
||||
jwkset_json = compat.JSONField(
|
||||
verbose_name=_('JSON WebKey set'),
|
||||
null=True,
|
||||
blank=True,
|
||||
|
|
Loading…
Reference in New Issue