Skip to content

[feature] Suggests next available subnet #29#196

Draft
pranshustuff wants to merge 6 commits into
openwisp:masterfrom
pranshustuff:subnet-validation
Draft

[feature] Suggests next available subnet #29#196
pranshustuff wants to merge 6 commits into
openwisp:masterfrom
pranshustuff:subnet-validation

Conversation

@pranshustuff

@pranshustuff pranshustuff commented Oct 12, 2025

Copy link
Copy Markdown

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 #29.

Description of Changes

Function clean() now calculates next available subnet and adds it as a suggestion in the error message.
Added an API endpoint to get next available subnet for a particular subnet. And modified tests.

Screenshot

subnet-issue-2025-10-12_05.56.39.mp4

Pranshu and others added 2 commits October 12, 2025 05:46
Functioin clean() now calculates next availible subnet
and adds it as a suggestion in the error message.
Added an api endpoint to get next availible subnet for
a particular subnet. And modified tests.

Fixes openwisp#29
@pranshustuff pranshustuff changed the title [feature] Suggests next availible subnet #29 [feature] Suggests next available subnet #29 Oct 12, 2025
@coderabbitai

coderabbitai Bot commented Jan 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds a "get next available subnet" API endpoint and wires it to a new subnet lookup view. The view accepts optional prefix and parent_subnet query parameters, validates the prefix, and returns either the next available subnet or an error response. The new route is exposed at subnet/<subnet_id>/get-next-available-subnet/.

Related issue: #29
Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API View
  participant Subnet Model
  Client->>API View: GET subnet/<subnet_id>/get-next-available-subnet/
  API View->>Subnet Model: get_next_available_subnet(target_prefix, parent_subnet)
  Subnet Model-->>API View: subnet or none
  API View-->>Client: JSON response or error
