Skip to content

Conversation

@Eeshu-Yadav
Copy link
Contributor

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #600.

Description of Changes

This PR replaces the third-party jsonfield library with Django's built-in JSONField. The third-party JSONField library hasn't received updates in 5 years, while Django's built-in JSONField is now mature and actively maintained.

Changes Made:

  • Replaced from jsonfield import JSONField with from django.db.models import JSONField in models
  • Updated RadiusBatch.user_credentials and OrganizationRadiusSettings.sms_meta_data fields
  • Removed "jsonfield~=3.1.0" dependency from setup.py
  • Added migration 0041_replace_jsonfield_with_django_builtin.py to handle field type transition
  • Updated test migrations to use Django's JSONField

Testing:

  • All existing tests pass with the new implementation
  • Verified RadiusBatch and SMS metadata functionality
  • Migration tested without data loss

@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch 2 times, most recently from a9ad394 to 998b688 Compare August 22, 2025 22:21
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch 2 times, most recently from a0d7e52 to 21b4925 Compare October 9, 2025 17:27
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch 4 times, most recently from 665a8b5 to fa44638 Compare October 22, 2025 18:37
@Eeshu-Yadav
Copy link
Contributor Author

@nemesifier kindly review

@coveralls
Copy link

coveralls commented Oct 22, 2025

Coverage Status

coverage: 88.141% (-0.006%) from 88.147%
when pulling 3f0f101 on Eeshu-Yadav:issues/600-replace-jsonfield-clean
into 43361fd on openwisp:master.

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Eeshu-Yadav, see my comments below.

