Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
50ad58f
feat(models): Add generic source/destination fields for ACL rules
pheus Aug 7, 2025
a92f2e9
feat(migrations): Update ACL rule migrations for source/destination
pheus Aug 7, 2025
d877eb4
feat(migrations): Add prefix assignment migration for ACL rules
pheus Aug 8, 2025
7387f20
feat(tests): Add generic object tests for ACL rules models
pheus Aug 9, 2025
f2a1dc8
feat(tables): Add generic src and dst columns for ACL rules
pheus Aug 9, 2025
3fcafb8
feat(filtersets): Extend filter support for ACL source/destination
pheus Aug 10, 2025
6924365
feat(forms): Add generic source and destination fields for ACL rules
pheus Aug 10, 2025
0df9b01
feat(templates): Update source/destination field labels
pheus Aug 10, 2025
2f1c747
feat(views): Update prefetch fields for ACL rules
pheus Aug 10, 2025
52a8f13
feat(serializers): Add generic source/destination support
pheus Aug 13, 2025
9790043
feat(views): Improve view docstrings and adjust prefetch fields
pheus Aug 13, 2025
dcf96d0
feat(graphql): Add generic source/destination field support
pheus Aug 14, 2025
6647390
feat(graphql): Add ContentType filters for ACL rules
pheus Aug 14, 2025
2b2f628
feat(tests): Update ACL rule tests for generic fields
pheus Aug 15, 2025
3732819
fix(choices): Correct typos in docstrings for ACL choices
pheus Aug 18, 2025
5b8390a
fix(filtersets): Update filter fields for ACL source/destination
pheus Aug 23, 2025
1fc9b60
feat(filtersets): Add remark and port filters for ACL rules
pheus Aug 23, 2025
dc69807
fix(migrations): Ensure database alias is used in ACL rule updates
pheus Aug 30, 2025
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
121 changes: 92 additions & 29 deletions netbox_acls/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@

from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from ipam.api.serializers import PrefixSerializer
from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer
from rest_framework import serializers
from utilities.api import get_serializer_for_model

