Skip to content

Fixed SubnetDivisionIndex foreign keys saved as NULL#1436

Closed
ApurveKaranwal wants to merge 1 commit into
openwisp:masterfrom
ApurveKaranwal:fix-subnet-division-null-fks
Closed

Fixed SubnetDivisionIndex foreign keys saved as NULL#1436
ApurveKaranwal wants to merge 1 commit into
openwisp:masterfrom
ApurveKaranwal:fix-subnet-division-null-fks

Conversation

@ApurveKaranwal

Copy link
Copy Markdown

Checklist

Reference to Existing Issue

Closes #1435

Description of Changes

This PR fixes a bug where SubnetDivisionIndex records were created with subnet_id = NULL and/or ip_id = NULL in the database.

Why the bug occurred:

The code was instantiating SubnetDivisionIndex instances by copying the .id properties directly (e.g., subnet_id=subnet_obj.id and ip_id=ip.id) before the subnets or IP addresses were bulk-saved. At the time of copy, these values were None. Although bulk_create subsequently populated the primary keys on the model instances, the primitive values copied earlier remained None and were saved as NULL in the database.

The Fix:

We now pass the model instances themselves (subnet=subnet_obj and ip=ip) instead of copying the .id properties. Since the index instances hold object references, Django correctly resolves their primary keys once the parent objects are bulk-saved in the database.

Test Coverage:

Added a new unit test method test_subnet_division_index_integrity() in openwisp_controller/subnet_division/tests/test_models.py. It asserts that:

  1. Every initially created SubnetDivisionIndex has a non-NULL subnet_id and correct ip_id (for IP indexes).
  2. The newly generated indexes after updating the number_of_ips (which triggers celery tasks) also have correct non-NULL foreign key fields.

Screenshot

Logical proof of the bug and fix (Python simulation):

[Buggy Way] After bulk_create, index_buggy.ip_id is: None (Expected: 42, Actual: None)
[Fixed Way] After bulk_create and save, index_fixed.ip_id is: 42 (Expected: 42, Actual: 42)

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Subnet division provisioning now assigns SubnetDivisionIndex foreign keys through related Subnet and IpAddress objects before bulk creation. This applies to initial subnet and IP generation and extra-IP provisioning during updates. A regression test verifies that generated index records retain non-null subnet and IP foreign keys before and after changing the rule’s IP count.

Estimated code review effort: 2 (Simple) | ~10 minutes


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
General Rules ❌ Error Issue #1435 is labeled bug but shows “No projects,” so the maintainer-validation rule isn’t met. citeturn13view0 Assign #1435 to one of the approved projects before merging, or document why the author is exempt as an organization member.
Title check ⚠️ Warning The title is relevant to the bug fix, but it misses the required [fix]/type prefix from the repository title format. Use a prefixed title like "[fix] SubnetDivisionIndex foreign keys saved as NULL".
✅ Passed checks (6 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the template with checklist, issue reference, change details, and a screenshot section.
Linked Issues check ✅ Passed The fix and new test satisfy #1435 by ensuring generated SubnetDivisionIndex rows keep valid subnet and IP foreign keys.
Out of Scope Changes check ✅ Passed The PR stays within scope, with only the foreign-key fix and supporting test changes.
Bug Fixes ✅ Passed Root cause is fixed by using related objects before bulk_create; the added regression test covers initial provisioning and number_of_ips updates and would fail without the patch.
Features ✅ Passed PASS: This is a bug fix, not a feature request; it adds a regression test and no UI/docs feature requirements apply.
Changes ✅ Passed PASS: The PR adds a focused integrity test, no UI change is present, and the fix stays internal with no public API or migration impact.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@kilo-code-bot

kilo-code-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The PR fixes issue #1435 (SubnetDivisionIndex foreign keys saved as NULL). The fix is correct and well-scoped:

  • rule_types/base.py — assigns the model instances (subnet=subnet_obj, ip=ip_obj) instead of copying .id values before bulk_create. Django now resolves the FK primary keys at save time once the parent objects are bulk-created, eliminating the NULL FK writes.
  • tasks.pyprovision_extra_ips now uses ip=ip for the not-yet-saved IpAddress (correct); subnet_id=subnet.id remains valid because that subnet is already persisted.
  • tests/test_models.py — adds test_subnet_division_index_integrity, a valid regression test covering both initial provisioning and the provision_extra_ips path, asserting non-NULL subnet_id/ip_id, an increased index count, and robust "_ip" keyword matching.

The change includes a regression test (per AGENTS.md) and aligns with the referenced issue. Prior CodeRabbit nitpicks (line length, "ip" vs "_ip", count assertion) are already addressed in the current code.

Files Reviewed (3 files)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py

Note: the incremental diff since the previously reviewed commit was polluted by an intervening rebase onto newer master; review was scoped to this PR's actual 3 changed files versus its merge-base.

Previous Review Summaries (6 snapshots, latest commit 7e5e442)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7e5e442)

