Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add migration for django 4, fix serializer validation #258

Merged
merged 6 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/258.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added migration to support Django 4.
1 change: 1 addition & 0 deletions changes/258.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed IPRangeSerializer requiring vrf field.
26 changes: 26 additions & 0 deletions nautobot_firewall_models/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""API serializers for firewall models."""

from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from nautobot.apps.api import NautobotModelSerializer, ValidatedModelSerializer

from nautobot_firewall_models import models
Expand All @@ -17,6 +19,30 @@ class Meta:
model = models.IPRange
fields = "__all__"

# Omit the UniqueTogetherValidators that would be automatically added to validate (start_address, end_address, vrf).
# This prevents vrf from being interpreted as a required field.
validators = []

def validate(self, data):
"""Custom validate method to enforce unique constraints on IPRange model."""
# Validate uniqueness of (start_address, end_address, vrf) since we omitted the automatically-created validator above.
if data.get("vrf", None):
validator = UniqueTogetherValidator(
queryset=models.IPRange.objects.all(), fields=("start_address", "end_address", "vrf")
)
validator(data, self)

else:
validator = UniqueTogetherValidator(
queryset=models.IPRange.objects.filter(vrf__isnull=True), fields=("start_address", "end_address")
)
validator(data, self)

# Enforce model validation
super().validate(data)

return data


class FQDNSerializer(NautobotModelSerializer):
"""FQDN Serializer."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Generated by Django 4.2.14 on 2024-08-06 00:36

from django.db import migrations
import django.db.models.deletion
import nautobot.extras.models.statuses
import nautobot_firewall_models.utils


class Migration(migrations.Migration):
dependencies = [
("extras", "0113_saved_views"),
glennmatthews marked this conversation as resolved.
Show resolved Hide resolved
("nautobot_firewall_models", "0020_field_cleanups"),
]

operations = [
migrations.AlterField(
model_name="addressobject",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="addressobjectgroup",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="applicationobject",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="applicationobjectgroup",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="fqdn",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="iprange",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="natpolicy",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="natpolicyrule",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="policy",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="policyrule",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="serviceobject",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="serviceobjectgroup",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="userobject",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="userobjectgroup",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
migrations.AlterField(
model_name="zone",
name="status",
field=nautobot.extras.models.statuses.StatusField(
default=nautobot_firewall_models.utils.get_default_status,
on_delete=django.db.models.deletion.PROTECT,
related_name="%(app_label)s_%(class)s_related",
to="extras.status",
),
),
]
69 changes: 67 additions & 2 deletions nautobot_firewall_models/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Unit tests for API views."""

# flake8: noqa: F403,405
# pylint: disable=invalid-name
# pylint: disable=duplicate-code
from nautobot.apps.testing import APIViewTestCases
from django.contrib.contenttypes.models import ContentType
from nautobot.apps.testing import APIViewTestCases, disable_warnings
from nautobot.dcim.models import Location, Platform, DeviceType, Device
from nautobot.extras.models import Status, Role
from nautobot.ipam.models import Prefix
from nautobot.ipam.models import Prefix, VRF
from nautobot.users.models import ObjectPermission
from rest_framework import status
gsnider2195 marked this conversation as resolved.
Show resolved Hide resolved

from nautobot_firewall_models import models
from . import fixtures
Expand All @@ -27,6 +31,67 @@ def setUpTestData(cls):
]
fixtures.create_ip_range()

def test_unique_validators(self):
"""Test the unique validators for IPRange."""
# Add object-level permission
obj_perm = ObjectPermission(name="Test permission", actions=["add"])
obj_perm.save()
obj_perm.users.add(self.user)
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))

vrfs = (
VRF.objects.create(name="test vrf 1"),
VRF.objects.create(name="test vrf 2"),
)

url = self._get_list_url()

# Create an IPRange object with a vrf
models.IPRange.objects.create(start_address="1.0.0.1", end_address="1.0.0.8", vrf=vrfs[0])

initial_count = self._get_queryset().count()

# Create an IPRange object with the same start and end address but a different vrf
with disable_warnings("django.request"):
data = {"start_address": "1.0.0.1", "end_address": "1.0.0.8", "vrf": vrfs[1].pk}
response = self.client.post(url, data, format="json", **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)

Choose a reason for hiding this comment

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

Suggested change
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertHttpStatus(response, rf_status.HTTP_201_CREATED)

self.assertEqual(self._get_queryset().count(), initial_count + 1)

# Creating an IPRange object with the same start and end address and the same vrf fails
with disable_warnings("django.request"):
data = {"start_address": "1.0.0.1", "end_address": "1.0.0.8", "vrf": vrfs[0].pk}
response = self.client.post(url, data, format="json", **self.header)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)

Choose a reason for hiding this comment

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

Suggested change
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
self.assertHttpStatus(response, rf_status.HTTP_400_BAD_REQUEST)

self.assertIn("non_field_errors", response.data)
self.assertEqual(
"The fields start_address, end_address, vrf must make a unique set.",
response.data["non_field_errors"][0],
)
self.assertEqual(self._get_queryset().count(), initial_count + 1)

# Create another IPRange object with no vrf
models.IPRange.objects.create(start_address="2.0.0.1", end_address="2.0.0.8")

# Creating an IPRange object with the same start and end address with no vrf fails
with disable_warnings("django.request"):
data = {"start_address": "2.0.0.1", "end_address": "2.0.0.8"}
response = self.client.post(url, data, format="json", **self.header)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
gsnider2195 marked this conversation as resolved.
Show resolved Hide resolved
self.assertIn("non_field_errors", response.data)
self.assertEqual(
"The fields start_address, end_address must make a unique set.",
response.data["non_field_errors"][0],
)
self.assertEqual(self._get_queryset().count(), initial_count + 2)

# Create an IPRange object with the same start and end address with a vrf
with disable_warnings("django.request"):
data = {"start_address": "2.0.0.1", "end_address": "2.0.0.8", "vrf": vrfs[0].pk}
response = self.client.post(url, data, format="json", **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
gsnider2195 marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(self._get_queryset().count(), initial_count + 3)


class FQDNAPIViewTest(APIViewTestCases.APIViewTestCase):
"""Test the Protocol viewsets."""
Expand Down
Loading