from ..constants import ACL_HOST_ASSIGNMENT_MODELS, ACL_INTERFACE_ASSIGNMENT_MODELS
from ..constants import (
ACL_HOST_ASSIGNMENT_MODELS,
ACL_INTERFACE_ASSIGNMENT_MODELS,
ACL_RULE_SOURCE_DESTINATION_MODELS,
)
from ..models import (
AccessList,
ACLExtendedRule,
Expand All @@ -28,8 +31,8 @@

# Sets a standard error message for ACL rules with an action of remark, but no remark set.
error_message_no_remark = "Action is set to remark, you MUST add a remark."
# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set.
error_message_action_remark_source_prefix_set = "Action is set to remark, Source Prefix CANNOT be set."
# Sets a standard error message for ACL rules with an action of remark, but no source is set.
error_message_action_remark_source_set = "Action is set to remark, Source CANNOT be set."
# Sets a standard error message for ACL rules with an action not set to remark, but no remark is set.
error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark."
# Sets a standard error message for ACL rules no associated with an ACL of the same type.
Expand Down Expand Up @@ -186,12 +189,18 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer):
view_name="plugins-api:netbox_acls-api:aclstandardrule-detail",
)
access_list = AccessListSerializer(nested=True, required=True)
source_prefix = PrefixSerializer(
nested=True,
source_type = ContentTypeField(
queryset=ContentType.objects.filter(ACL_RULE_SOURCE_DESTINATION_MODELS),
required=False,
default=None,
allow_null=True,
)
source_id = serializers.IntegerField(
required=False,
default=None,
allow_null=True,
)
source = serializers.SerializerMethodField(read_only=True)

class Meta:
"""
Expand All @@ -207,20 +216,36 @@ class Meta:
"index",
"action",
"remark",
"source_prefix",
"source_type",
"source_id",
"source",
"description",
"tags",
"created",
"custom_fields",
"last_updated",
)
brief_fields = ("id", "url", "display", "access_list", "index")
brief_fields = (
"id",
"url",
"display",
"access_list",
"index",
)

@extend_schema_field(serializers.JSONField(allow_null=True))
def get_source(self, obj):
if obj.source_id is None:
return None
serializer = get_serializer_for_model(obj.source)
context = {"request": self.context["request"]}
return serializer(obj.source, nested=True, context=context).data

def validate(self, data):
"""
Validate the ACLStandardRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set.
- Check if action set to remark, but source set.
"""
error_message = {}

Expand All @@ -230,10 +255,10 @@ def validate(self, data):
error_message["remark"] = [
error_message_no_remark,
]
# Check if action set to remark, but source_prefix set.
if data.get("source_prefix"):
error_message["source_prefix"] = [
error_message_action_remark_source_prefix_set,
# Check if action set to remark, but the source set.
if data.get("source"):
error_message["source"] = [
error_message_action_remark_source_set,
]

if error_message:
Expand All @@ -251,18 +276,30 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer):
view_name="plugins-api:netbox_acls-api:aclextendedrule-detail",
)
access_list = AccessListSerializer(nested=True, required=True)
source_prefix = PrefixSerializer(
nested=True,
source_type = ContentTypeField(
queryset=ContentType.objects.filter(ACL_RULE_SOURCE_DESTINATION_MODELS),
required=False,
default=None,
allow_null=True,
)
source_id = serializers.IntegerField(
required=False,
default=None,
allow_null=True,
)
destination_prefix = PrefixSerializer(
nested=True,
source = serializers.SerializerMethodField(read_only=True)
destination_type = ContentTypeField(
queryset=ContentType.objects.filter(ACL_RULE_SOURCE_DESTINATION_MODELS),
required=False,
default=None,
allow_null=True,
)
destination_id = serializers.IntegerField(
required=False,
default=None,
allow_null=True,
)
destination = serializers.SerializerMethodField(read_only=True)

class Meta:
"""
Expand All @@ -279,25 +316,51 @@ class Meta:
"action",
"remark",
"protocol",
"source_prefix",
"source_type",
"source_id",
"source",
"source_ports",
"destination_prefix",
"destination_type",
"destination_id",
"destination",
"destination_ports",
"description",
"tags",
"created",
"custom_fields",
"last_updated",
)
brief_fields = ("id", "url", "display", "access_list", "index")
brief_fields = (
"id",
"url",
"display",
"access_list",
"index",
)

@extend_schema_field(serializers.JSONField(allow_null=True))
def get_source(self, obj):
if obj.source_id is None:
return None
serializer = get_serializer_for_model(obj.source)
context = {"request": self.context["request"]}
return serializer(obj.source, nested=True, context=context).data

@extend_schema_field(serializers.JSONField(allow_null=True))
def get_destination(self, obj):
if obj.destination_id is None:
return None
serializer = get_serializer_for_model(obj.destination)
context = {"request": self.context["request"]}
return serializer(obj.destination, nested=True, context=context).data