Status: No Issues Found | Recommendation: Merge

The only change since the previous review is comment line-wrapping in test_models.py (two inline comments split across two lines to respect line length). No functional, logic, or security impact. The underlying NULL foreign-key fix in base.py remains correct.

Files Reviewed (1 file changed since last review)
  • openwisp_controller/subnet_division/tests/test_models.py

Previous review (commit 13241c3)

Status: No Issues Found | Recommendation: Merge

The incremental change since the previous review is a single line in create_ips: subnet_id=subnet_obj.id was replaced with subnet=subnet_obj. This correctly completes the NULL foreign-key fix by passing the model instance reference (instead of a prematurely-copied None id), so Django resolves the primary key after bulk_create. It matches the pattern already used for ip=ip_obj and for the subnet index at line 259. No crash, logic, or security concerns on the changed line.

Files Reviewed (1 file changed since last review)
  • openwisp_controller/subnet_division/rule_types/base.py

Previous review (commit a9d885c)

Status: No Issues Found | Recommendation: Merge

The incremental changes since the previous review are test-only refinements in test_subnet_division_index_integrity:

  • The keyword filter was tightened from "ip" in index.keyword to "_ip" in index.keyword. This correctly targets IP indexes (keyword format {subnet_name}_ip{n}) and avoids matching subnet indexes, making the ip_id non-NULL assertion more precise.
  • An initial_count capture and a post-update count assertion (initial_count + rule.number_of_subnets * 2) were added to confirm new indexes are actually provisioned after increasing number_of_ips.

The underlying fix (passing model instances subnet=subnet_obj / ip=ip instead of prematurely copied None .id values) remains correct and was validated in the prior review. No crash, logic, or security concerns on the changed lines.

Files Reviewed (1 file changed since last review)
  • openwisp_controller/subnet_division/tests/test_models.py

Previous review (commit d1282d7)

Status: No Issues Found | Recommendation: Merge

