Projet

Général

Profil

0001-add-compatibility-layer-for-support-of-Django-native.patch

Benjamin Dauvergne, 20 décembre 2018 12:12

Télécharger (9,34 ko)

Voir les différences:

Subject: [PATCH] add compatibility layer for support of Django native
 JSONField (fixes #29193)

 src/authentic2/apps.py                        |  49 ++++++++-
 src/authentic2/compat.py                      | 100 +++++++++++++++++-
 .../migrations/0007_auto_20181219_0005.py     |  22 ++++
 src/authentic2_auth_oidc/models.py            |   5 +-
 tox.ini                                       |   1 +
 5 files changed, 170 insertions(+), 7 deletions(-)
 create mode 100644 src/authentic2_auth_oidc/migrations/0007_auto_20181219_0005.py
src/authentic2/apps.py
3 3
from django.apps import AppConfig
4 4
from django.views import debug
5 5

  
6
from . import plugins
6
from django.db import connection
7
from django.db.models.signals import post_migrate
8

  
9
from . import plugins, compat
7 10

  
8 11

  
9 12
class Authentic2Config(AppConfig):
10 13
    name = 'authentic2'
11 14
    verbose_name = 'Authentic2'
12 15

  
16
    def post_migrate_update_json_column(self, sender, **kwargs):
17
        # adapted from https://github.com/kbussell/django-jsonfield-compat/blob/4f6ac4bfaea2224559b174b6d16d846b93d125c6/jsonfield_compat/convert.py
18
        # MIT License, kbussel
19
        if connection.vendor != 'postgresql':
20
            return
21

  
22
        if compat.has_postgresql_support():
23
            expected_type = 'JSONB'
24
        else:
25
            expected_type = 'TEXT'
26

  
27

  
28
        def convert_column_to_json(model, column_name):
29
            table_name = model._meta.db_table
30

  
31
            with connection.cursor() as cursor:
32
                cursor.execute(
33
                    "select data_type from information_schema.columns "
34
                    "where table_name = %s and column_name = %s;",
35
                    [table_name, column_name])
36

  
37
                current_type = cursor.fetchone()[0].upper()
38

  
39
                if current_type != expected_type:
40
                    print("{app}: Converting {col} to use native {type} field".format(
41
                        app=model._meta.app_label, col=column_name, type=expected_type))
42

  
43
                    cursor.execute(
44
                        "ALTER TABLE {table} ALTER COLUMN {col} "
45
                        "TYPE {type} USING {col}::{type};".format(
46
                            table=table_name, col=column_name, type=expected_type
47
                        )
48
                    )
49

  
50
        def convert_model_json_fields(model):
51
            json_fields = [f for f in model._meta.fields if f.__class__ == compat.JSONField]
52
            for field in json_fields:
53
                _, column_name = field.get_attname_column()
54
                convert_column_to_json(model, column_name)
55

  
56
        for model in list(sender.get_models()):
57
            convert_model_json_fields(model)
58

  
13 59
    def ready(self):
14 60
        plugins.init()
15 61
        debug.HIDDEN_SETTINGS = re.compile(
16 62
            'API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE|LDAP')
63
        post_migrate.connect(self.post_migrate_update_json_column)
src/authentic2/compat.py
1 1
from datetime import datetime
2
import inspect
2 3

  
4
import django
3 5
from django.conf import settings
6
from django.db import connection
7

  
8
from django.contrib.auth.tokens import PasswordResetTokenGenerator
4 9

  
5 10
try:
6 11
    from django.contrib.auth import get_user_model
......
14 19
except ImportError:
15 20
    from django.db.transaction import commit_on_success
16 21

  
17
from . import app_settings, utils
18

  
19 22
user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
20 23

  
21
from django.contrib.auth.tokens import PasswordResetTokenGenerator
22

  
23 24
default_token_generator = PasswordResetTokenGenerator()
25

  
26

  
27
def has_postgresql_support():
28
    if not settings.DATABASES['default'].get('NAME'):
29
        return False
30
    return connection.vendor == 'postgresql' and connection.pg_version > 90400
31

  
32

  
33
def use_django_native_field():
34
    return has_postgresql_support() and django.VERSION >= (1, 11)
35

  
36

  
37
class JSONField(object):
38
    __dj11_field = None
39
    __jsonfield_field = None
40
    __name = None
41

  
42
    def __init__(self, *args, **kwargs):
43
        self.__args = args
44
        self.__kwargs = kwargs
45
        if django.VERSION >= (1, 11):
46
            from django.contrib.postgres.fields import JSONField
47
            self.__dj11_field = JSONField(*args, **kwargs)
48
        try:
49
            from jsonfield.fields import JSONField
50
            self.__jsonfield_field = JSONField(*args, **kwargs)
51
        except ImportError:
52
            pass
53

  
54
    def __real_field__(self):
55
        if use_django_native_field():
56
            assert self.__dj11_field
57
            return self.__dj11_field
58
        assert self.__jsonfield_field
59
        return self.__jsonfield_field
60

  
61
    def __getattr__(self, key):
62
        return getattr(self.__real_field__(), key)
63

  
64
    def __setattr__(self, key, value):
65
        if key.startswith('_JSONField__'):
66
            super(JSONField, self).__setattr__(key, value)
67
        else:
68
            setattr(self.__real__field(), key, value)
69

  
70
    # we need to implement contribute_to_class so that the direct
71
    # implementation from the two sub-fields is not used directly
72
    def contribute_to_class(self, cls, name, private_only=False, virtual_only=False, **kwargs):
73
        assert not virtual_only and not private_only, 'virtual_only / private_only are not supported'
74
        assert not kwargs, 'new arguments to contribute_to_class not supported'
75
        self.__name = name
76
        if self.__dj11_field:
77
            self.__dj11_field.set_attributes_from_name(name)
78
            self.__dj11_field.model = cls
79
        if self.__jsonfield_field:
80
            self.__jsonfield_field.set_attributes_from_name(name)
81
            self.__jsonfield_field.model = cls
82
        cls._meta.add_field(self)
83

  
84
    # the next two methods are useful for compatibilit with the migration engine
85
    # inspect is used because migration autodetector cannot recognize this class
86
    # as a subclass of models.Field.
87
    def deconstruct(self):
88
        d = (self.__name, 'authentic2.compat.JSONField', self.__args, self.__kwargs)
89
        previous_frame = inspect.currentframe().f_back
90
        if inspect.getframeinfo(previous_frame)[2] in ('serialize', 'deep_deconstruct'):
91
            d = d[1:]
92
        return d
93

  
94
    def clone(self):
95
        from copy import copy
96
        new = copy(self)
97
        if self.__dj11_field:
98
            new.__dj11_field = new.__dj11_field.clone()
99
        if self.__jsonfield_field:
100
            new.__jsonfield_field = new.__jsonfield_field.clone()
101
        return new
102

  
103

  
104
try:
105
    from jsonfield import fields
106
except ImportError:
107
    pass
108
else:
109
    # prevent django-jsonfield from modifying postgresql connection when we are
110
    # not using it
111
    def configure_database_connection(connection, **kwargs):
112
        if django.VERSION < (1, 11):
113
            fields.configure_database_connection(connection, **kwargs)
114
    fields.connection_created.disconnect(fields.configure_database_connection)
115
    fields.connection_created.connect(configure_database_connection)
src/authentic2_auth_oidc/migrations/0007_auto_20181219_0005.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.17 on 2018-12-18 23:05
3
from __future__ import unicode_literals
4

  
5
import authentic2.compat
6
import authentic2_auth_oidc.models
7
from django.db import migrations
8

  
9

  
10
class Migration(migrations.Migration):
11

  
12
    dependencies = [
13
        ('authentic2_auth_oidc', '0006_oidcprovider_claims_parameter_supported'),
14
    ]
15

  
16
    operations = [
17
        migrations.AlterField(
18
            model_name='oidcprovider',
19
            name='jwkset_json',
20
            field=authentic2.compat.JSONField(blank=True, null=True, validators=[authentic2_auth_oidc.models.validate_jwkset], verbose_name='JSON WebKey set'),
21
        ),
22
    ]
src/authentic2_auth_oidc/models.py
6 6
from django.conf import settings
7 7
from django.core.exceptions import ValidationError
8 8

  
9
from jsonfield import JSONField
10 9

  
11 10
from jwcrypto.jwk import JWKSet, InvalidJWKValue, JWK
12 11

  
13 12
from django_rbac.utils import get_ou_model_name
14 13

  
14
from authentic2 import compat
15

  
15 16
from . import managers
16 17

  
17 18

  
......
89 90
        max_length=128,
90 91
        blank=True,
91 92
        verbose_name=_('scopes'))
92
    jwkset_json = JSONField(
93
    jwkset_json = compat.JSONField(
93 94
        verbose_name=_('JSON WebKey set'),
94 95
        null=True,
95 96
        blank=True,
tox.ini
31 31
  dj111: django<2.0
32 32
  dj111: django-tables<2.0
33 33
  pg: psycopg2-binary
34
  dj111: psycopg2-binary
34 35
  coverage
35 36
  pytest-cov
36 37
  pytest-django
37
-