misc: remove jsonfield requirement (#53289)
This commit is contained in:
parent
2be0bf2f7a
commit
c771a154f6
|
@ -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)
|
|
@ -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={},
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
|
@ -1,6 +1,5 @@
|
|||
django>=1.8, <1.9
|
||||
gadjo
|
||||
djangorestframework>=3.1, <3.7
|
||||
django-jsonfield >= 0.9.3
|
||||
requests
|
||||
vobject
|
||||
|
|
1
setup.py
1
setup.py
|
@ -165,7 +165,6 @@ setup(
|
|||
'gadjo',
|
||||
'djangorestframework>=3.4',
|
||||
'django-filter',
|
||||
'django-jsonfield >= 0.9.3',
|
||||
'vobject',
|
||||
'python-dateutil',
|
||||
'requests',
|
||||
|
|
|
@ -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'
|
Loading…
Reference in New Issue