Merge pull request #85 from Onemind-Services-LLC/housekeeping/cleanup

This commit is contained in:
Ryan Merolle 2023-01-21 09:16:24 -05:00 committed by GitHub
commit 91e5d9c1c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 90 deletions

View File

@ -5,7 +5,7 @@ services:
dockerfile: Dockerfile-plugin_dev dockerfile: Dockerfile-plugin_dev
context: . context: .
ports: ports:
- 8000:8080 - "8000:8080"
volumes: volumes:
- ../:/opt/netbox/netbox/netbox-acls - ../:/opt/netbox/netbox/netbox-acls
- ~/.gitconfig:/home/vscode/.gitconfig:z,ro - ~/.gitconfig:/home/vscode/.gitconfig:z,ro

View File

@ -3,7 +3,7 @@
# ``` # ```
# termination_x_name -> name of interface # termination_x_name -> name of interface
# termination_x_device -> name of the device interface belongs to # termination_x_device -> name of the device interface belongs to
# termination_x_class -> required if different than 'Interface' which is the default # termination_x_class -> required if different from 'Interface' which is the default
# ``` # ```
# #
# Supported termination classes: Interface, ConsolePort, ConsoleServerPort, FrontPort, RearPort, PowerPort, PowerOutlet # Supported termination classes: Interface, ConsolePort, ConsoleServerPort, FrontPort, RearPort, PowerPort, PowerOutlet

2
.gitignore vendored
View File

@ -161,3 +161,5 @@ cython_debug/
# VS Code # VS Code
.vscode/ .vscode/
# JetBrains
.idea/

View File

