From 552c898e3dfd05fa6418518b382aca44e6e2f82f Mon Sep 17 00:00:00 2001 From: Eeshu-Yadav Date: Tue, 12 Aug 2025 13:12:18 +0530 Subject: [PATCH] [models] Replace thirdparty JSONField with Django built-in JSONField #1061 Replaced jsonfield package's JSONField with Django's built-in JSONField across all models to use the modern, maintained Django implementation. Changes: - Updated imports from 'jsonfield import JSONField' to 'django.db.models import JSONField' - Removed jsonfield-specific parameters (load_kwargs, dump_kwargs) which are not needed - Created migrations to alter field types while preserving data - Removed unnecessary collections imports where no longer needed Django's JSONField preserves the same data format and is backward compatible, so no data migration is required. The built-in JSONField provides better performance and is actively maintained as part of Django core. Fixes #1061 --- openwisp_controller/config/base/base.py | 5 +- openwisp_controller/config/base/config.py | 4 +- .../config/base/device_group.py | 7 +- .../config/base/multitenancy.py | 5 +- openwisp_controller/config/base/template.py | 4 +- ...1_replace_jsonfield_with_django_builtin.py | 89 +++++++++++++++++++ openwisp_controller/connection/base/models.py | 9 +- ...0_replace_jsonfield_with_django_builtin.py | 43 +++++++++ 8 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 openwisp_controller/config/migrations/0061_replace_jsonfield_with_django_builtin.py create mode 100644 openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py diff --git a/openwisp_controller/config/base/base.py b/openwisp_controller/config/base/base.py index 8b55ad019..c41609d96 100644 --- a/openwisp_controller/config/base/base.py +++ b/openwisp_controller/config/base/base.py @@ -1,4 +1,3 @@ -import collections import hashlib import json import logging @@ -7,10 +6,10 @@ from cache_memoize import cache_memoize from django.core.exceptions import ValidationError from django.db import models +from django.db.models import JSONField from django.utils.functional import cached_property from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from netjsonconfig.exceptions import ValidationError as SchemaError from openwisp_utils.base import TimeStampedEditableModel @@ -126,8 +125,6 @@ class BaseConfig(BaseModel): _("configuration"), default=dict, help_text=_("configuration in NetJSON DeviceConfiguration format"), - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, ) __template__ = False diff --git a/openwisp_controller/config/base/config.py b/openwisp_controller/config/base/config.py index 59f05f212..083dc5f6f 100644 --- a/openwisp_controller/config/base/config.py +++ b/openwisp_controller/config/base/config.py @@ -5,8 +5,8 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError from django.db import models, transaction +from django.db.models import JSONField from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from model_utils import Choices from model_utils.fields import StatusField from netjsonconfig import OpenWrt @@ -97,8 +97,6 @@ class AbstractConfig(ChecksumCacheMixin, BaseConfig): 'en/stable/general/basics.html#context" target="_blank">' "context (configuration variables) in JSON format" ), - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, ) _CHECKSUM_CACHE_TIMEOUT = 60 * 60 * 24 * 30 # 10 days diff --git a/openwisp_controller/config/base/device_group.py b/openwisp_controller/config/base/device_group.py index 64fc77e5d..40774bb93 100644 --- a/openwisp_controller/config/base/device_group.py +++ b/openwisp_controller/config/base/device_group.py @@ -1,11 +1,10 @@ -import collections from copy import deepcopy import jsonschema from django.core.exceptions import ValidationError from django.db import models +from django.db.models import JSONField from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from jsonschema.exceptions import ValidationError as SchemaError from swapper import get_model_name, load_model @@ -39,8 +38,6 @@ class AbstractDeviceGroup(OrgMixin, TimeStampedEditableModel): meta_data = JSONField( blank=True, default=dict, - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, help_text=_( "Group meta data, use this field to store data which is related" " to this group and can be retrieved via the REST API." @@ -50,8 +47,6 @@ class AbstractDeviceGroup(OrgMixin, TimeStampedEditableModel): context = JSONField( blank=True, default=dict, - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, help_text=_( "This field can be used to add meta data for the group" ' or to add "Configuration Variables" to the devices.' diff --git a/openwisp_controller/config/base/multitenancy.py b/openwisp_controller/config/base/multitenancy.py index 9cdf15736..98a51004f 100644 --- a/openwisp_controller/config/base/multitenancy.py +++ b/openwisp_controller/config/base/multitenancy.py @@ -1,10 +1,9 @@ -import collections from copy import deepcopy import swapper from django.db import models +from django.db.models import JSONField from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from openwisp_utils.base import KeyField, UUIDModel @@ -34,8 +33,6 @@ class AbstractOrganizationConfigSettings(UUIDModel): context = JSONField( blank=True, default=dict, - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, help_text=_( 'This field can be used to add "Configuration Variables"' " to the devices." ), diff --git a/openwisp_controller/config/base/template.py b/openwisp_controller/config/base/template.py index dae518baf..bf3c848aa 100644 --- a/openwisp_controller/config/base/template.py +++ b/openwisp_controller/config/base/template.py @@ -4,8 +4,8 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models, transaction +from django.db.models import JSONField from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from netjsonconfig.exceptions import ValidationError as NetjsonconfigValidationError from swapper import get_model_name from taggit.managers import TaggableManager @@ -95,8 +95,6 @@ class AbstractTemplate(ShareableOrgMixinUniqueName, BaseConfig): "template; these default variables will " "be used during schema validation." ), - load_kwargs={"object_pairs_hook": OrderedDict}, - dump_kwargs={"indent": 4}, ) __template__ = True diff --git a/openwisp_controller/config/migrations/0061_replace_jsonfield_with_django_builtin.py b/openwisp_controller/config/migrations/0061_replace_jsonfield_with_django_builtin.py new file mode 100644 index 000000000..e4dde7f5e --- /dev/null +++ b/openwisp_controller/config/migrations/0061_replace_jsonfield_with_django_builtin.py @@ -0,0 +1,89 @@ +# Generated migration to replace third-party jsonfield with Django built-in JSONField + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('config', '0060_cleanup_api_task_notification_types'), + ] + + operations = [ + # Replace jsonfield.fields.JSONField with django.db.models.JSONField + migrations.AlterField( + model_name='config', + name='context', + field=models.JSONField( + blank=True, + default=dict, + help_text=( + 'Additional ' + "context (configuration variables) in JSON format" + ), + ), + ), + migrations.AlterField( + model_name='template', + name='config', + field=models.JSONField( + verbose_name='configuration', + default=dict, + help_text='configuration in NetJSON DeviceConfiguration format', + ), + ), + migrations.AlterField( + model_name='template', + name='default_values', + field=models.JSONField( + verbose_name='Default Values', + default=dict, + blank=True, + help_text=( + "A dictionary containing the default " + "values for the variables used by this " + "template; these default variables will " + "be used during schema validation." + ), + ), + ), + migrations.AlterField( + model_name='devicegroup', + name='meta_data', + field=models.JSONField( + blank=True, + default=dict, + help_text=( + "Group meta data, use this field to store data which is related" + " to this group and can be retrieved via the REST API." + ), + verbose_name='Metadata', + ), + ), + migrations.AlterField( + model_name='devicegroup', + name='context', + field=models.JSONField( + blank=True, + default=dict, + help_text=( + "This field can be used to add meta data for the group" + ' or to add "Configuration Variables" to the devices.' + ), + verbose_name='Configuration Variables', + ), + ), + migrations.AlterField( + model_name='organizationconfigsettings', + name='context', + field=models.JSONField( + blank=True, + default=dict, + help_text=( + 'This field can be used to add "Configuration Variables"' + " to the devices." + ), + verbose_name='Configuration Variables', + ), + ), + ] diff --git a/openwisp_controller/connection/base/models.py b/openwisp_controller/connection/base/models.py index 55f75c5f7..214afeb7b 100644 --- a/openwisp_controller/connection/base/models.py +++ b/openwisp_controller/connection/base/models.py @@ -1,16 +1,15 @@ -import collections import logging import django import jsonschema from django.core.exceptions import ValidationError from django.db import models, transaction +from django.db.models import JSONField from django.utils import timezone from django.utils.functional import cached_property from django.utils.module_loading import import_string from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ -from jsonfield import JSONField from jsonschema.exceptions import ValidationError as SchemaError from swapper import get_model_name, load_model @@ -101,8 +100,6 @@ class AbstractCredentials(ConnectorMixin, ShareableOrgMixinUniqueName, BaseModel _("parameters"), default=dict, help_text=_("global connection parameters"), - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, ) auto_add = models.BooleanField( _("auto add"), @@ -245,8 +242,6 @@ class AbstractDeviceConnection(ConnectorMixin, TimeStampedEditableModel): "local connection parameters (will override " "the global parameters if specified)" ), - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, ) # usability improvements is_working = models.BooleanField(null=True, blank=True, default=None) @@ -425,8 +420,6 @@ class AbstractCommand(TimeStampedEditableModel): input = JSONField( blank=True, null=True, - load_kwargs={"object_pairs_hook": collections.OrderedDict}, - dump_kwargs={"indent": 4}, ) output = models.TextField(blank=True) diff --git a/openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py b/openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py new file mode 100644 index 000000000..1797cdeda --- /dev/null +++ b/openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py @@ -0,0 +1,43 @@ +# Generated migration to replace third-party jsonfield with Django built-in JSONField + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('connection', '0009_alter_deviceconnection_unique_together'), + ] + + operations = [ + # Replace jsonfield.fields.JSONField with django.db.models.JSONField + migrations.AlterField( + model_name='credentials', + name='params', + field=models.JSONField( + verbose_name='parameters', + default=dict, + help_text='global connection parameters', + ), + ), + migrations.AlterField( + model_name='deviceconnection', + name='params', + field=models.JSONField( + verbose_name='parameters', + default=dict, + blank=True, + help_text=( + "local connection parameters (will override " + "the global parameters if specified)" + ), + ), + ), + migrations.AlterField( + model_name='command', + name='input', + field=models.JSONField( + blank=True, + null=True, + ), + ), + ]