The change fixes the SubnetDivisionIndex NULL foreign-key bug (issue #1435) cleanly and consistently across all three provisioning paths. Instances are now passed by reference (subnet=subnet_obj, ip=ip_obj) instead of copying .id values that are still None before bulk_create(). Django resolves the primary keys once the parent rows are saved. Object save ordering is correct in every case: parents are bulk_created before the SubnetDivisionIndex rows that reference them.

  • rule_types/base.py create_subnets (line 259): subnet=subnet_obj — correct, subnet not yet saved when index is built.
  • rule_types/base.py create_ips (line 298): ip=ip_obj — correct, IP not yet saved; subnet_id=subnet_obj.id is fine here since subnets are already bulk-created.
  • tasks.py _...index_objects path (line 112): ip=ip — correct, mirrors the base fix; IPs are bulk-created before indexes.

A regression test (test_subnet_division_index_integrity) covers both the initial provisioning path and the provision_extra_ips update path, asserting non-NULL subnet_id/ip_id.

No crash or security concerns on the changed lines. Remaining open comments are pre-existing style/lint nitpicks (E501/W293) already tracked by CodeRabbit and CI; they are out of scope for this review.

Files Reviewed (3 files)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py

Previous review (commit f4d90fc)

Status: No Issues Found | Recommendation: Merge

The incremental change resolves the advisory from the previous review. At openwisp_controller/subnet_division/rule_types/base.py:298, the FK is now built with ip=ip_obj instead of ip_id=ip_obj.id. Copying .id before IpAddress.objects.bulk_create() captured None and persisted NULL; passing the instance lets Django resolve the primary key once the parent rows are saved. This mirrors the correct subnet=subnet_obj pattern already used for subnet indexes, and is covered by the added test_subnet_division_index_integrity regression test.

No crash or security concerns on the changed line.

Files Reviewed (1 file in incremental diff)
  • openwisp_controller/subnet_division/rule_types/base.py

Previous review (commit 389f144)

Status: No Critical Issues Found | Recommendation: Merge

Nice, focused fix. Passing the model instances (subnet=subnet_obj, ip=ip) instead of copying .id before bulk_create correctly lets Django resolve the foreign keys once the parent rows are saved. The changed lines contain no crash or security concerns, and a regression test was added.

Advisory (not blocking, outside changed lines)

The same copy-before-bulk_create pattern still exists at openwisp_controller/subnet_division/rule_types/base.py:298 (ip_id=ip_obj.id), where the SubnetDivisionIndex instances are built before IpAddress.objects.bulk_create() populates the PKs. If the initial IP-index path is intended to be covered by the new test_subnet_division_index_integrity assertion, it may be worth confirming that this path resolves ip_id as well. This line is unchanged by the PR, so it is noted for context only.

Files Reviewed (3 files)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py

Reviewed by claude-opus-4.8 · Input: 26 · Output: 5.1K · Cached: 570.9K

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/subnet_division/tests/test_models.py`:
- Around line 1013-1036: Clean up test_subnet_division_index_integrity in the
regression test by removing the trailing whitespace on the blank line and
wrapping the long assertIsNotNone statements, including their f-string messages,
so every line stays within the repository’s 88-character limit while preserving
the assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b0d5d1eb-4f6e-4be6-91ff-a306f9e2856a

📥 Commits

Reviewing files that changed from the base of the PR and between 08a0821 and 389f144.

📒 Files selected for processing (3)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout. (13)
  • GitHub Check: CodeRabbit
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.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.11 | django~=5.2.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~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
🧠 Learnings (8)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
🔇 Additional comments (3)
openwisp_controller/subnet_division/rule_types/base.py (2)

256-263: LGTM!


268-304: 🗄️ Data Integrity & Integration

No NULL-FK issue here create_ips() uses ip=ip_obj, and the IP rows are bulk-created before SubnetDivisionIndex.objects.bulk_create(), so this row doesn’t leave ip_id unset.

			> Likely an incorrect or invalid review comment.
openwisp_controller/subnet_division/tasks.py (1)

99-116: LGTM! The ip=ip relation assignment at Line 112 correctly lets Django resolve ip_id from the cached ip instance once IpAddress.objects.bulk_create(ips) runs (before SubnetDivisionIndex.objects.bulk_create(indexes)), matching the required save ordering for this FK-resolution mechanism.

Comment thread openwisp_controller/subnet_division/tests/test_models.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/subnet_division/rule_types/base.py`:
- Line 297: In create_ips, replace the subnet_id=subnet_obj.id assignment with
the subnet=subnet_obj object assignment, matching the existing FK assignment
style used by create_subnets while preserving the created relationship.

In `@openwisp_controller/subnet_division/tests/test_models.py`:
- Around line 1013-1036: Strengthen test_subnet_division_index_integrity by
record­ing the initial index count, asserting the post-update count increased by
the two added IPs, and replacing both `"ip" in index.keyword` checks with the
`"_ip"` pattern. Remove the trailing whitespace from the blank line after adding
the template.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ae976e39-dfa9-4c8d-b8ac-b41b77e415da

📥 Commits

Reviewing files that changed from the base of the PR and between 08a0821 and f4d90fc.

📒 Files selected for processing (3)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py

Comment thread openwisp_controller/subnet_division/rule_types/base.py Outdated
Comment thread openwisp_controller/subnet_division/tests/test_models.py

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This pull request targets an issue that has not been validated by maintainers and seem to violate the OpenWISP Anti Spam Policy.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
openwisp_controller/subnet_division/tests/test_models.py (1)

1013-1047: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Lint failure and lingering test-robustness gaps from prior review rounds.

  • Line 1017's comment is 91 characters, exceeding the project's 88-char flake8 limit (E501), which will fail CI. Based on learnings, this repo enforces E501 via flake8's setup.cfg (max-line-length = 88), applicable to test files too.
  • Blank lines at 1016, 1029, 1034 split the method body; as per path instructions, avoid unnecessary blank lines inside function/method bodies.
  • "ip" in index.keyword is fragile — a rule label containing "ip" (e.g. "IPSEC") would make subnet-only keywords match too. Use "_ip" in index.keyword to match the actual keyword pattern.
  • No assertion that the index count actually increased after number_of_ips update, so the test would still pass even if provision_extra_ips created zero new indexes.
🔧 Suggested fix
     def test_subnet_division_index_integrity(self):
         rule = self._get_vpn_subdivision_rule()
         self.config.templates.add(self.template)
-
-        # Verify initial indexes have non-NULL subnet_id and ip_id (if they are IP indexes)
+        # Verify initial indexes have non-NULL subnet_id and ip_id
+        # (if they are IP indexes)
         indexes = rule.subnetdivisionindex_set.all()
         self.assertTrue(indexes.exists())
+        initial_index_count = indexes.count()
         for index in indexes:
             self.assertIsNotNone(
                 index.subnet_id,
                 f"SubnetDivisionIndex {index.keyword} has NULL subnet_id",
             )
-            if "ip" in index.keyword:
+            if "_ip" in index.keyword:
                 self.assertIsNotNone(
                     index.ip_id, f"SubnetDivisionIndex {index.keyword} has NULL ip_id"
                 )
-
         # Update number of IPs to trigger tasks.provision_extra_ips
         rule.number_of_ips = rule.number_of_ips + 2
         rule.save()
         rule.refresh_from_db()
-
         # Verify all indexes, including new ones, have non-NULL subnet_id and ip_id
         indexes = rule.subnetdivisionindex_set.all()
+        self.assertGreater(indexes.count(), initial_index_count)
         for index in indexes:
             self.assertIsNotNone(
                 index.subnet_id,
                 f"SubnetDivisionIndex {index.keyword} has NULL subnet_id after update",
             )
-            if "ip" in index.keyword:
+            if "_ip" in index.keyword:
                 self.assertIsNotNone(
                     index.ip_id,
                     f"SubnetDivisionIndex {index.keyword} has NULL ip_id after update",
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/subnet_division/tests/test_models.py` around lines 1013 -
1047, Update test_subnet_division_index_integrity to keep comments within the
88-character limit and remove unnecessary blank lines inside the method. Replace
each fragile "ip" keyword check with "_ip" matching, and record the initial
index count before updating number_of_ips, then assert the post-update count
increased before validating the new indexes.

Sources: Path instructions, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@openwisp_controller/subnet_division/tests/test_models.py`:
- Around line 1013-1047: Update test_subnet_division_index_integrity to keep
comments within the 88-character limit and remove unnecessary blank lines inside
the method. Replace each fragile "ip" keyword check with "_ip" matching, and
record the initial index count before updating number_of_ips, then assert the
post-update count increased before validating the new indexes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85380b9e-f3e7-4269-8e9e-9f565a1567dc

📥 Commits

Reviewing files that changed from the base of the PR and between f4d90fc and d1282d7.

📒 Files selected for processing (3)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout. (13)
  • GitHub Check: CodeRabbit
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.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.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
🧠 Learnings (8)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/rule_types/base.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
🔇 Additional comments (3)
openwisp_controller/subnet_division/rule_types/base.py (2)

220-266: LGTM!


268-304: 🎯 Functional Correctness

Correct fix: ip=ip_obj defers FK resolution until after IpAddress.objects.bulk_create().

Assigning the unsaved ip_obj instance lets Django resolve ip_id from the object's real pk once IpAddress.objects.bulk_create(generated_ips) (line 303) runs, before the caller's SubnetDivisionIndex.objects.bulk_create() executes. This relies on Django's parent-before-child pk resolution in bulk_create() (fixed via Django's _prepare_related_fields_for_save), which requires the parent bulk_create to complete first — which it does here.

openwisp_controller/subnet_division/tasks.py (1)

108-116: LGTM!

@coderabbitai coderabbitai Bot added the bug label Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/subnet_division/tests/test_models.py`:
- Line 1037: Wrap the long comment near the count and index assertions onto
multiple comment lines so each remains within the repository’s 88-character
flake8 limit, preserving its meaning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ed67f79-4cb3-41a5-b81f-39eee21627d5

📥 Commits

Reviewing files that changed from the base of the PR and between d1282d7 and a9d885c.

📒 Files selected for processing (3)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: CodeRabbit
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | 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.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/tasks.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/tasks.py
🧠 Learnings (8)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/subnet_division/tasks.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
🔇 Additional comments (3)
openwisp_controller/subnet_division/rule_types/base.py (1)

256-262: LGTM!

Also applies to: 294-302

openwisp_controller/subnet_division/tasks.py (1)

108-116: LGTM!

openwisp_controller/subnet_division/tests/test_models.py (1)

1013-1036: LGTM!

Also applies to: 1038-1052

Comment thread openwisp_controller/subnet_division/tests/test_models.py Outdated
@coderabbitai coderabbitai Bot removed the bug label Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
openwisp_controller/subnet_division/tests/test_models.py (1)

1037-1037: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Line 1037 exceeds the 88-character flake8 limit (91 chars).

This was flagged in a previous review but remains unaddressed. Based on learnings, this repo enforces E501 via flake8's setup.cfg (max-line-length = 88) on all Python files including tests.

🔧 Proposed fix: wrap the comment
-        # Verify count increased and all indexes, including new ones, have non-NULL subnet_id and ip_id
+        # Verify count increased and all indexes, including new ones,
+        # have non-NULL subnet_id and ip_id
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/subnet_division/tests/test_models.py` at line 1037, Wrap
the long comment in the test around the verification of subnet_id and ip_id so
each line is no longer than 88 characters, preserving its meaning.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@openwisp_controller/subnet_division/tests/test_models.py`:
- Line 1037: Wrap the long comment in the test around the verification of
subnet_id and ip_id so each line is no longer than 88 characters, preserving its
meaning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7515a87-2572-4e68-8fe8-6786c297f960

📥 Commits

Reviewing files that changed from the base of the PR and between a9d885c and 13241c3.

📒 Files selected for processing (3)
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/tests/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: CodeRabbit
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework.

Files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
🧠 Learnings (8)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/subnet_division/tasks.py
  • openwisp_controller/subnet_division/rule_types/base.py
  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/subnet_division/tests/test_models.py
🔇 Additional comments (3)
openwisp_controller/subnet_division/rule_types/base.py (1)

256-263: LGTM!

Also applies to: 294-302

openwisp_controller/subnet_division/tasks.py (1)

108-116: LGTM!

openwisp_controller/subnet_division/tests/test_models.py (1)

1013-1036: LGTM!

Also applies to: 1038-1052

@ApurveKaranwal

Copy link
Copy Markdown
Author

Hi @nemesifier sir,

I sincerely apologize for opening this PR before having the issue validated first. I have read the OpenWISP Contributing Guidelines and the Anti-Spam Policy, and I realize that skipping the triage process adds unnecessary overhead to your workflow.

I got eager to submit the fix and rushed, but I will make sure to strictly follow the guidelines by waiting for maintainer validation in the future. Thank you for your patience and for routing this for review!

Ensured that the subnet and ip foreign keys are populated correctly
on SubnetDivisionIndex records during initial provisioning and when
updating the number of IPs.

Closes #1435
@nemesifier

Copy link
Copy Markdown
Member

@ApurveKaranwal how did you find this issue? Are you using OpenWISP or are you hunting for bugs using AI tools? Please clarify. There's nothing wrong in using AI tools to hunt for bugs but you should not pretend it's not the case.

@ApurveKaranwal

Copy link
Copy Markdown
Author

Hey @nemesifier sir,

Just to give some context: I was digging into the subnet division code and running tests locally to see how the admin index locks work. I noticed the automated indexes weren't actually locking the subnets/IPs in the admin, and when I checked the database, the subnet_id and ip_id fields in SubnetDivisionIndex were indeed coming up as NULL.

Since tracing Django's bulk save behavior was a bit tricky, I used AI tools to help diagnose the ORM behavior (specifically why the .id attributes were None during instantiation) and to help write the regression test.

So it was a mix of looking at the codebase, inspecting the database, and using AI to help speed up the debugging process. I definitely wasn't trying to hide anything, and I really appreciate your patience. I just wanted to locate a genuine bug and contribute a solid fix to the project!

@pandafy pandafy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ApurveKaranwal please see #1435 (comment)

You can re-open this PR once the bug is validated.

SubnetDivisionIndex(
keyword=f"{division_rule.label}_subnet{subnet_id}",
subnet_id=subnet_obj.id,
subnet=subnet_obj,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How is this different?

Comment on lines +297 to +298
subnet=subnet_obj,
ip=ip_obj,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, how is this different?

e.message_dict.get("master_subnet", []),
)

def test_subnet_division_index_integrity(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you check that this test fails on the current master?

If yes, can you point out in the [issue comments[(https://github.com//issues/1435#issuecomment-4957428251) why the existing test cases are unable to detect the bug you reported?

@pandafy pandafy closed this Jul 13, 2026
@openwisp openwisp locked as spam and limited conversation to collaborators Jul 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] SubnetDivisionIndex foreign keys (subnet_id, ip_id) are saved as NULL during provisioning

3 participants