diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7ae9729..14d1a052 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## v1.2.0-alpha.0 - 2022-11-22
+
+### Added
+
+- #108 Application & ApplicationGroup support
+
+### Fixed
+
+- #117 Resolved `main` tab not loading on initial load of Policy & PolicyRule detail page
+
+### Changed
+
+- #117 Reorganized `models/` folder to improve developer experience
+
## v1.1.3 - 2022-11-16
### Fixed
diff --git a/docs/example.md b/docs/example.md
index 3ed820dd..c785f561 100644
--- a/docs/example.md
+++ b/docs/example.md
@@ -245,4 +245,4 @@ Below is an example response to the REST API GET request for a Policy object at
"status": "35206353-47f4-4e71-9e2c-807092b6c439",
"tenant": "5fabe6c7-84a6-45af-95a0-384f9ebcbeb8"
}
-```
\ No newline at end of file
+```
diff --git a/docs/images/policy.png b/docs/images/policy.png
index 6d8d3e71..62219e1c 100644
Binary files a/docs/images/policy.png and b/docs/images/policy.png differ
diff --git a/docs/models.md b/docs/models.md
index b2e21580..2ed8d8e5 100644
--- a/docs/models.md
+++ b/docs/models.md
@@ -96,6 +96,16 @@ This plugin uses [custom models to model many-to-many](https://docs.djangoprojec
heading-offset=2
%}
+{%
+ include-markdown "./models/applicationobject.md"
+ heading-offset=2
+%}
+
+{%
+ include-markdown "./models/applicationobjectgroup.md"
+ heading-offset=2
+%}
+
{%
include-markdown "./models/fqdn.md"
heading-offset=2
diff --git a/docs/models/applicationobject.md b/docs/models/applicationobject.md
new file mode 100644
index 00000000..490c77ef
--- /dev/null
+++ b/docs/models/applicationobject.md
@@ -0,0 +1,15 @@
+# ApplicationObject
+
+Defines an Applications that are typically referenced in NextGen Firewalls.
+
+## Attributes
+
+* Name (string)
+* Description (optional, string)
+* Category (optional, string)
+* Subategory (optional, string)
+* Technology (optional, string)
+* Risk (optional, positive integer)
+* Default Type (optional, int OR int range OR comma delimited list of int or int range)
+* Default IP Protocol (optional, string)
+* Status (FK to Status)
diff --git a/docs/models/applicationobjectgroup.md b/docs/models/applicationobjectgroup.md
new file mode 100644
index 00000000..94d51fa4
--- /dev/null
+++ b/docs/models/applicationobjectgroup.md
@@ -0,0 +1,10 @@
+# ApplicationObjectGroup
+
+Represents a group of Application Objects.
+
+## Attributes
+
+* Name (string)
+* Description (optional, string)
+* Application Objects (M2M to ApplicationObject)
+* Status (FK to Status)
diff --git a/docs/models/serviceobject.md b/docs/models/serviceobject.md
index 7dc4a653..a7a62c13 100644
--- a/docs/models/serviceobject.md
+++ b/docs/models/serviceobject.md
@@ -8,7 +8,7 @@ For well-known ports, it is best to use the port name as the name of the object.
* Name (string)
* Description (optional, string)
-* Port (optional, int OR int range)
+* Port (optional, int OR int range OR comma delimited list of int or int range)
* Must be specified as a valid layer 4 port OR port range (e.g. 80 OR 8080-8088).
* IP Protocol (string, choice field)
* IANA protocols (e.g. TCP UDP ICMP)
diff --git a/nautobot_firewall_models/api/nested_serializers.py b/nautobot_firewall_models/api/nested_serializers.py
index 4342668d..7f6d7845 100644
--- a/nautobot_firewall_models/api/nested_serializers.py
+++ b/nautobot_firewall_models/api/nested_serializers.py
@@ -5,6 +5,18 @@
from nautobot_firewall_models import models
+class NestedApplicationSerializer(WritableNestedSerializer):
+ """Nested serializer for FQDN."""
+
+ url = HyperlinkedIdentityField(view_name="plugins-api:nautobot_firewall_models-api:applicationobject-detail")
+
+ class Meta:
+ """Meta attributes."""
+
+ model = models.FQDN
+ fields = ["id", "url", "name"]
+
+
class NestedFQDNSerializer(WritableNestedSerializer):
"""Nested serializer for FQDN."""
diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py
index eed2d251..5d1f3483 100644
--- a/nautobot_firewall_models/api/serializers.py
+++ b/nautobot_firewall_models/api/serializers.py
@@ -18,7 +18,11 @@
from nautobot_firewall_models import models
-from nautobot_firewall_models.api.nested_serializers import NestedFQDNSerializer, NestedIPRangeSerializer
+from nautobot_firewall_models.api.nested_serializers import (
+ NestedFQDNSerializer,
+ NestedIPRangeSerializer,
+ # NestedApplicationSerializer,
+)
class StatusModelSerializerMixin(_StatusModelSerializerMixin): # pylint: disable=abstract-method
@@ -95,6 +99,40 @@ class Meta:
fields = "__all__"
+class ApplicationObjectSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer):
+ """ApplicationObject Serializer."""
+
+ url = serializers.HyperlinkedIdentityField(
+ view_name="plugins-api:nautobot_firewall_models-api:applicationobject-detail"
+ )
+
+ class Meta:
+ """Meta attributes."""
+
+ model = models.ApplicationObject
+ fields = "__all__"
+
+
+class ApplicationObjectGroupSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer):
+ """ApplicationObjectGroup Serializer."""
+
+ url = serializers.HyperlinkedIdentityField(
+ view_name="plugins-api:nautobot_firewall_models-api:applicationobject-detail"
+ )
+ application_objects = SerializedPKRelatedField(
+ queryset=models.ApplicationObject.objects.all(),
+ serializer=ApplicationObjectSerializer,
+ required=False,
+ many=True,
+ )
+
+ class Meta:
+ """Meta attributes."""
+
+ model = models.ApplicationObjectGroup
+ fields = "__all__"
+
+
class ServiceObjectSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, NautobotModelSerializer):
"""ServiceObject Serializer."""
@@ -209,6 +247,18 @@ class PolicyRuleSerializer(TaggedObjectSerializer, StatusModelSerializerMixin, N
required=False,
many=True,
)
+ applications = SerializedPKRelatedField(
+ queryset=models.ApplicationObject.objects.all(),
+ serializer=ApplicationObjectSerializer,
+ required=False,
+ many=True,
+ )
+ application_groups = SerializedPKRelatedField(
+ queryset=models.ApplicationObjectGroup.objects.all(),
+ serializer=ApplicationObjectGroupSerializer,
+ required=False,
+ many=True,
+ )
class Meta:
"""Meta attributes."""
diff --git a/nautobot_firewall_models/api/urls.py b/nautobot_firewall_models/api/urls.py
index 7defe02d..e6e06fe0 100644
--- a/nautobot_firewall_models/api/urls.py
+++ b/nautobot_firewall_models/api/urls.py
@@ -8,6 +8,8 @@
router = OrderedDefaultRouter()
router.register("address-object", views.AddressObjectViewSet)
router.register("address-object-group", views.AddressObjectGroupViewSet)
+router.register("application-object", views.ApplicationObjectViewSet)
+router.register("application-object-group", views.ApplicationObjectGroupViewSet)
router.register("capirca-policy", views.CapircaPolicyViewSet)
router.register("fqdn", views.FQDNViewSet)
router.register("ip-range", views.IPRangeViewSet)
diff --git a/nautobot_firewall_models/api/views.py b/nautobot_firewall_models/api/views.py
index a3f9f0e2..122a5078 100644
--- a/nautobot_firewall_models/api/views.py
+++ b/nautobot_firewall_models/api/views.py
@@ -40,6 +40,22 @@ class AddressObjectGroupViewSet(NautobotModelViewSet):
filterset_class = filters.AddressObjectGroupFilterSet
+class ApplicationObjectViewSet(NautobotModelViewSet):
+ """ApplicationObject viewset."""
+
+ queryset = models.ApplicationObject.objects.all()
+ serializer_class = serializers.ApplicationObjectSerializer
+ filterset_class = filters.ApplicationObjectFilterSet
+
+
+class ApplicationObjectGroupViewSet(NautobotModelViewSet):
+ """ApplicationObjectGroup viewset."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+ serializer_class = serializers.ApplicationObjectGroupSerializer
+ filterset_class = filters.ApplicationObjectGroupFilterSet
+
+
class ServiceObjectViewSet(NautobotModelViewSet):
"""ServiceObject viewset."""
diff --git a/nautobot_firewall_models/filters.py b/nautobot_firewall_models/filters.py
index bb194f54..dcd1973d 100644
--- a/nautobot_firewall_models/filters.py
+++ b/nautobot_firewall_models/filters.py
@@ -77,6 +77,28 @@ class Meta:
fields = ["id", "name", "address_objects", "description"]
+class ApplicationObjectFilterSet(BaseFilterSet, NautobotFilterSet):
+ """Filter for ApplicationObject."""
+
+ class Meta:
+ """Meta attributes for filter."""
+
+ model = models.ApplicationObject
+
+ fields = ["id", "name", "description", "category", "subcategory", "risk", "description"]
+
+
+class ApplicationObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet):
+ """Filter for ApplicationObjectGroup."""
+
+ class Meta:
+ """Meta attributes for filter."""
+
+ model = models.ApplicationObjectGroup
+
+ fields = ["id", "name", "application_objects", "description"]
+
+
class ServiceObjectFilterSet(BaseFilterSet, NautobotFilterSet):
"""Filter for ServiceObject."""
diff --git a/nautobot_firewall_models/forms.py b/nautobot_firewall_models/forms.py
index b9032a86..a55b677c 100644
--- a/nautobot_firewall_models/forms.py
+++ b/nautobot_firewall_models/forms.py
@@ -197,6 +197,110 @@ class Meta:
]
+class ApplicationObjectFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin):
+ """Filter form to filter searches."""
+
+ field_order = ["q", "name"]
+
+ model = models.ApplicationObject
+ q = forms.CharField(
+ required=False,
+ label="Search",
+ help_text="Search within Name or Description.",
+ )
+ name = forms.CharField(required=False, label="Name")
+ category = DynamicModelChoiceField(
+ queryset=models.ApplicationObject.objects.all(), required=False, label="Category"
+ )
+
+
+class ApplicationObjectForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm):
+ """ApplicationObject creation/edit form."""
+
+ class Meta:
+ """Meta attributes."""
+
+ model = models.ApplicationObject
+ fields = [
+ "name",
+ "description",
+ "category",
+ "subcategory",
+ "technology",
+ "risk",
+ "default_type",
+ "default_ip_protocol",
+ "status",
+ ]
+
+
+class ApplicationObjectBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm):
+ """ApplicationObject bulk edit form."""
+
+ pk = DynamicModelMultipleChoiceField(
+ queryset=models.ApplicationObject.objects.all(), widget=forms.MultipleHiddenInput
+ )
+ description = forms.CharField(required=False)
+ risk = forms.IntegerField(required=False)
+ technology = forms.CharField(required=False)
+ category = forms.CharField(required=False)
+ subcategory = forms.CharField(required=False)
+
+ class Meta:
+ """Meta attributes."""
+
+ nullable_fields = [
+ "description",
+ "default_ip_protocol",
+ "default_type",
+ "technology",
+ "category",
+ "subcategory",
+ ]
+
+
+class ApplicationObjectGroupFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin):
+ """Filter form to filter searches."""
+
+ field_order = ["q", "name"]
+
+ model = models.ApplicationObjectGroup
+ q = forms.CharField(
+ required=False,
+ label="Search",
+ help_text="Search within Name or Description.",
+ )
+ name = forms.CharField(required=False, label="Name")
+
+
+class ApplicationObjectGroupForm(BootstrapMixin, RelationshipModelFormMixin, forms.ModelForm):
+ """ApplicationObjectGroup creation/edit form."""
+
+ application_objects = DynamicModelMultipleChoiceField(queryset=models.ApplicationObject.objects.all())
+
+ class Meta:
+ """Meta attributes."""
+
+ model = models.ApplicationObjectGroup
+ fields = ["name", "description", "application_objects", "status", "tags"]
+
+
+class ApplicationObjectGroupBulkEditForm(BootstrapMixin, StatusModelBulkEditFormMixin, BulkEditForm):
+ """ApplicationObjectGroup bulk edit form."""
+
+ pk = DynamicModelMultipleChoiceField(
+ queryset=models.ApplicationObjectGroup.objects.all(), widget=forms.MultipleHiddenInput
+ )
+ description = forms.CharField(required=False)
+
+ class Meta:
+ """Meta attributes."""
+
+ nullable_fields = [
+ "description",
+ ]
+
+
class ServiceObjectFilterForm(BootstrapMixin, StatusModelFilterFormMixin, CustomFieldModelFilterFormMixin):
"""Filter form to filter searches."""
@@ -473,6 +577,14 @@ class PolicyRuleForm(BootstrapMixin, CustomFieldModelFormMixin, RelationshipMode
destination_service_groups = DynamicModelMultipleChoiceField(
queryset=models.ServiceObjectGroup.objects.all(), label="Destination Service Object Groups", required=False
)
+ applications = DynamicModelMultipleChoiceField(
+ queryset=models.ApplicationObject.objects.all(), label="Destination Application Objects", required=False
+ )
+ application_groups = DynamicModelMultipleChoiceField(
+ queryset=models.ApplicationObjectGroup.objects.all(),
+ label="Destination Application Object Groups",
+ required=False,
+ )
request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.")
class Meta:
@@ -495,6 +607,8 @@ class Meta:
"destination_zone",
"destination_services",
"destination_service_groups",
+ "applications",
+ "application_groups",
"action",
"log",
"status",
diff --git a/nautobot_firewall_models/homepage.py b/nautobot_firewall_models/homepage.py
index ce4abeda..b38407a2 100644
--- a/nautobot_firewall_models/homepage.py
+++ b/nautobot_firewall_models/homepage.py
@@ -9,45 +9,45 @@
name="Security",
items=(
HomePageItem(
- name="Policies",
+ name="Security Policies",
model=Policy,
weight=100,
link="plugins:nautobot_firewall_models:policy_list",
description="Firewall Policies",
permissions=["nautobot_firewall_models.view_policy"],
),
- HomePageItem(
- name="Policy Rules",
- model=PolicyRule,
- weight=200,
- link="plugins:nautobot_firewall_models:policyrule_list",
- description="Firewall Policies",
- permissions=["nautobot_firewall_models.view_policyrule"],
- ),
HomePageItem(
name="NAT Policies",
model=NATPolicy,
- weight=300,
+ weight=200,
link="plugins:nautobot_firewall_models:natpolicy_list",
description="NAT Policies",
permissions=["nautobot_firewall_models.view_natpolicy"],
),
- HomePageItem(
- name="NAT Policy Rules",
- model=NATPolicyRule,
- weight=400,
- link="plugins:nautobot_firewall_models:natpolicyrule_list",
- description="NAT Policies",
- permissions=["nautobot_firewall_models.view_natpolicyrule"],
- ),
HomePageItem(
name="Capirca Policies",
model=CapircaPolicy,
- weight=150,
+ weight=300,
link="plugins:nautobot_firewall_models:capircapolicy_list",
description="Firewall Policies",
permissions=["nautobot_firewall_models.view_capircapolicy"],
),
+ HomePageItem(
+ name="Security Rules",
+ model=PolicyRule,
+ weight=400,
+ link="plugins:nautobot_firewall_models:policyrule_list",
+ description="Firewall Policies",
+ permissions=["nautobot_firewall_models.view_policyrule"],
+ ),
+ HomePageItem(
+ name="NAT Rules",
+ model=NATPolicyRule,
+ weight=500,
+ link="plugins:nautobot_firewall_models:natpolicyrule_list",
+ description="NAT Policies",
+ permissions=["nautobot_firewall_models.view_natpolicyrule"],
+ ),
),
),
)
diff --git a/nautobot_firewall_models/jobs.py b/nautobot_firewall_models/jobs.py
index 9025e6e3..79928b72 100644
--- a/nautobot_firewall_models/jobs.py
+++ b/nautobot_firewall_models/jobs.py
@@ -5,7 +5,7 @@
from nautobot.dcim.models import Device
from nautobot_firewall_models.models import CapircaPolicy
-from nautobot_firewall_models.models.core_models import Policy
+from nautobot_firewall_models.models import Policy
LOGGER = logging.getLogger(__name__)
diff --git a/nautobot_firewall_models/migrations/0013_applications.py b/nautobot_firewall_models/migrations/0013_applications.py
new file mode 100644
index 00000000..3bb95ba6
--- /dev/null
+++ b/nautobot_firewall_models/migrations/0013_applications.py
@@ -0,0 +1,234 @@
+# Generated by Django 3.2.15 on 2022-11-22 21:23
+
+import django.core.serializers.json
+from django.db import migrations, models
+import django.db.models.deletion
+import nautobot.extras.models.mixins
+import nautobot.extras.models.statuses
+import nautobot_firewall_models.utils
+import taggit.managers
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("extras", "0047_enforce_custom_field_slug"),
+ ("nautobot_firewall_models", "0012_remove_status_m2m_through_models"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ApplicationObject",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
+ ),
+ ),
+ ("created", models.DateField(auto_now_add=True, null=True)),
+ ("last_updated", models.DateTimeField(auto_now=True, null=True)),
+ (
+ "_custom_field_data",
+ models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+ ),
+ ("description", models.CharField(blank=True, max_length=200)),
+ ("category", models.CharField(blank=True, max_length=48)),
+ ("subcategory", models.CharField(blank=True, max_length=48)),
+ ("technology", models.CharField(blank=True, max_length=48)),
+ ("risk", models.PositiveIntegerField(blank=True)),
+ ("default_type", models.CharField(blank=True, max_length=48)),
+ ("name", models.CharField(max_length=100, unique=True)),
+ ("default_ip_protocol", models.CharField(blank=True, max_length=48)),
+ (
+ "status",
+ nautobot.extras.models.statuses.StatusField(
+ default=nautobot_firewall_models.utils.get_default_status,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="nautobot_firewall_models_applicationobject_related",
+ to="extras.status",
+ ),
+ ),
+ ("tags", taggit.managers.TaggableManager(through="extras.TaggedItem", to="extras.Tag")),
+ ],
+ options={
+ "verbose_name_plural": "Application Objects",
+ "ordering": ["name"],
+ },
+ bases=(
+ models.Model,
+ nautobot.extras.models.mixins.DynamicGroupMixin,
+ nautobot.extras.models.mixins.NotesMixin,
+ ),
+ ),
+ migrations.CreateModel(
+ name="ApplicationObjectGroup",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
+ ),
+ ),
+ ("created", models.DateField(auto_now_add=True, null=True)),
+ ("last_updated", models.DateTimeField(auto_now=True, null=True)),
+ (
+ "_custom_field_data",
+ models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+ ),
+ ("description", models.CharField(blank=True, max_length=200)),
+ ("name", models.CharField(max_length=100, unique=True)),
+ ],
+ options={
+ "verbose_name_plural": "Application Object Groups",
+ "ordering": ["name"],
+ },
+ bases=(
+ models.Model,
+ nautobot.extras.models.mixins.DynamicGroupMixin,
+ nautobot.extras.models.mixins.NotesMixin,
+ ),
+ ),
+ migrations.AlterModelOptions(
+ name="natpolicy",
+ options={"ordering": ["name"], "verbose_name": "NAT Policy", "verbose_name_plural": "NAT Policies"},
+ ),
+ migrations.AlterModelOptions(
+ name="natpolicyrule",
+ options={
+ "ordering": ["index"],
+ "verbose_name": "NAT Policy Rule",
+ "verbose_name_plural": "NAT Policy Rules",
+ },
+ ),
+ migrations.AlterModelOptions(
+ name="iprange",
+ options={"ordering": ["start_address"], "verbose_name": "IP Range", "verbose_name_plural": "IP Ranges"},
+ ),
+ migrations.CreateModel(
+ name="ApplicationObjectGroupM2M",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
+ ),
+ ),
+ (
+ "application",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT, to="nautobot_firewall_models.applicationobject"
+ ),
+ ),
+ (
+ "application_group",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="nautobot_firewall_models.applicationobjectgroup",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AddField(
+ model_name="applicationobjectgroup",
+ name="application_objects",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="application_object_groups",
+ through="nautobot_firewall_models.ApplicationObjectGroupM2M",
+ to="nautobot_firewall_models.ApplicationObject",
+ ),
+ ),
+ migrations.AddField(
+ model_name="applicationobjectgroup",
+ name="status",
+ field=nautobot.extras.models.statuses.StatusField(
+ default=nautobot_firewall_models.utils.get_default_status,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="nautobot_firewall_models_applicationobjectgroup_related",
+ to="extras.status",
+ ),
+ ),
+ migrations.AddField(
+ model_name="applicationobjectgroup",
+ name="tags",
+ field=taggit.managers.TaggableManager(through="extras.TaggedItem", to="extras.Tag"),
+ ),
+ migrations.CreateModel(
+ name="ApplicationM2M",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
+ ),
+ ),
+ (
+ "app",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT, to="nautobot_firewall_models.applicationobject"
+ ),
+ ),
+ (
+ "pol_rule",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="nautobot_firewall_models.policyrule"
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="ApplicationGroupM2M",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
+ ),
+ ),
+ (
+ "app_group",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ to="nautobot_firewall_models.applicationobjectgroup",
+ ),
+ ),
+ (
+ "pol_rule",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="nautobot_firewall_models.policyrule"
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AddField(
+ model_name="policyrule",
+ name="application_groups",
+ field=models.ManyToManyField(
+ related_name="policy_rules",
+ through="nautobot_firewall_models.ApplicationGroupM2M",
+ to="nautobot_firewall_models.ApplicationObjectGroup",
+ ),
+ ),
+ migrations.AddField(
+ model_name="policyrule",
+ name="applications",
+ field=models.ManyToManyField(
+ related_name="policy_rules",
+ through="nautobot_firewall_models.ApplicationM2M",
+ to="nautobot_firewall_models.ApplicationObject",
+ ),
+ ),
+ ]
diff --git a/nautobot_firewall_models/migrations/0014_custom_status_application.py b/nautobot_firewall_models/migrations/0014_custom_status_application.py
new file mode 100644
index 00000000..e3459d58
--- /dev/null
+++ b/nautobot_firewall_models/migrations/0014_custom_status_application.py
@@ -0,0 +1,54 @@
+# Copied on 2022-11-18
+
+from django.db import migrations
+
+
+def create_app_status(apps, schema_editor):
+ """Initial subset of statuses for the Application models.
+
+ This was added along with 0013_applications in order to associate the same set of statuses with the new Application models
+ that are associated with the original set of security models.
+ """
+
+ statuses = ["active", "staged", "decommissioned"]
+ ContentType = apps.get_model("contenttypes.ContentType")
+ relevant_models = [
+ apps.get_model(model)
+ for model in ["nautobot_firewall_models.ApplicationObject", "nautobot_firewall_models.ApplicationObjectGroup"]
+ ]
+ for i in statuses:
+ status = apps.get_model("extras.Status").objects.get(slug=i)
+ for model in relevant_models:
+ ct = ContentType.objects.get_for_model(model)
+ status.content_types.add(ct)
+
+
+def remove_app_status(apps, schema_editor):
+ """Remove status content_type for Application models.
+
+ This was added along with 0013_applications in order to associate the same set of statuses with the new Application models
+ that are associated with the original set of security models.
+ """
+
+ statuses = ["active", "staged", "decommissioned"]
+ ContentType = apps.get_model("contenttypes.ContentType")
+ relevant_models = [
+ apps.get_model(model)
+ for model in ["nautobot_firewall_models.ApplicationObject", "nautobot_firewall_models.ApplicationObjectGroup"]
+ ]
+ for i in statuses:
+ status = apps.get_model("extras.Status").objects.get(slug=i)
+ for model in relevant_models:
+ ct = ContentType.objects.get_for_model(model)
+ status.content_types.remove(ct)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("nautobot_firewall_models", "0013_applications"),
+ ]
+
+ operations = [
+ migrations.RunPython(code=create_app_status, reverse_code=remove_app_status),
+ ]
diff --git a/nautobot_firewall_models/models/__init__.py b/nautobot_firewall_models/models/__init__.py
index b30a4e79..1d7b523a 100644
--- a/nautobot_firewall_models/models/__init__.py
+++ b/nautobot_firewall_models/models/__init__.py
@@ -1,27 +1,19 @@
"""Load models."""
-from .core_models import (
+from .address import (
AddressObject,
AddressObjectGroup,
+ AddressObjectGroupM2M,
FQDN,
+ FQDNIPAddressM2M,
IPRange,
+)
+from .capirca_policy import (
+ CapircaPolicy,
+)
+from .nat_policy import (
NATPolicy,
NATPolicyRule,
- Policy,
- PolicyRule,
- ServiceObject,
- ServiceObjectGroup,
- UserObject,
- UserObjectGroup,
- Zone,
-)
-from .through_models import (
- AddressObjectGroupM2M,
- DestAddrGroupM2M,
- DestAddrM2M,
- DestSvcGroupM2M,
- DestSvcM2M,
- FQDNIPAddressM2M,
NATOrigDestAddrGroupM2M,
NATOrigDestAddrM2M,
NATOrigDestSvcGroupM2M,
@@ -41,29 +33,46 @@
NATTransSrcAddrM2M,
NATTransSrcSvcGroupM2M,
NATTransSrcSvcM2M,
- PolicyRuleM2M,
+)
+from .security_policy import (
+ DestAddrGroupM2M,
+ DestAddrM2M,
+ DestSvcGroupM2M,
+ DestSvcM2M,
+ Policy,
PolicyDeviceM2M,
PolicyDynamicGroupM2M,
- ServiceObjectGroupM2M,
+ PolicyRule,
+ PolicyRuleM2M,
SrcAddrGroupM2M,
SrcAddrM2M,
SrcUserGroupM2M,
SrcUserM2M,
SrcSvcGroupM2M,
SrcSvcM2M,
- UserObjectGroupM2M,
+)
+from .service import (
+ ApplicationObject,
+ ApplicationObjectGroup,
+ ApplicationObjectGroupM2M,
+ ServiceObject,
+ ServiceObjectGroup,
+ ServiceObjectGroupM2M,
+)
+from .user import UserObject, UserObjectGroup, UserObjectGroupM2M
+from .zone import (
+ Zone,
ZoneInterfaceM2M,
ZoneVRFM2M,
)
-from .capirca_models import (
- CapircaPolicy,
-)
-
__all__ = (
"AddressObject",
"AddressObjectGroup",
"AddressObjectGroupM2M",
+ "ApplicationObject",
+ "ApplicationObjectGroup",
+ "ApplicationObjectGroupM2M",
"CapircaPolicy",
"DestAddrGroupM2M",
"DestAddrM2M",
diff --git a/nautobot_firewall_models/models/address.py b/nautobot_firewall_models/models/address.py
new file mode 100644
index 00000000..6216e1b3
--- /dev/null
+++ b/nautobot_firewall_models/models/address.py
@@ -0,0 +1,281 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code, too-many-lines
+
+from django.core.exceptions import ValidationError
+from django.db import models
+from django.db.models import Q
+from django.db.models.constraints import UniqueConstraint
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.utils import extras_features
+from nautobot.ipam.fields import VarbinaryIPField
+from netaddr import IPAddress
+
+from nautobot_firewall_models.utils import get_default_status
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class IPRange(PrimaryModel):
+ """IPRange model to track ranges of IPs in firewall rules."""
+
+ start_address = VarbinaryIPField(
+ null=False,
+ db_index=True,
+ help_text="Starting IPv4 or IPv6 host address",
+ )
+ end_address = VarbinaryIPField(
+ null=False,
+ db_index=True,
+ help_text="Ending IPv4 or IPv6 host address",
+ )
+ vrf = models.ForeignKey(
+ to="ipam.VRF", on_delete=models.PROTECT, related_name="ip_ranges", blank=True, null=True, verbose_name="VRF"
+ )
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ size = models.PositiveIntegerField(editable=False)
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["start_address"]
+ verbose_name = "IP Range"
+ verbose_name_plural = "IP Ranges"
+ constraints = [
+ UniqueConstraint(fields=["start_address", "end_address", "vrf"], name="unique_with_vrf"),
+ UniqueConstraint(fields=["start_address", "end_address"], condition=Q(vrf=None), name="unique_without_vrf"),
+ ]
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:iprange", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return f"{self.start_address}-{self.end_address}"
+
+ def save(self, *args, **kwargs):
+ """Overloads to inject size attr."""
+ # Record the range's size (number of IP addresses)
+ self.clean()
+ self.size = int(IPAddress(self.end_address) - IPAddress(self.start_address)) + 1
+ super().save(*args, **kwargs)
+
+ def clean(self, *args, **kwargs):
+ """Overloads to validate attr for form verification."""
+ if not getattr(self, "start_address") or not getattr(self, "end_address"):
+ raise ValidationError("Must have `start_address` and `end_address`.")
+ if IPAddress(self.start_address) > IPAddress(self.end_address):
+ raise ValidationError("`end_address` must be >= than `start_address`.")
+
+ super().clean(*args, **kwargs)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class FQDN(PrimaryModel):
+ """Models fully qualified domain names, can be used on some firewall in place of a static IP."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(
+ max_length=254, unique=True, help_text="Resolvable fully qualified domain name (e.g. networktocode.com)"
+ )
+ ip_addresses = models.ManyToManyField(
+ to="ipam.IPAddress",
+ blank=True,
+ through="FQDNIPAddressM2M",
+ related_name="fqdns",
+ help_text="IP(s) an FQDN should resolve to.",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name = "FQDN"
+ verbose_name_plural = "FQDNs"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:fqdn", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class AddressObject(PrimaryModel):
+ """Intermediate model to aggregate underlying address items, to allow for easier management."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for an address object type.")
+ fqdn = models.ForeignKey(to="nautobot_firewall_models.FQDN", on_delete=models.PROTECT, null=True, blank=True)
+ ip_range = models.ForeignKey(to="nautobot_firewall_models.IPRange", on_delete=models.PROTECT, null=True, blank=True)
+ ip_address = models.ForeignKey(to="ipam.IPAddress", on_delete=models.PROTECT, null=True, blank=True)
+ prefix = models.ForeignKey(to="ipam.Prefix", on_delete=models.PROTECT, null=True, blank=True)
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Address Objects"
+
+ def get_address_info(self):
+ """Method to Return the actual AddressObject type."""
+ keys = ["ip_range", "fqdn", "prefix", "ip_address"]
+ for key in keys:
+ if getattr(self, key):
+ return (key, getattr(self, key))
+ return (None, None)
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:addressobject", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+ def save(self, *args, **kwargs):
+ """Overloads to enforce clear."""
+ self.clean()
+ super().save(*args, **kwargs)
+
+ def clean(self, *args, **kwargs):
+ """Overloads to validate attr for form verification."""
+ address_types = ["fqdn", "ip_range", "ip_address", "prefix"]
+ address_count = 0
+ for i in address_types:
+ if hasattr(self, i) and getattr(self, i) is not None:
+ address_count += 1
+ if address_count != 1:
+ raise ValidationError(f"Must specify only one address from type {address_types}, {address_count} found.")
+
+ super().clean(*args, **kwargs)
+
+ @property
+ def address(self): # pylint: disable=inconsistent-return-statements
+ """Returns the assigned address object."""
+ for i in ["fqdn", "ip_range", "ip_address", "prefix"]:
+ if getattr(self, i):
+ return getattr(self, i)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class AddressObjectGroup(PrimaryModel):
+ """Groups together AddressObjects to better mimic grouping sets of address objects that have a some commonality."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for a group address objects.")
+ address_objects = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject",
+ blank=True,
+ through="AddressObjectGroupM2M",
+ related_name="address_object_groups",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Address Object Groups"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:addressobjectgroup", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class AddressObjectGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated AddressObject if assigned to a AddressObjectGroup."""
+
+ address = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ address_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.CASCADE)
+
+
+class FQDNIPAddressM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated IPAddress if assigned to a FQDN."""
+
+ fqdn = models.ForeignKey("nautobot_firewall_models.FQDN", on_delete=models.CASCADE)
+ ip_address = models.ForeignKey("ipam.IPAddress", on_delete=models.PROTECT)
diff --git a/nautobot_firewall_models/models/capirca_models.py b/nautobot_firewall_models/models/capirca_policy.py
similarity index 100%
rename from nautobot_firewall_models/models/capirca_models.py
rename to nautobot_firewall_models/models/capirca_policy.py
diff --git a/nautobot_firewall_models/models/core_models.py b/nautobot_firewall_models/models/core_models.py
deleted file mode 100644
index dde913d6..00000000
--- a/nautobot_firewall_models/models/core_models.py
+++ /dev/null
@@ -1,918 +0,0 @@
-"""Models for the Firewall plugin."""
-# pylint: disable=duplicate-code
-
-from django.core.exceptions import ValidationError
-from django.db import models
-from django.db.models import Q
-from django.db.models.constraints import UniqueConstraint
-from django.urls import reverse
-from nautobot.core.models.generics import PrimaryModel
-from nautobot.extras.models import StatusField
-from nautobot.extras.models.tags import TaggedItem
-from nautobot.extras.utils import extras_features
-from nautobot.ipam.fields import VarbinaryIPField
-from netaddr import IPAddress
-from taggit.managers import TaggableManager
-
-from nautobot_firewall_models import choices, validators
-from nautobot_firewall_models.utils import get_default_status, model_to_json
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class IPRange(PrimaryModel):
- """IPRange model to track ranges of IPs in firewall rules."""
-
- start_address = VarbinaryIPField(
- null=False,
- db_index=True,
- help_text="Starting IPv4 or IPv6 host address",
- )
- end_address = VarbinaryIPField(
- null=False,
- db_index=True,
- help_text="Ending IPv4 or IPv6 host address",
- )
- vrf = models.ForeignKey(
- to="ipam.VRF", on_delete=models.PROTECT, related_name="ip_ranges", blank=True, null=True, verbose_name="VRF"
- )
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- size = models.PositiveIntegerField(editable=False)
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["start_address"]
- verbose_name_plural = "IP Ranges"
- constraints = [
- UniqueConstraint(fields=["start_address", "end_address", "vrf"], name="unique_with_vrf"),
- UniqueConstraint(fields=["start_address", "end_address"], condition=Q(vrf=None), name="unique_without_vrf"),
- ]
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:iprange", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return f"{self.start_address}-{self.end_address}"
-
- def save(self, *args, **kwargs):
- """Overloads to inject size attr."""
- # Record the range's size (number of IP addresses)
- self.clean()
- self.size = int(IPAddress(self.end_address) - IPAddress(self.start_address)) + 1
- super().save(*args, **kwargs)
-
- def clean(self, *args, **kwargs):
- """Overloads to validate attr for form verification."""
- if not getattr(self, "start_address") or not getattr(self, "end_address"):
- raise ValidationError("Must have `start_address` and `end_address`.")
- if IPAddress(self.start_address) > IPAddress(self.end_address):
- raise ValidationError("`end_address` must be >= than `start_address`.")
-
- super().clean(*args, **kwargs)
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class FQDN(PrimaryModel):
- """Models fully qualified domain names, can be used on some firewall in place of a static IP."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(
- max_length=254, unique=True, help_text="Resolvable fully qualified domain name (e.g. networktocode.com)"
- )
- ip_addresses = models.ManyToManyField(
- to="ipam.IPAddress",
- blank=True,
- through="FQDNIPAddressM2M",
- related_name="fqdns",
- help_text="IP(s) an FQDN should resolve to.",
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name = "FQDN"
- verbose_name_plural = "FQDNs"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:fqdn", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class AddressObject(PrimaryModel):
- """Intermediate model to aggregate underlying address items, to allow for easier management."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for an address object type.")
- fqdn = models.ForeignKey(to=FQDN, on_delete=models.PROTECT, null=True, blank=True)
- ip_range = models.ForeignKey(to=IPRange, on_delete=models.PROTECT, null=True, blank=True)
- ip_address = models.ForeignKey(to="ipam.IPAddress", on_delete=models.PROTECT, null=True, blank=True)
- prefix = models.ForeignKey(to="ipam.Prefix", on_delete=models.PROTECT, null=True, blank=True)
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Address Objects"
-
- def get_address_info(self):
- """Method to Return the actual AddressObject type."""
- keys = ["ip_range", "fqdn", "prefix", "ip_address"]
- for key in keys:
- if getattr(self, key):
- return (key, getattr(self, key))
- return (None, None)
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:addressobject", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
- def save(self, *args, **kwargs):
- """Overloads to enforce clear."""
- self.clean()
- super().save(*args, **kwargs)
-
- def clean(self, *args, **kwargs):
- """Overloads to validate attr for form verification."""
- address_types = ["fqdn", "ip_range", "ip_address", "prefix"]
- address_count = 0
- for i in address_types:
- if hasattr(self, i) and getattr(self, i) is not None:
- address_count += 1
- if address_count != 1:
- raise ValidationError(f"Must specify only one address from type {address_types}, {address_count} found.")
-
- super().clean(*args, **kwargs)
-
- @property
- def address(self): # pylint: disable=inconsistent-return-statements
- """Returns the assigned address object."""
- for i in ["fqdn", "ip_range", "ip_address", "prefix"]:
- if getattr(self, i):
- return getattr(self, i)
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class AddressObjectGroup(PrimaryModel):
- """Groups together AddressObjects to better mimic grouping sets of address objects that have a some commonality."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for a group address objects.")
- address_objects = models.ManyToManyField(
- to=AddressObject, blank=True, through="AddressObjectGroupM2M", related_name="address_object_groups"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Address Object Groups"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:addressobjectgroup", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class UserObject(PrimaryModel):
- """Source users can be used to identify the origin of traffic for a user on some firewalls."""
-
- username = models.CharField(
- max_length=100, unique=True, help_text="Signifies the username in identify provider (e.g. john.smith)"
- )
- name = models.CharField(
- max_length=100,
- blank=True,
- help_text="Signifies the name of the user, commonly first & last name (e.g. John Smith)",
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["username"]
- verbose_name_plural = "User Objects"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:userobject", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.username
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class UserObjectGroup(PrimaryModel):
- """Grouping of individual user objects, does NOT have any relationship to AD groups or any other IDP group."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True)
- user_objects = models.ManyToManyField(
- to=UserObject, blank=True, through="UserObjectGroupM2M", related_name="user_object_groups"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "User Object Groups"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:userobjectgroup", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class Zone(PrimaryModel):
- """Zones common on firewalls and are typically seen as representations of area (e.g. DMZ trust untrust)."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True, help_text="Name of the zone (e.g. trust)")
- vrfs = models.ManyToManyField(to="ipam.VRF", blank=True, through="ZoneVRFM2M", related_name="zones")
- interfaces = models.ManyToManyField(
- to="dcim.Interface", blank=True, through="ZoneInterfaceM2M", related_name="zones"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Zones"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:zone", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class ServiceObject(PrimaryModel):
- """ServiceObject matches a IANA IP Protocol with a name and optional port number (e.g. TCP HTTPS 443)."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, help_text="Name of the service (e.g. HTTP)")
- port = models.CharField(
- null=True,
- blank=True,
- validators=[validators.validate_port],
- max_length=20,
- help_text="The port or port range to tie to a service (e.g. HTTP would be port 80)",
- )
- ip_protocol = models.CharField(
- choices=choices.IP_PROTOCOL_CHOICES, max_length=20, help_text="IANA IP Protocol (e.g. TCP UDP ICMP)"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Service Objects"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:serviceobject", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- if self.port:
- return f"{self.name} ({self.ip_protocol}/{self.port})"
- return f"{self.name} ({self.ip_protocol})"
-
- def save(self, *args, **kwargs):
- """Overload save to call full_clean to ensure validators run."""
- self.full_clean()
- super().save(*args, **kwargs)
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class ServiceObjectGroup(PrimaryModel):
- """Groups service objects."""
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True)
- service_objects = models.ManyToManyField(
- to=ServiceObject, blank=True, through="ServiceObjectGroupM2M", related_name="service_object_groups"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Service Object Groups"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:serviceobjectgroup", args=[self.pk])
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class PolicyRule(PrimaryModel):
- """
- A PolicyRule is a the equivalent of a single in a firewall policy or access list.
-
- Firewall policies are typically made up of several individual rules.
- """
-
- name = models.CharField(max_length=100)
- tags = TaggableManager(through=TaggedItem)
- 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_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"
- )
- 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_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"
- )
- 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(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
- 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)
-
- clone_fields = [
- "source_users",
- "source_user_groups",
- "source_addresses",
- "source_address_groups",
- "source_zone",
- "source_services",
- "source_service_groups",
- "destination_addresses",
- "destination_address_groups",
- "destination_zone",
- "destination_services",
- "destination_service_groups",
- "action",
- "log",
- "status",
- ]
-
- class Meta:
- """Meta class."""
-
- ordering = ["index"]
- verbose_name_plural = "Policy Rules"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:policyrule", args=[self.pk])
-
- def rule_details(self):
- """Convience method to convert to more consumable dictionary."""
- row = {}
- row["rule"] = self
- 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_groups"] = self.destination_address_groups.all()
- row["destination_addresses"] = self.destination_addresses.all()
- row["destination_zone"] = self.destination_zone
- row["destination_services"] = self.destination_services.all()
- row["destination_service_groups"] = self.destination_service_groups.all()
-
- row["action"] = self.action
- row["log"] = self.log
- row["status"] = self.status
- row["request_id"] = self.request_id
- return row
-
- def to_json(self):
- """Convience method to convert to json."""
- return model_to_json(self)
-
- def __str__(self):
- """Stringify instance."""
- if self.request_id and self.name:
- return f"{self.name} - {self.request_id}"
- if self.name:
- return self.name
- return str(self.id)
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class Policy(PrimaryModel):
- """
- The overarching model that is the full firewall policy with all underlying rules and child objects.
-
- Each Policy can be assigned to both devices and to dynamic groups which in turn can assign the policy to a related device.
- """
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True)
- policy_rules = models.ManyToManyField(to=PolicyRule, through="PolicyRuleM2M", related_name="policies")
- assigned_devices = models.ManyToManyField(
- to="dcim.Device", through="PolicyDeviceM2M", related_name="firewall_policies"
- )
- assigned_dynamic_groups = models.ManyToManyField(
- to="extras.DynamicGroup", through="PolicyDynamicGroupM2M", related_name="firewall_policies"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
- tenant = models.ForeignKey(
- to="tenancy.Tenant",
- on_delete=models.PROTECT,
- related_name="policies",
- blank=True,
- null=True,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "Policies"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:policy", args=[self.pk])
-
- def policy_details(self):
- """Convience method to convert to a Python list of dictionaries."""
- data = []
- for policy_rule in self.policy_rules.all():
- data.append(policy_rule.rule_details())
- return data
-
- def to_json(self):
- """Convience method to convert to json."""
- return model_to_json(self, "nautobot_firewall_models.api.serializers.PolicyDeepSerializer")
-
- def __str__(self):
- """Stringify instance."""
- return self.name
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class NATPolicyRule(PrimaryModel):
- """
- A NATPolicyRule is the equivalent of a single rule in a NAT policy or access list.
-
- NAT policies are typically made up of several individual rules.
- """
-
- # Metadata
- name = models.CharField(max_length=100)
- tags = TaggableManager(through=TaggedItem)
- remark = models.BooleanField(default=False)
- log = models.BooleanField(default=False)
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
- 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)
-
- # Data that can not undergo a translation
- source_zone = models.ForeignKey(
- to=Zone, null=True, blank=True, on_delete=models.SET_NULL, related_name="source_nat_policy_rules"
- )
- destination_zone = models.ForeignKey(
- to=Zone, on_delete=models.SET_NULL, null=True, blank=True, related_name="destination_nat_policy_rules"
- )
-
- # Original source data
- original_source_addresses = models.ManyToManyField(
- to=AddressObject, through="NATOrigSrcAddrM2M", related_name="original_source_nat_policy_rules"
- )
- original_source_address_groups = models.ManyToManyField(
- to=AddressObjectGroup, through="NATOrigSrcAddrGroupM2M", related_name="original_source_nat_policy_rules"
- )
- original_source_services = models.ManyToManyField(
- to=ServiceObject, through="NATOrigSrcSvcM2M", related_name="original_source_nat_policy_rules"
- )
- original_source_service_groups = models.ManyToManyField(
- to=ServiceObjectGroup, through="NATOrigSrcSvcGroupM2M", related_name="original_source_nat_policy_rules"
- )
-
- # Translated source data
- translated_source_addresses = models.ManyToManyField(
- to=AddressObject, through="NATTransSrcAddrM2M", related_name="translated_source_nat_policy_rules"
- )
- translated_source_address_groups = models.ManyToManyField(
- to=AddressObjectGroup, through="NATTransSrcAddrGroupM2M", related_name="translated_source_nat_policy_rules"
- )
- translated_source_services = models.ManyToManyField(
- to=ServiceObject, through="NATTransSrcSvcM2M", related_name="translated_source_nat_policy_rules"
- )
- translated_source_service_groups = models.ManyToManyField(
- to=ServiceObjectGroup, through="NATTransSrcSvcGroupM2M", related_name="translated_source_nat_policy_rules"
- )
-
- # Original destination data
- original_destination_addresses = models.ManyToManyField(
- to=AddressObject, through="NATOrigDestAddrM2M", related_name="original_destination_nat_policy_rules"
- )
- original_destination_address_groups = models.ManyToManyField(
- to=AddressObjectGroup, through="NATOrigDestAddrGroupM2M", related_name="original_destination_nat_policy_rules"
- )
- original_destination_services = models.ManyToManyField(
- to=ServiceObject, through="NATOrigDestSvcM2M", related_name="original_destination_nat_policy_rules"
- )
- original_destination_service_groups = models.ManyToManyField(
- to=ServiceObjectGroup, through="NATOrigDestSvcGroupM2M", related_name="original_destination_nat_policy_rules"
- )
-
- # Translated destination data
- translated_destination_addresses = models.ManyToManyField(
- to=AddressObject, through="NATTransDestAddrM2M", related_name="translated_destination_nat_policy_rules"
- )
- translated_destination_address_groups = models.ManyToManyField(
- to=AddressObjectGroup,
- through="NATTransDestAddrGroupM2M",
- related_name="translated_destination_nat_policy_rules",
- )
- translated_destination_services = models.ManyToManyField(
- to=ServiceObject, through="NATTransDestSvcM2M", related_name="translated_destination_nat_policy_rules"
- )
- translated_destination_service_groups = models.ManyToManyField(
- to=ServiceObjectGroup, through="NATTransDestSvcGroupM2M", related_name="translated_destination_nat_policy_rules"
- )
-
- clone_fields = [
- "destination_zone",
- "source_zone",
- "original_source_addresses",
- "original_source_address_groups",
- "original_source_services",
- "original_source_service_groups",
- "original_destination_addresses",
- "original_destination_address_groups",
- "original_destination_services",
- "original_destination_service_groups",
- "translated_source_addresses",
- "translated_source_address_groups",
- "translated_source_services",
- "translated_source_service_groups",
- "translated_destination_addresses",
- "translated_destination_address_groups",
- "translated_destination_services",
- "translated_destination_service_groups",
- "remark",
- "log",
- "status",
- ]
-
- class Meta:
- """Meta class."""
-
- ordering = ["index"]
- verbose_name_plural = "NAT Policy Rules"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:natpolicyrule", args=[self.pk])
-
- def rule_details(self):
- """Convenience method to convert to more consumable dictionary."""
- row = {}
- row["rule"] = self
- row["source_zone"] = self.source_zone
- row["destination_zone"] = self.destination_zone
-
- row["original_source_address_groups"] = self.original_source_address_groups.all()
- row["original_source_addresses"] = self.original_source_addresses.all()
- row["original_source_services"] = self.original_source_services.all()
- row["original_source_service_groups"] = self.original_source_service_groups.all()
-
- row["translated_source_address_groups"] = self.translated_source_address_groups.all()
- row["translated_source_addresses"] = self.translated_source_addresses.all()
- row["translated_source_services"] = self.translated_source_services.all()
- row["translated_source_service_groups"] = self.translated_source_service_groups.all()
-
- row["original_destination_address_groups"] = self.original_destination_address_groups.all()
- row["original_destination_addresses"] = self.original_destination_addresses.all()
- row["original_destination_services"] = self.original_destination_services.all()
- row["original_destination_service_groups"] = self.original_destination_service_groups.all()
-
- row["translated_destination_address_groups"] = self.translated_destination_address_groups.all()
- row["translated_destination_addresses"] = self.translated_destination_addresses.all()
- row["translated_destination_services"] = self.translated_destination_services.all()
- row["translated_destination_service_groups"] = self.translated_destination_service_groups.all()
-
- row["remark"] = self.remark
- row["log"] = self.log
- row["status"] = self.status
- row["request_id"] = self.request_id
- return row
-
- def to_json(self):
- """Convenience method to convert to json."""
- return model_to_json(self)
-
- def __str__(self):
- """Stringify instance."""
- if self.request_id and self.name:
- return f"{self.name} - {self.request_id}"
- if self.name:
- return self.name
- return str(self.id)
-
-
-@extras_features(
- "custom_fields",
- "custom_links",
- "custom_validators",
- "export_templates",
- "graphql",
- "relationships",
- "statuses",
- "webhooks",
-)
-class NATPolicy(PrimaryModel):
- """
- The overarching model that is the full NAT policy with all underlying rules and child objects.
-
- Each NATPolicy can be assigned to both devices and to dynamic groups which in turn can assign the policy to a related device.
- """
-
- description = models.CharField(
- max_length=200,
- blank=True,
- )
- name = models.CharField(max_length=100, unique=True)
- nat_policy_rules = models.ManyToManyField(to=NATPolicyRule, through="NATPolicyRuleM2M", related_name="nat_policies")
- assigned_devices = models.ManyToManyField(
- to="dcim.Device", through="NATPolicyDeviceM2M", related_name="nat_policies"
- )
- assigned_dynamic_groups = models.ManyToManyField(
- to="extras.DynamicGroup", through="NATPolicyDynamicGroupM2M", related_name="nat_policies"
- )
- status = StatusField(
- on_delete=models.PROTECT,
- related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
- default=get_default_status,
- )
- tenant = models.ForeignKey(
- to="tenancy.Tenant",
- on_delete=models.PROTECT,
- related_name="nat_policies",
- blank=True,
- null=True,
- )
-
- class Meta:
- """Meta class."""
-
- ordering = ["name"]
- verbose_name_plural = "NAT Policies"
-
- def get_absolute_url(self):
- """Return detail view URL."""
- return reverse("plugins:nautobot_firewall_models:natpolicy", args=[self.pk])
-
- def policy_details(self):
- """Convenience method to convert to a Python list of dictionaries."""
- return [rule.rule_details() for rule in self.nat_policy_rules.all()]
-
- def to_json(self):
- """Convenience method to convert to json."""
- return model_to_json(self)
-
- def __str__(self):
- """Stringify instance."""
- return self.name
diff --git a/nautobot_firewall_models/models/nat_policy.py b/nautobot_firewall_models/models/nat_policy.py
new file mode 100644
index 00000000..74da06dc
--- /dev/null
+++ b/nautobot_firewall_models/models/nat_policy.py
@@ -0,0 +1,472 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code, too-many-lines
+
+from django.db import models
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.models.tags import TaggedItem
+from nautobot.extras.utils import extras_features
+from taggit.managers import TaggableManager
+
+from nautobot_firewall_models.utils import get_default_status, model_to_json
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class NATPolicyRule(PrimaryModel):
+ """
+ A NATPolicyRule is the equivalent of a single rule in a NAT policy or access list.
+
+ NAT policies are typically made up of several individual rules.
+ """
+
+ # Metadata
+ name = models.CharField(max_length=100)
+ tags = TaggableManager(through=TaggedItem)
+ remark = models.BooleanField(default=False)
+ log = models.BooleanField(default=False)
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+ 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)
+
+ # Data that can not undergo a translation
+ source_zone = models.ForeignKey(
+ to="nautobot_firewall_models.Zone",
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="source_nat_policy_rules",
+ )
+ destination_zone = models.ForeignKey(
+ to="nautobot_firewall_models.Zone",
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ related_name="destination_nat_policy_rules",
+ )
+
+ # Original source data
+ original_source_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject",
+ through="NATOrigSrcAddrM2M",
+ related_name="original_source_nat_policy_rules",
+ )
+ original_source_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup",
+ through="NATOrigSrcAddrGroupM2M",
+ related_name="original_source_nat_policy_rules",
+ )
+ original_source_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject",
+ through="NATOrigSrcSvcM2M",
+ related_name="original_source_nat_policy_rules",
+ )
+ original_source_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObjectGroup",
+ through="NATOrigSrcSvcGroupM2M",
+ related_name="original_source_nat_policy_rules",
+ )
+
+ # Translated source data
+ translated_source_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject",
+ through="NATTransSrcAddrM2M",
+ related_name="translated_source_nat_policy_rules",
+ )
+ translated_source_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup",
+ through="NATTransSrcAddrGroupM2M",
+ related_name="translated_source_nat_policy_rules",
+ )
+ translated_source_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject",
+ through="NATTransSrcSvcM2M",
+ related_name="translated_source_nat_policy_rules",
+ )
+ translated_source_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObjectGroup",
+ through="NATTransSrcSvcGroupM2M",
+ related_name="translated_source_nat_policy_rules",
+ )
+
+ # Original destination data
+ original_destination_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject",
+ through="NATOrigDestAddrM2M",
+ related_name="original_destination_nat_policy_rules",
+ )
+ original_destination_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup",
+ through="NATOrigDestAddrGroupM2M",
+ related_name="original_destination_nat_policy_rules",
+ )
+ original_destination_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject",
+ through="NATOrigDestSvcM2M",
+ related_name="original_destination_nat_policy_rules",
+ )
+ original_destination_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObjectGroup",
+ through="NATOrigDestSvcGroupM2M",
+ related_name="original_destination_nat_policy_rules",
+ )
+
+ # Translated destination data
+ translated_destination_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject",
+ through="NATTransDestAddrM2M",
+ related_name="translated_destination_nat_policy_rules",
+ )
+ translated_destination_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup",
+ through="NATTransDestAddrGroupM2M",
+ related_name="translated_destination_nat_policy_rules",
+ )
+ translated_destination_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject",
+ through="NATTransDestSvcM2M",
+ related_name="translated_destination_nat_policy_rules",
+ )
+ translated_destination_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObjectGroup",
+ through="NATTransDestSvcGroupM2M",
+ related_name="translated_destination_nat_policy_rules",
+ )
+
+ clone_fields = [
+ "destination_zone",
+ "source_zone",
+ "original_source_addresses",
+ "original_source_address_groups",
+ "original_source_services",
+ "original_source_service_groups",
+ "original_destination_addresses",
+ "original_destination_address_groups",
+ "original_destination_services",
+ "original_destination_service_groups",
+ "translated_source_addresses",
+ "translated_source_address_groups",
+ "translated_source_services",
+ "translated_source_service_groups",
+ "translated_destination_addresses",
+ "translated_destination_address_groups",
+ "translated_destination_services",
+ "translated_destination_service_groups",
+ "remark",
+ "log",
+ "status",
+ ]
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["index"]
+ verbose_name = "NAT Policy Rule"
+ verbose_name_plural = "NAT Policy Rules"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:natpolicyrule", args=[self.pk])
+
+ def rule_details(self):
+ """Convenience method to convert to more consumable dictionary."""
+ row = {}
+ row["rule"] = self
+ row["source_zone"] = self.source_zone
+ row["destination_zone"] = self.destination_zone
+
+ row["original_source_address_groups"] = self.original_source_address_groups.all()
+ row["original_source_addresses"] = self.original_source_addresses.all()
+ row["original_source_services"] = self.original_source_services.all()
+ row["original_source_service_groups"] = self.original_source_service_groups.all()
+
+ row["translated_source_address_groups"] = self.translated_source_address_groups.all()
+ row["translated_source_addresses"] = self.translated_source_addresses.all()
+ row["translated_source_services"] = self.translated_source_services.all()
+ row["translated_source_service_groups"] = self.translated_source_service_groups.all()
+
+ row["original_destination_address_groups"] = self.original_destination_address_groups.all()
+ row["original_destination_addresses"] = self.original_destination_addresses.all()
+ row["original_destination_services"] = self.original_destination_services.all()
+ row["original_destination_service_groups"] = self.original_destination_service_groups.all()
+
+ row["translated_destination_address_groups"] = self.translated_destination_address_groups.all()
+ row["translated_destination_addresses"] = self.translated_destination_addresses.all()
+ row["translated_destination_services"] = self.translated_destination_services.all()
+ row["translated_destination_service_groups"] = self.translated_destination_service_groups.all()
+
+ row["remark"] = self.remark
+ row["log"] = self.log
+ row["status"] = self.status
+ row["request_id"] = self.request_id
+ return row
+
+ def to_json(self):
+ """Convenience method to convert to json."""
+ return model_to_json(self)
+
+ def __str__(self):
+ """Stringify instance."""
+ if self.request_id and self.name:
+ return f"{self.name} - {self.request_id}"
+ if self.name:
+ return self.name
+ return str(self.id)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class NATPolicy(PrimaryModel):
+ """
+ The overarching model that is the full NAT policy with all underlying rules and child objects.
+
+ Each NATPolicy can be assigned to both devices and to dynamic groups which in turn can assign the policy to a related device.
+ """
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True)
+ nat_policy_rules = models.ManyToManyField(to=NATPolicyRule, through="NATPolicyRuleM2M", related_name="nat_policies")
+ assigned_devices = models.ManyToManyField(
+ to="dcim.Device", through="NATPolicyDeviceM2M", related_name="nat_policies"
+ )
+ assigned_dynamic_groups = models.ManyToManyField(
+ to="extras.DynamicGroup", through="NATPolicyDynamicGroupM2M", related_name="nat_policies"
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+ tenant = models.ForeignKey(
+ to="tenancy.Tenant",
+ on_delete=models.PROTECT,
+ related_name="nat_policies",
+ blank=True,
+ null=True,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name = "NAT Policy"
+ verbose_name_plural = "NAT Policies"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:natpolicy", args=[self.pk])
+
+ def policy_details(self):
+ """Convenience method to convert to a Python list of dictionaries."""
+ return [rule.rule_details() for rule in self.nat_policy_rules.all()]
+
+ def to_json(self):
+ """Convenience method to convert to json."""
+ return model_to_json(self)
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class NATOrigDestAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination AddressObject if assigned to a NATPolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigDestAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination AddressObjectGroup if assigned to a NATPolicyRule."""
+
+ user = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigDestSvcGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination ServiceObjectGroup if assigned to a NATPolicyRule."""
+
+ svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigDestSvcM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination ServiceObject if assigned to a NATPolicyRule."""
+
+ svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigSrcAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source AddressObjectGroup if assigned to a NATPolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigSrcAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source AddressObject if assigned to a NATPolicyRule."""
+
+ addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigSrcSvcGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source ServiceObjectGroup if assigned to a NATPolicyRule."""
+
+ svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATOrigSrcSvcM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source ServiceObject if assigned to a NATPolicyRule."""
+
+ svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATPolicyDeviceM2M(BaseModel):
+ """Through model to add weight to the NATPolicy & Device relationship."""
+
+ nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
+ device = models.ForeignKey("dcim.Device", on_delete=models.PROTECT)
+ weight = models.PositiveSmallIntegerField(default=100)
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["weight"]
+ unique_together = ["nat_policy", "device"]
+
+
+class NATPolicyDynamicGroupM2M(BaseModel):
+ """Through model to add weight to the NATPolicy & DynamicGroup relationship."""
+
+ nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
+ dynamic_group = models.ForeignKey("extras.DynamicGroup", on_delete=models.PROTECT)
+ weight = models.PositiveSmallIntegerField(default=100)
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["weight"]
+ unique_together = ["nat_policy", "dynamic_group"]
+
+
+class NATPolicyNATRuleM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated NATPolicyRule if assigned to a NATPolicy."""
+
+ nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
+ nat_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.PROTECT)
+
+
+class NATPolicyRuleM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated NATPolicyRule if assigned to a NATPolicy."""
+
+ nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
+ nat_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.PROTECT)
+
+
+class NATSrcUserGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated UserGroup if assigned to a NATPolicyRule."""
+
+ user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATSrcUserM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a NATPolicyRule."""
+
+ user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransDestAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination AddressObjectGroup if assigned to a NATPolicyRule."""
+
+ user = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransDestAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination AddressObject if assigned to a NATPolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransDestSvcGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination ServiceObjectGroup if assigned to a NATPolicyRule."""
+
+ svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransDestSvcM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination ServiceObject if assigned to a NATPolicyRule."""
+
+ svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransSrcAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source AddressObjectGroup if assigned to a NATPolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransSrcAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source AddressObject if assigned to a NATPolicyRule."""
+
+ addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransSrcSvcGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source ServiceObjectGroup if assigned to a NATPolicyRule."""
+
+ svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
+
+
+class NATTransSrcSvcM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source ServiceObject if assigned to a NATPolicyRule."""
+
+ svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
+ nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
diff --git a/nautobot_firewall_models/models/security_policy.py b/nautobot_firewall_models/models/security_policy.py
new file mode 100644
index 00000000..61806b8d
--- /dev/null
+++ b/nautobot_firewall_models/models/security_policy.py
@@ -0,0 +1,364 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code, too-many-lines
+
+from django.db import models
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.models.tags import TaggedItem
+from nautobot.extras.utils import extras_features
+from taggit.managers import TaggableManager
+
+from nautobot_firewall_models import choices
+from nautobot_firewall_models.utils import get_default_status, model_to_json
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class PolicyRule(PrimaryModel):
+ """
+ A PolicyRule is a the equivalent of a single in a firewall policy or access list.
+
+ Firewall policies are typically made up of several individual rules.
+ """
+
+ name = models.CharField(max_length=100)
+ tags = TaggableManager(through=TaggedItem)
+ source_users = models.ManyToManyField(
+ to="nautobot_firewall_models.UserObject", through="SrcUserM2M", related_name="policy_rules"
+ )
+ source_user_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.UserObjectGroup", through="SrcUserGroupM2M", related_name="policy_rules"
+ )
+ source_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject", through="SrcAddrM2M", related_name="source_policy_rules"
+ )
+ source_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup", through="SrcAddrGroupM2M", related_name="source_policy_rules"
+ )
+ source_zone = models.ForeignKey(
+ to="nautobot_firewall_models.Zone",
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="source_policy_rules",
+ )
+ source_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject", through="SrcSvcM2M", related_name="source_policy_rules"
+ )
+ source_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObjectGroup", through="SrcSvcGroupM2M", related_name="source_policy_rules"
+ )
+ destination_addresses = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObject", through="DestAddrM2M", related_name="destination_policy_rules"
+ )
+ destination_address_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.AddressObjectGroup",
+ through="DestAddrGroupM2M",
+ related_name="destination_policy_rules",
+ )
+ destination_zone = models.ForeignKey(
+ to="nautobot_firewall_models.Zone",
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ related_name="destination_policy_rules",
+ )
+ destination_services = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject", through="DestSvcM2M", related_name="destination_policy_rules"
+ )
+ destination_service_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.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(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+ applications = models.ManyToManyField(
+ to="nautobot_firewall_models.ApplicationObject", through="ApplicationM2M", related_name="policy_rules"
+ )
+ application_groups = models.ManyToManyField(
+ to="nautobot_firewall_models.ApplicationObjectGroup", through="ApplicationGroupM2M", related_name="policy_rules"
+ )
+ 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)
+
+ clone_fields = [
+ "source_users",
+ "source_user_groups",
+ "source_addresses",
+ "source_address_groups",
+ "source_zone",
+ "source_services",
+ "source_service_groups",
+ "destination_addresses",
+ "destination_address_groups",
+ "destination_zone",
+ "destination_services",
+ "destination_service_groups",
+ "action",
+ "log",
+ "status",
+ ]
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["index"]
+ verbose_name_plural = "Policy Rules"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:policyrule", args=[self.pk])
+
+ def rule_details(self):
+ """Convience method to convert to more consumable dictionary."""
+ row = {}
+ row["rule"] = self
+ 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_groups"] = self.destination_address_groups.all()
+ row["destination_addresses"] = self.destination_addresses.all()
+ row["destination_zone"] = self.destination_zone
+ row["destination_services"] = self.destination_services.all()
+ row["destination_service_groups"] = self.destination_service_groups.all()
+
+ row["action"] = self.action
+ row["log"] = self.log
+ row["status"] = self.status
+ row["request_id"] = self.request_id
+ return row
+
+ def to_json(self):
+ """Convience method to convert to json."""
+ return model_to_json(self)
+
+ def __str__(self):
+ """Stringify instance."""
+ if self.request_id and self.name:
+ return f"{self.name} - {self.request_id}"
+ if self.name:
+ return self.name
+ return str(self.id)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class Policy(PrimaryModel):
+ """
+ The overarching model that is the full firewall policy with all underlying rules and child objects.
+
+ Each Policy can be assigned to both devices and to dynamic groups which in turn can assign the policy to a related device.
+ """
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True)
+ policy_rules = models.ManyToManyField(to=PolicyRule, through="PolicyRuleM2M", related_name="policies")
+ assigned_devices = models.ManyToManyField(
+ to="dcim.Device", through="PolicyDeviceM2M", related_name="firewall_policies"
+ )
+ assigned_dynamic_groups = models.ManyToManyField(
+ to="extras.DynamicGroup", through="PolicyDynamicGroupM2M", related_name="firewall_policies"
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+ tenant = models.ForeignKey(
+ to="tenancy.Tenant",
+ on_delete=models.PROTECT,
+ related_name="policies",
+ blank=True,
+ null=True,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Policies"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:policy", args=[self.pk])
+
+ def policy_details(self):
+ """Convience method to convert to a Python list of dictionaries."""
+ data = []
+ for policy_rule in self.policy_rules.all():
+ data.append(policy_rule.rule_details())
+ return data
+
+ def to_json(self):
+ """Convience method to convert to json."""
+ return model_to_json(self, "nautobot_firewall_models.api.serializers.PolicyDeepSerializer")
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class ApplicationM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination ApplicationObject if assigned to a PolicyRule."""
+
+ app = models.ForeignKey("nautobot_firewall_models.ApplicationObject", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class ApplicationGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination ApplicationObjectGroup if assigned to a PolicyRule."""
+
+ app_group = models.ForeignKey("nautobot_firewall_models.ApplicationObjectGroup", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class DestAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination Address if assigned to a PolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class DestAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination AddressGroup if assigned to a PolicyRule."""
+
+ user = models.ForeignKey("nautobot_firewall_models.AddressObject", 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 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)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class PolicyDeviceM2M(BaseModel):
+ """Through model to add weight to the the Policy & Device relationship."""
+
+ policy = models.ForeignKey("nautobot_firewall_models.Policy", on_delete=models.CASCADE)
+ device = models.ForeignKey("dcim.Device", on_delete=models.PROTECT)
+ weight = models.PositiveSmallIntegerField(default=100)
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["weight"]
+ unique_together = ["policy", "device"]
+
+
+class PolicyDynamicGroupM2M(BaseModel):
+ """Through model to add weight to the the Policy & DynamicGroup relationship."""
+
+ policy = models.ForeignKey("nautobot_firewall_models.Policy", on_delete=models.CASCADE)
+ dynamic_group = models.ForeignKey("extras.DynamicGroup", on_delete=models.PROTECT)
+ weight = models.PositiveSmallIntegerField(default=100)
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["weight"]
+ unique_together = ["policy", "dynamic_group"]
+
+
+class PolicyRuleM2M(BaseModel):
+ """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)
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["rule__index"]
+
+
+class SrcAddrM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated source Address if assigned to a PolicyRule."""
+
+ addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class SrcAddrGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated source AddressGroup if assigned to a PolicyRule."""
+
+ addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class SrcUserM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a PolicyRule."""
+
+ user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+class SrcUserGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated UserGroup if assigned to a PolicyRule."""
+
+ user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.PROTECT)
+ pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
+
+
+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)
diff --git a/nautobot_firewall_models/models/service.py b/nautobot_firewall_models/models/service.py
new file mode 100644
index 00000000..ea370580
--- /dev/null
+++ b/nautobot_firewall_models/models/service.py
@@ -0,0 +1,248 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code, too-many-lines
+
+from django.db import models
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.utils import extras_features
+
+from nautobot_firewall_models import choices, validators
+from nautobot_firewall_models.utils import get_default_status
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class ApplicationObject(PrimaryModel):
+ """Intermediate model to aggregate underlying application items, to allow for easier management."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ category = models.CharField(max_length=48, blank=True, help_text="Category of application.")
+ subcategory = models.CharField(max_length=48, blank=True, help_text="Sub-category of application.")
+ technology = models.CharField(max_length=48, blank=True, help_text="Type of application technology.")
+ risk = models.PositiveIntegerField(blank=True, help_text="Assessed risk of the application.")
+ default_type = models.CharField(max_length=48, blank=True, help_text="Default type, i.e. port or app-id.")
+ name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for an application object type.")
+ default_ip_protocol = models.CharField(
+ max_length=48, blank=True, help_text="Name descriptor for an application object type."
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Application Objects"
+
+ def get_application_info(self):
+ """Method to Return the actual ApplicationObject type."""
+ keys = ["description", "category", "subcategory", "name"]
+ for key in keys:
+ if getattr(self, key):
+ return (key, getattr(self, key))
+ return (None, None)
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:applicationobject", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+ def save(self, *args, **kwargs):
+ """Overloads to enforce clear."""
+ self.clean()
+ super().save(*args, **kwargs)
+
+ @property
+ def application(self): # pylint: disable=inconsistent-return-statements
+ """Returns the assigned application object."""
+ for i in ["description", "category", "subcategory", "name"]:
+ if getattr(self, i):
+ return getattr(self, i)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class ApplicationObjectGroup(PrimaryModel):
+ """Groups together ApplicationObjects to better mimic grouping sets of application objects that have a some commonality."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True, help_text="Name descriptor for a group application objects.")
+ application_objects = models.ManyToManyField(
+ to="nautobot_firewall_models.ApplicationObject",
+ blank=True,
+ through="ApplicationObjectGroupM2M",
+ related_name="application_object_groups",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Application Object Groups"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:applicationobjectgroup", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class ServiceObject(PrimaryModel):
+ """ServiceObject matches a IANA IP Protocol with a name and optional port number (e.g. TCP HTTPS 443)."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, help_text="Name of the service (e.g. HTTP)")
+ port = models.CharField(
+ null=True,
+ blank=True,
+ validators=[validators.validate_port],
+ max_length=20,
+ help_text="The port or port range to tie to a service (e.g. HTTP would be port 80)",
+ )
+ ip_protocol = models.CharField(
+ choices=choices.IP_PROTOCOL_CHOICES, max_length=20, help_text="IANA IP Protocol (e.g. TCP UDP ICMP)"
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Service Objects"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:serviceobject", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ if self.port:
+ return f"{self.name} ({self.ip_protocol}/{self.port})"
+ return f"{self.name} ({self.ip_protocol})"
+
+ def save(self, *args, **kwargs):
+ """Overload save to call full_clean to ensure validators run."""
+ self.full_clean()
+ super().save(*args, **kwargs)
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class ServiceObjectGroup(PrimaryModel):
+ """Groups service objects."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True)
+ service_objects = models.ManyToManyField(
+ to="nautobot_firewall_models.ServiceObject",
+ blank=True,
+ through="ServiceObjectGroupM2M",
+ related_name="service_object_groups",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Service Object Groups"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:serviceobjectgroup", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class ApplicationObjectGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated ApplicationObject if assigned to a ApplicationObjectGroup."""
+
+ application = models.ForeignKey("nautobot_firewall_models.ApplicationObject", on_delete=models.PROTECT)
+ application_group = models.ForeignKey("nautobot_firewall_models.ApplicationObjectGroup", on_delete=models.CASCADE)
+
+
+class ServiceObjectGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated ServiceGroup if assigned to a PolicyRule."""
+
+ service = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
+ service_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.CASCADE)
diff --git a/nautobot_firewall_models/models/through_models.py b/nautobot_firewall_models/models/through_models.py
deleted file mode 100644
index f6534936..00000000
--- a/nautobot_firewall_models/models/through_models.py
+++ /dev/null
@@ -1,324 +0,0 @@
-"""Set of through intermediate models."""
-
-from django.db import models
-from nautobot.core.models.generics import BaseModel
-
-
-class AddressObjectGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated AddressObject if assigned to a AddressObjectGroup."""
-
- address = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- address_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.CASCADE)
-
-
-class DestAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination Address if assigned to a PolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class DestAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated destination AddressGroup if assigned to a PolicyRule."""
-
- user = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class FQDNIPAddressM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated IPAddress if assigned to a FQDN."""
-
- fqdn = models.ForeignKey("nautobot_firewall_models.FQDN", on_delete=models.CASCADE)
- ip_address = models.ForeignKey("ipam.IPAddress", on_delete=models.PROTECT)
-
-
-class PolicyDeviceM2M(BaseModel):
- """Through model to add weight to the the Policy & Device relationship."""
-
- policy = models.ForeignKey("nautobot_firewall_models.Policy", on_delete=models.CASCADE)
- device = models.ForeignKey("dcim.Device", on_delete=models.PROTECT)
- weight = models.PositiveSmallIntegerField(default=100)
-
- class Meta:
- """Meta class."""
-
- ordering = ["weight"]
- unique_together = ["policy", "device"]
-
-
-class PolicyDynamicGroupM2M(BaseModel):
- """Through model to add weight to the the Policy & DynamicGroup relationship."""
-
- policy = models.ForeignKey("nautobot_firewall_models.Policy", on_delete=models.CASCADE)
- dynamic_group = models.ForeignKey("extras.DynamicGroup", on_delete=models.PROTECT)
- weight = models.PositiveSmallIntegerField(default=100)
-
- class Meta:
- """Meta class."""
-
- ordering = ["weight"]
- unique_together = ["policy", "dynamic_group"]
-
-
-class PolicyRuleM2M(BaseModel):
- """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)
-
- class Meta:
- """Meta class."""
-
- ordering = ["rule__index"]
-
-
-class ServiceObjectGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated ServiceGroup if assigned to a PolicyRule."""
-
- service = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
- service_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.CASCADE)
-
-
-class SrcAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated source Address if assigned to a PolicyRule."""
-
- addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class SrcAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated source AddressGroup if assigned to a PolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class SrcUserM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a PolicyRule."""
-
- user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class SrcUserGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated UserGroup if assigned to a PolicyRule."""
-
- user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.PROTECT)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-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 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)
- pol_rule = models.ForeignKey("nautobot_firewall_models.PolicyRule", on_delete=models.CASCADE)
-
-
-class UserObjectGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a UserGroup."""
-
- user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
- user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.CASCADE)
-
-
-class ZoneInterfaceM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated Interface if assigned to a Zone."""
-
- zone = models.ForeignKey("nautobot_firewall_models.Zone", on_delete=models.CASCADE)
- interface = models.ForeignKey("dcim.Interface", on_delete=models.PROTECT)
-
-
-class ZoneVRFM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated VRF if assigned to a Zone."""
-
- zone = models.ForeignKey("nautobot_firewall_models.Zone", on_delete=models.CASCADE)
- vrf = models.ForeignKey("ipam.vrf", on_delete=models.PROTECT)
-
-
-class NATPolicyNATRuleM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated NATPolicyRule if assigned to a NATPolicy."""
-
- nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
- nat_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.PROTECT)
-
-
-class NATPolicyRuleM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated NATPolicyRule if assigned to a NATPolicy."""
-
- nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
- nat_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.PROTECT)
-
-
-class NATPolicyDeviceM2M(BaseModel):
- """Through model to add weight to the NATPolicy & Device relationship."""
-
- nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
- device = models.ForeignKey("dcim.Device", on_delete=models.PROTECT)
- weight = models.PositiveSmallIntegerField(default=100)
-
- class Meta:
- """Meta class."""
-
- ordering = ["weight"]
- unique_together = ["nat_policy", "device"]
-
-
-class NATPolicyDynamicGroupM2M(BaseModel):
- """Through model to add weight to the NATPolicy & DynamicGroup relationship."""
-
- nat_policy = models.ForeignKey("nautobot_firewall_models.NATPolicy", on_delete=models.CASCADE)
- dynamic_group = models.ForeignKey("extras.DynamicGroup", on_delete=models.PROTECT)
- weight = models.PositiveSmallIntegerField(default=100)
-
- class Meta:
- """Meta class."""
-
- ordering = ["weight"]
- unique_together = ["nat_policy", "dynamic_group"]
-
-
-class NATSrcUserM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a NATPolicyRule."""
-
- user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATSrcUserGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated UserGroup if assigned to a NATPolicyRule."""
-
- user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigSrcAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source AddressObject if assigned to a NATPolicyRule."""
-
- addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigSrcAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source AddressObjectGroup if assigned to a NATPolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigSrcSvcM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source ServiceObject if assigned to a NATPolicyRule."""
-
- svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigSrcSvcGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original source ServiceObjectGroup if assigned to a NATPolicyRule."""
-
- svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransSrcAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source AddressObject if assigned to a NATPolicyRule."""
-
- addr = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransSrcAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source AddressObjectGroup if assigned to a NATPolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransSrcSvcM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source ServiceObject if assigned to a NATPolicyRule."""
-
- svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransSrcSvcGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated source ServiceObjectGroup if assigned to a NATPolicyRule."""
-
- svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigDestAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination AddressObjectGroup if assigned to a NATPolicyRule."""
-
- user = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigDestAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination AddressObject if assigned to a NATPolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigDestSvcM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination ServiceObject if assigned to a NATPolicyRule."""
-
- svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATOrigDestSvcGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated original destination ServiceObjectGroup if assigned to a NATPolicyRule."""
-
- svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransDestAddrM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination AddressObjectGroup if assigned to a NATPolicyRule."""
-
- user = models.ForeignKey("nautobot_firewall_models.AddressObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransDestAddrGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination AddressObject if assigned to a NATPolicyRule."""
-
- addr_group = models.ForeignKey("nautobot_firewall_models.AddressObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransDestSvcM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination ServiceObject if assigned to a NATPolicyRule."""
-
- svc = models.ForeignKey("nautobot_firewall_models.ServiceObject", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
-
-
-class NATTransDestSvcGroupM2M(BaseModel):
- """Custom through model to on_delete=models.PROTECT to prevent deleting associated translated destination ServiceObjectGroup if assigned to a NATPolicyRule."""
-
- svc_group = models.ForeignKey("nautobot_firewall_models.ServiceObjectGroup", on_delete=models.PROTECT)
- nat_pol_rule = models.ForeignKey("nautobot_firewall_models.NATPolicyRule", on_delete=models.CASCADE)
diff --git a/nautobot_firewall_models/models/user.py b/nautobot_firewall_models/models/user.py
new file mode 100644
index 00000000..3c436e28
--- /dev/null
+++ b/nautobot_firewall_models/models/user.py
@@ -0,0 +1,114 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code
+
+from django.db import models
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.utils import extras_features
+
+from nautobot_firewall_models.utils import get_default_status
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class UserObject(PrimaryModel):
+ """Source users can be used to identify the origin of traffic for a user on some firewalls."""
+
+ username = models.CharField(
+ max_length=100, unique=True, help_text="Signifies the username in identify provider (e.g. john.smith)"
+ )
+ name = models.CharField(
+ max_length=100,
+ blank=True,
+ help_text="Signifies the name of the user, commonly first & last name (e.g. John Smith)",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["username"]
+ verbose_name_plural = "User Objects"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:userobject", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.username
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class UserObjectGroup(PrimaryModel):
+ """Grouping of individual user objects, does NOT have any relationship to AD groups or any other IDP group."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True)
+ user_objects = models.ManyToManyField(
+ to="nautobot_firewall_models.UserObject",
+ blank=True,
+ through="UserObjectGroupM2M",
+ related_name="user_object_groups",
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "User Object Groups"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:userobjectgroup", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class UserObjectGroupM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated User if assigned to a UserGroup."""
+
+ user = models.ForeignKey("nautobot_firewall_models.UserObject", on_delete=models.PROTECT)
+ user_group = models.ForeignKey("nautobot_firewall_models.UserObjectGroup", on_delete=models.CASCADE)
diff --git a/nautobot_firewall_models/models/zone.py b/nautobot_firewall_models/models/zone.py
new file mode 100644
index 00000000..45a05fa5
--- /dev/null
+++ b/nautobot_firewall_models/models/zone.py
@@ -0,0 +1,77 @@
+"""Models for the Firewall plugin."""
+# pylint: disable=duplicate-code
+
+from django.db import models
+from django.urls import reverse
+from nautobot.core.models.generics import BaseModel, PrimaryModel
+from nautobot.extras.models import StatusField
+from nautobot.extras.utils import extras_features
+
+from nautobot_firewall_models.utils import get_default_status
+
+
+###########################
+# Core Models
+###########################
+
+
+@extras_features(
+ "custom_fields",
+ "custom_links",
+ "custom_validators",
+ "export_templates",
+ "graphql",
+ "relationships",
+ "statuses",
+ "webhooks",
+)
+class Zone(PrimaryModel):
+ """Zones common on firewalls and are typically seen as representations of area (e.g. DMZ trust untrust)."""
+
+ description = models.CharField(
+ max_length=200,
+ blank=True,
+ )
+ name = models.CharField(max_length=100, unique=True, help_text="Name of the zone (e.g. trust)")
+ vrfs = models.ManyToManyField(to="ipam.VRF", blank=True, through="ZoneVRFM2M", related_name="zones")
+ interfaces = models.ManyToManyField(
+ to="dcim.Interface", blank=True, through="ZoneInterfaceM2M", related_name="zones"
+ )
+ status = StatusField(
+ on_delete=models.PROTECT,
+ related_name="%(app_label)s_%(class)s_related", # e.g. dcim_device_related
+ default=get_default_status,
+ )
+
+ class Meta:
+ """Meta class."""
+
+ ordering = ["name"]
+ verbose_name_plural = "Zones"
+
+ def get_absolute_url(self):
+ """Return detail view URL."""
+ return reverse("plugins:nautobot_firewall_models:zone", args=[self.pk])
+
+ def __str__(self):
+ """Stringify instance."""
+ return self.name
+
+
+###########################
+# Through Models
+###########################
+
+
+class ZoneInterfaceM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated Interface if assigned to a Zone."""
+
+ zone = models.ForeignKey("nautobot_firewall_models.Zone", on_delete=models.CASCADE)
+ interface = models.ForeignKey("dcim.Interface", on_delete=models.PROTECT)
+
+
+class ZoneVRFM2M(BaseModel):
+ """Custom through model to on_delete=models.PROTECT to prevent deleting associated VRF if assigned to a Zone."""
+
+ zone = models.ForeignKey("nautobot_firewall_models.Zone", on_delete=models.CASCADE)
+ vrf = models.ForeignKey("ipam.vrf", on_delete=models.PROTECT)
diff --git a/nautobot_firewall_models/navigation.py b/nautobot_firewall_models/navigation.py
index d4959510..a7bd280c 100644
--- a/nautobot_firewall_models/navigation.py
+++ b/nautobot_firewall_models/navigation.py
@@ -61,6 +61,28 @@
name="Service",
weight=200,
items=[
+ NavMenuItem(
+ link="plugins:nautobot_firewall_models:applicationobject_list",
+ name="Applications",
+ permissions=["nautobot_firewall_models.view_applicationobject"],
+ buttons=[
+ NavMenuAddButton(
+ link="plugins:nautobot_firewall_models:applicationobject_add",
+ permissions=["nautobot_firewall_models.add_applicationobject"],
+ ),
+ ],
+ ),
+ NavMenuItem(
+ link="plugins:nautobot_firewall_models:applicationobjectgroup_list",
+ name="Application Groups",
+ permissions=["nautobot_firewall_models.view_applicationobjectgroup"],
+ buttons=[
+ NavMenuAddButton(
+ link="plugins:nautobot_firewall_models:applicationobjectgroup_add",
+ permissions=["nautobot_firewall_models.add_applicationobjectgroup"],
+ ),
+ ],
+ ),
NavMenuItem(
link="plugins:nautobot_firewall_models:serviceobject_list",
name="Service Objects",
diff --git a/nautobot_firewall_models/tables.py b/nautobot_firewall_models/tables.py
index 9ee3af04..0bdd135a 100644
--- a/nautobot_firewall_models/tables.py
+++ b/nautobot_firewall_models/tables.py
@@ -64,6 +64,35 @@ class Meta(BaseTable.Meta):
fields = ("pk", "name", "description", "address_objects", "status")
+class ApplicationObjectTable(StatusTableMixin, BaseTable):
+ """Table for list view."""
+
+ pk = ToggleColumn()
+ name = tables.Column(linkify=True)
+ actions = ButtonsColumn(models.ApplicationObject, buttons=("edit", "delete"))
+
+ class Meta(BaseTable.Meta):
+ """Meta attributes."""
+
+ model = models.ApplicationObject
+ fields = ("pk", "name", "description", "category", "subcategory", "technology", "risk", "default_type")
+
+
+class ApplicationObjectGroupTable(StatusTableMixin, BaseTable):
+ """Table for list view."""
+
+ pk = ToggleColumn()
+ name = tables.Column(linkify=True)
+ application_objects = tables.ManyToManyColumn(linkify_item=True)
+ actions = ButtonsColumn(models.ApplicationObjectGroup, buttons=("edit", "delete"))
+
+ class Meta(BaseTable.Meta):
+ """Meta attributes."""
+
+ model = models.ApplicationObjectGroup
+ fields = ("pk", "name", "description", "application_objects")
+
+
class ServiceObjectTable(StatusTableMixin, BaseTable):
"""Table for list view."""
@@ -162,6 +191,8 @@ class Meta(BaseTable.Meta):
"destination_zone",
"destination_services",
"destination_service_groups",
+ "applications",
+ "application_groups",
"action",
"description",
"request_id",
@@ -183,6 +214,8 @@ class Meta(BaseTable.Meta):
"destination_zone",
"destination_services",
"destination_service_groups",
+ "applications",
+ "application_groups",
"action",
"log",
"status",
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobject.html b/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobject.html
new file mode 100644
index 00000000..3ed51b19
--- /dev/null
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobject.html
@@ -0,0 +1,54 @@
+{% extends 'generic/object_detail.html' %}
+{% load helpers %}
+
+{% block content_left_page %}
+
+
+ Application Object
+
+
+
+ Name |
+ {{ object.name|placeholder }} |
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+ Category |
+ {{ object.category|placeholder }} |
+
+
+ Sub-Category |
+ {{ object.subcategory|placeholder }} |
+
+
+ Technology |
+ {{ object.technology|placeholder }} |
+
+
+ Risk |
+ {{ object.risk|placeholder }} |
+
+
+ Default Type |
+ {{ object.default_type|placeholder }} |
+
+
+ Default IP Protocol |
+ {{ object.default_ip_protocol|placeholder }} |
+
+
+ Status |
+
+ {{ object.get_status_display }}
+ |
+
+
+
+{% endblock %}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobjectgroup.html b/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobjectgroup.html
new file mode 100644
index 00000000..92de628a
--- /dev/null
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/applicationobjectgroup.html
@@ -0,0 +1,36 @@
+{% extends 'generic/object_detail.html' %}
+{% load helpers %}
+
+{% block content_left_page %}
+
+
+ Application Object Group
+
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+ Application Objects |
+
+
+ {% for i in object.application_objects.all %}
+ - {{ i|placeholder }}
+ {% empty %} None {% endfor %}
+
+ |
+
+
+ Status |
+
+ {{ object.get_status_display }}
+ |
+
+
+
+{% endblock %}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rule_application_object_row.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rule_application_object_row.html
new file mode 100644
index 00000000..0d618481
--- /dev/null
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policy_rule_application_object_row.html
@@ -0,0 +1,12 @@
+{% load helpers %}
+
+{% if application_group or application %}
+ {% for i in application_group %}
+ {{ i|placeholder }}
+ {% endfor %}
+ {% for i in application %}
+ {{ i|placeholder }}
+ {% endfor %}
+{% else %}
+ {% if nat %}—{% else %}ANY{% endif %}
+ {% endif %}
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 b7c77864..00755915 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
@@ -2,7 +2,7 @@
{{ m2m.rule.index|placeholder|ljust:5 }} → {{ m2m.rule.name|placeholder }} |
{% if m2m.rule.action == "remark" %}
- {{ m2m.rule }} |
+ {{ m2m.rule }} |
{% else %}
{% 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 %} |
@@ -11,6 +11,7 @@
{% 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.destination_services.all service_group=m2m.rule.destination_service_groups.all %} |
+ {% include './policy_rule_application_object_row.html' with application=m2m.rule.applications.all application_group=m2m.rule.application_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/policyrule_tablehead.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/policyrule_tablehead.html
index 50a6c8f9..c69ef05a 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,7 +1,7 @@
|
Index |
Source |
- Destination |
+ Destination |
Action |
Log |
@@ -13,4 +13,5 @@
Address |
Zone |
Service |
+ Application |
\ No newline at end of file
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 9d6083aa..83f91467 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
@@ -2,7 +2,7 @@
{{ rule.index }} |
{% if rule.action == "remark" %}
- {{ rule }} |
+ {{ rule }} |
{% else %}
{% 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 %} |
@@ -11,6 +11,7 @@
{% 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.destination_services.all service_group=rule.destination_service_groups.all %} |
+ {% include './policy_rule_application_object_row.html' with application=rule.applications.all application_group=rule.application_groups.all %} |
{% endif %}
{% include './policy_rule_action_row.html' with action=rule.action %} |
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicy_retrieve.html b/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicy_retrieve.html
index 1907c6b2..45760d10 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicy_retrieve.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicy_retrieve.html
@@ -1,7 +1,5 @@
{% extends 'generic/object_retrieve.html' %}
{% load helpers %}
-{% load plugins %}
-
{% block extra_nav_tabs %}
@@ -25,101 +23,68 @@
{% endif %}
{% endblock extra_nav_tabs%}
-{% block content %}
-
-
-
-
-
-
- NAT Policy
-
-
-
- Description |
- {{ object.description|placeholder }} |
-
-
- Devices |
-
-
- {% for i in object.assigned_devices.all %}
- - {{ i|placeholder }}
- {% empty %}
- None
- {% endfor %}
-
- |
-
-
- Dynamic Groups |
-
-
- {% for i in object.assigned_dynamic_groups.all %}
- - {{ i|placeholder }}
- {% empty %}
- None
- {% endfor %}
-
- |
-
-
- Tenant Group |
- {% if object.tenant and object.tenant.group %}{{ object.tenant.group }} {% else %}{{ None|placeholder }}{% endif %} |
-
-
- Tenant |
- {% if object.tenant %}{{ object.tenant }} {% else %}{{ None|placeholder }}{% endif %} |
-
-
- Status |
- {{ object.get_status_display }} |
-
-
-
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_fields_basic computed_fields_advanced_ui=False %}
- {% include 'inc/relationships_panel.html' %}
- {% include 'extras/inc/tags_panel.html' %}
- {% plugin_left_page object %}
-
-
- {% block content_right_page %}{% endblock content_right_page %}
- {% plugin_right_page object %}
-
-
-
-
- {% block content_full_width_page %}{% endblock content_full_width_page %}
- {% plugin_full_width_page object %}
-
-
-
-
-
-
- {% include 'inc/object_details_advanced_panel.html' %}
-
-
- {% block advanced_content_right_page %}{% endblock advanced_content_right_page %}
-
-
-
-
- {% block advanced_content_full_width_page %}{% endblock advanced_content_full_width_page %}
-
-
-
-
- {% include 'nautobot_firewall_models/inc/nat_policy_expanded_rules.html' %}
-
-
- {% include 'nautobot_firewall_models/inc/nat_policy_device_weight.html' %}
-
-
- {% include 'nautobot_firewall_models/inc/nat_policy_dynamic_group_weight.html' %}
+{% block content_left_page %}
+
+
+ NAT Policy
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+ Devices |
+
+
+ {% for i in object.assigned_devices.all %}
+ - {{ i|placeholder }}
+ {% empty %}
+ None
+ {% endfor %}
+
+ |
+
+
+ Dynamic Groups |
+
+
+ {% for i in object.assigned_dynamic_groups.all %}
+ - {{ i|placeholder }}
+ {% empty %}
+ None
+ {% endfor %}
+
+ |
+
+
+ Tenant Group |
+ {% if object.tenant and object.tenant.group %}{{ object.tenant.group }} {% else %}{{ None|placeholder }}{% endif %} |
+
+
+ Tenant |
+ {% if object.tenant %}{{ object.tenant }} {% else %}{{ None|placeholder }}{% endif %} |
+
+
+ Status |
+ {{ object.get_status_display }} |
+
+
-{% endblock content %}
+{% endblock content_left_page %}
+
+{% block extra_tab_content %}
+
+ {% include 'nautobot_firewall_models/inc/nat_policy_expanded_rules.html' %}
+
+
+ {% include 'nautobot_firewall_models/inc/nat_policy_device_weight.html' %}
+
+
+ {% include 'nautobot_firewall_models/inc/nat_policy_dynamic_group_weight.html' %}
+
+{% endblock extra_tab_content %}
+
{% block javascript %}
{{ block.super }}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicyrule_retrieve.html b/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicyrule_retrieve.html
index f4ea7427..61dacf58 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicyrule_retrieve.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/natpolicyrule_retrieve.html
@@ -1,75 +1,52 @@
{% extends 'generic/object_retrieve.html' %}
{% load helpers %}
-{% load plugins %}
-{% block content %}
-
-
-
-
-
-
- NAT Policy Rule Details
-
-
- {% include 'nautobot_firewall_models/inc/natpolicyrule_tablehead.html' %}
- {% include 'nautobot_firewall_models/inc/natpolicyrule_tablerow.html' with nat_rule=object %}
-
-
-
-
-
-
-
-
- Description
-
-
- {% if object.description %}
- {{ object.description }}
- {% else %}
- No description set
- {% endif %}
-
-
-
-
-
-
- {% block content_left_page %}{% endblock content_left_page %}
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_fields_basic computed_fields_advanced_ui=False %}
- {% include 'inc/relationships_panel.html' %}
- {% include 'extras/inc/tags_panel.html' %}
- {% plugin_left_page object %}
-
-
- {% block content_right_page %}{% endblock content_right_page %}
- {% plugin_right_page object %}
-
-
-
-
- {% block content_full_width_page %}{% endblock content_full_width_page %}
- {% plugin_full_width_page object %}
-
-
+
+{% block content_full_width_page %}
+
+
+ NAT Policy Rule Details
-
-
-
- {% include 'inc/object_details_advanced_panel.html' %}
-
-
- {% block advanced_content_right_page %}{% endblock advanced_content_right_page %}
-
-
-
-
- {% block advanced_content_full_width_page %}{% endblock advanced_content_full_width_page %}
-
-
+
+ {% include 'nautobot_firewall_models/inc/natpolicyrule_tablehead.html' %}
+ {% include 'nautobot_firewall_models/inc/natpolicyrule_tablerow.html' with nat_rule=object %}
+
+
+{% endblock content_full_width_page %}
+
+{% block content_left_page %}
+
+
+ Description
+
+
+ {% if object.description %}
+ {{ object.description }}
+ {% else %}
+ No description set
+ {% endif %}
-{% endblock %}
+{% endblock content_left_page %}
+
+{% block content_right_page %}
+
+
+ Assigned To NAT Policies
+
+
+ {% if object.nat_policies.exists %}
+
+ {% else %}
+ Not
+ {% endif %}
+
+
+{% endblock content_right_page %}
+
{% block javascript %}
{{ block.super }}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html b/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html
index 807e71cd..a4984d8c 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/policy.html
@@ -1,12 +1,5 @@
{% extends 'generic/object_detail.html' %}
-{% load buttons %}
-{% load static %}
-{% load custom_links %}
{% load helpers %}
-{% load plugins %}
-
-{% block title %}{{ object }}{% endblock %}
-
{% block extra_nav_tabs %}
@@ -30,101 +23,68 @@
{% endif %}
{% endblock extra_nav_tabs%}
-{% block content %}
-
-
-
-
-
-
- Policy
-
-
-
- Description |
- {{ object.description|placeholder }} |
-
-
- Devices |
-
-
- {% for i in object.assigned_devices.all %}
- - {{ i|placeholder }}
- {% empty %}
- None
- {% endfor %}
-
- |
-
-
- Dynamic Groups |
-
-
- {% for i in object.assigned_dynamic_groups.all %}
- - {{ i|placeholder }}
- {% empty %}
- None
- {% endfor %}
-
- |
-
-
- Tenant Group |
- {% if object.tenant and object.tenant.group %}{{ object.tenant.group }} {% else %}{{ None|placeholder }}{% endif %} |
-
-
- Tenant |
- {% if object.tenant %}{{ object.tenant }} {% else %}{{ None|placeholder }}{% endif %} |
-
-
- Status |
- {{ object.get_status_display }} |
-
-
-
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_fields_basic computed_fields_advanced_ui=False %}
- {% include 'inc/relationships_panel.html' %}
- {% include 'extras/inc/tags_panel.html' %}
- {% plugin_left_page object %}
-
-
- {% block content_right_page %}{% endblock content_right_page %}
- {% plugin_right_page object %}
-
-
-
-
- {% block content_full_width_page %}{% endblock content_full_width_page %}
- {% plugin_full_width_page object %}
-
-
-
-
-
-
- {% include 'inc/object_details_advanced_panel.html' %}
-
-
- {% block advanced_content_right_page %}{% endblock advanced_content_right_page %}
-
-
-
-
- {% block advanced_content_full_width_page %}{% endblock advanced_content_full_width_page %}
-
-
-
-
- {% include 'nautobot_firewall_models/inc/policy_expanded_rules.html' %}
-
-
- {% include 'nautobot_firewall_models/inc/policy_device_weight.html' %}
-
-
- {% include 'nautobot_firewall_models/inc/policy_dynamic_group_weight.html' %}
+{% block content_left_page %}
+
+
+ Policy
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+ Devices |
+
+
+ {% for i in object.assigned_devices.all %}
+ - {{ i|placeholder }}
+ {% empty %}
+ None
+ {% endfor %}
+
+ |
+
+
+ Dynamic Groups |
+
+
+ {% for i in object.assigned_dynamic_groups.all %}
+ - {{ i|placeholder }}
+ {% empty %}
+ None
+ {% endfor %}
+
+ |
+
+
+ Tenant Group |
+ {% if object.tenant and object.tenant.group %}{{ object.tenant.group }} {% else %}{{ None|placeholder }}{% endif %} |
+
+
+ Tenant |
+ {% if object.tenant %}{{ object.tenant }} {% else %}{{ None|placeholder }}{% endif %} |
+
+
+ Status |
+ {{ object.get_status_display }} |
+
+
-{% endblock content %}
+{% endblock content_left_page %}
+
+{% block extra_tab_content %}
+
+ {% include 'nautobot_firewall_models/inc/policy_expanded_rules.html' %}
+
+
+ {% include 'nautobot_firewall_models/inc/policy_device_weight.html' %}
+
+
+ {% include 'nautobot_firewall_models/inc/policy_dynamic_group_weight.html' %}
+
+{% endblock extra_tab_content %}
+
{% block javascript %}
{{ block.super }}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/policyrule.html b/nautobot_firewall_models/templates/nautobot_firewall_models/policyrule.html
index 12b5ad56..474325e2 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/policyrule.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/policyrule.html
@@ -1,75 +1,52 @@
{% extends 'generic/object_retrieve.html' %}
{% load helpers %}
-{% load plugins %}
-{% block content %}
-
-
-
-
-
-
- Policy Rule Details
-
-
- {% include 'nautobot_firewall_models/inc/policyrule_tablehead.html' %}
- {% include 'nautobot_firewall_models/inc/policyrule_tablerow.html' with rule=object %}
-
-
-
-
-
-
-
-
- Description
-
-
- {% if object.description %}
- {{ object.description }}
- {% else %}
- No description set
- {% endif %}
-
-
-
-
-
-
- {% block content_left_page %}{% endblock content_left_page %}
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_fields_basic computed_fields_advanced_ui=False %}
- {% include 'inc/relationships_panel.html' %}
- {% include 'extras/inc/tags_panel.html' %}
- {% plugin_left_page object %}
-
-
- {% block content_right_page %}{% endblock content_right_page %}
- {% plugin_right_page object %}
-
-
-
-
- {% block content_full_width_page %}{% endblock content_full_width_page %}
- {% plugin_full_width_page object %}
-
-
+
+{% block content_full_width_page %}
+
+
+ Policy Rule Details
-
-
-
- {% include 'inc/object_details_advanced_panel.html' %}
-
-
- {% block advanced_content_right_page %}{% endblock advanced_content_right_page %}
-
-
-
-
- {% block advanced_content_full_width_page %}{% endblock advanced_content_full_width_page %}
-
-
+
+ {% include 'nautobot_firewall_models/inc/policyrule_tablehead.html' %}
+ {% include 'nautobot_firewall_models/inc/policyrule_tablerow.html' with rule=object %}
+
+
+{% endblock content_full_width_page %}
+
+{% block content_left_page %}
+
+
+ Description
+
+
+ {% if object.description %}
+ {{ object.description }}
+ {% else %}
+ No description set
+ {% endif %}
-{% endblock %}
+{% endblock content_left_page %}
+
+{% block content_right_page %}
+
+
+ Assigned To Policies
+
+
+ {% if object.policies.exists %}
+
+ {% else %}
+ Not
+ {% endif %}
+
+
+{% endblock content_right_page %}
+
{% block javascript %}
{{ block.super }}
diff --git a/nautobot_firewall_models/tests/fixtures.py b/nautobot_firewall_models/tests/fixtures.py
index f6b70109..5cf90373 100644
--- a/nautobot_firewall_models/tests/fixtures.py
+++ b/nautobot_firewall_models/tests/fixtures.py
@@ -75,6 +75,40 @@ def create_env():
zone2 = Zone.objects.create(name="LAN", status=status)
Zone.objects.create(name="DMZ", status=status)
+ app1 = ApplicationObject.objects.create(
+ name="app1",
+ category="web",
+ subcategory="streaming",
+ default_type="443",
+ default_ip_protocol="TCP",
+ status=status,
+ risk=3,
+ )
+ app2 = ApplicationObject.objects.create(
+ name="app2",
+ category="web",
+ subcategory="streaming",
+ default_type="443",
+ default_ip_protocol="TCP",
+ status=status,
+ risk=2,
+ )
+ app3 = ApplicationObject.objects.create(
+ name="app3",
+ category="web",
+ subcategory="streaming",
+ default_type="443",
+ default_ip_protocol="TCP",
+ status=status,
+ risk=1,
+ )
+ app_grp1 = ApplicationObjectGroup.objects.create(name="streaming")
+ app_grp1.application_objects.set([app1])
+ app_grp2 = ApplicationObjectGroup.objects.create(name="gaming")
+ app_grp2.application_objects.set([app3, app2])
+ app_grp3 = ApplicationObjectGroup.objects.create(name="news")
+ app_grp3.application_objects.set([app1, app2, app3])
+
pol_rule1 = PolicyRule.objects.create(
action="deny", log=True, name="Policy Rule 1", status=status, request_id="req1", index=10
)
@@ -87,6 +121,8 @@ def create_env():
pol_rule1.destination_address_groups.set([addr_grp3])
pol_rule1.destination_services.set([svc_obj1])
pol_rule1.destination_service_groups.set([svc_grp1])
+ pol_rule1.applications.set([app1])
+ pol_rule1.application_groups.set([app_grp1])
pol_rule2 = PolicyRule.objects.create(
source_zone=zone1,
destination_zone=zone2,
@@ -105,6 +141,8 @@ def create_env():
pol_rule2.destination_address_groups.set([addr_grp3])
pol_rule2.destination_services.set([svc_obj1, svc_obj2])
pol_rule2.destination_service_groups.set([svc_grp1, svc_grp2])
+ pol_rule2.applications.set([app2])
+ pol_rule2.application_groups.set([app_grp2])
pol_rule3 = PolicyRule.objects.create(
source_zone=zone1,
destination_zone=zone2,
@@ -123,6 +161,8 @@ def create_env():
pol_rule3.destination_address_groups.set([addr_grp3])
pol_rule3.destination_services.set([svc_obj1, svc_obj2, svc_obj3])
pol_rule3.destination_service_groups.set([svc_grp1, svc_grp2, svc_grp3])
+ pol_rule3.applications.set([app2, app3])
+ pol_rule3.application_groups.set([app_grp1, app_grp2, app_grp3])
pol_rule4 = PolicyRule.objects.create(
name="END OF ACCESS LIST", action="remark", log=False, request_id="req4", index=99
)
diff --git a/nautobot_firewall_models/tests/test_api.py b/nautobot_firewall_models/tests/test_api.py
index 21c4c65d..3678f1fd 100644
--- a/nautobot_firewall_models/tests/test_api.py
+++ b/nautobot_firewall_models/tests/test_api.py
@@ -52,6 +52,73 @@ def test_notes_url_on_object(self):
pass
+class ApplicationObjectAPIViewTest(APIViewTestCases.APIViewTestCase):
+ """Test the ApplicationObject viewsets."""
+
+ model = models.ApplicationObject
+ bulk_update_data = {"description": "test update description"}
+
+ @classmethod
+ def setUpTestData(cls):
+ """Create test data for API calls."""
+ create_env()
+
+ cls.create_data = [
+ {"name": "obj2", "risk": 1},
+ {"name": "obj1", "risk": 1},
+ ]
+
+ @skip("Not implemented")
+ def test_list_objects_brief(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_without_permission(self):
+ pass
+
+
+class ApplicationObjectGroupAPIViewTest(APIViewTestCases.APIViewTestCase):
+ """Test the ApplicationObjectGroup viewsets."""
+
+ model = models.ApplicationObjectGroup
+ bulk_update_data = {"description": "test update description"}
+
+ @classmethod
+ def setUpTestData(cls):
+ """Create test data for API calls."""
+ create_env()
+ app_obj = models.ApplicationObject.objects.first()
+
+ cls.create_data = [
+ {"name": "test1", "application_objects": [app_obj.id]},
+ {"name": "test2", "application_objects": [app_obj.id]},
+ ]
+
+ @skip("Not implemented")
+ def test_list_objects_brief(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_without_permission(self):
+ pass
+
+
class AddressObjectAPIViewTest(APIViewTestCases.APIViewTestCase):
"""Test the AddressObject viewsets."""
diff --git a/nautobot_firewall_models/tests/test_basic.py b/nautobot_firewall_models/tests/test_basic.py
deleted file mode 100644
index 950372f6..00000000
--- a/nautobot_firewall_models/tests/test_basic.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""Basic tests that do not require Django."""
-import unittest
-import os
-import toml
-
-from nautobot_firewall_models import __version__ as project_version
-
-
-class TestVersion(unittest.TestCase):
- """Test Version is the same."""
-
- def test_version(self):
- """Verify that pyproject.toml version is same as version specified in the package."""
- parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
- poetry_version = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["version"]
- self.assertEqual(project_version, poetry_version)
diff --git a/nautobot_firewall_models/tests/test_ui_views.py b/nautobot_firewall_models/tests/test_ui_views.py
index 9708c8dd..303e52f5 100644
--- a/nautobot_firewall_models/tests/test_ui_views.py
+++ b/nautobot_firewall_models/tests/test_ui_views.py
@@ -179,6 +179,108 @@ def test_delete_object_without_permission(self):
pass
+class ApplicationObjectUIViewTest(ViewTestCases.PrimaryObjectViewTestCase):
+ """Test the ApplicationObject viewsets."""
+
+ model = ApplicationObject
+ bulk_edit_data = {"description": "test update description"}
+
+ @classmethod
+ def setUpTestData(cls):
+ """Create test data for API calls."""
+ create_env()
+
+ status = Status.objects.get(slug="active").id
+ cls.form_data = {"name": "obj1", "risk": 1, "status": status}
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_constrained_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_without_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_permission_csv_file(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects_with_constrained_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects_with_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_with_constrained_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_with_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_without_permission(self):
+ pass
+
+
+class ApplicationObjectGroupUIViewTest(ViewTestCases.PrimaryObjectViewTestCase):
+ """Test the ApplicationObjectGroup viewsets."""
+
+ model = ApplicationObjectGroup
+ bulk_edit_data = {"description": "test update description"}
+
+ @classmethod
+ def setUpTestData(cls):
+ """Create test data for API calls."""
+ create_env()
+ status = Status.objects.get(slug="active").id
+ app_obj = ApplicationObject.objects.first()
+ cls.form_data = {"name": "test1", "application_objects": [app_obj.id], "status": status}
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_constrained_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_without_permission(self):
+ pass
+
+ @skip("Not implemented")
+ def test_bulk_import_objects_with_permission_csv_file(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects_with_constrained_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_bulk_delete_objects_with_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_with_constrained_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_with_permission(self):
+ pass
+
+ @skip("on_delete set to PROTECT")
+ def test_delete_object_without_permission(self):
+ pass
+
+
class ServiceObjectUIViewTest(ViewTestCases.PrimaryObjectViewTestCase):
"""Test the ServiceObject viewsets."""
diff --git a/nautobot_firewall_models/urls.py b/nautobot_firewall_models/urls.py
index f94c1df3..8402874b 100644
--- a/nautobot_firewall_models/urls.py
+++ b/nautobot_firewall_models/urls.py
@@ -13,6 +13,8 @@
zone,
address_object,
address_object_group,
+ application_object,
+ application_object_group,
service_object,
service_object_group,
user_object,
@@ -148,6 +150,94 @@
name="addressobjectgroup_notes",
kwargs={"model": models.AddressObjectGroup},
),
+ # ApplicationObject URLs
+ path("application-object/", application_object.ApplicationObjectListView.as_view(), name="applicationobject_list"),
+ # Order is important for these URLs to work (add/delete/edit) to be before any that require uuid/slug
+ path(
+ "application-object/add/", application_object.ApplicationObjectEditView.as_view(), name="applicationobject_add"
+ ),
+ path(
+ "application-object/delete/",
+ application_object.ApplicationObjectBulkDeleteView.as_view(),
+ name="applicationobject_bulk_delete",
+ ),
+ path(
+ "application-object/edit/",
+ application_object.ApplicationObjectBulkEditView.as_view(),
+ name="applicationobject_bulk_edit",
+ ),
+ path("application-object/ /", application_object.ApplicationObjectView.as_view(), name="applicationobject"),
+ path(
+ "application-object//delete/",
+ application_object.ApplicationObjectDeleteView.as_view(),
+ name="applicationobject_delete",
+ ),
+ path(
+ "application-object//edit/",
+ application_object.ApplicationObjectEditView.as_view(),
+ name="applicationobject_edit",
+ ),
+ path(
+ "application-object//changelog/",
+ ObjectChangeLogView.as_view(),
+ name="applicationobject_changelog",
+ kwargs={"model": models.ApplicationObject},
+ ),
+ path(
+ "application-object//notes/",
+ ObjectNotesView.as_view(),
+ name="applicationobject_notes",
+ kwargs={"model": models.ApplicationObject},
+ ),
+ # ApplicationObjectGroup URLs
+ path(
+ "application-object-group/",
+ application_object_group.ApplicationObjectGroupListView.as_view(),
+ name="applicationobjectgroup_list",
+ ),
+ # Order is important for these URLs to work (add/delete/edit) to be before any that require uuid/slug
+ path(
+ "application-object-group/add/",
+ application_object_group.ApplicationObjectGroupEditView.as_view(),
+ name="applicationobjectgroup_add",
+ ),
+ path(
+ "application-object-group/delete/",
+ application_object_group.ApplicationObjectGroupBulkDeleteView.as_view(),
+ name="applicationobjectgroup_bulk_delete",
+ ),
+ path(
+ "application-object-group/edit/",
+ application_object_group.ApplicationObjectGroupBulkEditView.as_view(),
+ name="applicationobjectgroup_bulk_edit",
+ ),
+ path(
+ "application-object-group//",
+ application_object_group.ApplicationObjectGroupView.as_view(),
+ name="applicationobjectgroup",
+ ),
+ path(
+ "application-object-group//delete/",
+ application_object_group.ApplicationObjectGroupDeleteView.as_view(),
+ name="applicationobjectgroup_delete",
+ ),
+ path(
+ "application-object-group//edit/",
+ application_object_group.ApplicationObjectGroupEditView.as_view(),
+ name="applicationobjectgroup_edit",
+ ),
+ path(
+ "application-group//changelog/",
+ ObjectChangeLogView.as_view(),
+ name="applicationobjectgroup_changelog",
+ kwargs={"model": models.ApplicationObjectGroup},
+ ),
+ path(
+ "application-group//notes/",
+ ObjectNotesView.as_view(),
+ name="applicationobjectgroup_notes",
+ kwargs={"model": models.ApplicationObjectGroup},
+ ),
# ServiceObject URLs
path("service-object/", service_object.ServiceObjectListView.as_view(), name="serviceobject_list"),
# Order is important for these URLs to work (add/delete/edit) to be before any that require uuid/slug
diff --git a/nautobot_firewall_models/views/application_object.py b/nautobot_firewall_models/views/application_object.py
new file mode 100644
index 00000000..90a47936
--- /dev/null
+++ b/nautobot_firewall_models/views/application_object.py
@@ -0,0 +1,50 @@
+"""Application Object Views."""
+
+from nautobot.core.views import generic
+
+from nautobot_firewall_models import filters, forms, models, tables
+
+
+class ApplicationObjectListView(generic.ObjectListView):
+ """List view."""
+
+ queryset = models.ApplicationObject.objects.all()
+ filterset = filters.ApplicationObjectFilterSet
+ filterset_form = forms.ApplicationObjectFilterForm
+ table = tables.ApplicationObjectTable
+ action_buttons = ("add",)
+
+
+class ApplicationObjectView(generic.ObjectView):
+ """Detail view."""
+
+ queryset = models.ApplicationObject.objects.all()
+
+
+class ApplicationObjectDeleteView(generic.ObjectDeleteView):
+ """Delete view."""
+
+ queryset = models.ApplicationObject.objects.all()
+
+
+class ApplicationObjectEditView(generic.ObjectEditView):
+ """Edit view."""
+
+ queryset = models.ApplicationObject.objects.all()
+ model_form = forms.ApplicationObjectForm
+
+
+class ApplicationObjectBulkDeleteView(generic.BulkDeleteView):
+ """View for deleting one or more ApplicationObject records."""
+
+ queryset = models.ApplicationObject.objects.all()
+ table = tables.ApplicationObjectTable
+
+
+class ApplicationObjectBulkEditView(generic.BulkEditView):
+ """View for editing one or more ApplicationObject records."""
+
+ queryset = models.ApplicationObject.objects.all()
+ filterset = filters.ApplicationObjectFilterSet
+ table = tables.ApplicationObjectTable
+ form = forms.ApplicationObjectBulkEditForm
diff --git a/nautobot_firewall_models/views/application_object_group.py b/nautobot_firewall_models/views/application_object_group.py
new file mode 100644
index 00000000..5d7c0248
--- /dev/null
+++ b/nautobot_firewall_models/views/application_object_group.py
@@ -0,0 +1,50 @@
+"""Application Object Group Views."""
+
+from nautobot.core.views import generic
+
+from nautobot_firewall_models import filters, forms, models, tables
+
+
+class ApplicationObjectGroupListView(generic.ObjectListView):
+ """List view."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+ filterset = filters.ApplicationObjectGroupFilterSet
+ filterset_form = forms.ApplicationObjectGroupFilterForm
+ table = tables.ApplicationObjectGroupTable
+ action_buttons = ("add",)
+
+
+class ApplicationObjectGroupView(generic.ObjectView):
+ """Detail view."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+
+
+class ApplicationObjectGroupDeleteView(generic.ObjectDeleteView):
+ """Delete view."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+
+
+class ApplicationObjectGroupEditView(generic.ObjectEditView):
+ """Edit view."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+ model_form = forms.ApplicationObjectGroupForm
+
+
+class ApplicationObjectGroupBulkDeleteView(generic.BulkDeleteView):
+ """View for deleting one or more ApplicationObjectGroup records."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+ table = tables.ApplicationObjectGroupTable
+
+
+class ApplicationObjectGroupBulkEditView(generic.BulkEditView):
+ """View for editing one or more ApplicationObjectGroup records."""
+
+ queryset = models.ApplicationObjectGroup.objects.all()
+ filterset = filters.ApplicationObjectGroupFilterSet
+ table = tables.ApplicationObjectGroupTable
+ form = forms.ApplicationObjectGroupBulkEditForm
diff --git a/poetry.lock b/poetry.lock
index 894f61c6..026336cf 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,6 +1,6 @@
[[package]]
name = "absl-py"
-version = "1.2.0"
+version = "1.3.0"
description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py."
category = "main"
optional = false
@@ -45,7 +45,7 @@ python-versions = ">=3.7"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
-tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
[[package]]
name = "astroid"
@@ -57,7 +57,6 @@ python-versions = ">=3.6.2"
[package.dependencies]
lazy-object-proxy = ">=1.4.0"
-setuptools = ">=20.0"
typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""}
typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
wrapt = ">=1.11,<2"
@@ -73,14 +72,6 @@ python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
-[[package]]
-name = "atomicwrites"
-version = "1.4.1"
-description = "Atomic file writes."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
[[package]]
name = "attrs"
version = "22.1.0"
@@ -90,10 +81,10 @@ optional = false
python-versions = ">=3.5"
[package.extras]
-dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
-docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
-tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "backcall"
@@ -118,9 +109,9 @@ PyYAML = ">=5.3.1"
stevedore = ">=1.20.0"
[package.extras]
-test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml"]
+test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"]
toml = ["toml"]
-yaml = ["PyYAML"]
+yaml = ["pyyaml"]
[[package]]
name = "billiard"
@@ -132,11 +123,11 @@ python-versions = "*"
[[package]]
name = "black"
-version = "22.6.0"
+version = "22.10.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
-python-versions = ">=3.6.2"
+python-versions = ">=3.7"
[package.dependencies]
click = ">=8.0.0"
@@ -223,7 +214,7 @@ s3 = ["boto3 (>=1.9.125)"]
slmq = ["softlayer-messaging (>=1.0.3)"]
solar = ["ephem"]
sqlalchemy = ["sqlalchemy"]
-sqs = ["kombu[sqs]"]
+sqs = ["kombu"]
tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
yaml = ["PyYAML (>=3.10)"]
zookeeper = ["kazoo (>=1.3.1)"]
@@ -231,7 +222,7 @@ zstd = ["zstandard"]
[[package]]
name = "certifi"
-version = "2022.6.15"
+version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -257,7 +248,7 @@ optional = false
python-versions = ">=3.6.0"
[package.extras]
-unicode_backport = ["unicodedata2"]
+unicode-backport = ["unicodedata2"]
[[package]]
name = "click"
@@ -294,7 +285,7 @@ python-versions = "*"
click = ">=4.0"
[package.extras]
-dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"]
+dev = ["pytest (>=3.6)", "pytest-cov", "wheel", "coveralls"]
[[package]]
name = "click-repl"
@@ -311,11 +302,11 @@ six = "*"
[[package]]
name = "colorama"
-version = "0.4.5"
+version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
[[package]]
name = "coreapi"
@@ -344,7 +335,7 @@ jinja2 = "*"
[[package]]
name = "coverage"
-version = "6.4.4"
+version = "6.5.0"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@@ -355,7 +346,7 @@ toml = ["tomli"]
[[package]]
name = "cryptography"
-version = "37.0.4"
+version = "38.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@@ -365,12 +356,12 @@ python-versions = ">=3.6"
cffi = ">=1.12"
[package.extras]
-docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
-docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
-sdist = ["setuptools_rust (>=0.11.4)"]
+sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
-test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
+test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "decorator"
@@ -388,34 +379,20 @@ category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-[[package]]
-name = "deprecated"
-version = "1.2.13"
-description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[package.dependencies]
-wrapt = ">=1.10,<2"
-
-[package.extras]
-dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"]
-
[[package]]
name = "dill"
-version = "0.3.5.1"
+version = "0.3.6"
description = "serialize all of python"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
+python-versions = ">=3.7"
[package.extras]
graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "django"
-version = "3.2.15"
+version = "3.2.16"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
@@ -479,7 +456,7 @@ python-crontab = ">=2.3.4"
[[package]]
name = "django-constance"
-version = "2.8.0"
+version = "2.9.1"
description = "Django live settings with pluggable backends, including Redis."
category = "main"
optional = false
@@ -528,7 +505,7 @@ Django = "*"
[[package]]
name = "django-debug-toolbar"
-version = "3.6.0"
+version = "3.7.0"
description = "A configurable set of panels that display various debug information about the current request/response."
category = "dev"
optional = false
@@ -540,7 +517,7 @@ sqlparse = ">=0.2.0"
[[package]]
name = "django-extensions"
-version = "3.2.0"
+version = "3.2.1"
description = "Extensions for Django"
category = "main"
optional = false
@@ -599,7 +576,7 @@ tests = ["coverage"]
[[package]]
name = "django-mptt"
-version = "0.13.4"
+version = "0.14.0"
description = "Utilities for implementing Modified Preorder Tree Traversal with your Django Models and working with trees of Model instances."
category = "main"
optional = false
@@ -684,14 +661,14 @@ tablib = ["tablib"]
[[package]]
name = "django-taggit"
-version = "1.5.1"
+version = "3.0.0"
description = "django-taggit is a reusable Django application for simple tagging."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-Django = ">=2.2"
+Django = ">=3.2"
[[package]]
name = "django-timezone-field"
@@ -706,7 +683,7 @@ django = ">=2.2"
pytz = "*"
[package.extras]
-rest_framework = ["djangorestframework (>=3.0.0)"]
+rest-framework = ["djangorestframework (>=3.0.0)"]
[[package]]
name = "django-tree-queries"
@@ -733,25 +710,25 @@ Django = "*"
[package.extras]
gunicorn = ["gunicorn"]
pyuwsgi = ["pyuwsgi"]
-test = ["mock", "pytest"]
+test = ["pytest", "mock"]
uvicorn = ["uvicorn (>0.6)"]
waitress = ["waitress"]
[[package]]
name = "djangorestframework"
-version = "3.13.1"
+version = "3.14.0"
description = "Web APIs for Django, made easy."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-django = ">=2.2"
+django = ">=3.0"
pytz = "*"
[[package]]
name = "drf-spectacular"
-version = "0.22.1"
+version = "0.24.2"
description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework"
category = "main"
optional = false
@@ -773,7 +750,7 @@ sidecar = ["drf-spectacular-sidecar"]
[[package]]
name = "drf-spectacular-sidecar"
-version = "2022.8.1"
+version = "2022.11.1"
description = "Serve self-contained distribution builds of Swagger UI and Redoc with Django"
category = "main"
optional = false
@@ -784,7 +761,7 @@ Django = ">=2.2"
[[package]]
name = "drf-yasg"
-version = "1.21.3"
+version = "1.21.4"
description = "Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code."
category = "main"
optional = false
@@ -805,6 +782,17 @@ uritemplate = ">=3.0.0"
[package.extras]
validation = ["swagger-spec-validator (>=2.1.0)"]
+[[package]]
+name = "exceptiongroup"
+version = "1.0.4"
+description = "Backport of PEP 654 (exception groups)"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)"]
+
[[package]]
name = "flake8"
version = "3.9.2"
@@ -839,7 +827,7 @@ python-versions = "*"
python-dateutil = ">=2.8.1"
[package.extras]
-dev = ["flake8", "markdown", "twine", "wheel"]
+dev = ["twine", "markdown", "flake8", "wheel"]
[[package]]
name = "gitdb"
@@ -854,7 +842,7 @@ smmap = ">=3.0.1,<6"
[[package]]
name = "gitpython"
-version = "3.1.27"
+version = "3.1.29"
description = "GitPython is a python library used to interact with Git repositories"
category = "main"
optional = false
@@ -881,7 +869,7 @@ six = ">=1.10.0,<2"
[package.extras]
django = ["graphene-django"]
sqlalchemy = ["graphene-sqlalchemy"]
-test = ["coveralls", "fastdiff (==0.2.0)", "iso8601", "mock", "promise", "pytest", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytz", "six", "snapshottest"]
+test = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-mock", "fastdiff (==0.2.0)", "snapshottest", "coveralls", "promise", "six", "mock", "pytz", "iso8601"]
[[package]]
name = "graphene-django"
@@ -901,9 +889,9 @@ six = ">=1.10.0"
text-unidecode = "*"
[package.extras]
-dev = ["black (==19.10b0)", "coveralls", "django-filter (<2)", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "flake8 (==3.7.9)", "flake8-black (==0.1.1)", "flake8-bugbear (==20.1.4)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"]
+dev = ["black (==19.10b0)", "flake8 (==3.7.9)", "flake8-black (==0.1.1)", "flake8-bugbear (==20.1.4)", "pytest (>=3.6.3)", "pytest-cov", "coveralls", "mock", "pytz", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)", "django-filter (<2)", "django-filter (>=2)"]
rest_framework = ["djangorestframework (>=3.6.3)"]
-test = ["coveralls", "django-filter (<2)", "django-filter (>=2)", "djangorestframework (>=3.6.3)", "mock", "pytest (>=3.6.3)", "pytest-cov", "pytest-django (>=3.3.2)", "pytz"]
+test = ["pytest (>=3.6.3)", "pytest-cov", "coveralls", "mock", "pytz", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)", "django-filter (<2)", "django-filter (>=2)"]
[[package]]
name = "graphene-django-optimizer"
@@ -928,7 +916,7 @@ six = ">=1.10.0"
[package.extras]
gevent = ["gevent (>=1.1)"]
-test = ["coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-benchmark (==3.2.3)", "pytest-cov (==2.8.1)", "pytest-django (==3.9.0)", "pytest-mock (==2.0.0)", "six (==1.14.0)"]
+test = ["six (==1.14.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-django (==3.9.0)", "pytest-cov (==2.8.1)", "coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pytest-benchmark (==3.2.3)", "pytest-mock (==2.0.0)"]
[[package]]
name = "graphql-relay"
@@ -945,7 +933,7 @@ six = ">=1.12"
[[package]]
name = "idna"
-version = "3.3"
+version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@@ -953,7 +941,7 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
-version = "4.12.0"
+version = "4.13.0"
description = "Read metadata from Python packages"
category = "main"
optional = false
@@ -964,13 +952,13 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
-docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
+docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"]
perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
[[package]]
name = "importlib-resources"
-version = "5.9.0"
+version = "5.10.0"
description = "Read resources from Python packages"
category = "main"
optional = false
@@ -980,8 +968,8 @@ python-versions = ">=3.7"
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
-docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[[package]]
name = "inflection"
@@ -1001,7 +989,7 @@ python-versions = "*"
[[package]]
name = "invoke"
-version = "1.7.1"
+version = "1.7.3"
description = "Pythonic task execution"
category = "dev"
optional = false
@@ -1034,7 +1022,6 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
pickleshare = "*"
prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
pygments = "*"
-setuptools = ">=18.5"
traitlets = ">=4.2"
[package.extras]
@@ -1043,10 +1030,10 @@ doc = ["Sphinx (>=1.3)"]
kernel = ["ipykernel"]
nbconvert = ["nbconvert"]
nbformat = ["nbformat"]
-notebook = ["ipywidgets", "notebook"]
+notebook = ["notebook", "ipywidgets"]
parallel = ["ipyparallel"]
qtconsole = ["qtconsole"]
-test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"]
+test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"]
[[package]]
name = "isort"
@@ -1057,10 +1044,10 @@ optional = false
python-versions = ">=3.6.1,<4.0"
[package.extras]
-colors = ["colorama (>=0.4.3,<0.5.0)"]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
-requirements_deprecated_finder = ["pip-api", "pipreqs"]
[[package]]
name = "itypes"
@@ -1072,7 +1059,7 @@ python-versions = "*"
[[package]]
name = "jedi"
-version = "0.18.1"
+version = "0.18.2"
description = "An autocompletion tool for Python that can be used for text editors."
category = "dev"
optional = false
@@ -1082,16 +1069,17 @@ python-versions = ">=3.6"
parso = ">=0.8.0,<0.9.0"
[package.extras]
+docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx-rtd-theme (==0.4.3)", "sphinx (==1.8.5)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
-testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
+testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "jinja2"
-version = "3.0.3"
+version = "3.1.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.0"
@@ -1150,11 +1138,11 @@ zookeeper = ["kazoo (>=1.3.1)"]
[[package]]
name = "lazy-object-proxy"
-version = "1.7.1"
+version = "1.8.0"
description = "A fast and thorough lazy object proxy."
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[[package]]
name = "markdown"
@@ -1207,38 +1195,41 @@ python-versions = ">=3.6"
[[package]]
name = "mkdocs"
-version = "1.3.1"
+version = "1.4.2"
description = "Project documentation with Markdown."
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
-click = ">=3.3"
+click = ">=7.0"
+colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
ghp-import = ">=1.0"
-importlib-metadata = ">=4.3"
-Jinja2 = ">=2.10.2"
-Markdown = ">=3.2.1,<3.4"
+importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
+jinja2 = ">=2.11.1"
+markdown = ">=3.2.1,<3.4"
mergedeep = ">=1.3.4"
packaging = ">=20.5"
-PyYAML = ">=3.10"
+pyyaml = ">=5.1"
pyyaml-env-tag = ">=0.1"
+typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""}
watchdog = ">=2.0"
[package.extras]
i18n = ["babel (>=2.9.0)"]
+min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml-env-tag (==0.1)", "pyyaml (==5.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
[[package]]
name = "mkdocs-include-markdown-plugin"
-version = "3.6.1"
+version = "3.9.1"
description = "Mkdocs Markdown includer plugin."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
-dev = ["bump2version (==1.0.1)", "flake8 (==3.9.2)", "flake8-implicit-str-concat (==0.2.0)", "flake8-print (==4.0.0)", "isort (==5.9.1)", "mdpo (==0.3.61)", "mkdocs (==1.2.3)", "pre-commit (==2.13.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pyupgrade (==2.19.4)", "yamllint (==1.26.1)"]
-test = ["pytest (==6.2.5)", "pytest-cov (==3.0.0)"]
+dev = ["bump2version (==1.0.1)", "mkdocs (==1.4.0)", "pre-commit", "pytest (==7.1.3)", "pytest-cov (==3.0.0)", "tox"]
+test = ["mkdocs (==1.4.0)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"]
[[package]]
name = "mock"
@@ -1249,7 +1240,7 @@ optional = false
python-versions = ">=3.6"
[package.extras]
-build = ["blurb", "twine", "wheel"]
+build = ["twine", "wheel", "blurb"]
docs = ["sphinx"]
test = ["pytest (<5.4)", "pytest-cov"]
@@ -1263,7 +1254,7 @@ python-versions = "*"
[[package]]
name = "nautobot"
-version = "1.4.1"
+version = "1.5.1"
description = "Source of truth and network automation platform."
category = "main"
optional = false
@@ -1271,11 +1262,11 @@ python-versions = ">=3.7,<4.0"
[package.dependencies]
celery = ">=5.2.7,<5.3.0"
-Django = ">=3.2.15,<3.3.0"
+Django = ">=3.2.16,<3.3.0"
django-ajax-tables = ">=1.1.1,<1.2.0"
django-cacheops = ">=6.0,<6.1"
django-celery-beat = ">=2.2.1,<2.3.0"
-django-constance = {version = ">=2.8.0,<2.9.0", extras = ["database"]}
+django-constance = {version = ">=2.9.0,<2.10.0", extras = ["database"]}
django-cors-headers = ">=3.13.0,<3.14.0"
django-cryptography = ">=1.0,<1.1"
django-db-file-storage = ">=0.5.5,<0.6.0"
@@ -1283,32 +1274,32 @@ django-extensions = ">=3.2.0,<3.3.0"
django-filter = ">=21.1,<21.2"
django-health-check = ">=3.16.5,<3.17.0"
django-jinja = ">=2.10.2,<2.11.0"
-django-mptt = ">=0.13.4,<0.14.0"
+django-mptt = ">=0.14.0,<0.15.0"
django-prometheus = ">=2.2.0,<2.3.0"
django-redis = ">=5.2.0,<5.3.0"
django-rq = ">=2.5.1,<2.6.0"
django-tables2 = ">=2.4.1,<2.5.0"
-django-taggit = ">=1.5.1,<1.6.0"
+django-taggit = ">=3.0.0,<3.1.0"
django-timezone-field = ">=4.1.2,<4.2.0"
django-tree-queries = ">=0.11,<0.12"
django-webserver = ">=1.2.0,<1.3.0"
-djangorestframework = ">=3.13.1,<3.14.0"
-drf-spectacular = {version = ">=0.22.1,<0.23.0", extras = ["sidecar"]}
+djangorestframework = ">=3.14.0,<3.15.0"
+drf-spectacular = {version = ">=0.24.2,<0.25.0", extras = ["sidecar"]}
drf-yasg = {version = ">=1.20.0,<2.0.0", extras = ["validation"]}
-GitPython = ">=3.1.27,<3.2.0"
+GitPython = ">=3.1.29,<3.2.0"
graphene-django = ">=2.15.0,<2.16.0"
graphene-django-optimizer = ">=0.8.0,<0.9.0"
importlib-metadata = {version = ">=4.4,<5.0", markers = "python_version < \"3.8\""}
-Jinja2 = ">=3.0.3,<3.1.0"
+Jinja2 = ">=3.1.0,<3.2.0"
jsonschema = ">=4.7.0,<4.8.0"
Markdown = ">=3.3.7,<3.4.0"
MarkupSafe = ">=2.1.1,<2.2.0"
netaddr = ">=0.8.0,<0.9.0"
-netutils = ">=1.1.0,<1.2.0"
-Pillow = ">=9.2.0,<9.3.0"
-psycopg2-binary = ">=2.9.3,<2.10.0"
+netutils = ">=1.2.0,<1.3.0"
+Pillow = ">=9.3.0,<9.4.0"
+psycopg2-binary = ">=2.9.5,<2.10.0"
pycryptodome = ">=3.13.0,<3.14.0"
-pyuwsgi = ">=2.0.20,<2.1.0"
+pyuwsgi = ">=2.0.21,<2.1.0"
PyYAML = ">=6.0,<6.1"
social-auth-app-django = ">=5.0.0,<5.1.0"
svgwrite = ">=1.4.2,<1.5.0"
@@ -1316,9 +1307,9 @@ svgwrite = ">=1.4.2,<1.5.0"
[package.extras]
all = ["django-auth-ldap (>=4.1.0,<4.2.0)", "django-storages (>=1.12.3,<1.13.0)", "mysqlclient (>=2.1.0,<2.2.0)", "napalm (>=3.4.1,<3.5.0)", "social-auth-core[openidconnect,saml] (>=4.3.0,<4.4.0)"]
ldap = ["django-auth-ldap (>=4.1.0,<4.2.0)"]
+remote-storage = ["django-storages (>=1.12.3,<1.13.0)"]
mysql = ["mysqlclient (>=2.1.0,<2.2.0)"]
napalm = ["napalm (>=3.4.1,<3.5.0)"]
-remote_storage = ["django-storages (>=1.12.3,<1.13.0)"]
sso = ["social-auth-core[openidconnect,saml] (>=4.3.0,<4.4.0)"]
[[package]]
@@ -1331,7 +1322,7 @@ python-versions = "*"
[[package]]
name = "netutils"
-version = "1.1.0"
+version = "1.2.0"
description = "Common helper functions useful in network automation."
category = "main"
optional = false
@@ -1339,7 +1330,7 @@ python-versions = ">=3.6,<4.0"
[[package]]
name = "oauthlib"
-version = "3.2.1"
+version = "3.2.2"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
@@ -1375,15 +1366,15 @@ testing = ["docopt", "pytest (<6.0.0)"]
[[package]]
name = "pathspec"
-version = "0.9.0"
+version = "0.10.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.7"
[[package]]
name = "pbr"
-version = "5.10.0"
+version = "5.11.0"
description = "Python Build Reasonableness"
category = "dev"
optional = false
@@ -1410,7 +1401,7 @@ python-versions = "*"
[[package]]
name = "pillow"
-version = "9.2.0"
+version = "9.3.0"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
@@ -1422,15 +1413,15 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
[[package]]
name = "platformdirs"
-version = "2.5.2"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "2.5.4"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.4)", "sphinx (>=5.3)"]
+test = ["appdirs (==1.4.4)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"]
[[package]]
name = "pluggy"
@@ -1457,7 +1448,7 @@ python-versions = "*"
[[package]]
name = "prometheus-client"
-version = "0.14.1"
+version = "0.15.0"
description = "Python client for the Prometheus monitoring system."
category = "main"
optional = false
@@ -1478,11 +1469,11 @@ python-versions = "*"
six = "*"
[package.extras]
-test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"]
+test = ["pytest (>=2.7.3)", "pytest-cov", "coveralls", "futures", "pytest-benchmark", "mock"]
[[package]]
name = "prompt-toolkit"
-version = "3.0.30"
+version = "3.0.33"
description = "Library for building powerful interactive command lines in Python"
category = "main"
optional = false
@@ -1493,7 +1484,7 @@ wcwidth = "*"
[[package]]
name = "psycopg2-binary"
-version = "2.9.3"
+version = "2.9.5"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
@@ -1507,14 +1498,6 @@ category = "dev"
optional = false
python-versions = "*"
-[[package]]
-name = "py"
-version = "1.11.0"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
[[package]]
name = "pycodestyle"
version = "2.7.0"
@@ -1574,17 +1557,17 @@ plugins = ["importlib-metadata"]
[[package]]
name = "pyjwt"
-version = "2.4.0"
+version = "2.6.0"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.extras]
-crypto = ["cryptography (>=3.3.1)"]
-dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.3.1)", "mypy", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
-docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+crypto = ["cryptography (>=3.4.0)"]
+dev = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.4.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "pre-commit"]
+docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
[[package]]
name = "pylint"
@@ -1620,8 +1603,8 @@ pylint = ">=2.0,<3"
pylint-plugin-utils = ">=0.7"
[package.extras]
-for_tests = ["coverage", "django-tables2", "django-tastypie", "factory-boy", "pylint (>=2.13)", "pytest", "wheel"]
-with_django = ["Django"]
+for_tests = ["django-tables2", "factory-boy", "coverage", "pytest", "wheel", "django-tastypie", "pylint (>=2.13)"]
+with_django = ["django"]
[[package]]
name = "pylint-plugin-utils"
@@ -1643,11 +1626,11 @@ optional = false
python-versions = ">=3.6.8"
[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
+diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pyrsistent"
-version = "0.18.1"
+version = "0.19.2"
description = "Persistent/Functional/Immutable data structures"
category = "main"
optional = false
@@ -1655,22 +1638,21 @@ python-versions = ">=3.7"
[[package]]
name = "pytest"
-version = "7.1.2"
+version = "7.2.0"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
-atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
-py = ">=1.8.2"
-tomli = ">=1.0.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
@@ -1718,7 +1700,7 @@ postgresql = ["psycopg2"]
[[package]]
name = "pytz"
-version = "2022.2.1"
+version = "2022.6"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
@@ -1726,7 +1708,7 @@ python-versions = "*"
[[package]]
name = "pyuwsgi"
-version = "2.0.20"
+version = "2.0.21"
description = "The uWSGI server"
category = "main"
optional = false
@@ -1753,7 +1735,7 @@ pyyaml = "*"
[[package]]
name = "redis"
-version = "4.3.4"
+version = "4.3.5"
description = "Python client for Redis database and key-value store"
category = "main"
optional = false
@@ -1761,7 +1743,6 @@ python-versions = ">=3.6"
[package.dependencies]
async-timeout = ">=4.0.2"
-deprecated = ">=1.2.3"
importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
packaging = ">=20.4"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
@@ -1786,7 +1767,7 @@ urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-oauthlib"
@@ -1805,7 +1786,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "rq"
-version = "1.11.0"
+version = "1.11.1"
description = "RQ is a simple, lightweight, library for creating background jobs, and processing them."
category = "main"
optional = false
@@ -1832,7 +1813,7 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
[[package]]
name = "ruamel.yaml.clib"
-version = "0.2.6"
+version = "0.2.7"
description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
category = "main"
optional = false
@@ -1846,19 +1827,6 @@ category = "main"
optional = false
python-versions = "*"
-[[package]]
-name = "setuptools"
-version = "65.3.0"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
-
[[package]]
name = "singledispatch"
version = "3.7.0"
@@ -1871,8 +1839,8 @@ python-versions = ">=2.6"
six = "*"
[package.extras]
-docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-flake8", "unittest2"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "unittest2", "pytest-checkdocs (>=2.4)"]
[[package]]
name = "six"
@@ -1927,15 +1895,15 @@ requests = ">=2.9.1"
requests-oauthlib = ">=0.6.1"
[package.extras]
-all = ["cryptography (>=2.1.1)", "lxml (<4.7)", "python-jose (>=3.0.0)", "python3-saml (>=1.2.1)"]
-allpy3 = ["cryptography (>=2.1.1)", "lxml (<4.7)", "python-jose (>=3.0.0)", "python3-saml (>=1.2.1)"]
+all = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "lxml (<4.7)", "cryptography (>=2.1.1)"]
+allpy3 = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "lxml (<4.7)", "cryptography (>=2.1.1)"]
azuread = ["cryptography (>=2.1.1)"]
openidconnect = ["python-jose (>=3.0.0)"]
-saml = ["lxml (<4.7)", "python3-saml (>=1.2.1)"]
+saml = ["python3-saml (>=1.2.1)", "lxml (<4.7)"]
[[package]]
name = "sqlparse"
-version = "0.4.2"
+version = "0.4.3"
description = "A non-validating SQL parser."
category = "main"
optional = false
@@ -1943,7 +1911,7 @@ python-versions = ">=3.5"
[[package]]
name = "stevedore"
-version = "3.5.0"
+version = "3.5.2"
description = "Manage dynamic plugins for Python applications"
category = "dev"
optional = false
@@ -1963,16 +1931,16 @@ python-versions = ">=3.6"
[[package]]
name = "swagger-spec-validator"
-version = "2.7.4"
+version = "3.0.3"
description = "Validation of Swagger specifications"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
[package.dependencies]
jsonschema = "*"
pyyaml = "*"
-six = "*"
+typing-extensions = "*"
[[package]]
name = "text-unidecode"
@@ -2000,13 +1968,14 @@ python-versions = ">=3.7"
[[package]]
name = "traitlets"
-version = "5.3.0"
+version = "5.5.0"
description = ""
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
+docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["pre-commit", "pytest"]
[[package]]
@@ -2019,7 +1988,7 @@ python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
-version = "4.3.0"
+version = "4.4.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
@@ -2042,8 +2011,8 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
@@ -2077,13 +2046,13 @@ python-versions = "*"
name = "wrapt"
version = "1.14.1"
description = "Module for decorators, wrappers and monkey patching."
-category = "main"
+category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "yamllint"
-version = "1.27.1"
+version = "1.28.0"
description = "A linter for YAML files."
category = "dev"
optional = false
@@ -2092,19 +2061,18 @@ python-versions = ">=3.6"
[package.dependencies]
pathspec = ">=0.5.3"
pyyaml = "*"
-setuptools = "*"
[[package]]
name = "zipp"
-version = "3.8.1"
+version = "3.10.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[extras]
nautobot = ["nautobot"]
@@ -2112,13 +2080,10 @@ nautobot = ["nautobot"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "6f114e030e8d6aaba7454cd91f681a1238f96a6b9e49fbdc5cb54a4c6fbeecec"
+content-hash = "8105d644864c0e77085eff1cc7472267668a3e3bc89da2bde7243cc5fd6770b9"
[metadata.files]
-absl-py = [
- {file = "absl-py-1.2.0.tar.gz", hash = "sha256:f568809938c49abbda89826223c992b630afd23c638160ad7840cfe347710d97"},
- {file = "absl_py-1.2.0-py3-none-any.whl", hash = "sha256:5d15f85b8cc859c6245bc9886ba664460ed96a6fee895416caa37d669ee74a9a"},
-]
+absl-py = []
amqp = [
{file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"},
{file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"},
@@ -2135,21 +2100,12 @@ asgiref = [
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
]
-astroid = [
- {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"},
- {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"},
-]
+astroid = []
async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
-atomicwrites = [
- {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
-]
-attrs = [
- {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
- {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
-]
+attrs = []
backcall = [
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
@@ -2162,47 +2118,17 @@ billiard = [
{file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"},
{file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"},
]
-black = [
- {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
- {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
- {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
- {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
- {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
- {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
- {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
- {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
- {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
- {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
- {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
- {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
- {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
- {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
- {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
- {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
- {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
- {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
- {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
- {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
- {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
- {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
- {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
-]
+black = []
cached-property = [
{file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
{file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
]
-capirca = [
- {file = "capirca-2.0.6-py3-none-any.whl", hash = "sha256:ef10e67d0dea8db5e7de11c27a4dce7c76a9fe4ac3dfaa1d570e36c16fe3d4e0"},
- {file = "capirca-2.0.6.tar.gz", hash = "sha256:a24bb05cb3f6d28184892112718a7b4dfb45d2d104f2df46feafa32e9dd33edd"},
-]
+capirca = []
celery = [
{file = "celery-5.2.7-py3-none-any.whl", hash = "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14"},
{file = "celery-5.2.7.tar.gz", hash = "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"},
]
-certifi = [
- {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
- {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
-]
+certifi = []
cffi = [
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
@@ -2269,10 +2195,7 @@ cffi = [
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
]
-charset-normalizer = [
- {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
- {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
-]
+charset-normalizer = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
@@ -2289,10 +2212,7 @@ click-repl = [
{file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"},
{file = "click_repl-0.2.0-py3-none-any.whl", hash = "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b"},
]
-colorama = [
- {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
- {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
-]
+colorama = []
coreapi = [
{file = "coreapi-2.3.3-py2.py3-none-any.whl", hash = "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"},
{file = "coreapi-2.3.3.tar.gz", hash = "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb"},
@@ -2301,82 +2221,8 @@ coreschema = [
{file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"},
{file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
]
-coverage = [
- {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
- {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"},
- {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"},
- {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"},
- {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"},
- {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"},
- {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"},
- {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"},
- {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"},
- {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"},
- {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"},
- {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"},
- {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"},
- {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"},
- {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"},
- {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"},
- {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"},
- {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"},
- {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"},
- {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"},
-]
-cryptography = [
- {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
- {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
- {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
- {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
- {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
- {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
- {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
- {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
- {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
- {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
- {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
- {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
- {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
- {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
- {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
- {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
- {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
- {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
- {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
- {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
- {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
- {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
-]
+coverage = []
+cryptography = []
decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
@@ -2385,18 +2231,8 @@ defusedxml = [
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
]
-deprecated = [
- {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
- {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
-]
-dill = [
- {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
- {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
-]
-django = [
- {file = "Django-3.2.15-py3-none-any.whl", hash = "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713"},
- {file = "Django-3.2.15.tar.gz", hash = "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"},
-]
+dill = []
+django = []
django-ajax-tables = [
{file = "django_ajax_tables-1.1.1-py3-none-any.whl", hash = "sha256:62e0138949153c0a994eefbf469f5496b1ad98bc073e170bc021a1aada7a32d0"},
{file = "django_ajax_tables-1.1.1.tar.gz", hash = "sha256:5a7e7bc7940aa6332a564916cde22010a858a3d29fc1090ce8061010ec76337c"},
@@ -2413,14 +2249,8 @@ django-celery-beat = [
{file = "django-celery-beat-2.2.1.tar.gz", hash = "sha256:97ae5eb309541551bdb07bf60cc57cadacf42a74287560ced2d2c06298620234"},
{file = "django_celery_beat-2.2.1-py2.py3-none-any.whl", hash = "sha256:ab43049634fd18dc037927d7c2c7d5f67f95283a20ebbda55f42f8606412e66c"},
]
-django-constance = [
- {file = "django-constance-2.8.0.tar.gz", hash = "sha256:0a492454acc78799ce7b9f7a28a00c53427d513f34f8bf6fdc90a46d8864b2af"},
- {file = "django_constance-2.8.0-py3-none-any.whl", hash = "sha256:60fec73e397d5f4f7440f611b18d3e7ce5342647f316fedc47b62e1411c849e7"},
-]
-django-cors-headers = [
- {file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"},
- {file = "django_cors_headers-3.13.0-py3-none-any.whl", hash = "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4"},
-]
+django-constance = []
+django-cors-headers = []
django-cryptography = [
{file = "django-cryptography-1.0.tar.gz", hash = "sha256:13de5cf8f1250744c104b9e24774d03aa6d8488959dd40cdc016934043652445"},
{file = "django_cryptography-1.0-py3-none-any.whl", hash = "sha256:0a99980b1cee7cc5e52f9b20b322620fea7cc124d770273e7bd285b20fd9d222"},
@@ -2428,14 +2258,8 @@ django-cryptography = [
django-db-file-storage = [
{file = "django-db-file-storage-0.5.5.tar.gz", hash = "sha256:5d5da694b78ab202accab4508b958e0e37b3d146310e76f6f6125e1bdeaaad14"},
]
-django-debug-toolbar = [
- {file = "django-debug-toolbar-3.6.0.tar.gz", hash = "sha256:95fc2fd29c56cc86678aae9f6919ececefe892f2a78c4004b193a223a8380c3d"},
- {file = "django_debug_toolbar-3.6.0-py3-none-any.whl", hash = "sha256:fe7fe3f21865218827e2162ecc06eba386dfe8cffe4f3501c49bb4359e06a0e6"},
-]
-django-extensions = [
- {file = "django-extensions-3.2.0.tar.gz", hash = "sha256:7dc7cd1da50d83b76447a58f5d7e5c8e6cd83f21e9b7e5f97e6b644f4d4e21a6"},
- {file = "django_extensions-3.2.0-py3-none-any.whl", hash = "sha256:4c234a7236e9e41c17d9036f6dae7a3a9b212527105b8a0d24b2459b267825f0"},
-]
+django-debug-toolbar = []
+django-extensions = []
django-filter = [
{file = "django-filter-21.1.tar.gz", hash = "sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e"},
{file = "django_filter-21.1-py3-none-any.whl", hash = "sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"},
@@ -2452,10 +2276,7 @@ django-js-asset = [
{file = "django_js_asset-2.0.0-py3-none-any.whl", hash = "sha256:86f9f300d682537ddaf0487dc2ab356581b8f50c069bdba91d334a46e449f923"},
{file = "django_js_asset-2.0.0.tar.gz", hash = "sha256:adc1ee1efa853fad42054b540c02205344bb406c9bddf87c9e5377a41b7db90f"},
]
-django-mptt = [
- {file = "django-mptt-0.13.4.tar.gz", hash = "sha256:80c9fb34df7796a4e5af0cb6b8ade3697555b1aa438bd07a01f32b3ab5202b63"},
- {file = "django_mptt-0.13.4-py3-none-any.whl", hash = "sha256:75745b621ae31d97957ed924155a750dfa8cacd9543799ada2d713fd6bc3f5c7"},
-]
+django-mptt = []
django-picklefield = [
{file = "django-picklefield-3.1.tar.gz", hash = "sha256:c786cbeda78d6def2b43bff4840d19787809c8909f7ad683961703060398d356"},
{file = "django_picklefield-3.1-py3-none-any.whl", hash = "sha256:d77c504df7311e8ec14e8b779f10ca6fec74de6c7f8e2c136e1ef60cf955125d"},
@@ -2476,38 +2297,21 @@ django-tables2 = [
{file = "django-tables2-2.4.1.tar.gz", hash = "sha256:6c72dd208358539e789e4c0efd7d151e43283a4aa4093a35f44c43489e7ddeaa"},
{file = "django_tables2-2.4.1-py2.py3-none-any.whl", hash = "sha256:50762bf3d7c61a4eb70e763c3e278650d7266bb78d0497fc8fafcf4e507c9a64"},
]
-django-taggit = [
- {file = "django-taggit-1.5.1.tar.gz", hash = "sha256:e5bb62891f458d55332e36a32e19c08d20142c43f74bc5656c803f8af25c084a"},
- {file = "django_taggit-1.5.1-py3-none-any.whl", hash = "sha256:dfe9e9c10b5929132041de0c00093ef0072c73c2a97d0f74a818ae50fa77149a"},
-]
+django-taggit = []
django-timezone-field = [
{file = "django-timezone-field-4.1.2.tar.gz", hash = "sha256:cffac62452d060e365938aa9c9f7b72d70d8b26b9c60243bce227b35abd1b9df"},
{file = "django_timezone_field-4.1.2-py3-none-any.whl", hash = "sha256:897c06e40b619cf5731a30d6c156886a7c64cba3a90364832148da7ef32ccf36"},
]
-django-tree-queries = [
- {file = "django_tree_queries-0.11.0-py3-none-any.whl", hash = "sha256:d74fe9e36dc67cb2c6f7df1969ef700627b0c49af2ada15990dd3ae44e4ddb23"},
- {file = "django_tree_queries-0.11.0.tar.gz", hash = "sha256:768bc75fd5ab617e19bec5c7e207964d7f2f6155f5f3d8c3332b41d9d0e6b436"},
-]
+django-tree-queries = []
django-webserver = [
{file = "django-webserver-1.2.0.tar.gz", hash = "sha256:c976979d15b5ff9a212f7904d3b779e22219aebb4857860fcaf20e4e40f1da40"},
{file = "django_webserver-1.2.0-py2.py3-none-any.whl", hash = "sha256:09200631f266484b9e944e38e92681d6e9aa7d90d089a5c86d5fb08fddad84fe"},
]
-djangorestframework = [
- {file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
- {file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
-]
-drf-spectacular = [
- {file = "drf-spectacular-0.22.1.tar.gz", hash = "sha256:866e16ddaae167a1234c76cd8c351161373551db994ce9665b347b32d5daf38b"},
- {file = "drf_spectacular-0.22.1-py3-none-any.whl", hash = "sha256:17ac5e31e5d6150dd5fa10843b429202f4f38069202acc44394cc5a771de63d9"},
-]
-drf-spectacular-sidecar = [
- {file = "drf-spectacular-sidecar-2022.8.1.tar.gz", hash = "sha256:369c1ccc0e037ca36c82844f05f4508519eac6fd27a6282ebf768b61901e091d"},
- {file = "drf_spectacular_sidecar-2022.8.1-py3-none-any.whl", hash = "sha256:e7cf6a0896151ec141666e91d84e3768a10f33af4b74f09b3640a3b19271c539"},
-]
-drf-yasg = [
- {file = "drf-yasg-1.21.3.tar.gz", hash = "sha256:b2eebb438fa641503a08dac791be24183ea26dbcfe371a9824ea91f6e3a988aa"},
- {file = "drf_yasg-1.21.3-py3-none-any.whl", hash = "sha256:85899b8b173cac7d39af3fea205bdc1a599aed8de3389a3bcccc29b37b9b983e"},
-]
+djangorestframework = []
+drf-spectacular = []
+drf-spectacular-sidecar = []
+drf-yasg = []
+exceptiongroup = []
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
@@ -2524,10 +2328,7 @@ gitdb = [
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
]
-gitpython = [
- {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
- {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
-]
+gitpython = []
graphene = [
{file = "graphene-2.1.9-py2.py3-none-any.whl", hash = "sha256:3d446eb1237c551052bc31155cf1a3a607053e4f58c9172b83a1b597beaa0868"},
{file = "graphene-2.1.9.tar.gz", hash = "sha256:b9f2850e064eebfee9a3ef4a1f8aa0742848d97652173ab44c82cc8a62b9ed93"},
@@ -2547,18 +2348,9 @@ graphql-relay = [
{file = "graphql-relay-2.0.1.tar.gz", hash = "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb"},
{file = "graphql_relay-2.0.1-py3-none-any.whl", hash = "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d"},
]
-idna = [
- {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
- {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
-]
-importlib-metadata = [
- {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
- {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
-]
-importlib-resources = [
- {file = "importlib_resources-5.9.0-py3-none-any.whl", hash = "sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7"},
- {file = "importlib_resources-5.9.0.tar.gz", hash = "sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681"},
-]
+idna = []
+importlib-metadata = []
+importlib-resources = []
inflection = [
{file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
{file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
@@ -2567,14 +2359,8 @@ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
-invoke = [
- {file = "invoke-1.7.1-py3-none-any.whl", hash = "sha256:2dc975b4f92be0c0a174ad2d063010c8a1fdb5e9389d69871001118b4fcac4fb"},
- {file = "invoke-1.7.1.tar.gz", hash = "sha256:7b6deaf585eee0a848205d0b8c0014b9bf6f287a8eb798818a642dff1df14b19"},
-]
-ipaddress = [
- {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"},
- {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"},
-]
+invoke = []
+ipaddress = []
ipython = [
{file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"},
{file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"},
@@ -2587,61 +2373,17 @@ itypes = [
{file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"},
{file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"},
]
-jedi = [
- {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
- {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
-]
+jedi = []
jinja2 = [
- {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
- {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
-]
-jsonschema = [
- {file = "jsonschema-4.7.2-py3-none-any.whl", hash = "sha256:c7448a421b25e424fccfceea86b4e3a8672b4436e1988ccbde92c80828d4f085"},
- {file = "jsonschema-4.7.2.tar.gz", hash = "sha256:73764f461d61eb97a057c929368610a134d1d1fffd858acfe88864ee94f1f1d3"},
+ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
+jsonschema = []
kombu = [
{file = "kombu-5.2.4-py3-none-any.whl", hash = "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"},
{file = "kombu-5.2.4.tar.gz", hash = "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610"},
]
-lazy-object-proxy = [
- {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
- {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
- {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
- {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
- {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
- {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
- {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
-]
+lazy-object-proxy = []
markdown = [
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
@@ -2688,10 +2430,7 @@ markupsafe = [
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
-matplotlib-inline = [
- {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
- {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
-]
+matplotlib-inline = []
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
@@ -2700,14 +2439,8 @@ mergedeep = [
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
]
-mkdocs = [
- {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"},
- {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"},
-]
-mkdocs-include-markdown-plugin = [
- {file = "mkdocs_include_markdown_plugin-3.6.1-py3-none-any.whl", hash = "sha256:ea36a7d50ee98028f03574d7bf40a307e16211ad3013a4e42f64494b3c106e9e"},
- {file = "mkdocs_include_markdown_plugin-3.6.1.tar.gz", hash = "sha256:5e7416f23081085a220f7534b2fc7456e74c5a65f3b401da1f29b9e9132b46e5"},
-]
+mkdocs = []
+mkdocs-include-markdown-plugin = []
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
{file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"},
@@ -2716,22 +2449,13 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
-nautobot = [
- {file = "nautobot-1.4.1-py3-none-any.whl", hash = "sha256:35d6f7c4add7937c06221add486806fed86de3ebf3da1281b4bc1d81b748bdb1"},
- {file = "nautobot-1.4.1.tar.gz", hash = "sha256:76ce3c17397c9a9a5db32c99c11eaeb46827c3dde635a35d864606515cd4f240"},
-]
+nautobot = []
netaddr = [
{file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
{file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"},
]
-netutils = [
- {file = "netutils-1.1.0-py3-none-any.whl", hash = "sha256:bcb4367689c773cd30bf898b15bed38f22dc8bd69e64f2272bb9dd4726bf41d4"},
- {file = "netutils-1.1.0.tar.gz", hash = "sha256:90ab637c60ae18c515191224c52a7b0a1eb6fdde2f8dd40dfac638f2dd06ef2c"},
-]
-oauthlib = [
- {file = "oauthlib-3.2.1-py3-none-any.whl", hash = "sha256:88e912ca1ad915e1dcc1c06fc9259d19de8deacd6fd17cc2df266decc2e49066"},
- {file = "oauthlib-3.2.1.tar.gz", hash = "sha256:1565237372795bf6ee3e5aba5e2a85bd5a65d0e2aa5c628b9a97b7d7a0da3721"},
-]
+netutils = []
+oauthlib = []
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
@@ -2740,14 +2464,8 @@ parso = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
]
-pathspec = [
- {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
- {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
-]
-pbr = [
- {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"},
- {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"},
-]
+pathspec = []
+pbr = []
pexpect = [
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
@@ -2756,155 +2474,23 @@ pickleshare = [
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
-pillow = [
- {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"},
- {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"},
- {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"},
- {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"},
- {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"},
- {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"},
- {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"},
- {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"},
- {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"},
- {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"},
- {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc"},
- {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437"},
- {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"},
- {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"},
- {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"},
- {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"},
- {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"},
- {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"},
- {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"},
- {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"},
- {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"},
- {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"},
- {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"},
- {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"},
- {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"},
- {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"},
- {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"},
- {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"},
- {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"},
- {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"},
- {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"},
- {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"},
- {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"},
- {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"},
- {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"},
- {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"},
- {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"},
- {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"},
- {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"},
- {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"},
- {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"},
- {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"},
- {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"},
- {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"},
- {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"},
- {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"},
- {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"},
- {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"},
- {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"},
- {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"},
- {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"},
- {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"},
- {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"},
- {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"},
- {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"},
- {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"},
- {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"},
- {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"},
-]
-platformdirs = [
- {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
- {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
-]
+pillow = []
+platformdirs = []
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
-ply = [
- {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
- {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
-]
-prometheus-client = [
- {file = "prometheus_client-0.14.1-py3-none-any.whl", hash = "sha256:522fded625282822a89e2773452f42df14b5a8e84a86433e3f8a189c1d54dc01"},
- {file = "prometheus_client-0.14.1.tar.gz", hash = "sha256:5459c427624961076277fdc6dc50540e2bacb98eebde99886e59ec55ed92093a"},
-]
+ply = []
+prometheus-client = []
promise = [
{file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
]
-prompt-toolkit = [
- {file = "prompt_toolkit-3.0.30-py3-none-any.whl", hash = "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"},
- {file = "prompt_toolkit-3.0.30.tar.gz", hash = "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0"},
-]
-psycopg2-binary = [
- {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"},
-]
+prompt-toolkit = []
+psycopg2-binary = []
ptyprocess = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
-py = [
- {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
- {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
-]
pycodestyle = [
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
@@ -2953,14 +2539,8 @@ pyflakes = [
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
-pygments = [
- {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
- {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
-]
-pyjwt = [
- {file = "PyJWT-2.4.0-py3-none-any.whl", hash = "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf"},
- {file = "PyJWT-2.4.0.tar.gz", hash = "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba"},
-]
+pygments = []
+pyjwt = []
pylint = [
{file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"},
{file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"},
@@ -2977,33 +2557,8 @@ pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
-pyrsistent = [
- {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
- {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
- {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
- {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
- {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
- {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
- {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
- {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
- {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
- {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
- {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
- {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
- {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
- {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
- {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
- {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
- {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
- {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
- {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
- {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
- {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
-]
-pytest = [
- {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
- {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
-]
+pyrsistent = []
+pytest = []
python-crontab = [
{file = "python-crontab-2.6.0.tar.gz", hash = "sha256:1e35ed7a3cdc3100545b43e196d34754e6551e7f95e4caebbe0e1c0ca41c2f1b"},
]
@@ -3015,48 +2570,8 @@ python3-openid = [
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
]
-pytz = [
- {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
- {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
-]
-pyuwsgi = [
- {file = "pyuwsgi-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd09a7328558728f6e38f77609f44d16f18d8b4dc5f8b8776f657c8892b70337"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210ad768b8af02bbfd3c0345a51c6d2c56a2333ac268d9a97ecf2d4209f78ef8"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e33111308a79dbd202094963fd3a537dab30854e375d9be018f642fcd359e03d"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0306c3ca6f781ed7409774e44a2a90217824d3efb44e075a1f39a78a54ffe7d"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3eeea48fd965b68801618fc20eda2cb65dc2aeb4121955c9c422685f046311da"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dd048b85abb90c79a34afbf787995ab2933cb2a8b210a6e064e5fca0a9bdf2d8"},
- {file = "pyuwsgi-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f612a72437c2934e39393fb575fab791e919efc102a0aa1a27b5d9a301d462e9"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:78ac2d8b7d307232f8276cb8a45300211b0fae179c0494f2a3e3178c70693f81"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f84fc44bba77d05fa9d01189028501ff8e6a2d63db184ceea42085ce28196317"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:342c312949c2f494f5d9ac272965715483cb22ed0353c18abb1b4590769ef00f"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5740b84a016a246c69edef0df11912560df9a45c2adf6e47701329a974e37a71"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:82058f6b6656f12dbc66c963be2d7c0b977f481ddca5d4d3918e26def003b60d"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:dc99724df6d5613300d3357c4017cde840830ca132fe98f0a4feab9a6af48622"},
- {file = "pyuwsgi-2.0.20-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:70096084ccfa18f058dfb3380aa10e80d48958ddfdb888b827f06aacb72f80b8"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e7a8576452635612c82f660e5be99ba42cdcdff142948f5c263b5278f2c17b0"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2844205e0095a29d3a6e9e23bd37bcf18d65e4e5ab23a14a45f5159c03a3b890"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2c0d5dafb9faacc8ed5b662f9eacb22e723fbc3cb7152b2ebf09a92b8bd6c0d"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087397457837c5fd653eda91c5d2c7948e06da589c786d26145bdcc397656bb2"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:da24ddb7e3522d36b4cdc2dbf131141d559084862f515d37f9a4b7b5c8b538b1"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:99b0c5a87aa95dcbaca2ccb599d783f37461600e098881d4ec9796ba68e3ff8f"},
- {file = "pyuwsgi-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:df08a05dda4a92da46bd56af755dac091b145815c3cf6dda1855e01e40d12a99"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c8ac4b2f411d15222f53a96627b8fa79056b39606925f429f59a51668bd95ab"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f1494d00df440865b1ee269a350187766c9c364ba5f124eddb064f366c4b6d8a"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c5ee8782932dbacdb4dd98714c3575cbbb88ba87d7bcb3149c828725ba048897"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cdc4b32234b40fa2d515108a2f56a159896134ef16394f26dba764635142976"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:300417d6669171a454da19991fc7f910b3975232dac5b2dbc359e26f1038624b"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:015a8d310503eb8c0028ac8f98c7df3b49bc937c7db137aaf149ca7fae64c777"},
- {file = "pyuwsgi-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9fc3d256270adf24b87f1dff893f3bc59bd5ee20c4ebb5add25e944c36359e1e"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:052fe12d6a9ab9e9fc7814e40d2d28061aaea1a610069008d773e1d8a71feb11"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7d160c7bfa8f2974fc6b807a8fc080be0d130b1a4ad0b463c0320e707e6a6eb6"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7691ca470e541133c444b23f83c6f07ecca12f4771a49e0e3a04b05f4c80f2d3"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c87fb1d56dd2c6f18b87b08496be326afb6cf57c6a059261b01ce99046d9147"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8eb97cf51f58df05ec0dd74d83510172a2f5bcd8adc91382e24690a2abddc2f3"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a8fc4d52232f55df16e9cbf6faf4bf16910d15042229d5f8c717278fd4ea37a5"},
- {file = "pyuwsgi-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1acedb1841aa0e60e1a7245ae483a5fcb30e39cf0cdfe3987e14de4746daf75b"},
- {file = "pyuwsgi-2.0.20.tar.gz", hash = "sha256:8b0936a964511fa0e2ac2847c2435e95130e7c98619f06fe1f67320527576043"},
-]
+pytz = []
+pyuwsgi = []
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -3065,13 +2580,6 @@ pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
- {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
- {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
- {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
- {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
@@ -3103,10 +2611,7 @@ pyyaml-env-tag = [
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
]
-redis = [
- {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"},
- {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"},
-]
+redis = []
requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
@@ -3115,54 +2620,16 @@ requests-oauthlib = [
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
]
-rq = [
- {file = "rq-1.11.0-py2.py3-none-any.whl", hash = "sha256:c0bbf898b56817da053cdc992ab46da2729a7ccd70dd50316ad9209b7d7b71c1"},
- {file = "rq-1.11.0.tar.gz", hash = "sha256:50d0cf687cfb2530eac9396c7426e420958a166e8f4666bd2096bdcf7f4ad03e"},
-]
+rq = []
"ruamel.yaml" = [
{file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
{file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
]
-"ruamel.yaml.clib" = [
- {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
- {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"},
- {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
- {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
- {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
- {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
- {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
- {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
- {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"},
- {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"},
- {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"},
- {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d3c620a54748a3d4cf0bcfe623e388407c8e85a4b06b8188e126302bcab93ea8"},
- {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"},
- {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"},
- {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"},
- {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"},
- {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:210c8fcfeff90514b7133010bf14e3bad652c8efde6b20e00c43854bf94fa5a6"},
- {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"},
- {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"},
- {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"},
- {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"},
- {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:61bc5e5ca632d95925907c569daa559ea194a4d16084ba86084be98ab1cec1c6"},
- {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"},
- {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"},
- {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"},
- {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"},
- {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1b4139a6ffbca8ef60fdaf9b33dec05143ba746a6f0ae0f9d11d38239211d335"},
- {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"},
- {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"},
- {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"},
-]
+"ruamel.yaml.clib" = []
rx = [
{file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"},
{file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
]
-setuptools = [
- {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"},
- {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"},
-]
singledispatch = [
{file = "singledispatch-3.7.0-py2.py3-none-any.whl", hash = "sha256:bc77afa97c8a22596d6d4fc20f1b7bdd2b86edc2a65a4262bdd7cc3cc19aa989"},
{file = "singledispatch-3.7.0.tar.gz", hash = "sha256:c1a4d5c1da310c3fd8fccfb8d4e1cb7df076148fd5d858a819e37fffe44f3092"},
@@ -3187,22 +2654,10 @@ social-auth-core = [
{file = "social-auth-core-4.3.0.tar.gz", hash = "sha256:4686f0e43cf12954216875a32e944847bb1dc69e7cd9573d16a9003bb05ca477"},
{file = "social_auth_core-4.3.0-py3-none-any.whl", hash = "sha256:1e3440d104f743b02dfe258c9d4dba5b4065abf24b2f7eb362b47054d21797df"},
]
-sqlparse = [
- {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
- {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
-]
-stevedore = [
- {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"},
- {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"},
-]
-svgwrite = [
- {file = "svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"},
- {file = "svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3"},
-]
-swagger-spec-validator = [
- {file = "swagger-spec-validator-2.7.4.tar.gz", hash = "sha256:2aee5e1fc0503be9f8299378b10c92169572781573c6de3315e831fd0559ba73"},
- {file = "swagger_spec_validator-2.7.4-py2.py3-none-any.whl", hash = "sha256:4e373a4db5262e7257fde17d84c5c0178327b8057985ab1be63f580bfa009855"},
-]
+sqlparse = []
+stevedore = []
+svgwrite = []
+swagger-spec-validator = []
text-unidecode = [
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
@@ -3215,10 +2670,7 @@ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
-traitlets = [
- {file = "traitlets-5.3.0-py3-none-any.whl", hash = "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a"},
- {file = "traitlets-5.3.0.tar.gz", hash = "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2"},
-]
+traitlets = []
typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
@@ -3245,18 +2697,12 @@ typed-ast = [
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
-typing-extensions = [
- {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
- {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
-]
+typing-extensions = []
uritemplate = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
]
-urllib3 = [
- {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
- {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
-]
+urllib3 = []
vine = [
{file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
{file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"},
@@ -3358,10 +2804,5 @@ wrapt = [
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
-yamllint = [
- {file = "yamllint-1.27.1.tar.gz", hash = "sha256:e688324b58560ab68a1a3cff2c0a474e3fed371dfe8da5d1b9817b7df55039ce"},
-]
-zipp = [
- {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
- {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
-]
+yamllint = []
+zipp = []
diff --git a/pyproject.toml b/pyproject.toml
index d928bd88..80458a25 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "nautobot-firewall-models"
-version = "1.1.3"
+version = "1.2.0-alpha.0"
description = "Nautobot plugin to model firewall objects."
authors = ["Network to Code, LLC "]
license = "Apache-2.0"
@@ -20,7 +20,7 @@ packages = [
[tool.poetry.dependencies]
# Used for local development
-nautobot = "^1.3.0"
+nautobot = "^1.4.0"
python = "^3.7"
netutils = "^1.0.0"
capirca = "^2.0.6"
diff --git a/tasks.py b/tasks.py
index 563589f5..0f5a61f2 100644
--- a/tasks.py
+++ b/tasks.py
@@ -356,7 +356,7 @@ def yamllint(context):
@task
def check_migrations(context):
"""Check for missing migrations."""
- command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check"
+ command = "nautobot-server makemigrations --dry-run --check"
run_command(context, command)
|