Loading

Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
General Rules ❌ Error New endpoint/docs/i18n rules are incomplete; invalid parent_subnet can 500 and get_next_available_subnet brute-forces large ranges. Document the new API in docs/user/rest-api.rst, wrap new model error text in gettext, validate parent_subnet/prefix inputs, and cap/optimize subnet search.
Features ⚠️ Warning Docs were not updated for the new subnet endpoint; only get-next-available-ip is documented, though tests and issue tracking look fine. Add REST/user docs for /subnet//get-next-available-subnet/ (and changelog if desired); no UI screenshot is needed because this isn’t a UI feature.
Changes ⚠️ Warning REST API docs were not updated: docs/user/rest-api.rst still lists only get-next-available-ip, not the new get-next-available-subnet endpoint or its params. Update the REST API/user docs to cover get-next-available-subnet (URL, query params, response/error cases) and note the validation-message change if it’s user-facing.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, uses the required [feature] prefix, and matches the subnet suggestion feature.
Description check ✅ Passed The description includes the checklist, linked issue, change summary, and screenshot section, with only documentation left unchecked.
Linked Issues check ✅ Passed The PR adds the requested model method and exposes the same next-available-subnet behavior via API, matching issue #29.
Out of Scope Changes check ✅ Passed All changes appear tied to subnet suggestion functionality, tests, and sample wiring; no unrelated code changes are evident.
Bug Fixes ✅ Passed Yes: overlapping-subnet validation now computes a real alternative, and the added model tests fail without the patch; no UI/browser test is needed for this API/model change.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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_ipam/base/models.py`:
- Around line 201-266: The loop over
parent_network.subnets(new_prefix=target_prefix) in get_next_available_subnet
can produce huge iteration counts for large parent networks; add a safeguard by
capping evaluated candidates (e.g., introduce MAX_CANDIDATE_SUBNETS or an
optional parameter max_candidates on get_next_available_subnet) and iterate
using a bounded iterator (islice) so you stop after the limit, returning None
(or raising a specific exception) if the cap is reached; ensure the check still
calls _validate_candidate_subnet and preserves existing organization filtering,
and reference get_next_available_subnet, parent_network.subnets, target_prefix,
and _validate_candidate_subnet when making the change.
🧹 Nitpick comments (6)
openwisp_ipam/tests/test_api.py (2)

155-161: Consider using a more explicit assertion for subnet validation.

Using assertTrue(ip_network(response.data["subnet"])) only checks that the result is truthy. Consider asserting on a specific property to ensure the response is valid.

♻️ Suggested improvement
     def test_get_next_available_subnet_success(self):
         subnet = self._create_subnet(subnet="10.0.0.0/24")
         url = reverse("ipam:get_next_available_subnet", kwargs={"subnet_id": subnet.pk})
         response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
         self.assertIn("subnet", response.data)
-        self.assertTrue(ip_network(response.data["subnet"]))
+        # Verify it's a valid network with the expected prefix length
+        result_network = ip_network(response.data["subnet"])
+        self.assertEqual(result_network.prefixlen, 24)

170-186: Subnets created via Subnet.objects.create bypass validation and lack organization.

The subnets created on lines 176-177 use Subnet.objects.create() which bypasses full_clean() and doesn't assign an organization. This may not accurately simulate real-world conditions where subnets would have an organization. Additionally, the name field is set to "Full" which seems like a placeholder.

♻️ Consider using `_create_subnet` for consistency
     def test_get_next_available_subnet_no_available(self):
         parent_subnet = self._create_subnet(
             subnet="10.0.0.0/23"
         )  # 512 IPs, can fit two /24s

         # fill all available /24 subnets
-        Subnet.objects.create(subnet="10.0.0.0/24", name="Full")
-        Subnet.objects.create(subnet="10.0.1.0/24", name="Full")
+        self._create_subnet(
+            subnet="10.0.0.0/24",
+            organization=parent_subnet.organization
+        )
+        self._create_subnet(
+            subnet="10.0.1.0/24",
+            organization=parent_subnet.organization
+        )

         url = reverse(
             "ipam:get_next_available_subnet", kwargs={"subnet_id": parent_subnet.pk}
         )
openwisp_ipam/api/views.py (1)

177-209: Consider validating parent_subnet format and prefix range.

The prefix is validated as an integer, but there's no validation that:

  1. parent_subnet is a valid CIDR notation string (invalid values will cause ip_network() to raise ValueError in the model)
  2. prefix is within a valid range for the subnet's IP version (0-32 for IPv4, 0-128 for IPv6)

While the model method may handle some of these gracefully, returning a user-friendly error at the API layer would improve the developer experience.

♻️ Suggested validation improvements
+from ipaddress import ip_network
+
 class AvailableSubnetView(
     ProtectedAPIMixin, FilterByOrganizationManaged, RetrieveAPIView
 ):
     subnet_model = Subnet
     queryset = Subnet.objects.none()
     serializer_class = serializers.Serializer

     def get(self, request, *args, **kwargs):
         subnet = get_object_or_404(self.subnet_model, pk=self.kwargs["subnet_id"])
         target_prefix = request.query_params.get("prefix")
         parent_subnet = request.query_params.get("parent_subnet")

         # Convert prefix to int if provided
         if target_prefix:
             try:
                 target_prefix = int(target_prefix)
             except ValueError:
                 return Response(
                     {"error": _("Invalid prefix length. Must be an integer.")},
                     status=status.HTTP_400_BAD_REQUEST,
                 )
+            # Validate prefix range
+            max_prefix = 32 if subnet.subnet.version == 4 else 128
+            if not (0 <= target_prefix <= max_prefix):
+                return Response(
+                    {"error": _("Prefix length out of valid range.")},
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
+
+        # Validate parent_subnet format if provided
+        if parent_subnet:
+            try:
+                ip_network(parent_subnet, strict=False)
+            except ValueError:
+                return Response(
+                    {"error": _("Invalid parent_subnet format.")},
+                    status=status.HTTP_400_BAD_REQUEST,
+                )

         next_subnet = subnet.get_next_available_subnet(
             target_prefix=target_prefix, parent_subnet=parent_subnet
         )
openwisp_ipam/base/models.py (3)

59-75: Improve exception handling with proper chaining.

The static analysis correctly identifies that the raised ValidationError loses the original exception context. Using raise ... from e preserves the traceback chain for debugging. Also, the bare raise e should just be raise.

♻️ Proposed fix for exception handling
         except ValidationError as e:
             next_subnet = self.get_next_available_subnet()
             if next_subnet:
                 self._suggested_subnet = next_subnet
                 error_dict = e.message_dict.copy()
                 if "subnet" in error_dict:
                     original_message = error_dict["subnet"][0]
                     error_dict["subnet"] = [
                         f"{original_message} Suggested alternative: {next_subnet}"
                     ]
                 else:
                     error_dict["subnet"] = [  # fallback
                         f"Subnet conflict found. Suggested alternative: {next_subnet}"
                     ]
-                raise ValidationError(error_dict)
+                raise ValidationError(error_dict) from e
             else:
-                raise e
+                raise

221-245: Duplicated organization query logic.

The organization_query construction is duplicated in lines 221-226 and 240-245. Consider extracting this into a helper method or computing it once and reusing.

♻️ Extract organization query logic
+    def _get_organization_query(self):
+        """Returns Q object for filtering subnets by organization context."""
+        if self.organization:
+            return Q(organization_id=self.organization_id) | Q(organization_id__isnull=True)
+        return Q()
+
     def get_next_available_subnet(self, target_prefix=None, parent_subnet=None):
         # ... earlier code ...
         else:
             current_network = ip_network(self.subnet)
             parent_network = self._find_optimal_parent_subnet(current_network)

-            if self.organization:
-                organization_query = Q(organization_id=self.organization_id) | Q(
-                    organization_id__isnull=True
-                )
-            else:
-                organization_query = Q()
+            organization_query = self._get_organization_query()

             # ... middle code ...

-        if self.organization:
-            organization_query = Q(organization_id=self.organization_id) | Q(
-                organization_id__isnull=True
-            )
-        else:
-            organization_query = Q()
+        organization_query = self._get_organization_query()

268-286: Consider moving return True to an else block.

As noted by static analysis, returning inside a try block can make the code flow less clear. Moving the successful return to an else block improves readability.

♻️ Improved structure
     def _validate_candidate_subnet(self, candidate_subnet):
         try:
             temp_instance = self._meta.model(
                 name=self.name,
                 subnet=str(candidate_subnet),
                 description=self.description,
                 master_subnet=self.master_subnet,
                 organization=self.organization,
             )

             temp_instance._validate_multitenant_uniqueness()
             temp_instance._validate_multitenant_master_subnet()
             temp_instance._validate_multitenant_unique_child_subnet()
             temp_instance._validate_overlapping_subnets()
             temp_instance._validate_master_subnet_consistency()
-
-            return True
         except ValidationError:
             return False
+        else:
+            return True
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 170c07d and 5c53ffe.

📒 Files selected for processing (6)
  • openwisp_ipam/api/urls.py
  • openwisp_ipam/api/views.py
  • openwisp_ipam/base/models.py
  • openwisp_ipam/tests/test_api.py
  • openwisp_ipam/tests/test_models.py
  • tests/openwisp2/sample_ipam/views.py
🧰 Additional context used
🧬 Code graph analysis (5)
tests/openwisp2/sample_ipam/views.py (2)
openwisp_ipam/api/views.py (1)
  • AvailableSubnetView (177-209)
openwisp_ipam/base/models.py (1)
  • get_next_available_subnet (201-266)
openwisp_ipam/api/views.py (2)
openwisp_ipam/models.py (1)
  • Subnet (6-9)
openwisp_ipam/base/models.py (1)
  • get_next_available_subnet (201-266)
openwisp_ipam/tests/test_api.py (1)
openwisp_ipam/api/views.py (2)
  • get (172-174)
  • get (184-209)
openwisp_ipam/api/urls.py (1)
openwisp_ipam/base/models.py (1)
  • get_next_available_subnet (201-266)
openwisp_ipam/base/models.py (1)
openwisp_ipam/admin.py (1)
  • organization (235-236)
🪛 Ruff (0.14.14)
openwisp_ipam/api/views.py

[warning] 184-184: Unused method argument: args

(ARG002)


[warning] 184-184: Unused method argument: kwargs

(ARG002)

openwisp_ipam/base/models.py

[warning] 73-73: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 75-75: Use raise without specifying exception name

Remove exception name

(TRY201)


[warning] 284-284: Consider moving this statement to an else block

(TRY300)

⏰ 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.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | 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
🔇 Additional comments (8)
openwisp_ipam/api/urls.py (1)

18-22: LGTM!

The new URL route follows the established pattern of the existing get-next-available-ip endpoint. The naming convention is consistent with the codebase.

tests/openwisp2/sample_ipam/views.py (2)

57-63: LGTM!

The new AvailableSubnetView class follows the established pattern used by other views in this sample module, properly extending the base view and including a descriptive docstring.


10-10: LGTM!

The import and module-level binding are consistent with how other views are set up in this file.

Also applies to: 113-113

openwisp_ipam/tests/test_models.py (2)

141-144: LGTM!

The test assertions are correctly updated to expect the new error message format that includes the suggested alternative subnet.

Also applies to: 155-158


393-398: LGTM!

The assertion for the duplicate subnet validation correctly expects the new message format with the suggested alternative.

openwisp_ipam/api/views.py (1)

320-320: LGTM!

The module-level binding correctly exposes the new view.

openwisp_ipam/base/models.py (2)

116-125: LGTM!

The new duplicate check for organization-specific subnets correctly prevents creating duplicate subnets within the same organization, complementing the existing shared subnet validation.


77-92: LGTM!

The _find_optimal_parent_subnet helper uses reasonable heuristics to determine parent prefix lengths for both IPv4 and IPv6, providing a good balance between search space and availability.

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

Comment on lines +201 to +266
def get_next_available_subnet(self, target_prefix=None, parent_subnet=None):
if target_prefix is None:
try:
network = (
ip_network(self.subnet)
if isinstance(self.subnet, str)
else self.subnet
)
except ValueError:
return None
target_prefix = network.prefixlen

if parent_subnet:
parent_network = ip_network(parent_subnet)
elif self.master_subnet:
parent_network = ip_network(self.master_subnet.subnet)
else:
current_network = ip_network(self.subnet)
parent_network = self._find_optimal_parent_subnet(current_network)

if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()

existing_subnets = self._meta.model.objects.filter(
organization_query
).exclude(pk=self.pk)
existing_networks = [ip_network(sub.subnet) for sub in existing_subnets]

for existing in existing_networks:
if parent_network.overlaps(existing) and parent_network.subnet_of(
existing
):
parent_network = self._find_optimal_parent_subnet(existing)
break

if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()

existing_subnets = self._meta.model.objects.filter(organization_query).exclude(
pk=self.pk
)
existing_networks = {
ip_network(sub.subnet)
for sub in existing_subnets
if ip_network(sub.subnet).subnet_of(parent_network)
or ip_network(sub.subnet).overlaps(parent_network)
}

for candidate_subnet in parent_network.subnets(new_prefix=target_prefix):
if any(
candidate_subnet.overlaps(existing) for existing in existing_networks
):
continue

if self._validate_candidate_subnet(candidate_subnet):
return str(candidate_subnet)

return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential performance concern with large parent networks.

When parent_network is large (e.g., a /8 network with target_prefix /24), parent_network.subnets(new_prefix=target_prefix) generates 2^16 = 65,536 candidate subnets to iterate through. For IPv6 with larger prefix differences, this could be exponentially worse.

Consider adding a safeguard to limit the number of candidates evaluated or document the expected behavior for large ranges.

💡 Suggested safeguard
+        MAX_CANDIDATES = 10000  # Prevent runaway iteration
+        candidate_count = 0
         for candidate_subnet in parent_network.subnets(new_prefix=target_prefix):
+            candidate_count += 1
+            if candidate_count > MAX_CANDIDATES:
+                return None  # Too many candidates to evaluate
+
             if any(
                 candidate_subnet.overlaps(existing) for existing in existing_networks
             ):
                 continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_next_available_subnet(self, target_prefix=None, parent_subnet=None):
if target_prefix is None:
try:
network = (
ip_network(self.subnet)
if isinstance(self.subnet, str)
else self.subnet
)
except ValueError:
return None
target_prefix = network.prefixlen
if parent_subnet:
parent_network = ip_network(parent_subnet)
elif self.master_subnet:
parent_network = ip_network(self.master_subnet.subnet)
else:
current_network = ip_network(self.subnet)
parent_network = self._find_optimal_parent_subnet(current_network)
if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()
existing_subnets = self._meta.model.objects.filter(
organization_query
).exclude(pk=self.pk)
existing_networks = [ip_network(sub.subnet) for sub in existing_subnets]
for existing in existing_networks:
if parent_network.overlaps(existing) and parent_network.subnet_of(
existing
):
parent_network = self._find_optimal_parent_subnet(existing)
break
if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()
existing_subnets = self._meta.model.objects.filter(organization_query).exclude(
pk=self.pk
)
existing_networks = {
ip_network(sub.subnet)
for sub in existing_subnets
if ip_network(sub.subnet).subnet_of(parent_network)
or ip_network(sub.subnet).overlaps(parent_network)
}
for candidate_subnet in parent_network.subnets(new_prefix=target_prefix):
if any(
candidate_subnet.overlaps(existing) for existing in existing_networks
):
continue
if self._validate_candidate_subnet(candidate_subnet):
return str(candidate_subnet)
return None
def get_next_available_subnet(self, target_prefix=None, parent_subnet=None):
if target_prefix is None:
try:
network = (
ip_network(self.subnet)
if isinstance(self.subnet, str)
else self.subnet
)
except ValueError:
return None
target_prefix = network.prefixlen
if parent_subnet:
parent_network = ip_network(parent_subnet)
elif self.master_subnet:
parent_network = ip_network(self.master_subnet.subnet)
else:
current_network = ip_network(self.subnet)
parent_network = self._find_optimal_parent_subnet(current_network)
if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()
existing_subnets = self._meta.model.objects.filter(
organization_query
).exclude(pk=self.pk)
existing_networks = [ip_network(sub.subnet) for sub in existing_subnets]
for existing in existing_networks:
if parent_network.overlaps(existing) and parent_network.subnet_of(
existing
):
parent_network = self._find_optimal_parent_subnet(existing)
break
if self.organization:
organization_query = Q(organization_id=self.organization_id) | Q(
organization_id__isnull=True
)
else:
organization_query = Q()
existing_subnets = self._meta.model.objects.filter(organization_query).exclude(
pk=self.pk
)
existing_networks = {
ip_network(sub.subnet)
for sub in existing_subnets
if ip_network(sub.subnet).subnet_of(parent_network)
or ip_network(sub.subnet).overlaps(parent_network)
}
MAX_CANDIDATES = 10000 # Prevent runaway iteration
candidate_count = 0
for candidate_subnet in parent_network.subnets(new_prefix=target_prefix):
candidate_count += 1
if candidate_count > MAX_CANDIDATES:
return None # Too many candidates to evaluate
if any(
candidate_subnet.overlaps(existing) for existing in existing_networks
):
continue
if self._validate_candidate_subnet(candidate_subnet):
return str(candidate_subnet)
return None
🤖 Prompt for AI Agents
In `@openwisp_ipam/base/models.py` around lines 201 - 266, The loop over
parent_network.subnets(new_prefix=target_prefix) in get_next_available_subnet
can produce huge iteration counts for large parent networks; add a safeguard by
capping evaluated candidates (e.g., introduce MAX_CANDIDATE_SUBNETS or an
optional parameter max_candidates on get_next_available_subnet) and iterate
using a bounded iterator (islice) so you stop after the limit, returning None
(or raising a specific exception) if the cap is reached; ensure the check still
calls _validate_candidate_subnet and preserves existing organization filtering,
and reference get_next_available_subnet, parent_network.subnets, target_prefix,
and _validate_candidate_subnet when making the change.

@coveralls

coveralls commented Jan 31, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 99.323% (-0.3%) from 99.624% — pranshustuff:subnet-validation into openwisp:master

@pranshustuff

Copy link
Copy Markdown
Author

As coderabbit pointed out the brute-force algorithm might not work for large parent subnets like /8 or /16. However, limiting the search to 10,000 subnets isn't a good solution. I will try to find a different algorithm to find the next available subnet.

@pranshustuff pranshustuff marked this pull request as draft February 7, 2026 19:40

@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.

Thanks @pranshustuff, this is a useful feature for #29 and I am glad to see the organization scoping done correctly (Q(organization_id=...) | Q(organization_id__isnull=True)), which is exactly the multi-tenant behavior we want. The API view with ProtectedAPIMixin + FilterByOrganizationManaged is the right wrapping.

A few things to address:

  1. Performance for large parents / fine prefixes. get_next_available_subnet loads all org subnets into Python and then iterates parent_network.subnets(new_prefix=target_prefix) checking overlap against the in-memory set. For a large parent (say /16) and a fine target_prefix (say /30) this is tens of thousands of candidates times the existing set. Please bound this (e.g. cap iterations and return a clear "no space"/"range too large" response) so a single API call cannot become very expensive.
  2. Duplication. The organization Q(...) block and the existing_subnets query are repeated three times in the method. Factor them into one helper to avoid drift.
  3. Validate inputs. parent_subnet is taken from the query string and passed to ip_network(parent_subnet) without a try/except in the model method. A malformed parent_subnet would raise ValueError and 500. Validate it and return 400.
  4. Docs are unchecked; a new API endpoint needs documentation.

Good feature with the right org handling. Address the bounding and the parent_subnet validation and this is close.

@openwisp-companion

Copy link
Copy Markdown

Hi @pranshustuff 👋,

This is a friendly reminder that this pull request has had no activity for 7 days since changes were requested.

We'd love to see this contribution merged! Please take a moment to:

  • Address the review feedback
  • Push your changes
  • Let us know if you have any questions or need clarification

If you're busy or need more time, no worries! Just leave a comment to let us know you're still working on it.

Note: within 7 more days, the linked issue will be unassigned to allow other contributors to work on it.

Thank you for your contribution! 🙏

@openwisp-companion

Copy link
Copy Markdown

Hi @pranshustuff 👋,

This pull request has been marked as stale due to 14 days of inactivity after changes were requested.

As a result, any linked issues are being unassigned from you so other contributors can pick them up.

However, you can still continue working on this PR! If you push new commits or respond to the review feedback:

  • The issue will be reassigned to you
  • Your contribution is still very welcome

If you need more time or have questions about the requested changes, please let us know. We're happy to help! 🤝

@openwisp-companion openwisp-companion Bot removed the stale label Jul 5, 2026
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.

[model/api] Get next available subnet

3 participants