misc: remove jsonfield requirement (#53289)

pull/1/head
Lauréline Guérin 2021-04-20 15:39:12 +02:00
parent 2be0bf2f7a
commit c771a154f6
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
10 changed files with 175 additions and 12 deletions

View File

@ -0,0 +1,49 @@
# chrono - agendas system
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from django.contrib.postgres.fields import JSONField
from django.core.management.base import BaseCommand, CommandError
from django.db import connection
class Command(BaseCommand):
help = 'Ensure all JSON fields are of type jsonb'
def handle(self, **options):
for app in apps.get_models():
for field in app._meta.get_fields():
if isinstance(field, JSONField):
table_name = app._meta.db_table
column_name = app._meta.get_field(field.name).column
with connection.cursor() as cursor:
query = '''SELECT table_schema
FROM information_schema.columns
WHERE table_name = %s AND column_name = %s AND data_type != %s'''
cursor.execute(query, [table_name, column_name, 'jsonb'])
for line in cursor.fetchall():
alter_query = '''ALTER TABLE "%(schema_name)s"."%(table_name)s"
ALTER COLUMN "%(column_name)s"
TYPE jsonb USING "%(column_name)s"::jsonb'''
params = {
'schema_name': line[0],
'table_name': table_name,
'column_name': column_name,
}
try:
cursor.execute(alter_query % params)
except Exception as e:
raise CommandError(e)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import jsonfield.fields
from django.contrib.postgres.fields import JSONField
from django.db import migrations, models
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('extra_data', jsonfield.fields.JSONField(null=True)),
('extra_data', JSONField(null=True)),
('event', models.ForeignKey(to='agendas.Event', on_delete=models.CASCADE)),
],
options={},

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import django.db.models.deletion
import jsonfield.fields
from django.contrib.postgres.fields import JSONField
from django.db import migrations, models
@ -23,7 +23,7 @@ class Migration(migrations.Migration):
),
('timestamp', models.DateTimeField(auto_now_add=True)),
('seen', models.BooleanField(default=False)),
('booking_errors', jsonfield.fields.JSONField(default=dict)),
('booking_errors', JSONField(default=dict)),
('bookings', models.ManyToManyField(to='agendas.Booking')),
],
options={

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import django.db.models.deletion
import jsonfield.fields
from django.contrib.postgres.fields import JSONField
from django.db import migrations, models
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='event',
name='recurrence_rule',
field=jsonfield.fields.JSONField(null=True, verbose_name='Recurrence rule'),
field=JSONField(null=True, verbose_name='Recurrence rule', blank=True),
),
migrations.AddField(
model_name='event',

View File

@ -0,0 +1,16 @@
from django.db import migrations
from chrono.utils.db import EnsureJsonbType
class Migration(migrations.Migration):
dependencies = [
('agendas', '0081_recurrenceexceptionsreport'),
]
operations = [
EnsureJsonbType(model_name='booking', field_name='extra_data'),
EnsureJsonbType(model_name='event', field_name='recurrence_rule'),
EnsureJsonbType(model_name='eventcancellationreport', field_name='booking_errors'),
]

View File

@ -30,7 +30,7 @@ import vobject
from dateutil.rrule import DAILY, WEEKLY, rrule, rruleset
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.fields import ArrayField, JSONField
from django.core.exceptions import FieldDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import connection, models, transaction
@ -48,7 +48,6 @@ from django.utils.timezone import is_aware, localtime, make_aware, make_naive, n
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from jsonfield import JSONField
from chrono.interval import Interval, IntervalSet
from chrono.utils.requests_wrapper import requests as requests_wrapper
@ -1151,7 +1150,7 @@ class Event(models.Model):
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
start_datetime = models.DateTimeField(_('Date/time'))
repeat = models.CharField(_('Repeat'), max_length=16, blank=True, choices=REPEAT_CHOICES)
recurrence_rule = JSONField(_('Recurrence rule'), null=True)
recurrence_rule = JSONField(_('Recurrence rule'), null=True, blank=True)
recurrence_end_date = models.DateField(_('Recurrence end date'), null=True, blank=True)
primary_event = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='recurrences')
duration = models.PositiveIntegerField(_('Duration (in minutes)'), default=None, null=True, blank=True)
@ -2341,7 +2340,7 @@ class EventCancellationReport(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
seen = models.BooleanField(default=False)
bookings = models.ManyToManyField(Booking)
booking_errors = JSONField()
booking_errors = JSONField(default=dict)
def __str__(self):
return '%s - %s' % (self.timestamp.strftime('%Y-%m-%d %H:%M:%S'), self.event)

49
chrono/utils/db.py Normal file
View File

@ -0,0 +1,49 @@
# chrono - agendas system
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db import connection
from django.db.migrations.operations.base import Operation
class EnsureJsonbType(Operation):
reversible = True
def __init__(self, model_name, field_name):
self.model_name = model_name
self.field_name = field_name
def state_forwards(self, app_label, state):
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if connection.vendor == 'postgresql':
model = from_state.apps.get_model(app_label, self.model_name)
table_name = model._meta.db_table
field = model._meta.get_field(self.field_name)
_, column_name = field.get_attname_column()
with schema_editor.connection.cursor() as cursor:
cursor.execute(
'ALTER TABLE {table} ALTER COLUMN {col} TYPE jsonb USING {col}::jsonb;'.format(
table=table_name, col=column_name
)
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass
def describe(self):
return "Migrate to postgres jsonb type"

View File

@ -1,6 +1,5 @@
django>=1.8, <1.9
gadjo
djangorestframework>=3.1, <3.7
django-jsonfield >= 0.9.3
requests
vobject

View File

@ -165,7 +165,6 @@ setup(
'gadjo',
'djangorestframework>=3.4',
'django-filter',
'django-jsonfield >= 0.9.3',
'vobject',
'python-dateutil',
'requests',

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import pytest
from django.core.management import call_command
from django.db import connection
pytestmark = pytest.mark.django_db
@pytest.mark.skipif(connection.vendor != 'postgresql', reason='only postgresql is supported')
def test_ensure_jsonb_fields():
json_fields = (
'extra_data',
'booking_errors',
'recurrence_rule',
)
with connection.cursor() as cursor:
query = '''SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE column_name IN %(json_fields)s'''
cursor.execute(query, {'json_fields': json_fields})
# make sure the data_type is correct
for line in cursor.fetchall():
assert line[2] == 'jsonb'
# alter columns
cursor.execute(
'''ALTER TABLE agendas_booking
ALTER COLUMN extra_data TYPE text USING extra_data::text'''
)
cursor.execute(
'''ALTER TABLE agendas_eventcancellationreport
ALTER COLUMN booking_errors TYPE text USING booking_errors::text'''
)
cursor.execute(
'''ALTER TABLE agendas_event
ALTER COLUMN recurrence_rule TYPE text USING recurrence_rule::text'''
)
call_command('ensure_jsonb')
with connection.cursor() as cursor:
query = '''SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE column_name IN %(json_fields)s'''
cursor.execute(query, {'json_fields': json_fields})
# check the data_type is correct
for line in cursor.fetchall():
assert line[2] == 'jsonb'