From e18ecee2b8d93d2981f0e6d52957b83b473881fe Mon Sep 17 00:00:00 2001 From: Jeremy White Date: Fri, 26 Aug 2022 17:19:07 -0500 Subject: [PATCH 1/8] initial b4 refactor --- CHANGELOG.md | 20 +- README.md | 2 +- docs/index.md | 2 +- docs/models/policyrule.md | 7 +- docs/models/policyrulem2m.md | 16 - nautobot_firewall_models/__init__.py | 2 +- nautobot_firewall_models/api/serializers.py | 91 +- nautobot_firewall_models/api/views.py | 23 +- nautobot_firewall_models/forms.py | 126 +- .../migrations/0006_renaming_part1.py | 154 ++ .../migrations/0007_renaming_part2.py | 55 + .../migrations/0008_renaming_part3.py | 29 + nautobot_firewall_models/models/__init__.py | 12 +- .../models/core_models.py | 46 +- .../models/through_models.py | 32 +- nautobot_firewall_models/tables.py | 18 +- .../assign_policy_rule_index.html | 25 - .../{ => inc}/policy_expanded_rules.html | 0 .../inc/policy_rules_tablerow.html | 11 +- .../inc/policy_rules_tablerow_edit.html | 16 - .../inc/policyrule_tablehead.html | 7 +- .../inc/policyrule_tablerow.html | 9 +- .../nautobot_firewall_models/policy.html | 12 +- .../nautobot_firewall_models/policyrule.html | 4 +- nautobot_firewall_models/tests/fixtures.py | 68 +- nautobot_firewall_models/tests/test_api.py | 28 +- .../tests/test_capirca.py | 82 +- nautobot_firewall_models/tests/test_models.py | 30 +- .../tests/test_ui_views.py | 7 +- nautobot_firewall_models/urls.py | 73 +- nautobot_firewall_models/utils/capirca.py | 30 +- nautobot_firewall_models/views/policy.py | 36 +- poetry.lock | 1284 ++++++++++++++--- pyproject.toml | 2 +- tasks.py | 2 +- 35 files changed, 1748 insertions(+), 613 deletions(-) delete mode 100644 docs/models/policyrulem2m.md create mode 100644 nautobot_firewall_models/migrations/0006_renaming_part1.py create mode 100644 nautobot_firewall_models/migrations/0007_renaming_part2.py create mode 100644 nautobot_firewall_models/migrations/0008_renaming_part3.py delete mode 100644 nautobot_firewall_models/templates/nautobot_firewall_models/assign_policy_rule_index.html rename nautobot_firewall_models/templates/nautobot_firewall_models/{ => inc}/policy_expanded_rules.html (100%) delete mode 100644 nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow_edit.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e37a96..b3bd553d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,43 @@ # Changelog +## v0.1.0-beta.4 - 2022-08-XX + +### Changed + +- #XX Dropped support for Nautobot < v1.4.0 + +### Added + +- #XX Added support for Notes + ## v0.1.0-beta.3 - 2022-07-19 ### Changed + - #68 Update Policy Rules Expanded to be more intuitive - #69 Change to use arrow in UI elements ### Added + - #63 Capirca Integration + ## v0.1.0-beta.2 - 2022-07-10 ### Changed + - Update Serializers to current standards - Update development environment to current standards - Update CI matrix for better runtime & coverage of versions ### Fixed + - Pydocstyle.ini being properly used - Dockerfile to work with `NAUTOBOT_VER` from build args, previously poetry superceeded the build arg. - Updates for `status` attributes to account for defaulting. - Static URLs for images in `README.md` to fix broken link in PyPI page rendering. ### Added + - Ability to expand Policy detail API view with query param `deep=True`. - Added `to_json` method on `Policy` and `PolicyRule` models. - Added `rule_details` as a helper for working with a `PolicyRule` @@ -31,9 +47,11 @@ ## v0.1.0-beta.1 - 2022-07-08 ### Fixed + - Issues with docs rendering in PyPI & ReadTheDocs ## v0.1.0-beta.2 - 2022-07-08 ### Announcements -- Initial Release \ No newline at end of file + +- Initial Release diff --git a/README.md b/README.md index 2b43de00..f58b59f8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The plugin is available as a Python package in PyPI and can be installed with `p pip install nautobot-firewall-models ``` -> The plugin is compatible with Nautobot 1.3.0 and higher +> The plugin is compatible with Nautobot 1.4.0 and higher To ensure Nautobot Firewall Models Plugin is automatically re-installed during future upgrades, create a file named `local_requirements.txt` (if not already existing) in the Nautobot root directory (alongside `requirements.txt`) and list the `nautobot-firewall-models` package: diff --git a/docs/index.md b/docs/index.md index 77e6f9dd..85e2cca6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ The plugin is available as a Python package in PyPI and can be installed with `p pip install nautobot-firewall-models ``` -> The plugin is compatible with Nautobot 1.3.0 and higher +> The plugin is compatible with Nautobot 1.4.0 and higher To ensure Nautobot Firewall Models Plugin is automatically re-installed during future upgrades, create a file named `local_requirements.txt` (if not already existing) in the Nautobot root directory (alongside `requirements.txt`) and list the `nautobot-firewall-models` package: diff --git a/docs/models/policyrule.md b/docs/models/policyrule.md index fc760bc6..a0d0f55b 100644 --- a/docs/models/policyrule.md +++ b/docs/models/policyrule.md @@ -22,7 +22,12 @@ It is recommended to use a descriptive name to best identify a PolicyRule, other * Action (string, choice of deny drop allow) * Log (boolean) * Request ID (optional, string) - * Meant to represent an upstream request (e.g. an service request from an ITSM solution). + * Meant to represent an upstream request (e.g. an service request from an ITSM solution). +* Index (optional, int) + * Sets the index of the PolicyRule in the Policy. + * Example `20 permit icmp host 1.1.1.1 any` would have an index of `20`. + * Set as optional for now, will be set to required at a later date with default as the highest value + 10. + * Uniqueness does not apply when not set. ## Examples diff --git a/docs/models/policyrulem2m.md b/docs/models/policyrulem2m.md deleted file mode 100644 index dd231882..00000000 --- a/docs/models/policyrulem2m.md +++ /dev/null @@ -1,16 +0,0 @@ -# PolicyRuleM2M - -Allows for creating an index value that is only relevant to the relationship, this allows for a Policy Rule to potentially be used multiple times across multiple Policies. - -This model is not directly exposed to the user but can be accessed via the Policy object, and the index value is set in the Policy detail view. - -## Attributes - -* Index (optional, int) - * Sets the index of the PolicyRule in the Policy. - * Example `20 permit icmp host 1.1.1.1 any` would have an index of `20`. - * Must be unique. - * Set as optional for now, will be set to required at a later date with default as the highest value + 10. - * Uniqueness does not apply when not set. -* Policy (FK to Policy) -* Policy Rules (FK to PolicyRule) diff --git a/nautobot_firewall_models/__init__.py b/nautobot_firewall_models/__init__.py index 9d37c98e..a6cf3b07 100644 --- a/nautobot_firewall_models/__init__.py +++ b/nautobot_firewall_models/__init__.py @@ -21,7 +21,7 @@ class NautobotFirewallModelsConfig(PluginConfig): description = "Nautobot plugin to model firewall objects.." base_url = "firewall" required_settings = [] - min_version = "1.3.0" + min_version = "1.4.0" max_version = "1.9999" default_settings = {"capirca_remark_pass": True, "capirca_os_map": {}, "allowed_status": ["active"]} caching_config = {"*": {"timeout": 0}} diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py index 5126db4a..26e388da 100644 --- a/nautobot_firewall_models/api/serializers.py +++ b/nautobot_firewall_models/api/serializers.py @@ -10,6 +10,7 @@ from nautobot.extras.api.serializers import ( StatusModelSerializerMixin as _StatusModelSerializerMixin, TaggedObjectSerializer, + NautobotModelSerializer, ) from nautobot.extras.models import DynamicGroup, Status from nautobot.ipam.models import IPAddress @@ -30,9 +31,7 @@ class StatusModelSerializerMixin(_StatusModelSerializerMixin): # pylint: disabl status = StatusSerializerField(queryset=Status.objects.all(), required=False) -class IPRangeSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class IPRangeSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """IPRange Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:iprange-detail") @@ -46,9 +45,7 @@ class Meta: fields = "__all__" -class FQDNSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class FQDNSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """FQDN Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:fqdn-detail") @@ -63,9 +60,7 @@ class Meta: fields = "__all__" -class AddressObjectSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class AddressObjectSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """AddressObject Serializer.""" url = serializers.HyperlinkedIdentityField( @@ -83,9 +78,7 @@ class Meta: fields = "__all__" -class AddressObjectGroupSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class AddressObjectGroupSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """AddressObjectGroup Serializer.""" url = serializers.HyperlinkedIdentityField( @@ -102,9 +95,7 @@ class Meta: fields = "__all__" -class ServiceObjectSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class ServiceObjectSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """ServiceObject Serializer.""" url = serializers.HyperlinkedIdentityField( @@ -118,9 +109,7 @@ class Meta: fields = "__all__" -class ServiceObjectGroupSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class ServiceObjectGroupSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """ServiceObjectGroup Serializer.""" url = serializers.HyperlinkedIdentityField( @@ -139,9 +128,7 @@ class Meta: fields = "__all__" -class UserObjectSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class UserObjectSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """UserObject Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:userobject-detail") @@ -153,9 +140,7 @@ class Meta: fields = "__all__" -class UserObjectGroupSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class UserObjectGroupSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """UserObjectGroup Serializer.""" url = serializers.HyperlinkedIdentityField( @@ -172,9 +157,7 @@ class Meta: fields = "__all__" -class ZoneSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class ZoneSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """Zone Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:zone-detail") @@ -187,42 +170,40 @@ class Meta: fields = "__all__" -class PolicyRuleSerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class PolicyRuleSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """PolicyRule Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:policyrule-detail") - source_user = SerializedPKRelatedField( + source_users = SerializedPKRelatedField( queryset=models.UserObject.objects.all(), serializer=UserObjectSerializer, required=False, many=True ) - source_user_group = SerializedPKRelatedField( + source_user_groups = SerializedPKRelatedField( queryset=models.UserObjectGroup.objects.all(), serializer=UserObjectGroupSerializer, required=False, many=True ) - source_address = SerializedPKRelatedField( + source_addresses = SerializedPKRelatedField( queryset=models.AddressObject.objects.all(), serializer=AddressObjectSerializer, required=False, many=True ) - source_address_group = SerializedPKRelatedField( + source_address_groups = SerializedPKRelatedField( queryset=models.AddressObjectGroup.objects.all(), serializer=AddressObjectGroupSerializer, required=False, many=True, ) source_zone = ZoneSerializer(required=False) - destination_address = SerializedPKRelatedField( + destination_addresses = SerializedPKRelatedField( queryset=models.AddressObject.objects.all(), serializer=AddressObjectSerializer, required=False, many=True ) - destination_address_group = SerializedPKRelatedField( + destination_address_groups = SerializedPKRelatedField( queryset=models.AddressObjectGroup.objects.all(), serializer=AddressObjectGroupSerializer, required=False, many=True, ) destination_zone = ZoneSerializer(required=False) - service = SerializedPKRelatedField( + destination_services = SerializedPKRelatedField( queryset=models.ServiceObject.objects.all(), serializer=ServiceObjectSerializer, required=False, many=True ) - service_group = SerializedPKRelatedField( + destination_service_groups = SerializedPKRelatedField( queryset=models.ServiceObjectGroup.objects.all(), serializer=ServiceObjectGroupSerializer, required=False, @@ -243,7 +224,7 @@ class Meta: """Meta attributes.""" model = models.PolicyRuleM2M - fields = ["rule", "index"] + fields = ["rule"] class PolicyRuleM2MDeepNestedSerializer(PolicyRuleM2MNestedSerializer): @@ -272,13 +253,10 @@ class Meta: fields = ["dynamic_group", "weight"] -class PolicySerializer( - TaggedObjectSerializer, StatusModelSerializerMixin, CustomFieldModelSerializer, ValidatedModelSerializer -): +class PolicySerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer): """Policy Serializer.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:policy-detail") - policy_rules = PolicyRuleM2MNestedSerializer(many=True, required=False, source="policyrulem2m_set") assigned_devices = PolicyDeviceM2MNestedSerializer(many=True, required=False, source="policydevicem2m_set") assigned_dynamic_groups = PolicyDynamicGroupM2MNestedSerializer( many=True, required=False, source="policydynamicgroupm2m_set" @@ -292,13 +270,10 @@ class Meta: def create(self, validated_data): """Overload create to account for custom m2m field.""" - policy_rules = validated_data.pop("policyrulem2m_set", None) assigned_devices = validated_data.pop("policydevicem2m_set", None) assigned_dynamic_groups = validated_data.pop("policydynamicgroupm2m_set", None) instance = super().create(validated_data) - if policy_rules is not None: - return self._save_policy_rules(instance, policy_rules) if assigned_devices is not None: return self._save_assigned_devices(instance, assigned_devices) if assigned_dynamic_groups is not None: @@ -308,14 +283,11 @@ def create(self, validated_data): def update(self, instance, validated_data): """Overload create to account for update m2m field.""" - policy_rules = validated_data.pop("policyrulem2m_set", None) assigned_devices = validated_data.pop("policydevicem2m_set", None) assigned_dynamic_groups = validated_data.pop("policydynamicgroupm2m_set", None) instance = super().update(instance, validated_data) - if policy_rules is not None: - return self._save_policy_rules(instance, policy_rules) if assigned_devices is not None: return self._save_assigned_devices(instance, assigned_devices) if assigned_dynamic_groups is not None: @@ -323,19 +295,6 @@ def update(self, instance, validated_data): return instance - def _save_policy_rules(self, instance, policy_rules): - # pylint: disable=R0201 - """Helper function for custom m2m field.""" - instance.policy_rules.clear() - for p_r in policy_rules: - models.PolicyRuleM2M.objects.create( - rule=models.PolicyRule.objects.get(id=p_r["rule"].id), - index=p_r.get("index", None), - policy=instance, - ) - - return instance - def _save_assigned_devices(self, instance, assigned_devices): # pylint: disable=R0201 """Helper function for custom m2m field.""" @@ -357,7 +316,7 @@ def _save_assigned_dynamic_groups(self, instance, assigned_dynamic_groups): for d_g in assigned_dynamic_groups: models.PolicyDynamicGroupM2M.objects.create( dynamic_group=DynamicGroup.objects.get(id=d_g["dynamic_group"].id), - index=d_g.get("weight", None), + weight=d_g.get("weight", None), policy=instance, ) @@ -368,7 +327,7 @@ def validate(self, data): """Overload validate to pop field for custom m2m relationship.""" # Remove custom fields data and tags (if any) prior to model validation attrs = data.copy() - attrs.pop("policyrulem2m_set", None) + # attrs.pop("policyrulem2m_set", None) attrs.pop("policydevicem2m_set", None) attrs.pop("policydynamicgroupm2m_set", None) super().validate(attrs) @@ -378,7 +337,9 @@ def validate(self, data): class PolicyDeepSerializer(PolicySerializer): """Overload for create & update views.""" - policy_rules = PolicyRuleM2MDeepNestedSerializer(many=True, required=False, source="policyrulem2m_set") + policy_rules = SerializedPKRelatedField( + queryset=models.PolicyRule.objects.all(), serializer=PolicyRuleSerializer, required=False, many=True + ) class CapircaPolicySerializer(TaggedObjectSerializer, CustomFieldModelSerializer, ValidatedModelSerializer): diff --git a/nautobot_firewall_models/api/views.py b/nautobot_firewall_models/api/views.py index 76bed2c9..aab15598 100644 --- a/nautobot_firewall_models/api/views.py +++ b/nautobot_firewall_models/api/views.py @@ -2,12 +2,13 @@ from nautobot.core.api.views import ModelViewSet from nautobot.core.settings_funcs import is_truthy +from nautobot.extras.api.views import NautobotModelViewSet from nautobot_firewall_models import filters, models from nautobot_firewall_models.api import serializers -class IPRangeViewSet(ModelViewSet): +class IPRangeViewSet(NautobotModelViewSet): """IPRange viewset.""" queryset = models.IPRange.objects.all() @@ -15,7 +16,7 @@ class IPRangeViewSet(ModelViewSet): filterset_class = filters.IPRangeFilterSet -class FQDNViewSet(ModelViewSet): +class FQDNViewSet(NautobotModelViewSet): """FQDN viewset.""" queryset = models.FQDN.objects.all() @@ -23,7 +24,7 @@ class FQDNViewSet(ModelViewSet): filterset_class = filters.FQDNFilterSet -class AddressObjectViewSet(ModelViewSet): +class AddressObjectViewSet(NautobotModelViewSet): """AddressObject viewset.""" queryset = models.AddressObject.objects.all() @@ -31,7 +32,7 @@ class AddressObjectViewSet(ModelViewSet): filterset_class = filters.AddressObjectFilterSet -class AddressObjectGroupViewSet(ModelViewSet): +class AddressObjectGroupViewSet(NautobotModelViewSet): """AddressObjectGroup viewset.""" queryset = models.AddressObjectGroup.objects.all() @@ -39,7 +40,7 @@ class AddressObjectGroupViewSet(ModelViewSet): filterset_class = filters.AddressObjectGroupFilterSet -class ServiceObjectViewSet(ModelViewSet): +class ServiceObjectViewSet(NautobotModelViewSet): """ServiceObject viewset.""" queryset = models.ServiceObject.objects.all() @@ -47,7 +48,7 @@ class ServiceObjectViewSet(ModelViewSet): filterset_class = filters.ServiceObjectFilterSet -class ServiceObjectGroupViewSet(ModelViewSet): +class ServiceObjectGroupViewSet(NautobotModelViewSet): """ServiceObjectGroup viewset.""" queryset = models.ServiceObjectGroup.objects.all() @@ -55,7 +56,7 @@ class ServiceObjectGroupViewSet(ModelViewSet): filterset_class = filters.ServiceObjectGroupFilterSet -class UserObjectViewSet(ModelViewSet): +class UserObjectViewSet(NautobotModelViewSet): """UserObject viewset.""" queryset = models.UserObject.objects.all() @@ -63,7 +64,7 @@ class UserObjectViewSet(ModelViewSet): filterset_class = filters.UserObjectFilterSet -class UserObjectGroupViewSet(ModelViewSet): +class UserObjectGroupViewSet(NautobotModelViewSet): """UserObjectGroup viewset.""" queryset = models.UserObjectGroup.objects.all() @@ -71,7 +72,7 @@ class UserObjectGroupViewSet(ModelViewSet): filterset_class = filters.UserObjectGroupFilterSet -class ZoneViewSet(ModelViewSet): +class ZoneViewSet(NautobotModelViewSet): """Zone viewset.""" queryset = models.Zone.objects.all() @@ -79,7 +80,7 @@ class ZoneViewSet(ModelViewSet): filterset_class = filters.ZoneFilterSet -class PolicyRuleViewSet(ModelViewSet): +class PolicyRuleViewSet(NautobotModelViewSet): """PolicyRule viewset.""" queryset = models.PolicyRule.objects.all() @@ -87,7 +88,7 @@ class PolicyRuleViewSet(ModelViewSet): filterset_class = filters.PolicyRuleFilterSet -class PolicyViewSet(ModelViewSet): +class PolicyViewSet(NautobotModelViewSet): """Policy viewset.""" queryset = models.Policy.objects.all() diff --git a/nautobot_firewall_models/forms.py b/nautobot_firewall_models/forms.py index f110a4ba..d6d0ca5c 100644 --- a/nautobot_firewall_models/forms.py +++ b/nautobot_firewall_models/forms.py @@ -3,14 +3,14 @@ from django import forms from nautobot.dcim.models import Interface, Device from nautobot.extras.forms import ( - AddRemoveTagsForm, - StatusFilterFormMixin, - StatusBulkEditFormMixin, - CustomFieldFilterForm, - CustomFieldBulkEditForm, + TagsBulkEditFormMixin, + StatusModelFilterFormMixin, + StatusModelBulkEditFormMixin, + CustomFieldModelFilterFormMixin, + CustomFieldModelBulkEditFormMixin, CustomFieldModelCSVForm, - CustomFieldModelForm, - RelationshipModelForm, + CustomFieldModelFormMixin, + RelationshipModelFormMixin, ) from nautobot.extras.models import Tag, DynamicGroup from nautobot.ipam.models import VRF, Prefix, IPAddress @@ -28,7 +28,7 @@ from nautobot_firewall_models import models, fields, choices -class IPRangeFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class IPRangeFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "start_address", "end_address", "vrf"] @@ -44,7 +44,7 @@ class IPRangeFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilter vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) -class IPRangeForm(BootstrapMixin, fields.IPRangeFieldMixin, forms.ModelForm): +class IPRangeForm(BootstrapMixin, fields.IPRangeFieldMixin, RelationshipModelFormMixin, forms.ModelForm): """IPRange creation/edit form.""" vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) @@ -56,7 +56,7 @@ class Meta: fields = ["vrf", "description", "status", "tags"] -class IPRangeBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class IPRangeBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """IPRange bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.IPRange.objects.all(), widget=forms.MultipleHiddenInput) @@ -71,7 +71,7 @@ class Meta: nullable_fields = ["description", "vrf"] -class FQDNFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class FQDNFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -85,7 +85,7 @@ class FQDNFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterFor name = forms.CharField(required=False, label="Name") -class FQDNForm(BootstrapMixin, forms.ModelForm): +class FQDNForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """FQDN creation/edit form.""" ip_addresses = DynamicModelMultipleChoiceField(queryset=IPAddress.objects.all(), required=False) @@ -97,7 +97,7 @@ class Meta: fields = ["name", "description", "ip_addresses", "status", "tags"] -class FQDNBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class FQDNBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """FQDN bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.FQDN.objects.all(), widget=forms.MultipleHiddenInput) @@ -110,7 +110,7 @@ class Meta: nullable_fields = ["description", "ip_addresses"] -class AddressObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class AddressObjectFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -128,7 +128,7 @@ class AddressObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomField fqdn = DynamicModelChoiceField(queryset=models.FQDN.objects.all(), required=False, label="FQDN") -class AddressObjectForm(BootstrapMixin, forms.ModelForm): +class AddressObjectForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """AddressObject creation/edit form.""" ip_address = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="IP Address") @@ -143,7 +143,7 @@ class Meta: fields = ["name", "description", "fqdn", "ip_range", "ip_address", "prefix", "status", "tags"] -class AddressObjectBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class AddressObjectBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """AddressObject bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all(), widget=forms.MultipleHiddenInput) @@ -155,7 +155,7 @@ class Meta: nullable_fields = ["description", "fqdn", "ip_range", "ip_address", "prefix"] -class AddressObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class AddressObjectGroupFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -169,7 +169,7 @@ class AddressObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, Custom name = forms.CharField(required=False, label="Name") -class AddressObjectGroupForm(BootstrapMixin, forms.ModelForm): +class AddressObjectGroupForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """AddressObjectGroup creation/edit form.""" address_objects = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all()) @@ -181,7 +181,7 @@ class Meta: fields = ["name", "description", "address_objects", "status", "tags"] -class AddressObjectGroupBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class AddressObjectGroupBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """AddressObjectGroup bulk edit form.""" pk = DynamicModelMultipleChoiceField( @@ -197,7 +197,7 @@ class Meta: ] -class ServiceObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class ServiceObjectFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -213,7 +213,7 @@ class ServiceObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomField ip_protocol = forms.ChoiceField(choices=add_blank_choice(choices.IP_PROTOCOL_CHOICES), required=False) -class ServiceObjectForm(BootstrapMixin, forms.ModelForm): +class ServiceObjectForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """ServiceObject creation/edit form.""" port = forms.CharField( @@ -228,7 +228,7 @@ class Meta: fields = ["name", "description", "port", "ip_protocol", "status", "tags"] -class ServiceObjectBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class ServiceObjectBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """ServiceObject bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), widget=forms.MultipleHiddenInput) @@ -244,7 +244,7 @@ class Meta: nullable_fields = ["description", "port"] -class ServiceObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class ServiceObjectGroupFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -258,7 +258,7 @@ class ServiceObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, Custom name = forms.CharField(required=False, label="Name") -class ServiceObjectGroupForm(BootstrapMixin, forms.ModelForm): +class ServiceObjectGroupForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """ServiceObjectGroup creation/edit form.""" service_objects = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), required=False) @@ -270,7 +270,7 @@ class Meta: fields = ["name", "description", "service_objects", "status", "tags"] -class ServiceObjectGroupBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class ServiceObjectGroupBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """ServiceObjectGroup bulk edit form.""" pk = DynamicModelMultipleChoiceField( @@ -286,7 +286,7 @@ class Meta: ] -class UserObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class UserObjectFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "username", "name"] @@ -301,7 +301,7 @@ class UserObjectFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFil username = forms.CharField(required=False, label="Username") -class UserObjectForm(BootstrapMixin, forms.ModelForm): +class UserObjectForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """UserObject creation/edit form.""" username = forms.CharField(label="Username") @@ -317,7 +317,7 @@ class Meta: fields = ["username", "name", "status", "tags"] -class UserObjectBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class UserObjectBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """UserObject bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), widget=forms.MultipleHiddenInput) @@ -331,7 +331,7 @@ class Meta: ] -class UserObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class UserObjectGroupFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -345,7 +345,7 @@ class UserObjectGroupFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFie name = forms.CharField(required=False, label="Name") -class UserObjectGroupForm(BootstrapMixin, forms.ModelForm): +class UserObjectGroupForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """UserObjectGroup creation/edit form.""" user_objects = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), required=False) @@ -357,7 +357,7 @@ class Meta: fields = ["name", "description", "user_objects", "status", "tags"] -class UserObjectGroupBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class UserObjectGroupBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """UserObjectGroup bulk edit form.""" pk = DynamicModelMultipleChoiceField( @@ -373,7 +373,7 @@ class Meta: ] -class ZoneFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class ZoneFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -389,7 +389,7 @@ class ZoneFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterFor interfaces = DynamicModelChoiceField(queryset=Interface.objects.all(), label="Interface") -class ZoneForm(BootstrapMixin, forms.ModelForm): +class ZoneForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm): """Zone creation/edit form.""" vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False, label="VRF") @@ -405,7 +405,7 @@ class Meta: fields = ["name", "description", "vrfs", "device", "interfaces", "status", "tags"] -class ZoneBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class ZoneBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """Zone bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.Zone.objects.all(), widget=forms.MultipleHiddenInput) @@ -419,7 +419,7 @@ class Meta: nullable_fields = ["description", "vrfs", "interfaces"] -class PolicyRuleFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm): +class PolicyRuleFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin): """Filter form to filter searches.""" field_order = ["q", "name"] @@ -434,37 +434,43 @@ class PolicyRuleFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFil tag = TagFilterField(models.PolicyRule) -class PolicyRuleForm(BootstrapMixin, CustomFieldModelForm): +class PolicyRuleForm(BootstrapMixin, CustomFieldModelFormMixin, RelationshipModelFormMixin): """PolicyRule creation/edit form.""" name = forms.CharField(required=False, label="Name") tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False) - source_user = DynamicModelMultipleChoiceField( + source_users = DynamicModelMultipleChoiceField( queryset=models.UserObject.objects.all(), label="Source User Objects", required=False ) - source_user_group = DynamicModelMultipleChoiceField( + source_user_groups = DynamicModelMultipleChoiceField( queryset=models.UserObjectGroup.objects.all(), label="Source User Object Groups", required=False ) - source_address = DynamicModelMultipleChoiceField( + source_addresses = DynamicModelMultipleChoiceField( queryset=models.AddressObject.objects.all(), label="Source Address Objects", required=False ) - source_address_group = DynamicModelMultipleChoiceField( + source_address_groups = DynamicModelMultipleChoiceField( queryset=models.AddressObjectGroup.objects.all(), label="Source Address Object Groups", required=False ) source_zone = DynamicModelChoiceField(queryset=models.Zone.objects.all(), label="Source Zone", required=False) - destination_address = DynamicModelMultipleChoiceField( + source_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Service Objects", required=False + ) + source_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), label="Service Object Groups", required=False + ) + destination_addresses = DynamicModelMultipleChoiceField( queryset=models.AddressObject.objects.all(), label="Destination Address Objects", required=False ) - destination_address_group = DynamicModelMultipleChoiceField( + destination_address_groups = DynamicModelMultipleChoiceField( queryset=models.AddressObjectGroup.objects.all(), label="Destination Address Object Groups", required=False ) destination_zone = DynamicModelChoiceField( queryset=models.Zone.objects.all(), label="Destination Zone", required=False ) - service = DynamicModelMultipleChoiceField( + destination_services = DynamicModelMultipleChoiceField( queryset=models.ServiceObject.objects.all(), label="Service Objects", required=False ) - service_group = DynamicModelMultipleChoiceField( + destination_service_groups = DynamicModelMultipleChoiceField( queryset=models.ServiceObjectGroup.objects.all(), label="Service Object Groups", required=False ) request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.") @@ -476,16 +482,18 @@ class Meta: fields = ( # pylint: disable=duplicate-code "name", - "source_user", - "source_user_group", - "source_address", - "source_address_group", + "source_users", + "source_user_groups", + "source_addresses", + "source_address_groups", "source_zone", - "destination_address", - "destination_address_group", + "source_services", + "source_service_groups", + "destination_addresses", + "destination_address_groups", "destination_zone", - "service", - "service_group", + "destination_services", + "destination_service_groups", "action", "log", "status", @@ -496,7 +504,7 @@ class Meta: # TODO: Refactor -class PolicyRuleBulkEditForm(BootstrapMixin, AddRemoveTagsForm, StatusBulkEditFormMixin, BulkEditForm): +class PolicyRuleBulkEditForm(BootstrapMixin, TagsBulkEditFormMixin, StatusModelBulkEditFormMixin, BulkEditForm): """PolicyRule bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), widget=forms.MultipleHiddenInput) @@ -509,7 +517,7 @@ class Meta: nullable_fields = ["description", "tags"] -class PolicyFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterForm, TenancyFilterForm): +class PolicyFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin, TenancyFilterForm): """Filter form to filter searches.""" field_order = ["q", "name", "assigned_devices", "assigned_dynamic_groups"] @@ -525,7 +533,7 @@ class PolicyFilterForm(BootstrapMixin, StatusFilterFormMixin, CustomFieldFilterF assigned_dynamic_groups = DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False) -class PolicyForm(BootstrapMixin, forms.ModelForm, TenancyForm): +class PolicyForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm, TenancyForm): """Policy creation/edit form.""" assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) @@ -549,7 +557,7 @@ class Meta: ] -class PolicyBulkEditForm(BootstrapMixin, StatusBulkEditFormMixin, BulkEditForm): +class PolicyBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm): """Policy bulk edit form.""" pk = DynamicModelMultipleChoiceField(queryset=models.Policy.objects.all(), widget=forms.MultipleHiddenInput) @@ -570,7 +578,7 @@ class Meta: # CapircaPolicy -class CapircaPolicyForm(BootstrapMixin, CustomFieldModelForm, RelationshipModelForm): +class CapircaPolicyForm(BootstrapMixin, CustomFieldModelFormMixin, RelationshipModelFormMixin): """Filter Form for CapircaPolicy instances.""" device = DynamicModelChoiceField(queryset=Device.objects.all()) @@ -588,7 +596,7 @@ class Meta: ) -class CapircaPolicyFilterForm(BootstrapMixin, CustomFieldFilterForm): +class CapircaPolicyFilterForm(BootstrapMixin, CustomFieldModelFilterFormMixin): """Form for CapircaPolicy instances.""" model = models.CapircaPolicy @@ -596,7 +604,7 @@ class CapircaPolicyFilterForm(BootstrapMixin, CustomFieldFilterForm): q = forms.CharField(required=False, label="Search") -class CapircaPolicyBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm): +class CapircaPolicyBulkEditForm(BootstrapMixin, TagsBulkEditFormMixin, CustomFieldModelBulkEditFormMixin): """BulkEdit form for CapircaPolicy instances.""" pk = forms.ModelMultipleChoiceField(queryset=models.CapircaPolicy.objects.all(), widget=forms.MultipleHiddenInput) diff --git a/nautobot_firewall_models/migrations/0006_renaming_part1.py b/nautobot_firewall_models/migrations/0006_renaming_part1.py new file mode 100644 index 00000000..36218a59 --- /dev/null +++ b/nautobot_firewall_models/migrations/0006_renaming_part1.py @@ -0,0 +1,154 @@ +# Generated by Django 3.2.15 on 2022-08-26 18:03 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ("nautobot_firewall_models", "0005_capircapolicy"), + ] + + operations = [ + migrations.RenameModel( + old_name="SvcGroupM2M", + new_name="DestSvcGroupM2M", + ), + migrations.RenameModel( + old_name="SvcM2M", + new_name="DestSvcM2M", + ), + migrations.RenameField( + model_name="policyrule", + old_name="destination_address_group", + new_name="destination_address_groups", + ), + migrations.RenameField( + model_name="policyrule", + old_name="destination_address", + new_name="destination_addresses", + ), + migrations.RenameField( + model_name="policyrule", + old_name="source_address_group", + new_name="source_address_groups", + ), + migrations.RenameField( + model_name="policyrule", + old_name="source_address", + new_name="source_addresses", + ), + migrations.RenameField( + model_name="policyrule", + old_name="source_user_group", + new_name="source_user_groups", + ), + migrations.RenameField( + model_name="policyrule", + old_name="source_user", + new_name="source_users", + ), + migrations.RemoveField( + model_name="policyrule", + name="service", + ), + migrations.RemoveField( + model_name="policyrule", + name="service_group", + ), + migrations.AddField( + model_name="policyrule", + name="destination_service_groups", + field=models.ManyToManyField( + related_name="destination_policy_rules", + through="nautobot_firewall_models.DestSvcGroupM2M", + to="nautobot_firewall_models.ServiceObjectGroup", + ), + ), + migrations.AddField( + model_name="policyrule", + name="destination_services", + field=models.ManyToManyField( + related_name="destination_policy_rules", + through="nautobot_firewall_models.DestSvcM2M", + to="nautobot_firewall_models.ServiceObject", + ), + ), + migrations.AddField( + model_name="policyrule", + name="index", + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + migrations.CreateModel( + name="SrcSvcM2M", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True + ), + ), + ( + "pol_rule", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="nautobot_firewall_models.policyrule" + ), + ), + ( + "svc", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="nautobot_firewall_models.serviceobject" + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="SrcSvcGroupM2M", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True + ), + ), + ( + "pol_rule", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="nautobot_firewall_models.policyrule" + ), + ), + ( + "svc_group", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="nautobot_firewall_models.serviceobjectgroup" + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="policyrule", + name="source_service_groups", + field=models.ManyToManyField( + related_name="source_policy_rules", + through="nautobot_firewall_models.SrcSvcGroupM2M", + to="nautobot_firewall_models.ServiceObjectGroup", + ), + ), + migrations.AddField( + model_name="policyrule", + name="source_services", + field=models.ManyToManyField( + related_name="source_policy_rules", + through="nautobot_firewall_models.SrcSvcM2M", + to="nautobot_firewall_models.ServiceObject", + ), + ), + ] diff --git a/nautobot_firewall_models/migrations/0007_renaming_part2.py b/nautobot_firewall_models/migrations/0007_renaming_part2.py new file mode 100644 index 00000000..0753e45c --- /dev/null +++ b/nautobot_firewall_models/migrations/0007_renaming_part2.py @@ -0,0 +1,55 @@ +"""Custom migration for moving index to the PolicyRule.""" +from django.db import migrations +from django.db.models import Count + + +def move_index(apps, schedma_editor): + """Custom migration for moving index to the PolicyRule.""" + # Get models to work with + PolicyRuleM2M = apps.get_model("nautobot_firewall_models.PolicyRuleM2M") + PolicyRule = apps.get_model("nautobot_firewall_models.PolicyRule") + # Get list of duplicates + duplicates = PolicyRuleM2M.objects.values("rule").annotate(Count("id")).filter(id__count__gt=1) + duplicates = [i["rule"] for i in duplicates] + # Create Rules for Duplicates + for rule in PolicyRuleM2M.objects.filter(rule__in=duplicates): + source_users = rule.rule.source_users.all() + source_user_groups = rule.rule.source_user_groups.all() + source_addresses = rule.rule.source_addresses.all() + source_address_groups = rule.rule.source_address_groups.all() + destination_addresses = rule.rule.destination_addresses.all() + destination_address_groups = rule.rule.destination_address_groups.all() + destination_services = rule.rule.destination_services.all() + destination_service_groups = rule.rule.destination_service_groups.all() + temp_rule = rule.rule + temp_rule.id = None + temp_rule.name = f"{rule.rule.name} for {rule.policy.name}" + temp_rule.index = rule.index + temp_rule.save() + temp_rule.source_users.set(source_users) + temp_rule.source_user_groups.set(source_user_groups) + temp_rule.source_addresses.set(source_addresses) + temp_rule.source_address_groups.set(source_address_groups) + temp_rule.destination_addresses.set(destination_addresses) + temp_rule.destination_address_groups.set(destination_address_groups) + temp_rule.destination_services.set(destination_services) + temp_rule.destination_service_groups.set(destination_service_groups) + rule.rule = temp_rule + rule.save() + # Move Indexes for non-duplicates + for rule in PolicyRuleM2M.objects.exclude(rule__in=duplicates): + rule.rule.index = rule.index + rule.rule.save() + # Remove the duplicates + PolicyRule.objects.filter(id__in=duplicates).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("nautobot_firewall_models", "0006_renaming_part1"), + ] + + operations = [ + migrations.RunPython(code=move_index), + ] diff --git a/nautobot_firewall_models/migrations/0008_renaming_part3.py b/nautobot_firewall_models/migrations/0008_renaming_part3.py new file mode 100644 index 00000000..d9a4c58f --- /dev/null +++ b/nautobot_firewall_models/migrations/0008_renaming_part3.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.15 on 2022-08-26 20:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("nautobot_firewall_models", "0007_renaming_part2"), + ] + + operations = [ + migrations.AlterModelOptions( + name="policyrulem2m", + options={}, + ), + migrations.RemoveConstraint( + model_name="policyrulem2m", + name="unique_with_index", + ), + migrations.RemoveConstraint( + model_name="policyrulem2m", + name="unique_without_index", + ), + migrations.RemoveField( + model_name="policyrulem2m", + name="index", + ), + ] diff --git a/nautobot_firewall_models/models/__init__.py b/nautobot_firewall_models/models/__init__.py index fe7d4a49..f7b65c64 100644 --- a/nautobot_firewall_models/models/__init__.py +++ b/nautobot_firewall_models/models/__init__.py @@ -17,6 +17,8 @@ AddressObjectGroupM2M, DestAddrGroupM2M, DestAddrM2M, + DestSvcGroupM2M, + DestSvcM2M, FQDNIPAddressM2M, PolicyRuleM2M, PolicyDeviceM2M, @@ -26,8 +28,8 @@ SrcAddrM2M, SrcUserGroupM2M, SrcUserM2M, - SvcGroupM2M, - SvcM2M, + SrcSvcGroupM2M, + SrcSvcM2M, UserObjectGroupM2M, ZoneInterfaceM2M, ZoneVRFM2M, @@ -44,6 +46,8 @@ "CapircaPolicy", "DestAddrGroupM2M", "DestAddrM2M", + "DestSvcGroupM2M", + "DestSvcM2M", "FQDN", "FQDNIPAddressM2M", "IPRange", @@ -59,8 +63,8 @@ "SrcAddrM2M", "SrcUserGroupM2M", "SrcUserM2M", - "SvcGroupM2M", - "SvcM2M", + "SrcSvcGroupM2M", + "SrcSvcM2M", "UserObject", "UserObjectGroup", "UserObjectGroupM2M", diff --git a/nautobot_firewall_models/models/core_models.py b/nautobot_firewall_models/models/core_models.py index 14f3711c..186e6d61 100644 --- a/nautobot_firewall_models/models/core_models.py +++ b/nautobot_firewall_models/models/core_models.py @@ -500,28 +500,38 @@ class PolicyRule(PrimaryModel): name = models.CharField(max_length=100) tags = TaggableManager(through=TaggedItem) - source_user = models.ManyToManyField(to=UserObject, through="SrcUserM2M", related_name="policy_rules") - source_user_group = models.ManyToManyField( + source_users = models.ManyToManyField(to=UserObject, through="SrcUserM2M", related_name="policy_rules") + source_user_groups = models.ManyToManyField( to=UserObjectGroup, through="SrcUserGroupM2M", related_name="policy_rules" ) - source_address = models.ManyToManyField(to=AddressObject, through="SrcAddrM2M", related_name="source_policy_rules") - source_address_group = models.ManyToManyField( + source_addresses = models.ManyToManyField( + to=AddressObject, through="SrcAddrM2M", related_name="source_policy_rules" + ) + source_address_groups = models.ManyToManyField( to=AddressObjectGroup, through="SrcAddrGroupM2M", related_name="source_policy_rules" ) source_zone = models.ForeignKey( to=Zone, null=True, blank=True, on_delete=models.SET_NULL, related_name="source_policy_rules" ) - destination_address = models.ManyToManyField( + source_services = models.ManyToManyField(to=ServiceObject, through="SrcSvcM2M", related_name="source_policy_rules") + source_service_groups = models.ManyToManyField( + to=ServiceObjectGroup, through="SrcSvcGroupM2M", related_name="source_policy_rules" + ) + destination_addresses = models.ManyToManyField( to=AddressObject, through="DestAddrM2M", related_name="destination_policy_rules" ) - destination_address_group = models.ManyToManyField( + destination_address_groups = models.ManyToManyField( to=AddressObjectGroup, through="DestAddrGroupM2M", related_name="destination_policy_rules" ) destination_zone = models.ForeignKey( to=Zone, on_delete=models.SET_NULL, null=True, blank=True, related_name="destination_policy_rules" ) - service = models.ManyToManyField(to=ServiceObject, through="SvcM2M", related_name="policy_rules") - service_group = models.ManyToManyField(to=ServiceObjectGroup, through="SvcGroupM2M", related_name="policy_rules") + destination_services = models.ManyToManyField( + to=ServiceObject, through="DestSvcM2M", related_name="destination_policy_rules" + ) + destination_service_groups = models.ManyToManyField( + to=ServiceObjectGroup, through="DestSvcGroupM2M", related_name="destination_policy_rules" + ) action = models.CharField(choices=choices.ACTION_CHOICES, max_length=20) log = models.BooleanField(default=False) status = StatusField( @@ -531,6 +541,7 @@ class PolicyRule(PrimaryModel): ) request_id = models.CharField(max_length=100, null=True, blank=True) description = models.CharField(max_length=200, null=True, blank=True) + index = models.PositiveSmallIntegerField(null=True, blank=True) class Meta: """Meta class.""" @@ -546,18 +557,19 @@ def rule_details(self): """Convience method to convert to more consumable dictionary.""" row = {} row["rule"] = self - row["source_address_group"] = self.source_address_group.all() - row["source_address"] = self.source_address.all() - row["source_user"] = self.source_user.all() - row["source_user_group"] = self.source_user_group.all() + row["source_address_groups"] = self.source_address_groups.all() + row["source_addresses"] = self.source_addresses.all() + row["source_users"] = self.source_users.all() + row["source_user_groupes"] = self.source_user_groups.all() row["source_zone"] = self.source_zone + row["source_services"] = self.source_services.all() + row["source_service_groups"] = self.source_service_groups.all() - row["destination_address_group"] = self.destination_address_group.all() - row["destination_address"] = self.destination_address.all() + row["destination_address_groups"] = self.destination_address_groups.all() + row["destination_addresses"] = self.destination_addresses.all() row["destination_zone"] = self.destination_zone - - row["service"] = self.service.all() - row["service_group"] = self.service_group.all() + row["destination_services"] = self.destination_services.all() + row["destination_service_groups"] = self.destination_service_groups.all() row["action"] = self.action row["log"] = self.log diff --git a/nautobot_firewall_models/models/through_models.py b/nautobot_firewall_models/models/through_models.py index 61ea1ffc..2a7edb1a 100644 --- a/nautobot_firewall_models/models/through_models.py +++ b/nautobot_firewall_models/models/through_models.py @@ -1,8 +1,6 @@ """Set of through intermediate models.""" from django.db import models -from django.db.models import Q -from django.db.models.constraints import UniqueConstraint from nautobot.core.models.generics import BaseModel @@ -63,20 +61,10 @@ class Meta: class PolicyRuleM2M(BaseModel): - """Through model to add index to the the Policy & PolicyRule relationship.""" + """Custom through model to on_delete=models.PROTECT to prevent deleting associated PolicyRule if assigned to a Policy.""" policy = models.ForeignKey("nautobot_firewall_models.Policy", on_delete=models.CASCADE) rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.PROTECT) - index = models.PositiveSmallIntegerField(null=True, blank=True) - - class Meta: - """Meta class.""" - - ordering = ["index"] - constraints = [ - UniqueConstraint(fields=["policy", "rule", "index"], name="unique_with_index"), - UniqueConstraint(fields=["policy", "rule"], name="unique_without_index", condition=Q(index=None)), - ] class ServiceObjectGroupM2M(BaseModel): @@ -114,14 +102,28 @@ class SrcUserGroupM2M(BaseModel): pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE) -class SvcM2M(BaseModel): +class SrcSvcM2M(BaseModel): + """Custom through model to on_delete=models.PROTECT to prevent deleting associated Service if assigned to a PolicyRule.""" + + svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT) + pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE) + + +class SrcSvcGroupM2M(BaseModel): + """Custom through model to on_delete=models.PROTECT to prevent deleting associated ServiceGroup if assigned to a PolicyRule.""" + + svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT) + pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE) + + +class DestSvcM2M(BaseModel): """Custom through model to on_delete=models.PROTECT to prevent deleting associated Service if assigned to a PolicyRule.""" svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT) pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE) -class SvcGroupM2M(BaseModel): +class DestSvcGroupM2M(BaseModel): """Custom through model to on_delete=models.PROTECT to prevent deleting associated ServiceGroup if assigned to a PolicyRule.""" svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT) diff --git a/nautobot_firewall_models/tables.py b/nautobot_firewall_models/tables.py index 8b5faa42..7e2f46a7 100644 --- a/nautobot_firewall_models/tables.py +++ b/nautobot_firewall_models/tables.py @@ -150,16 +150,18 @@ class Meta(BaseTable.Meta): # pylint: disable=duplicate-code "pk", "name", - "source_user", - "source_user_group", - "source_address", - "source_address_group", + "source_users", + "source_user_groups", + "source_addresses", + "source_address_groups", "source_zone", - "destination_address", - "destination_address_group", + "source_services", + "source_service_groups", + "destination_addresses", + "destination_address_groups", "destination_zone", - "service", - "service_group", + "destination_services", + "destination_service_groups", "action", "log", "status", diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/assign_policy_rule_index.html b/nautobot_firewall_models/templates/nautobot_firewall_models/assign_policy_rule_index.html deleted file mode 100644 index b717f248..00000000 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/assign_policy_rule_index.html +++ /dev/null @@ -1,25 +0,0 @@ -{% load buttons %} -{% load static %} -{% load custom_links %} -{% load helpers %} -{% load plugins %} - -
-
-
-
- Assign Policy Rule Index -
-
- {% csrf_token %} - - {% include 'nautobot_firewall_models/inc/policyrule_tablehead.html' with parent_policy=True %} - {% for m2m in object.policyrulem2m_set.all %} - {% include 'nautobot_firewall_models/inc/policy_rules_tablerow_edit.html' with m2m=m2m parent_policy=True %} - {% endfor %} -
- -
-
-
-
\ No newline at end of file diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/policy_expanded_rules.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_expanded_rules.html similarity index 100% rename from nautobot_firewall_models/templates/nautobot_firewall_models/policy_expanded_rules.html rename to nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_expanded_rules.html diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow.html index 792543af..cb6939d9 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow.html @@ -1,15 +1,16 @@ {% load helpers %} - {{ m2m.index|placeholder|ljust:5 }} → {{ m2m.rule.name|placeholder }} + {{ m2m.rule.index|placeholder|ljust:5 }} → {{ m2m.rule.name|placeholder }} {% if m2m.rule.action == "remark" %} {{ m2m.rule }} {% else %} - {% include './policy_rule_address_object_row.html' with address=m2m.rule.source_address.all address_group=m2m.rule.source_address_group.all %} - {% include './policy_rule_user_object_row.html' with user=m2m.rule.source_user.all user_group=m2m.rule.source_user_group.all %} + {% include './policy_rule_address_object_row.html' with address=m2m.rule.source_addresses.all address_group=m2m.rule.source_address_groups.all %} + {% include './policy_rule_user_object_row.html' with user=m2m.rule.source_users.all user_group=m2m.rule.source_user_groups.all %} {% include './policy_rule_zone_object_row.html' with zone=m2m.rule.source_zone %} - {% include './policy_rule_address_object_row.html' with address=m2m.rule.destination_address.all address_group=m2m.rule.destination_address_group.all %} + {% include './policy_rule_service_object_row.html' with service=m2m.rule.source_services.all service_group=m2m.rule.source_service_groups.all %} + {% include './policy_rule_address_object_row.html' with address=m2m.rule.destination_addresses.all address_group=m2m.rule.destination_address_groups.all %} {% include './policy_rule_zone_object_row.html' with zone=m2m.rule.destination_zone %} - {% include './policy_rule_service_object_row.html' with service=m2m.rule.service.all service_group=m2m.rule.service_group.all %} + {% include './policy_rule_service_object_row.html' with service=m2m.rule.destination_services.all service_group=m2m.rule.destination_service_groups.all %} {% endif %} {% include './policy_rule_action_row.html' with action=m2m.rule.action %} diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow_edit.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow_edit.html deleted file mode 100644 index fdbbf416..00000000 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rules_tablerow_edit.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load helpers %} - - - {% if m2m.rule.action == "remark" %} - {{ m2m.rule.name }} - {% else %} - {% include './policy_rule_address_object_row.html' with address=m2m.rule.source_address.all address_group=m2m.rule.source_address_group.all %} - {% include './policy_rule_user_object_row.html' with user=m2m.rule.source_user.all user_group=m2m.rule.source_user_group.all %} - {% include './policy_rule_zone_object_row.html' with zone=m2m.rule.source_zone %} - {% include './policy_rule_address_object_row.html' with address=m2m.rule.destination_address.all address_group=m2m.rule.destination_address_group.all %} - {% include './policy_rule_zone_object_row.html' with zone=m2m.rule.destination_zone %} - {% include './policy_rule_service_object_row.html' with service=m2m.rule.service.all service_group=m2m.rule.service_group.all %} - {% endif %} - {{ m2m.rule.action|placeholder }} - {% if m2m.rule.log %}{% else %}{% endif %} - diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablehead.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablehead.html index 358be0c9..14878e40 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablehead.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablehead.html @@ -1,6 +1,6 @@ - {% if parent_policy %}Index{% endif %} - Source + Index + Source Destination Action Log @@ -8,7 +8,8 @@ Address User - Zone + Zone + Service Address Zone Service diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablerow.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablerow.html index a7dd9682..da964522 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablerow.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablerow.html @@ -3,12 +3,13 @@ {% if rule.action == "remark" %} {{ rule }} {% else %} - {% include './policy_rule_address_object_row.html' with address=rule.source_address.all address_group=rule.source_address_group.all %} - {% include './policy_rule_user_object_row.html' with user=rule.source_user.all user_group=rule.source_user_group.all %} + {% include './policy_rule_address_object_row.html' with address=rule.source_addresses.all address_group=rule.source_address_groups.all %} + {% include './policy_rule_user_object_row.html' with user=rule.source_users.all user_group=rule.source_user_groups.all %} {% include './policy_rule_zone_object_row.html' with zone=rule.source_zone %} - {% include './policy_rule_address_object_row.html' with address=rule.destination_address.all address_group=rule.destination_address_group.all %} + {% include './policy_rule_service_object_row.html' with service=rule.source_services.all service_group=rule.source_service_groups.all %} + {% include './policy_rule_address_object_row.html' with address=rule.destination_addresses.all address_group=rule.destination_address_groups.all %} {% include './policy_rule_zone_object_row.html' with zone=rule.destination_zone %} - {% include './policy_rule_service_object_row.html' with service=rule.service.all service_group=rule.service_group.all %} + {% include './policy_rule_service_object_row.html' with service=rule.destination_services.all service_group=rule.destination_service_groups.all %} {% endif %} {% include './policy_rule_action_row.html' with action=rule.action %} diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html b/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html index 030c14bd..231441ba 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html @@ -14,13 +14,6 @@ Policy Rules Expanded - {% if perms.nautobot_firewall_models.edit_policy %} - - {% endif %} {% if perms.nautobot_firewall_models.edit_policy %}