name="user_credentials",
field=models.JSONField(blank=True, null=True, verbose_name="PDF"),
),
migrations.RunPython(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data migrations need to be executed in a dedicated migration file, please refer to https://stackoverflow.com/a/66799446 for more information.

batch.user_credentials = json.loads(batch.user_credentials)
batch.save(update_fields=["user_credentials"])
except (json.JSONDecodeError, TypeError):
# If it's already decoded or invalid JSON, skip it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't fail. If it fails we must let it fail loudly, unless there can be failure which would be common and in that case we should catch the exception and resolve this automatically.

Why do you think it would fail here?

to proper JSON objects for Django's built-in JSONField.
"""
RadiusBatch = apps.get_model("openwisp_radius", "RadiusBatch")
for batch in RadiusBatch.objects.exclude(user_credentials__isnull=True):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for batch in RadiusBatch.objects.exclude(user_credentials__isnull=True):
for batch in RadiusBatch.objects.exclude(user_credentials__isnull=True).iterator():

Not using iterator() may crash instances with large amount of rows and must be avoided.

@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch from fa44638 to a763c2b Compare November 18, 2025 15:50
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch from a763c2b to ba82cd9 Compare November 30, 2025 13:28
@Eeshu-Yadav
Copy link
Contributor Author

@nemesifier kindly review

Copilot AI review requested due to automatic review settings January 30, 2026 10:49
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch from ba82cd9 to 1c67f5d Compare January 30, 2026 10:49
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This PR replaces a third-party jsonfield.JSONField with Django's built-in models.JSONField across models and migrations, removes the jsonfield dependency from setup.py, updates imports in models, changes AbstractRadiusBatch.prefix_add to store Python objects (not JSON strings) in user_credentials, adds a schema migration adjusting JSONField options, and adds a data migration that converts existing double-encoded JSON strings in radiusbatch.user_credentials into proper JSON objects.

Sequence Diagram(s)

sequenceDiagram
    participant Migration as Migration (RunPython)
    participant ORM as ORM (apps.get_model)
    participant DB as Database
    participant JSON as json.loads

    Migration->>ORM: get RadiusBatch model
    Migration->>DB: query RadiusBatch objects (exclude null user_credentials)
    DB-->>Migration: iterator of RadiusBatch rows
    loop per RadiusBatch
        Migration->>Migration: read instance.user_credentials
        alt value is string
            Migration->>JSON: json.loads(string)
            JSON-->>Migration: python_obj
            Migration->>DB: save instance with user_credentials=python_obj (update_fields)
            DB-->>Migration: acknowledge save
        else value already object
            Migration-->>Migration: skip
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: replacing third-party JSONField with Django's built-in JSONField, which is the primary objective of the changeset.
Description check ✅ Passed The PR description is comprehensive, covering all required sections including checklist completion, issue reference (#600), detailed description of changes with specific file modifications, and testing verification.
Linked Issues check ✅ Passed The PR successfully meets all coding objectives from issue #600: replaces third-party jsonfield with Django's built-in JSONField, includes data migration (0044_convert_user_credentials_data.py) ensuring no data loss, and updates all affected models and test migrations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the migration objective: import replacements, field updates, dependency removal, migrations for schema and data conversion, and test migration updates—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c67f5d and 3f0f101.

📒 Files selected for processing (5)
  • openwisp_radius/base/models.py
  • openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py
  • openwisp_radius/migrations/0044_convert_user_credentials_data.py
  • setup.py
  • tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py
💤 Files with no reviewable changes (1)
  • setup.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py
🧰 Additional context used
🧬 Code graph analysis (2)
openwisp_radius/migrations/0044_convert_user_credentials_data.py (2)
openwisp_radius/utils.py (1)
  • get_model (37-39)
openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py (1)
  • Migration (6-31)
openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py (2)
openwisp_radius/migrations/0044_convert_user_credentials_data.py (1)
  • Migration (25-38)
tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py (1)
  • Migration (26-811)
🪛 Ruff (0.14.14)
openwisp_radius/migrations/0044_convert_user_credentials_data.py

[warning] 27-32: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


[warning] 34-38: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py

[warning] 8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


[warning] 12-31: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🔇 Additional comments (5)
openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py (1)

12-30: Migration changes look consistent.

No concerns with these field alterations.

openwisp_radius/base/models.py (2)

20-20: No review comment on this import change.


1017-1023: No action required. The user_credentials field is a Django JSONField which automatically handles serialization/deserialization. The prefix_generate_users() function returns a Python object (list of lists like [[username, password], ...]), and all downstream consumers already handle it correctly:

  • Template rendering in utils.py:206 uses it directly as a Python object
  • Test code in test_batch_add_users.py:132 accesses it as batch.user_credentials[0][1]
  • Django's JSONField transparently serializes for storage and deserializes to Python on retrieval

The code is working as intended; no json.loads breaking changes will occur.

Likely an incorrect or invalid review comment.

openwisp_radius/migrations/0044_convert_user_credentials_data.py (2)

8-23: Data conversion logic looks solid.

Nice use of the migration DB alias and .iterator() for large tables.


25-37: Migration wiring looks good.

Dependencies and operation order are appropriate.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@openwisp_radius/migrations/0044_convert_user_credentials_data.py`:
- Around line 8-17: The migration's DB operations must be routed to the
migration database: obtain db_alias = schema_editor.connection.alias inside
convert_user_credentials_data and use it when querying and saving RadiusBatch
(e.g., use RadiusBatch.objects.using(db_alias).exclude(...).iterator() and call
batch.save(update_fields=["user_credentials"], using=db_alias)) so reads/writes
go to the correct database during migrations.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43361fd and 1c67f5d.

📒 Files selected for processing (5)
  • openwisp_radius/base/models.py
  • openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py
  • openwisp_radius/migrations/0044_convert_user_credentials_data.py
  • setup.py
  • tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py
💤 Files with no reviewable changes (1)
  • setup.py
🧰 Additional context used
🧬 Code graph analysis (1)
openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py (1)
openwisp_radius/migrations/0044_convert_user_credentials_data.py (1)
  • Migration (20-33)
🪛 Ruff (0.14.14)
openwisp_radius/migrations/0044_convert_user_credentials_data.py

[warning] 8-8: Unused function argument: schema_editor

(ARG001)


[warning] 22-27: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


[warning] 29-33: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py

[warning] 8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


[warning] 12-31: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: CodeQL analysis (python)
  • GitHub Check: Agent
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
🔇 Additional comments (4)
openwisp_radius/base/models.py (2)

20-20: Import adjustment looks fine.

Line 20 change is consistent with the rest of the file.


1017-1023: prefix_add update looks consistent.

Line 1022 assignment aligns with the surrounding logic in prefix_add.

openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py (1)

13-30: Migration update looks good.

The field alterations are straightforward and consistent.

tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py (1)

544-553: Sample migration updates look good.

These updates are consistent with the rest of the migration.

Also applies to: 696-698

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request migrates from the unmaintained third-party jsonfield library to Django's built-in JSONField. The third-party library hasn't been updated in 5 years, while Django's JSONField is actively maintained and natively supported across all databases used by the project.

Changes:

  • Replaced third-party JSONField with Django's built-in models.JSONField in model definitions
  • Removed jsonfield~=3.1.0 dependency from setup.py
  • Added migrations to handle the field type transition and data conversion

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
setup.py Removed the deprecated jsonfield dependency
openwisp_radius/base/models.py Updated imports to use Django's JSONField and removed manual json.dumps() call that was causing double-encoding
openwisp_radius/migrations/0043_alter_organizationradiussettings_sms_meta_data_and_more.py AlterField migration to change field types from third-party JSONField to Django's JSONField
openwisp_radius/migrations/0044_convert_user_credentials_data.py Data migration to convert double-encoded JSON strings to proper Python objects
tests/openwisp2/sample_radius/migrations/0002_initial_openwisp_app.py Updated test migrations to use Django's JSONField

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

RadiusBatch = apps.get_model("openwisp_radius", "RadiusBatch")
for batch in RadiusBatch.objects.exclude(user_credentials__isnull=True).iterator():
if isinstance(batch.user_credentials, str):
batch.user_credentials = json.loads(batch.user_credentials)
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration should add error handling for edge cases where user_credentials might be an empty string or invalid JSON. Currently, if user_credentials is an empty string, json.loads will raise a JSONDecodeError and fail the migration. Consider adding a try-except block to handle this gracefully, or add an additional check to skip empty strings before attempting to parse.

Suggested change
batch.user_credentials = json.loads(batch.user_credentials)
# Skip empty or whitespace-only strings which would fail json.loads
if not batch.user_credentials.strip():
continue
# Safely attempt to decode JSON; skip records with invalid JSON
try:
decoded_credentials = json.loads(batch.user_credentials)
except json.JSONDecodeError:
continue
batch.user_credentials = decoded_credentials

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nemesifier previously requested that this migration "fail loudly" on invalid data rather than silently skip errors, to ensure data integrity issues are caught and investigated rather than hidden. If empty strings or invalid JSON turn out to be common during testing, we can address them explicitly with proper handling.

- Replaced third-party jsonfield.JSONField with Django's built-in
  models.JSONField in all model definitions
- Updated RadiusBatch.prefix_add method to directly assign
  user_credentials list instead of using json.dumps()
- Added migration 0043 to replace JSONField in model definitions
- Added migration 0044 to convert existing double-encoded JSON data
  in user_credentials field (data migration in separate file)
- Used .iterator() in data migration for memory efficiency
- Removed jsonfield dependency from setup.py
- Updated test migrations to use Django's JSONField
- Removed unused json import

Closes openwisp#600
@Eeshu-Yadav Eeshu-Yadav force-pushed the issues/600-replace-jsonfield-clean branch from 1c67f5d to 3f0f101 Compare January 30, 2026 11:05
@Eeshu-Yadav
Copy link
Contributor Author

@nemesifier kindly review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[change:radius] Replace thirdparty JSONField with Django built in JSONField

3 participants