@ -3,9 +3,9 @@
## Reporting Bugs ## Reporting Bugs
* First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases) * First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases)
of NetBox or this plugin [latest stable version](https://github.com/ryanmerolle/netbox-acls/releases). of NetBox or this plugin is at [latest stable version](https://github.com/ryanmerolle/netbox-acls/releases).
If you're running an older version, it's possible that the bug has already been fixed If you're running an older version, it's possible that the bug has already been fixed
or you are running a version of the plugin not tested with the NetBox version or, you are running a version of the plugin not tested with the NetBox version
you are running [Compatibility Matrix](./README.md#compatibility). you are running [Compatibility Matrix](./README.md#compatibility).
* Next, check the GitHub [issues list](https://github.com/ryanmerolle/netbox-acls/issues) * Next, check the GitHub [issues list](https://github.com/ryanmerolle/netbox-acls/issues)

View File

@ -19,7 +19,7 @@ Based on the NetBox plugin tutorial by [jeremystretch](https://github.com/jeremy
- [demo repository](https://github.com/netbox-community/netbox-plugin-demo) - [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
- [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial) - [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
All credit should go to Jeremy. Thanks Jeremy! All credit should go to Jeremy. Thanks, Jeremy!
This project just looks to build on top of this framework and model presented. This project just looks to build on top of this framework and model presented.
@ -69,12 +69,12 @@ PLUGINS_CONFIG = {
To develop this plugin further one can use the included .devcontainer configuration. This configuration creates a docker container which includes a fully working netbox installation. Currently it should work when using WSL 2. For this to work make sure you have Docker Desktop installed and the WSL 2 integrations activated. To develop this plugin further one can use the included .devcontainer configuration. This configuration creates a docker container which includes a fully working netbox installation. Currently it should work when using WSL 2. For this to work make sure you have Docker Desktop installed and the WSL 2 integrations activated.
1. In the WSL terminal, enter `code` to run Visual studio code. 1. In the WSL terminal, enter `code` to run Visual studio code.
1. Install the devcontainer extension "ms-vscode-remote.remote-containers" 2. Install the devcontainer extension "ms-vscode-remote.remote-containers"
1. Press Ctrl+Shift+P and use the "Dev Container: Clone Repository in Container Volume" function to clone this repository. This will take a while depending on your computer 3. Press Ctrl+Shift+P and use the "Dev Container: Clone Repository in Container Volume" function to clone this repository. This will take a while depending on your computer
1. If you'd like the netbox instance to be prepopulated run `make Makefile example_initializers` and `make Makefile load_initializers` 4. If you'd like the netbox instance to be prepopulated run `make Makefile example_initializers` and `make Makefile load_initializers`
1. Start the netbox instance using `make Makefile all` 5. Start the netbox instance using `make Makefile all`
Your netbox instance will be served under 0.0.0.0:8000 so it should now be available under localhost:8000. Your netbox instance will be served under 0.0.0.0:8000, so it should now be available under localhost:8000.
## Screenshots ## Screenshots

View File

@ -9,6 +9,7 @@ from drf_yasg.utils import swagger_serializer_method
from ipam.api.serializers import NestedPrefixSerializer from ipam.api.serializers import NestedPrefixSerializer
from netbox.api.fields import ContentTypeField from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.constants import NESTED_SERIALIZER_PREFIX
from rest_framework import serializers from rest_framework import serializers
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
@ -82,7 +83,10 @@ class AccessListSerializer(NetBoxModelSerializer):
@swagger_serializer_method(serializer_or_field=serializers.DictField) @swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_assigned_object(self, obj): def get_assigned_object(self, obj):
serializer = get_serializer_for_model(obj.assigned_object, prefix="Nested") serializer = get_serializer_for_model(
obj.assigned_object,
prefix=NESTED_SERIALIZER_PREFIX,
)
context = {"request": self.context["request"]} context = {"request": self.context["request"]}
return serializer(obj.assigned_object, context=context).data return serializer(obj.assigned_object, context=context).data
@ -96,6 +100,7 @@ class AccessListSerializer(NetBoxModelSerializer):
# Check that the GFK object is valid. # Check that the GFK object is valid.
if "assigned_object_type" in data and "assigned_object_id" in data: if "assigned_object_type" in data and "assigned_object_id" in data:
# TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed.
try: try:
assigned_object = data[ # noqa: F841 assigned_object = data[ # noqa: F841
"assigned_object_type" "assigned_object_type"
@ -161,21 +166,25 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
@swagger_serializer_method(serializer_or_field=serializers.DictField) @swagger_serializer_method(serializer_or_field=serializers.DictField)
def get_assigned_object(self, obj): def get_assigned_object(self, obj):
serializer = get_serializer_for_model(obj.assigned_object, prefix="Nested") serializer = get_serializer_for_model(
obj.assigned_object,
prefix=NESTED_SERIALIZER_PREFIX,
)
context = {"request": self.context["request"]} context = {"request": self.context["request"]}
return serializer(obj.assigned_object, context=context).data return serializer(obj.assigned_object, context=context).data
def validate(self, data): def validate(self, data):
""" """
Validate the AccessList django model model's inputs before allowing it to update the instance. Validate the AccessList django model's inputs before allowing it to update the instance.
- Check that the GFK object is valid. - Check that the GFK object is valid.
- Check that the associated interface's parent host has the selected ACL defined. - Check that the associated interface's parent host has the selected ACL defined.
""" """
error_message = {} error_message = {}
acl_host = data["access_list"].assigned_object acl_host = data["access_list"].assigned_object
# Check that the GFK object is vlaid. # Check that the GFK object is valid.
if "assigned_object_type" in data and "assigned_object_id" in data: if "assigned_object_type" in data and "assigned_object_id" in data:
# TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed.
try: try:
assigned_object = data[ # noqa: F841 assigned_object = data[ # noqa: F841
"assigned_object_type" "assigned_object_type"
@ -200,6 +209,8 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
.get_object_for_this_type(id=data["assigned_object_id"]) .get_object_for_this_type(id=data["assigned_object_id"])
.virtual_machine .virtual_machine
) )
else:
interface_host = None
# Check that the associated interface's parent host has the selected ACL defined. # Check that the associated interface's parent host has the selected ACL defined.
if acl_host != interface_host: if acl_host != interface_host:
error_acl_not_assigned_to_host = ( error_acl_not_assigned_to_host = (
@ -253,7 +264,7 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer):
def validate(self, data): def validate(self, data):
""" """
Validate the ACLStandardRule django model model's inputs before allowing it to update the instance: Validate the ACLStandardRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set. - Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set. - Check if action set to remark, but source_prefix set.
""" """
@ -322,7 +333,7 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer):
def validate(self, data): def validate(self, data):
""" """
Validate the ACLExtendedRule django model model's inputs before allowing it to update the instance: Validate the ACLExtendedRule django model's inputs before allowing it to update the instance:
- Check if action set to remark, but no remark set. - Check if action set to remark, but no remark set.
- Check if action set to remark, but source_prefix set. - Check if action set to remark, but source_prefix set.
- Check if action set to remark, but source_ports set. - Check if action set to remark, but source_ports set.

View File

@ -51,9 +51,12 @@ class ACLAssignmentDirectionChoices(ChoiceSet):
Defines the direction of the application of the ACL on an associated interface. Defines the direction of the application of the ACL on an associated interface.
""" """
DIRECTION_INGRESS = "ingress"
DIRECTION_EGRESS = "egress"
CHOICES = [ CHOICES = [
("ingress", "Ingress", "blue"), (DIRECTION_INGRESS, "Ingress", "blue"),
("egress", "Egress", "purple"), (DIRECTION_EGRESS, "Egress", "purple"),
] ]
@ -62,9 +65,12 @@ class ACLTypeChoices(ChoiceSet):
Defines the choices availble for the Access Lists plugin specific to ACL type. Defines the choices availble for the Access Lists plugin specific to ACL type.
""" """
TYPE_STANDARD = "standard"
TYPE_EXTENDED = "extended"
CHOICES = [ CHOICES = [
("extended", "Extended", "purple"), (TYPE_EXTENDED, "Extended", "purple"),
("standard", "Standard", "blue"), (TYPE_STANDARD, "Standard", "blue"),
] ]
@ -73,8 +79,12 @@ class ACLProtocolChoices(ChoiceSet):
Defines the choices availble for the Access Lists plugin specific to ACL Rule protocol. Defines the choices availble for the Access Lists plugin specific to ACL Rule protocol.
""" """
PROTOCOL_ICMP = "icmp"
PROTOCOL_TCP = "tcp"
PROTOCOL_UDP = "udp"
CHOICES = [ CHOICES = [
("icmp", "ICMP", "purple"), (PROTOCOL_ICMP, "ICMP", "purple"),
("tcp", "TCP", "blue"), (PROTOCOL_TCP, "TCP", "blue"),
("udp", "UDP", "orange"), (PROTOCOL_UDP, "UDP", "orange"),
] ]

View File

@ -22,6 +22,7 @@ from virtualization.models import (
VMInterface, VMInterface,
) )
from ..choices import ACLTypeChoices
from ..models import ( from ..models import (
AccessList, AccessList,
ACLExtendedRule, ACLExtendedRule,
@ -236,8 +237,12 @@ class AccessListForm(NetBoxModelForm):
"name": [error_same_acl_name], "name": [error_same_acl_name],
} }
# Check if Access List has no existing rules before change the Access List's type. # Check if Access List has no existing rules before change the Access List's type.
if (acl_type == "extended" and self.instance.aclstandardrules.exists()) or ( if (
acl_type == "standard" and self.instance.aclextendedrules.exists() acl_type == ACLTypeChoices.TYPE_EXTENDED
and self.instance.aclstandardrules.exists()
) or (
acl_type == ACLTypeChoices.TYPE_STANDARD
and self.instance.aclextendedrules.exists()
): ):
error_message["type"] = [ error_message["type"] = [
"This ACL has ACL rules associated, CANNOT change ACL type.", "This ACL has ACL rules associated, CANNOT change ACL type.",
@ -312,10 +317,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
), ),
) )
comments = CommentField() comments = CommentField()
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -355,7 +356,7 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
""" """
Validates form inputs before submitting: Validates form inputs before submitting:
- Check if both interface and vminterface are set. - Check if both interface and vminterface are set.
- Check if neither interface or vminterface are set. - Check if neither interface nor vminterface are set.
- Check that an interface's parent device/virtual_machine is assigned to the Access List. - Check that an interface's parent device/virtual_machine is assigned to the Access List.
- Check that an interface's parent device/virtual_machine is assigned to the Access List. - Check that an interface's parent device/virtual_machine is assigned to the Access List.
- Check for duplicate entry. (Because of GFK) - Check for duplicate entry. (Because of GFK)
@ -367,7 +368,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
direction = cleaned_data.get("direction") direction = cleaned_data.get("direction")
interface = cleaned_data.get("interface") interface = cleaned_data.get("interface")
vminterface = cleaned_data.get("vminterface") vminterface = cleaned_data.get("vminterface")
assigned_object = cleaned_data.get("assigned_object")
# Check if both interface and vminterface are set. # Check if both interface and vminterface are set.
if interface and vminterface: if interface and vminterface:
@ -403,40 +403,42 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
).pk ).pk
access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object
# Check that an interface's parent device/virtual_machine is assigned to the Access List. # Check that an interface's parent device/virtual_machine is assigned to the Access List.
if access_list_host != host: if access_list_host != host:
error_acl_not_assigned_to_host = "Access List not present on selected host." error_acl_not_assigned_to_host = (
error_message |= { "Access List not present on selected host."
"access_list": [error_acl_not_assigned_to_host], )
assigned_object_type: [error_acl_not_assigned_to_host], error_message |= {
host_type: [error_acl_not_assigned_to_host], "access_list": [error_acl_not_assigned_to_host],
} assigned_object_type: [error_acl_not_assigned_to_host],
# Check for duplicate entry. host_type: [error_acl_not_assigned_to_host],
if ACLInterfaceAssignment.objects.filter( }
access_list=access_list, # Check for duplicate entry.
assigned_object_id=assigned_object_id, if ACLInterfaceAssignment.objects.filter(
assigned_object_type=assigned_object_type_id, access_list=access_list,
direction=direction, assigned_object_id=assigned_object_id,
).exists(): assigned_object_type=assigned_object_type_id,
error_duplicate_entry = "An ACL with this name is already associated to this interface & direction." direction=direction,
error_message |= { ).exists():
"access_list": [error_duplicate_entry], error_duplicate_entry = "An ACL with this name is already associated to this interface & direction."
"direction": [error_duplicate_entry], error_message |= {
assigned_object_type: [error_duplicate_entry], "access_list": [error_duplicate_entry],
} "direction": [error_duplicate_entry],
# Check that the interface does not have an existing ACL applied in the direction already. assigned_object_type: [error_duplicate_entry],
if ACLInterfaceAssignment.objects.filter( }
assigned_object_id=assigned_object_id, # Check that the interface does not have an existing ACL applied in the direction already.
assigned_object_type=assigned_object_type_id, if ACLInterfaceAssignment.objects.filter(
direction=direction, assigned_object_id=assigned_object_id,
).exists(): assigned_object_type=assigned_object_type_id,
error_interface_already_assigned = ( direction=direction,
"Interfaces can only have 1 Access List assigned in each direction." ).exists():
) error_interface_already_assigned = (
error_message |= { "Interfaces can only have 1 Access List assigned in each direction."
"direction": [error_interface_already_assigned], )
assigned_object_type: [error_interface_already_assigned], error_message |= {
} "direction": [error_interface_already_assigned],
assigned_object_type: [error_interface_already_assigned],
}
if error_message: if error_message:
raise forms.ValidationError(error_message) raise forms.ValidationError(error_message)
@ -460,7 +462,7 @@ class ACLStandardRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField( access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all(), queryset=AccessList.objects.all(),
query_params={ query_params={
"type": "standard", "type": ACLTypeChoices.TYPE_STANDARD,
}, },
help_text=mark_safe( help_text=mark_safe(
"<b>*Note:</b> This field will only display Standard ACLs.", "<b>*Note:</b> This field will only display Standard ACLs.",
@ -473,10 +475,6 @@ class ACLStandardRuleForm(NetBoxModelForm):
help_text=help_text_acl_rule_logic, help_text=help_text_acl_rule_logic,
label="Source Prefix", label="Source Prefix",
) )
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False,
)
fieldsets = ( fieldsets = (
("Access List Details", ("access_list", "description", "tags")), ("Access List Details", ("access_list", "description", "tags")),
@ -542,17 +540,14 @@ class ACLExtendedRuleForm(NetBoxModelForm):
access_list = DynamicModelChoiceField( access_list = DynamicModelChoiceField(
queryset=AccessList.objects.all(), queryset=AccessList.objects.all(),
query_params={ query_params={
"type": "extended", "type": ACLTypeChoices.TYPE_EXTENDED,
}, },
help_text=mark_safe( help_text=mark_safe(
"<b>*Note:</b> This field will only display Extended ACLs.", "<b>*Note:</b> This field will only display Extended ACLs.",
), ),
label="Access List", label="Access List",
) )
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False,
)
source_prefix = DynamicModelChoiceField( source_prefix = DynamicModelChoiceField(
queryset=Prefix.objects.all(), queryset=Prefix.objects.all(),
required=False, required=False,

View File

@ -8,7 +8,7 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from netbox.models import NetBoxModel from netbox.models import NetBoxModel
from ..choices import ACLProtocolChoices, ACLRuleActionChoices from ..choices import ACLProtocolChoices, ACLRuleActionChoices, ACLTypeChoices
from .access_lists import AccessList from .access_lists import AccessList
__all__ = ( __all__ = (
@ -86,7 +86,7 @@ class ACLStandardRule(ACLRule):
on_delete=models.CASCADE, on_delete=models.CASCADE,
to=AccessList, to=AccessList,
verbose_name="Standard Access List", verbose_name="Standard Access List",
limit_choices_to={"type": "standard"}, limit_choices_to={"type": ACLTypeChoices.TYPE_STANDARD},
related_name="aclstandardrules", related_name="aclstandardrules",
) )

View File

@ -31,6 +31,9 @@ class ACLInterfaceAssignments(PluginTemplateExtension):
elif ctype.model == "vminterface": elif ctype.model == "vminterface":
parent_type = "virtual_machine" parent_type = "virtual_machine"
parent_id = obj.virtual_machine.pk parent_id = obj.virtual_machine.pk
else:
parent_type = None
parent_id = None
return self.render( return self.render(
"inc/assigned_interface/access_lists.html", "inc/assigned_interface/access_lists.html",

View File

@ -6,7 +6,7 @@ Specifically, all the various interactions with a client.
from django.db.models import Count from django.db.models import Count
from netbox.views import generic from netbox.views import generic
from . import filtersets, forms, models, tables from . import choices, filtersets, forms, models, tables
__all__ = ( __all__ = (
"AccessListView", "AccessListView",
@ -48,15 +48,22 @@ class AccessListView(generic.ObjectView):
""" """
Depending on the Access List type, the list view will return the required ACL Rule using the previous defined tables in tables.py. Depending on the Access List type, the list view will return the required ACL Rule using the previous defined tables in tables.py.
""" """
if instance.type == "extended":
table = tables.ACLExtendedRuleTable(instance.aclextendedrules.all())
elif instance.type == "standard":
table = tables.ACLStandardRuleTable(instance.aclstandardrules.all())
table.configure(request)
return { if instance.type == choices.ACLTypeChoices.TYPE_EXTENDED:
"rules_table": table, table = tables.ACLExtendedRuleTable(instance.aclextendedrules.all())
} elif instance.type == choices.ACLTypeChoices.TYPE_STANDARD:
table = tables.ACLStandardRuleTable(instance.aclstandardrules.all())
else:
table = None
if table:
table.columns.hide("access_list")
table.configure(request)
return {
"rules_table": table,
}
return {}
class AccessListListView(generic.ObjectListView): class AccessListListView(generic.ObjectListView):
@ -84,7 +91,7 @@ class AccessListEditView(generic.ObjectEditView):
class AccessListDeleteView(generic.ObjectDeleteView): class AccessListDeleteView(generic.ObjectDeleteView):
""" """
Defines the delete view for the AccessLists django model. Defines delete view for the AccessLists django model.
""" """
queryset = models.AccessList.objects.all() queryset = models.AccessList.objects.all()
@ -129,10 +136,20 @@ class ACLInterfaceAssignmentEditView(generic.ObjectEditView):
form = forms.ACLInterfaceAssignmentForm form = forms.ACLInterfaceAssignmentForm
template_name = "netbox_acls/aclinterfaceassignment_edit.html" template_name = "netbox_acls/aclinterfaceassignment_edit.html"
def get_extra_addanother_params(self, request):
"""
Returns a dictionary of additional parameters to be passed to the "Add Another" button.
"""
return {
"access_list": request.GET.get("access_list") or request.POST.get("access_list"),
"direction": request.GET.get("direction") or request.POST.get("direction"),
}
class ACLInterfaceAssignmentDeleteView(generic.ObjectDeleteView): class ACLInterfaceAssignmentDeleteView(generic.ObjectDeleteView):
""" """
Defines the delete view for the ACLInterfaceAssignments django model. Defines delete view for the ACLInterfaceAssignments django model.
""" """
queryset = models.ACLInterfaceAssignment.objects.all() queryset = models.ACLInterfaceAssignment.objects.all()
@ -176,10 +193,19 @@ class ACLStandardRuleEditView(generic.ObjectEditView):
queryset = models.ACLStandardRule.objects.all() queryset = models.ACLStandardRule.objects.all()
form = forms.ACLStandardRuleForm form = forms.ACLStandardRuleForm
def get_extra_addanother_params(self, request):
"""
Returns a dictionary of additional parameters to be passed to the "Add Another" button.
"""
return {
"access_list": request.GET.get("access_list") or request.POST.get("access_list"),
}
class ACLStandardRuleDeleteView(generic.ObjectDeleteView): class ACLStandardRuleDeleteView(generic.ObjectDeleteView):
""" """
Defines the delete view for the ACLStandardRules django model. Defines delete view for the ACLStandardRules django model.
""" """
queryset = models.ACLStandardRule.objects.all() queryset = models.ACLStandardRule.objects.all()
@ -223,10 +249,19 @@ class ACLExtendedRuleEditView(generic.ObjectEditView):
queryset = models.ACLExtendedRule.objects.all() queryset = models.ACLExtendedRule.objects.all()
form = forms.ACLExtendedRuleForm form = forms.ACLExtendedRuleForm
def get_extra_addanother_params(self, request):
"""
Returns a dictionary of additional parameters to be passed to the "Add Another" button.
"""
return {
"access_list": request.GET.get("access_list") or request.POST.get("access_list"),
}
class ACLExtendedRuleDeleteView(generic.ObjectDeleteView): class ACLExtendedRuleDeleteView(generic.ObjectDeleteView):
""" """
Defines the delete view for the ACLExtendedRules django model. Defines delete view for the ACLExtendedRules django model.
""" """
queryset = models.ACLExtendedRule.objects.all() queryset = models.ACLExtendedRule.objects.all()