def validate(self, data):
"""
Validate the ACLExtendedRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set.
- Check if action set to remark, but source set.
- Check if action set to remark, but source_ports set.
- Check if action set to remark, but destination_prefix set.
- Check if action set to remark, but destination set.
- Check if action set to remark, but destination_ports set.
- Check if action set to remark, but protocol set.
- Check if action set to remark, but protocol set.
Expand All @@ -310,19 +373,19 @@ def validate(self, data):
error_message["remark"] = [
error_message_no_remark,
]
# Check if action set to remark, but source_prefix set.
if data.get("source_prefix"):
error_message["source_prefix"] = [
error_message_action_remark_source_prefix_set,
# Check if action set to remark, but the source set.
if data.get("source"):
error_message["source"] = [
error_message_action_remark_source_set,
]
# Check if action set to remark, but source_ports set.
if data.get("source_ports"):
error_message["source_ports"] = [
"Action is set to remark, Source Ports CANNOT be set.",
]
# Check if action set to remark, but destination_prefix set.
if data.get("destination_prefix"):
error_message["destination_prefix"] = [
# Check if action set to remark, but destination set.
if data.get("destination"):
error_message["destination"] = [
"Action is set to remark, Destination Prefix CANNOT be set.",
]
# Check if action set to remark, but destination_ports set.
Expand Down
14 changes: 7 additions & 7 deletions netbox_acls/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

class AccessListViewSet(NetBoxModelViewSet):
"""
Defines the view set for the django AccessList model & associates it to a view.
Defines the view set for the django AccessList model and associates it with a view.
"""

queryset = (
Expand All @@ -41,7 +41,7 @@ class AccessListViewSet(NetBoxModelViewSet):

class ACLInterfaceAssignmentViewSet(NetBoxModelViewSet):
"""
Defines the view set for the django ACLInterfaceAssignment model & associates it to a view.
Defines the view set for the django ACLInterfaceAssignment model and associates it with a view.
"""

queryset = models.ACLInterfaceAssignment.objects.prefetch_related(
Expand All @@ -54,28 +54,28 @@ class ACLInterfaceAssignmentViewSet(NetBoxModelViewSet):

class ACLStandardRuleViewSet(NetBoxModelViewSet):
"""
Defines the view set for the django ACLStandardRule model & associates it to a view.
Defines the view set for the django ACLStandardRule model and associates it with a view.
"""

queryset = models.ACLStandardRule.objects.prefetch_related(
"access_list",
"source",
"tags",
"source_prefix",
)
serializer_class = ACLStandardRuleSerializer
filterset_class = filtersets.ACLStandardRuleFilterSet


class ACLExtendedRuleViewSet(NetBoxModelViewSet):
"""
Defines the view set for the django ACLExtendedRule model & associates it to a view.
Defines the view set for the django ACLExtendedRule model and associates it with a view.
"""

queryset = models.ACLExtendedRule.objects.prefetch_related(
"access_list",
"source",
"destination",
"tags",
"source_prefix",
"destination_prefix",
)
serializer_class = ACLExtendedRuleSerializer
filterset_class = filtersets.ACLExtendedRuleFilterSet
8 changes: 4 additions & 4 deletions netbox_acls/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class ACLActionChoices(ChoiceSet):
"""
Defines the choices availble for the Access Lists plugin specific to ACL default_action.
Defines the choices available for the Access Lists plugin specific to ACL default_action.
"""

ACTION_DENY = "deny"
Expand All @@ -32,7 +32,7 @@ class ACLActionChoices(ChoiceSet):

class ACLRuleActionChoices(ChoiceSet):
"""
Defines the choices availble for the Access Lists plugin specific to ACL rule actions.
Defines the choices available for the Access Lists plugin specific to ACL rule actions.
"""

ACTION_DENY = "deny"
Expand Down Expand Up @@ -62,7 +62,7 @@ class ACLAssignmentDirectionChoices(ChoiceSet):

class ACLTypeChoices(ChoiceSet):
"""
Defines the choices availble for the Access Lists plugin specific to ACL type.
Defines the choices available for the Access Lists plugin specific to ACL type.
"""

TYPE_STANDARD = "standard"
Expand All @@ -76,7 +76,7 @@ class ACLTypeChoices(ChoiceSet):

class ACLProtocolChoices(ChoiceSet):
"""
Defines the choices availble for the Access Lists plugin specific to ACL Rule protocol.
Defines the choices available for the Access Lists plugin specific to ACL Rule protocol.
"""

PROTOCOL_ICMP = "icmp"
Expand Down
25 changes: 25 additions & 0 deletions netbox_acls/constants.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
"""
Constants for filters
"""

from django.db.models import Q

#
# AccessList
#

ACL_HOST_ASSIGNMENT_MODELS = Q(
Q(app_label="dcim", model="device")
| Q(app_label="dcim", model="virtualchassis")
| Q(app_label="virtualization", model="virtualmachine"),
)

#
# ACLInterfaceAssignment
#

ACL_INTERFACE_ASSIGNMENT_MODELS = Q(
Q(app_label="dcim", model="interface") | Q(app_label="virtualization", model="vminterface"),
)

#
# AccessList Rule
#

ACL_RULE_SOURCE_DESTINATION_MODELS = Q(
Q(
app_label="ipam",
model__in=(
"aggregate",
"ipaddress",
"iprange",
"prefix",
),
)
)
Loading