From 4b2ba171ee797cf1970f33e39c5befc470f29493 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Tue, 31 Dec 2019 20:47:13 +0000 Subject: [PATCH 01/98] Clean tagged VLANs --- netbox/dcim/forms.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 1774fc986..e4bd12c80 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -74,6 +74,15 @@ class InterfaceCommonForm: elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL: self.cleaned_data['tagged_vlans'] = [] + # Validate tagged VLANs; must be a global VLAN or in the same site + else: + for tagged_vlan in tagged_vlans: + if tagged_vlan.site not in [self.cleaned_data['device'].site, None]: + raise forms.ValidationError({ + 'tagged_vlans': "The tagged VLAN ({}) must belong to the same site as the interface's parent " + "device/VM, or it must be global".format(tagged_vlan) + }) + class BulkRenameForm(forms.Form): """ From 3b1808d9fd89a871cf2ca2a013b95e0b63def61b Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 1 Jan 2020 12:09:51 +0000 Subject: [PATCH 02/98] Removed trailing space --- netbox/dcim/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index e4bd12c80..19fcc30dc 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -74,7 +74,7 @@ class InterfaceCommonForm: elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL: self.cleaned_data['tagged_vlans'] = [] - # Validate tagged VLANs; must be a global VLAN or in the same site + # Validate tagged VLANs; must be a global VLAN or in the same site else: for tagged_vlan in tagged_vlans: if tagged_vlan.site not in [self.cleaned_data['device'].site, None]: From d3ac75e45aa5cbcf57ddff9ddd5ba8b3acc95ebb Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 1 Jan 2020 16:43:21 +0000 Subject: [PATCH 03/98] Removed no-longer-used choices (now handled via API-based select) --- netbox/dcim/forms.py | 90 -------------------------------------------- 1 file changed, 90 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 19fcc30dc..da4beb6bb 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2236,36 +2236,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG ) - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - - site = getattr(self.instance.parent, 'site', None) - if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices - class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): name_pattern = ExpandableNameField( @@ -2346,36 +2316,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): else: self.fields['lag'].queryset = Interface.objects.none() - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - - site = getattr(self.parent, 'site', None) - if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices - class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( @@ -2458,36 +2398,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo else: self.fields['lag'].choices = [] - # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site - vlan_choices = [] - global_vlans = VLAN.objects.filter(site=None, group=None) - vlan_choices.append( - ('Global', [(vlan.pk, vlan) for vlan in global_vlans]) - ) - for group in VLANGroup.objects.filter(site=None): - global_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append( - (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans]) - ) - if self.parent_obj is not None: - site = getattr(self.parent_obj, 'site', None) - if site is not None: - - # Add non-grouped site VLANs - site_vlans = VLAN.objects.filter(site=site, group=None) - vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans])) - - # Add grouped site VLANs - for group in VLANGroup.objects.filter(site=site): - site_group_vlans = VLAN.objects.filter(group=group) - vlan_choices.append(( - '{} / {}'.format(group.site.name, group.name), - [(vlan.pk, vlan) for vlan in site_group_vlans] - )) - - self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices - self.fields['tagged_vlans'].choices = vlan_choices - class InterfaceBulkRenameForm(BulkRenameForm): pk = forms.ModelMultipleChoiceField( From da1d1e068eb9306f2d24c87ba1db5a3d11232ddd Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 1 Jan 2020 17:34:26 +0000 Subject: [PATCH 04/98] Filter VLANs to only those in the current site or global --- netbox/dcim/forms.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index da4beb6bb..7fc50f54b 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2185,6 +2185,9 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2194,6 +2197,9 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2236,6 +2242,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG ) + # Add the current site to the list of filtered VLANs + self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.instance.device.site.pk + self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.instance.device.site.pk + class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): name_pattern = ExpandableNameField( @@ -2287,6 +2297,9 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2296,6 +2309,9 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2316,6 +2332,10 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): else: self.fields['lag'].queryset = Interface.objects.none() + # Add the current site to the list of filtered VLANs + self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent.site.pk + self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent.site.pk + class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( @@ -2367,6 +2387,9 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2376,6 +2399,9 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', + additional_query_params={ + 'site_id': 'null' + }, full=True ) ) @@ -2398,6 +2424,10 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo else: self.fields['lag'].choices = [] + # Add the current site to the list of filtered VLANs + self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent_obj.site.pk + self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent_obj.site.pk + class InterfaceBulkRenameForm(BulkRenameForm): pk = forms.ModelMultipleChoiceField( From e1f5168183f654c20a4049cfc990be8bd372f30b Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 1 Jan 2020 17:43:39 +0000 Subject: [PATCH 05/98] 3589 changelog --- docs/release-notes/version-2.6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 548492438..c6a509ab0 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -9,6 +9,7 @@ ## Bug Fixes * [#3106](https://github.com/netbox-community/netbox/issues/3106) - Restrict queryset of chained fields when form validation fails +* [#3589](https://github.com/netbox-community/netbox/issues/3589) - Limit the list of available VLANs to those valid and perform input validation on the tagged VLANs * [#3695](https://github.com/netbox-community/netbox/issues/3695) - Include A/Z termination sites for circuits in global search * [#3712](https://github.com/netbox-community/netbox/issues/3712) - Scrolling to target (hash) did not account for the header size * [#3780](https://github.com/netbox-community/netbox/issues/3780) - Fix AttributeError exception in API docs From 2421d6d6ed68cf982985d7b91b4b2d4b78067d04 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 1 Jan 2020 19:53:13 +0000 Subject: [PATCH 06/98] More informative error message --- netbox/dcim/forms.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 7fc50f54b..3c515152e 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -76,12 +76,14 @@ class InterfaceCommonForm: # Validate tagged VLANs; must be a global VLAN or in the same site else: - for tagged_vlan in tagged_vlans: - if tagged_vlan.site not in [self.cleaned_data['device'].site, None]: - raise forms.ValidationError({ - 'tagged_vlans': "The tagged VLAN ({}) must belong to the same site as the interface's parent " - "device/VM, or it must be global".format(tagged_vlan) - }) + valid_sites = [None, self.cleaned_data['device'].site] + invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites] + + if invalid_vlans: + raise forms.ValidationError({ + 'tagged_vlans': "The tagged VLANs ({}) must belong to the same site as the interface's parent " + "device/VM, or they must be global".format(', '.join(invalid_vlans)) + }) class BulkRenameForm(forms.Form): From 350834c2bf6dc338a75558da67373e0fbf602b94 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 2 Jan 2020 09:16:18 +0000 Subject: [PATCH 07/98] Fixes #2365: Toggle for showing available prefixes/ip addresses --- docs/release-notes/version-2.6.md | 1 + netbox/ipam/views.py | 29 ++++++++++++++++++++++++---- netbox/templates/ipam/aggregate.html | 8 ++++++++ netbox/templates/ipam/prefix.html | 8 ++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 548492438..f916f5083 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -2,6 +2,7 @@ ## Enhancements +* [#2365](https://github.com/netbox-community/netbox/issues/2365) - Toggle for showing available prefixes/ip addresses * [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts * [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets * [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 2cc1a0ea8..24d8efa82 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -333,7 +333,14 @@ class AggregateView(PermissionRequiredMixin, View): ).annotate_depth( limit=0 ) - child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes) + + # Update the ipam_show_available cookie if request specifies it + if request.GET.get('show_available') is not None: + request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' + + # Add available prefixes to the table if the cookie requested it + if request.session.get('ipam_show_available'): + child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'): @@ -356,6 +363,7 @@ class AggregateView(PermissionRequiredMixin, View): 'aggregate': aggregate, 'prefix_table': prefix_table, 'permissions': permissions, + 'show_available': request.session.get('ipam_show_available'), }) @@ -511,8 +519,12 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'site', 'vlan', 'role', ).annotate_depth(limit=0) - # Annotate available prefixes - if child_prefixes: + # Update the ipam_show_available cookie if request specifies it + if request.GET.get('show_available') is not None: + request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' + + # Add available prefixes to the table if the cookie requested it + if child_prefixes and request.session.get('ipam_show_available'): child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) @@ -539,6 +551,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'prefixes', + 'show_available': request.session.get('ipam_show_available'), }) @@ -553,7 +566,14 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): ipaddresses = prefix.get_child_ips().prefetch_related( 'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for' ) - ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) + + # Update the ipam_show_available cookie if request specifies it + if request.GET.get('show_available') is not None: + request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' + + # Add available IP addresses to the table if the cookie requested it + if request.session.get('ipam_show_available'): + ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) ip_table = tables.IPAddressTable(ipaddresses) if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'): @@ -579,6 +599,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'ip-addresses', + 'show_available': request.session.get('ipam_show_available'), }) diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index daa7b5107..ae79e9b66 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -40,6 +40,14 @@

{% block title %}{{ aggregate }}{% endblock %}

{% include 'inc/created_updated.html' with obj=aggregate %} + {% if show_available is not None %} + + {% endif %}
{% custom_links aggregate %}
diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 9ea34804e..43b723553 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -53,6 +53,14 @@

{% block title %}{{ prefix }}{% endblock %}

{% include 'inc/created_updated.html' with obj=prefix %} + {% if show_available is not None %} + + {% endif %}
{% custom_links prefix %}
From 6461f8bb13d8f64897dccc8a415f223170d40001 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 2 Jan 2020 15:48:30 +0000 Subject: [PATCH 08/98] Corrected ticket number --- docs/release-notes/version-2.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index ce72351b3..d1c7f6619 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -2,7 +2,7 @@ ## Enhancements -* [#2365](https://github.com/netbox-community/netbox/issues/2365) - Toggle for showing available prefixes/ip addresses +* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses * [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results * [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses * [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception From fabac288a0d90bee863e3e5d77e129eb7b930704 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 2 Jan 2020 16:13:47 +0000 Subject: [PATCH 09/98] `is not None` not needed as the value 'false' is a string --- netbox/ipam/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 24d8efa82..590b63703 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -335,7 +335,7 @@ class AggregateView(PermissionRequiredMixin, View): ) # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available') is not None: + if request.GET.get('show_available'): request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' # Add available prefixes to the table if the cookie requested it @@ -520,7 +520,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): ).annotate_depth(limit=0) # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available') is not None: + if request.GET.get('show_available'): request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' # Add available prefixes to the table if the cookie requested it @@ -568,7 +568,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): ) # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available') is not None: + if request.GET.get('show_available'): request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' # Add available IP addresses to the table if the cookie requested it From 2b2dcdf47a0edfc63d32ff1a92ebbc5895714ad9 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 2 Jan 2020 16:19:12 +0000 Subject: [PATCH 10/98] Added default to cookie --- netbox/ipam/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 590b63703..1ea9a717d 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -363,7 +363,7 @@ class AggregateView(PermissionRequiredMixin, View): 'aggregate': aggregate, 'prefix_table': prefix_table, 'permissions': permissions, - 'show_available': request.session.get('ipam_show_available'), + 'show_available': request.session.get('ipam_show_available', False), }) @@ -551,7 +551,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'prefixes', - 'show_available': request.session.get('ipam_show_available'), + 'show_available': request.session.get('ipam_show_available', False), }) @@ -599,7 +599,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'ip-addresses', - 'show_available': request.session.get('ipam_show_available'), + 'show_available': request.session.get('ipam_show_available', False), }) From 857eb9774e4bbaa3aac0e5fcaaae758e63e54891 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 2 Jan 2020 16:29:11 +0000 Subject: [PATCH 11/98] Templatized show_available toggle --- netbox/templates/ipam/aggregate.html | 9 +-------- netbox/templates/ipam/inc/toggle_available.html | 9 +++++++++ netbox/templates/ipam/prefix.html | 9 +-------- 3 files changed, 11 insertions(+), 16 deletions(-) create mode 100644 netbox/templates/ipam/inc/toggle_available.html diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index ae79e9b66..d2dfe8888 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -40,14 +40,7 @@

{% block title %}{{ aggregate }}{% endblock %}

{% include 'inc/created_updated.html' with obj=aggregate %} - {% if show_available is not None %} - - {% endif %} + {% include 'ipam/inc/toggle_available.html' %}
{% custom_links aggregate %}
diff --git a/netbox/templates/ipam/inc/toggle_available.html b/netbox/templates/ipam/inc/toggle_available.html new file mode 100644 index 000000000..21d734d2d --- /dev/null +++ b/netbox/templates/ipam/inc/toggle_available.html @@ -0,0 +1,9 @@ +{% load helpers %} +{% if show_available is not None %} + +{% endif %} diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 43b723553..56267786f 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -53,14 +53,7 @@

{% block title %}{{ prefix }}{% endblock %}

{% include 'inc/created_updated.html' with obj=prefix %} - {% if show_available is not None %} - - {% endif %} + {% include 'ipam/inc/toggle_available.html' %}
{% custom_links prefix %}
From 97a75087990925162033c47993e2d20c44e96635 Mon Sep 17 00:00:00 2001 From: hSaria <34197532+hSaria@users.noreply.github.com> Date: Thu, 2 Jan 2020 17:52:22 +0000 Subject: [PATCH 12/98] Limit tagged validation to tagged interfaces Co-Authored-By: Jeremy Stretch --- netbox/dcim/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 3c515152e..902541353 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -75,7 +75,7 @@ class InterfaceCommonForm: self.cleaned_data['tagged_vlans'] = [] # Validate tagged VLANs; must be a global VLAN or in the same site - else: + elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED: valid_sites = [None, self.cleaned_data['device'].site] invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites] From 2e9ce282971d63d82409b5567d6935ab3e464097 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Jan 2020 14:01:10 -0500 Subject: [PATCH 13/98] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 819ca7c83..75f67e2a6 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured # Environment setup # -VERSION = '2.6.11' +VERSION = '2.6.12-dev' # Hostname HOSTNAME = platform.node() From 2190c5a8a1704b812c32e51f98de070203ffd76f Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 19:24:39 +0000 Subject: [PATCH 14/98] Removed changelog (temporarily while merging) --- docs/release-notes/version-2.6.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index d1c7f6619..36afbc3e2 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -2,7 +2,6 @@ ## Enhancements -* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses * [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results * [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses * [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception From 0abfd4f9c31d44881e5ad582a7df90a986a72279 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 19:25:33 +0000 Subject: [PATCH 15/98] Changelog --- docs/release-notes/version-2.6.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 2bf55d857..9aa23200f 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,3 +1,9 @@ +# v2.6.12 (FUTURE) + +## Enhancements + +* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses + # v2.6.11 (2020-01-03) ## Bug Fixes From c7073285c8fae6edb07dc2b2d84a6ad1a2fa2f8d Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 19:27:02 +0000 Subject: [PATCH 16/98] Line seperator --- docs/release-notes/version-2.6.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 9aa23200f..ffd1e3ee7 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,8 @@ * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses +--- + # v2.6.11 (2020-01-03) ## Bug Fixes From 43766f696b74b3a0da9657de56411e4b23605379 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 19:38:51 +0000 Subject: [PATCH 17/98] Closes #3090: Filter field for interface --- docs/release-notes/version-2.6.md | 6 ++++++ netbox/templates/dcim/device.html | 19 +++++++++++++++++++ .../virtualization/virtualmachine.html | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 2bf55d857..c4589a009 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,3 +1,9 @@ +# v2.6.12 (FUTURE) + +## Enhancements + +* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces + # v2.6.11 (2020-01-03) ## Bug Fixes diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 57e2b03b8..55332ca80 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -556,6 +556,9 @@ Show IPs +
+ +
@@ -912,6 +915,22 @@ $('button.toggle-ips').click(function() { $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); return false; }); +$('input.interface-filter').on('input', function() { + var filter = new RegExp(this.value); + + for (interface of $(this).closest('form').find('tbody > tr')) { + // Slice off 'interface_' at the start of the ID + if (filter && filter.test(interface.id.slice(10))) { + // Match the toggle in case the filter now matches the interface + $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked')); + $(interface).show(); + } else { + // Uncheck to prevent actions from including it when it doesn't match + $(interface).find('input:checkbox[name=pk]').prop('checked', false); + $(interface).hide(); + } + } +}); diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 2498039ff..736d6bc5c 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -253,6 +253,9 @@ Show IPs +
+ +
@@ -325,5 +328,21 @@ $('button.toggle-ips').click(function() { $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); return false; }); +$('input.interface-filter').on('input', function() { + var filter = new RegExp(this.value); + + for (interface of $(this).closest('form').find('tbody > tr')) { + // Slice off 'interface_' at the start of the ID + if (filter && filter.test(interface.id.slice(10))) { + // Match the toggle in case the filter now matches the interface + $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked')); + $(interface).show(); + } else { + // Uncheck to prevent actions from including it when it doesn't match + $(interface).find('input:checkbox[name=pk]').prop('checked', false); + $(interface).hide(); + } + } +}); {% endblock %} From c61cc7bf9c3b5bbd43eafcd48027a8db7160351f Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 19:58:41 +0000 Subject: [PATCH 18/98] Height was a touch off --- netbox/templates/dcim/device.html | 2 +- netbox/templates/virtualization/virtualmachine.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 55332ca80..3b3beb3e0 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -557,7 +557,7 @@
- +
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 736d6bc5c..298daf94f 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -254,7 +254,7 @@
- +
From c556930934cad62dd39cc96ee70521116cb1d4c1 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 3 Jan 2020 20:12:21 +0000 Subject: [PATCH 19/98] Forgot le seperator --- docs/release-notes/version-2.6.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index c4589a009..c099d5115 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,8 @@ * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces +--- + # v2.6.11 (2020-01-03) ## Bug Fixes From bb1bb8872717acc913a95d9113211d9f1b9829f5 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 4 Jan 2020 13:30:31 +0000 Subject: [PATCH 20/98] Fixed #3187: Rack multi-selection field --- netbox/dcim/forms.py | 28 ++++++++++++++++++++++++++++ netbox/dcim/views.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index a5ce2811c..bbdbe251d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -703,6 +703,34 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): ) +# +# Rack elevations +# + +class RackElevationFilterForm(RackFilterForm): + field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant'] + id = ChainedModelChoiceField( + queryset=Rack.objects.all(), + label='Rack', + chains=( + ('site', 'site'), + ('group_id', 'group_id'), + ), + required=False, + widget=APISelectMultiple( + api_url='/api/dcim/racks/', + display_field='display_name', + ) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Filter the rack field based on the site and group + self.fields['site'].widget.add_filter_for('id', 'site') + self.fields['group_id'].widget.add_filter_for('id', 'group_id') + + # # Rack reservations # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 959e1043e..2d98515cf 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -388,7 +388,7 @@ class RackElevationListView(PermissionRequiredMixin, View): 'page': page, 'total_count': total_count, 'face_id': face_id, - 'filter_form': forms.RackFilterForm(request.GET), + 'filter_form': forms.RackElevationFilterForm(request.GET), }) From 0d7701fa20c46450bd1a4f90827252e5eca896a2 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 4 Jan 2020 13:32:07 +0000 Subject: [PATCH 21/98] Changelog (may conflict because adding headers) --- docs/release-notes/version-2.6.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 2bf55d857..ccbcfb754 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,3 +1,11 @@ +# v2.6.12 (FUTURE) + +## Enhancements + +* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations + +--- + # v2.6.11 (2020-01-03) ## Bug Fixes From 700b608d68ed62afffe96618c0d59f1c9ff975ba Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 4 Jan 2020 18:17:41 +0000 Subject: [PATCH 22/98] Fixes #2050: Image preview for attachments --- netbox/project-static/js/forms.js | 39 +++++++++++++++++++++ netbox/templates/inc/image_attachments.html | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 55f9afbd5..43c722ae5 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -398,4 +398,43 @@ $(document).ready(function() { // Account for the header height when hash-scrolling window.addEventListener('load', headerOffsetScroll); window.addEventListener('hashchange', headerOffsetScroll); + + // Offset between the preview window and the window edges + const IMAGE_PREVIEW_OFFSET_X = 20 + const IMAGE_PREVIEW_OFFSET_Y = 10 + + // Preview an image attachment when the link is hovered over + $('a.image-preview').on('mouseover', function(e) { + // Twice the offset to account for all sides of the picture + var maxWidth = window.innerWidth - (e.clientX + (IMAGE_PREVIEW_OFFSET_X * 2)); + var maxHeight = window.innerHeight - (e.clientY + (IMAGE_PREVIEW_OFFSET_Y * 2)); + var img = $('').attr('id', 'image-preview-window').css({ + display: 'none', + position: 'absolute', + maxWidth: maxWidth + 'px', + maxHeight: maxHeight + 'px', + left: e.pageX + IMAGE_PREVIEW_OFFSET_X + 'px', + top: e.pageY + IMAGE_PREVIEW_OFFSET_Y + 'px', + boxShadow: '0 0px 12px 3px rgba(0, 0, 0, 0.4)', + }); + + // Remove any existing preview windows and add the current one + $('#image-preview-window').remove(); + $('body').append(img); + + // Once loaded, show the preview if the image is indeed an image + img.on('load', function(e) { + if (e.target.complete && e.target.naturalWidth) { + $('#image-preview-window').fadeIn('fast'); + } + }); + + // Begin loading + img.attr('src', e.target.href); + }); + + // Fade the image out; it will be deleted when another one is previewed + $('a.image-preview').on('mouseout', function() { + $('#image-preview-window').fadeOut('fast') + }); }); diff --git a/netbox/templates/inc/image_attachments.html b/netbox/templates/inc/image_attachments.html index 1487b5349..2fee4dc78 100644 --- a/netbox/templates/inc/image_attachments.html +++ b/netbox/templates/inc/image_attachments.html @@ -10,7 +10,7 @@ From f84a37720230800c410b031d212c81b7fbf8ed52 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 4 Jan 2020 18:19:05 +0000 Subject: [PATCH 23/98] Changelog (may conflict with other merges) --- docs/release-notes/version-2.6.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 2bf55d857..942f1c55b 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,3 +1,11 @@ +# v2.6.12 (FUTURE) + +## Enhancements + +* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link + +--- + # v2.6.11 (2020-01-03) ## Bug Fixes From 291038b7fa982ebaa69354295723647bb886415c Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sun, 5 Jan 2020 09:10:46 +0000 Subject: [PATCH 24/98] Removed changes that will be covered in #3840 --- netbox/dcim/forms.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 932e9695a..52f49558f 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2210,9 +2210,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2222,9 +2219,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2267,10 +2261,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm): device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG ) - # Add the current site to the list of filtered VLANs - self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.instance.device.site.pk - self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.instance.device.site.pk - class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): name_pattern = ExpandableNameField( @@ -2322,9 +2312,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2334,9 +2321,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2357,10 +2341,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): else: self.fields['lag'].queryset = Interface.objects.none() - # Add the current site to the list of filtered VLANs - self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent.site.pk - self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent.site.pk - class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( @@ -2412,9 +2392,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelect( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2424,9 +2401,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo widget=APISelectMultiple( api_url="/api/ipam/vlans/", display_field='display_name', - additional_query_params={ - 'site_id': 'null' - }, full=True ) ) @@ -2449,10 +2423,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo else: self.fields['lag'].choices = [] - # Add the current site to the list of filtered VLANs - self.fields['untagged_vlan'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent_obj.site.pk - self.fields['tagged_vlans'].widget.attrs['1-data-additional-query-param-site_id'] = self.parent_obj.site.pk - class InterfaceBulkRenameForm(BulkRenameForm): pk = forms.ModelMultipleChoiceField( From c0a325df40a81b25e923132fc66055b07609a719 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sun, 5 Jan 2020 09:11:58 +0000 Subject: [PATCH 25/98] Updated changelog --- docs/release-notes/version-2.6.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 77c2f42b7..6b4cebae7 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,3 +1,11 @@ +# v2.6.12 (FUTURE) + +## Bug Fixes + +* [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface + +--- + # v2.6.11 (2020-01-03) ## Bug Fixes @@ -24,7 +32,6 @@ ## Bug Fixes * [#3106](https://github.com/netbox-community/netbox/issues/3106) - Restrict queryset of chained fields when form validation fails -* [#3589](https://github.com/netbox-community/netbox/issues/3589) - Limit the list of available VLANs to those valid and perform input validation on the tagged VLANs * [#3695](https://github.com/netbox-community/netbox/issues/3695) - Include A/Z termination sites for circuits in global search * [#3712](https://github.com/netbox-community/netbox/issues/3712) - Scrolling to target (hash) did not account for the header size * [#3780](https://github.com/netbox-community/netbox/issues/3780) - Fix AttributeError exception in API docs From 7f41bae8126d2d12b1bbb7217d7bc0c744efb0ac Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 6 Jan 2020 20:05:07 +0000 Subject: [PATCH 26/98] Limit toggle selector to visible input fields --- netbox/project-static/js/forms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 55f9afbd5..20d6c9126 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -7,7 +7,7 @@ $(document).ready(function() { // "Toggle" checkbox for object lists (PK column) $('input:checkbox.toggle').click(function() { - $(this).closest('table').find('input:checkbox[name=pk]').prop('checked', $(this).prop('checked')); + $(this).closest('table').find('input:checkbox[name=pk]:visible').prop('checked', $(this).prop('checked')); // Show the "select all" box if present if ($(this).is(':checked')) { From 098301abb7bbb31049acc405899b114bdfcef151 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Jan 2020 22:04:03 -0500 Subject: [PATCH 27/98] Initial work on filter tests --- netbox/circuits/tests/test_filters.py | 283 ++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 netbox/circuits/tests/test_filters.py diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filters.py new file mode 100644 index 000000000..f932766d9 --- /dev/null +++ b/netbox/circuits/tests/test_filters.py @@ -0,0 +1,283 @@ +from django.test import TestCase + +from circuits.constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_OFFLINE, CIRCUIT_STATUS_PLANNED +from circuits.filters import CircuitFilter, CircuitTerminationFilter, CircuitTypeFilter, ProviderFilter +from circuits.models import Circuit, CircuitTermination, CircuitType, Provider +from dcim.models import Region, Site + + +class ProviderTestCase(TestCase): + queryset = Provider.objects.all() + + @classmethod + def setUpTestData(cls): + + providers = ( + Provider(name='Provider 1', slug='provider-1', asn=65001, account='1234'), + Provider(name='Provider 2', slug='provider-2', asn=65002, account='2345'), + Provider(name='Provider 3', slug='provider-3', asn=65003, account='3456'), + Provider(name='Provider 4', slug='provider-4', asn=65004, account='4567'), + Provider(name='Provider 5', slug='provider-5', asn=65005, account='5678'), + ) + Provider.objects.bulk_create(providers) + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + ) + Site.objects.bulk_create(sites) + + circuit_types = ( + CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'), + CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'), + ) + CircuitType.objects.bulk_create(circuit_types) + + circuits = ( + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'), + Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 1'), + ) + Circuit.objects.bulk_create(circuits) + + CircuitTermination.objects.bulk_create(( + CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000), + CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A', port_speed=1000), + )) + + def test_name(self): + params = {'name': ['Provider 1', 'Provider 2']} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['provider-1', 'provider-2']} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + def test_asn(self): + params = {'asn': ['65001', '65002']} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + def test_account(self): + params = {'account': ['1234', '2345']} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 3) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + + +class CircuitTypeTestCase(TestCase): + queryset = CircuitType.objects.all() + + @classmethod + def setUpTestData(cls): + + CircuitType.objects.bulk_create(( + CircuitType(name='Circuit Type 1', slug='circuit-type-1'), + CircuitType(name='Circuit Type 2', slug='circuit-type-2'), + CircuitType(name='Circuit Type 3', slug='circuit-type-3'), + )) + + def test_id(self): + params = {'id': [self.queryset.first().pk]} + self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + + def test_name(self): + params = {'name': ['Circuit Type 1']} + self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + + def test_slug(self): + params = {'slug': ['circuit-type-1']} + self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + + +class CircuitTestCase(TestCase): + queryset = Circuit.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + circuit_types = ( + CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'), + CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'), + ) + CircuitType.objects.bulk_create(circuit_types) + + providers = ( + Provider(name='Provider 1', slug='provider-1'), + Provider(name='Provider 2', slug='provider-2'), + ) + Provider.objects.bulk_create(providers) + + circuits = ( + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CIRCUIT_STATUS_ACTIVE), + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CIRCUIT_STATUS_ACTIVE), + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', commit_rate=3000, status=CIRCUIT_STATUS_PLANNED), + Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', commit_rate=4000, status=CIRCUIT_STATUS_PLANNED), + Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', commit_rate=5000, status=CIRCUIT_STATUS_OFFLINE), + Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', commit_rate=6000, status=CIRCUIT_STATUS_OFFLINE), + ) + Circuit.objects.bulk_create(circuits) + + circuit_terminations = (( + CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000), + CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=1000), + CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=1000), + )) + CircuitTermination.objects.bulk_create(circuit_terminations) + + def test_cid(self): + params = {'cid': ['Test Circuit 1', 'Test Circuit 2']} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + + def test_install_date(self): + params = {'install_date': ['2020-01-01', '2020-01-02']} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + + def test_commit_rate(self): + params = {'commit_rate': ['1000', '2000']} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + + def test_provider(self): + provider = Provider.objects.first() + params = {'provider_id': [provider.pk]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + params = {'provider': [provider.slug]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + + def test_type(self): + circuit_type = CircuitType.objects.first() + params = {'type_id': [circuit_type.pk]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + params = {'type': [circuit_type.slug]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + + def test_status(self): + params = {'status': [CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_PLANNED]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 4) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + + +class CircuitTerminationTestCase(TestCase): + queryset = CircuitTermination.objects.all() + + @classmethod + def setUpTestData(cls): + + sites = ( + Site(name='Test Site 1', slug='test-site-1'), + Site(name='Test Site 2', slug='test-site-2'), + Site(name='Test Site 3', slug='test-site-3'), + ) + Site.objects.bulk_create(sites) + + circuit_types = ( + CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'), + ) + CircuitType.objects.bulk_create(circuit_types) + + providers = ( + Provider(name='Provider 1', slug='provider-1'), + ) + Provider.objects.bulk_create(providers) + + circuits = ( + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'), + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2'), + Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3'), + ) + Circuit.objects.bulk_create(circuits) + + circuit_terminations = (( + CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC'), + CircuitTermination(circuit=circuits[0], site=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF'), + CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'), + CircuitTermination(circuit=circuits[1], site=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'), + CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'), + CircuitTermination(circuit=circuits[2], site=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'), + )) + CircuitTermination.objects.bulk_create(circuit_terminations) + + def test_term_side(self): + params = {'term_side': 'A'} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 3) + + def test_port_speed(self): + params = {'port_speed': ['1000', '2000']} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + + def test_upstream_speed(self): + params = {'upstream_speed': ['1000', '2000']} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + + def test_xconnect_id(self): + params = {'xconnect_id': ['ABC', 'DEF']} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 2) + + def test_circuit_id(self): + circuits = Circuit.objects.all()[:2] + params = {'circuit_id': [circuits[0].pk, circuits[1].pk]} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) From cc7fa94e7469bdf9cd08c657e38e463212626830 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Jan 2020 16:30:23 -0500 Subject: [PATCH 28/98] Add IPAM filter tests (WIP) --- netbox/ipam/tests/test_filters.py | 416 ++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 netbox/ipam/tests/test_filters.py diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py new file mode 100644 index 000000000..05252559b --- /dev/null +++ b/netbox/ipam/tests/test_filters.py @@ -0,0 +1,416 @@ +from django.test import TestCase + +from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site +from ipam.constants import * +from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, RIRFilter, RoleFilter, VLANFilter, VRFFilter +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VRF +from virtualization.models import Cluster, ClusterType, VirtualMachine + + +class VRFTestCase(TestCase): + queryset = VRF.objects.all() + + @classmethod + def setUpTestData(cls): + + vrfs = ( + VRF(name='VRF 1', rd='65000:100', enforce_unique=False), + VRF(name='VRF 2', rd='65000:200', enforce_unique=False), + VRF(name='VRF 3', rd='65000:300', enforce_unique=False), + VRF(name='VRF 4', rd='65000:400', enforce_unique=True), + VRF(name='VRF 5', rd='65000:500', enforce_unique=True), + VRF(name='VRF 6', rd='65000:600', enforce_unique=True), + ) + VRF.objects.bulk_create(vrfs) + + def test_name(self): + params = {'name': ['VRF 1', 'VRF 2']} + self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 2) + + def test_rd(self): + params = {'rd': ['65000:100', '65000:200']} + self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 2) + + def test_enforce_unique(self): + params = {'enforce_unique': 'true'} + self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + params = {'enforce_unique': 'false'} + self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + + +class RIRTestCase(TestCase): + queryset = RIR.objects.all() + + @classmethod + def setUpTestData(cls): + + rirs = ( + RIR(name='RIR 1', slug='rir-1', is_private=False), + RIR(name='RIR 2', slug='rir-2', is_private=False), + RIR(name='RIR 3', slug='rir-3', is_private=False), + RIR(name='RIR 4', slug='rir-4', is_private=True), + RIR(name='RIR 5', slug='rir-5', is_private=True), + RIR(name='RIR 6', slug='rir-6', is_private=True), + ) + RIR.objects.bulk_create(rirs) + + def test_name(self): + params = {'name': ['RIR 1', 'RIR 2']} + self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['rir-1', 'rir-2']} + self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 2) + + def test_is_private(self): + params = {'is_private': 'true'} + self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + params = {'is_private': 'false'} + self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + + +class AggregateTestCase(TestCase): + queryset = Aggregate.objects.all() + + @classmethod + def setUpTestData(cls): + + rirs = ( + RIR(name='RIR 1', slug='rir-1'), + RIR(name='RIR 2', slug='rir-2'), + RIR(name='RIR 3', slug='rir-3'), + ) + RIR.objects.bulk_create(rirs) + + aggregates = ( + Aggregate(family=4, prefix='10.1.0.0/16', rir=rirs[0], date_added='2020-01-01'), + Aggregate(family=4, prefix='10.2.0.0/16', rir=rirs[0], date_added='2020-01-02'), + Aggregate(family=4, prefix='10.3.0.0/16', rir=rirs[1], date_added='2020-01-03'), + Aggregate(family=6, prefix='2001:db8:1::/48', rir=rirs[1], date_added='2020-01-04'), + Aggregate(family=6, prefix='2001:db8:2::/48', rir=rirs[2], date_added='2020-01-05'), + Aggregate(family=6, prefix='2001:db8:3::/48', rir=rirs[2], date_added='2020-01-06'), + ) + Aggregate.objects.bulk_create(aggregates) + + def test_family(self): + params = {'family': '4'} + self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 3) + + def test_date_added(self): + params = {'date_added': ['2020-01-01', '2020-01-02']} + self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 2) + + # TODO: Test for multiple values + def test_prefix(self): + params = {'prefix': '10.1.0.0/16'} + self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 1) + + def test_rir(self): + rirs = RIR.objects.all()[:2] + params = {'rir_id': [rirs[0].pk, rirs[1].pk]} + self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 4) + params = {'rir': [rirs[0].slug, rirs[1].slug]} + self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 4) + + +class RoleTestCase(TestCase): + queryset = Role.objects.all() + + @classmethod + def setUpTestData(cls): + + roles = ( + Role(name='Role 1', slug='role-1'), + Role(name='Role 2', slug='role-2'), + Role(name='Role 3', slug='role-3'), + ) + Role.objects.bulk_create(roles) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Role 1', 'Role 2']} + self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['role-1', 'role-2']} + self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + + +class PrefixTestCase(TestCase): + queryset = Prefix.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + vrfs = ( + VRF(name='VRF 1', rd='65000:100'), + VRF(name='VRF 2', rd='65000:200'), + VRF(name='VRF 3', rd='65000:300'), + ) + VRF.objects.bulk_create(vrfs) + + vlans = ( + VLAN(vid=1, name='VLAN 1'), + VLAN(vid=2, name='VLAN 2'), + VLAN(vid=3, name='VLAN 3'), + ) + VLAN.objects.bulk_create(vlans) + + roles = ( + Role(name='Role 1', slug='role-1'), + Role(name='Role 2', slug='role-2'), + Role(name='Role 3', slug='role-3'), + ) + Role.objects.bulk_create(roles) + + prefixes = ( + Prefix(family=4, prefix='10.0.0.0/24', site=None, vrf=None, vlan=None, role=None, is_pool=True), + Prefix(family=4, prefix='10.0.1.0/24', site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]), + Prefix(family=4, prefix='10.0.2.0/24', site=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PREFIX_STATUS_DEPRECATED), + Prefix(family=4, prefix='10.0.3.0/24', site=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PREFIX_STATUS_RESERVED), + Prefix(family=6, prefix='2001:db8::/64', site=None, vrf=None, vlan=None, role=None, is_pool=True), + Prefix(family=6, prefix='2001:db8:0:1::/64', site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]), + Prefix(family=6, prefix='2001:db8:0:2::/64', site=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PREFIX_STATUS_DEPRECATED), + Prefix(family=6, prefix='2001:db8:0:3::/64', site=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PREFIX_STATUS_RESERVED), + Prefix(family=4, prefix='10.0.0.0/16'), + Prefix(family=6, prefix='2001:db8::/32'), + ) + Prefix.objects.bulk_create(prefixes) + + def test_family(self): + params = {'family': '6'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 5) + + def test_is_pool(self): + params = {'is_pool': 'true'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + params = {'is_pool': 'false'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 8) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 3) + + def test_within(self): + params = {'within': '10.0.0.0/16'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_within_include(self): + params = {'within_include': '10.0.0.0/16'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 5) + + def test_contains(self): + params = {'contains': '10.0.1.0/24'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + params = {'contains': '2001:db8:0:1::/64'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + + def test_mask_length(self): + params = {'mask_length': '24'} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_vrf(self): + vrfs = VRF.objects.all()[:2] + params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + params = {'vrf': [vrfs[0].rd, vrfs[1].rd]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_vlan(self): + vlans = VLAN.objects.all()[:2] + params = {'vlan_id': [vlans[0].pk, vlans[1].pk]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + # TODO: Test for multiple values + params = {'vlan_vid': vlans[0].vid} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + + def test_role(self): + roles = Role.objects.all()[:2] + params = {'role_id': [roles[0].pk, roles[1].pk]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + params = {'role': [roles[0].slug, roles[1].slug]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + def test_status(self): + params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + +class IPAddressTestCase(TestCase): + queryset = IPAddress.objects.all() + + @classmethod + def setUpTestData(cls): + + vrfs = ( + VRF(name='VRF 1', rd='65000:100'), + VRF(name='VRF 2', rd='65000:200'), + VRF(name='VRF 3', rd='65000:300'), + ) + VRF.objects.bulk_create(vrfs) + + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(device_type=device_type, name='Device 1', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 2', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 3', site=site, device_role=device_role), + ) + Device.objects.bulk_create(devices) + + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + cluster = Cluster.objects.create(type=clustertype, name='Cluster 1') + + virtual_machines = ( + VirtualMachine(name='Virtual Machine 1', cluster=cluster), + VirtualMachine(name='Virtual Machine 2', cluster=cluster), + VirtualMachine(name='Virtual Machine 3', cluster=cluster), + ) + VirtualMachine.objects.bulk_create(virtual_machines) + + interfaces = ( + Interface(device=devices[0], name='Interface 1'), + Interface(device=devices[1], name='Interface 2'), + Interface(device=devices[2], name='Interface 3'), + Interface(virtual_machine=virtual_machines[0], name='Interface 1'), + Interface(virtual_machine=virtual_machines[1], name='Interface 2'), + Interface(virtual_machine=virtual_machines[2], name='Interface 3'), + ) + Interface.objects.bulk_create(interfaces) + + ipaddresses = ( + IPAddress(family=4, address='10.0.0.1/24', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'), + IPAddress(family=4, address='10.0.0.2/24', vrf=vrfs[0], interface=interfaces[0], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'), + IPAddress(family=4, address='10.0.0.3/24', vrf=vrfs[1], interface=interfaces[1], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'), + IPAddress(family=4, address='10.0.0.4/24', vrf=vrfs[2], interface=interfaces[2], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'), + IPAddress(family=6, address='2001:db8::1/64', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'), + IPAddress(family=6, address='2001:db8::2/64', vrf=vrfs[0], interface=interfaces[3], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'), + IPAddress(family=6, address='2001:db8::3/64', vrf=vrfs[1], interface=interfaces[4], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'), + IPAddress(family=6, address='2001:db8::4/64', vrf=vrfs[2], interface=interfaces[5], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'), + ) + IPAddress.objects.bulk_create(ipaddresses) + + def test_family(self): + params = {'family': '6'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def test_dns_name(self): + params = {'dns_name': ['ipaddress-a', 'ipaddress-b']} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 3) + + def test_parent(self): + params = {'parent': '10.0.0.0/24'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + params = {'parent': '2001:db8::/64'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def filter_address(self): + # Check IPv4 and IPv6, with and without a mask + params = {'address': '10.0.0.1/24'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + params = {'address': '10.0.0.1'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + params = {'address': '2001:db8::1/64'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + params = {'address': '2001:db8::1'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + + def test_mask_length(self): + params = {'mask_length': '24'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def test_vrf(self): + vrfs = VRF.objects.all()[:2] + params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + params = {'vrf': [vrfs[0].rd, vrfs[1].rd]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + # TODO: Test for multiple values + def test_device(self): + device = Device.objects.first() + params = {'device_id': device.pk} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + params = {'device': device.name} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + + def test_virtual_machine(self): + vms = VirtualMachine.objects.all()[:2] + params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + params = {'virtual_machine': [vms[0].name, vms[1].name]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + + def test_interface(self): + interfaces = Interface.objects.all()[:2] + params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + params = {'interface': ['Interface 1', 'Interface 2']} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def test_assigned_to_interface(self): + params = {'assigned_to_interface': 'true'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 6) + params = {'assigned_to_interface': 'false'} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + def test_role(self): + params = {'role': [IPADDRESS_ROLE_SECONDARY, IPADDRESS_ROLE_VIP]} + self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) From 3dfa45c1a5408293943520df4073e99a48943a65 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Jan 2020 21:35:34 -0500 Subject: [PATCH 29/98] Add remaining IPAM filter tests --- netbox/ipam/tests/test_filters.py | 227 +++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py index 05252559b..b44b2a70e 100644 --- a/netbox/ipam/tests/test_filters.py +++ b/netbox/ipam/tests/test_filters.py @@ -2,8 +2,11 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site from ipam.constants import * -from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, RIRFilter, RoleFilter, VLANFilter, VRFFilter -from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VRF +from ipam.filters import ( + AggregateFilter, IPAddressFilter, PrefixFilter, RIRFilter, RoleFilter, ServiceFilter, VLANFilter, VLANGroupFilter, + VRFFilter, +) +from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from virtualization.models import Cluster, ClusterType, VirtualMachine @@ -414,3 +417,223 @@ class IPAddressTestCase(TestCase): def test_role(self): params = {'role': [IPADDRESS_ROLE_SECONDARY, IPADDRESS_ROLE_VIP]} self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + + +class VLANGroupTestCase(TestCase): + queryset = VLANGroup.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + vlan_groups = ( + VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=sites[0]), + VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=sites[1]), + VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=sites[2]), + VLANGroup(name='VLAN Group 4', slug='vlan-group-4', site=None), + ) + VLANGroup.objects.bulk_create(vlan_groups) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['VLAN Group 1', 'VLAN Group 2']} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['vlan-group-1', 'vlan-group-2']} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + + +class VLANTestCase(TestCase): + queryset = VLAN.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + roles = ( + Role(name='Role 1', slug='role-1'), + Role(name='Role 2', slug='role-2'), + Role(name='Role 3', slug='role-3'), + ) + Role.objects.bulk_create(roles) + + groups = ( + VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=sites[0]), + VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=sites[1]), + VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=None), + ) + VLANGroup.objects.bulk_create(groups) + + vlans = ( + VLAN(vid=101, name='VLAN 101', site=sites[0], group=groups[0], role=roles[0], status=VLAN_STATUS_ACTIVE), + VLAN(vid=102, name='VLAN 102', site=sites[0], group=groups[0], role=roles[0], status=VLAN_STATUS_ACTIVE), + VLAN(vid=201, name='VLAN 201', site=sites[1], group=groups[1], role=roles[1], status=VLAN_STATUS_DEPRECATED), + VLAN(vid=202, name='VLAN 202', site=sites[1], group=groups[1], role=roles[1], status=VLAN_STATUS_DEPRECATED), + VLAN(vid=301, name='VLAN 301', site=sites[2], group=groups[2], role=roles[2], status=VLAN_STATUS_RESERVED), + VLAN(vid=302, name='VLAN 302', site=sites[2], group=groups[2], role=roles[2], status=VLAN_STATUS_RESERVED), + ) + VLAN.objects.bulk_create(vlans) + + def test_name(self): + params = {'name': ['VLAN 101', 'VLAN 102']} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 2) + + def test_rd(self): + params = {'vid': ['101', '201', '301']} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 3) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 3) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + + def test_group(self): + groups = VLANGroup.objects.all()[:2] + params = {'group_id': [groups[0].pk, groups[1].pk]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + params = {'group': [groups[0].slug, groups[1].slug]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + + def test_role(self): + roles = Role.objects.all()[:2] + params = {'role_id': [roles[0].pk, roles[1].pk]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + params = {'role': [roles[0].slug, roles[1].slug]} + self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + + def test_status(self): + params = {'status': [VLAN_STATUS_ACTIVE, VLAN_STATUS_DEPRECATED]} + self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + + +class ServiceTestCase(TestCase): + queryset = Service.objects.all() + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(device_type=device_type, name='Device 1', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 2', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 3', site=site, device_role=device_role), + ) + Device.objects.bulk_create(devices) + + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + cluster = Cluster.objects.create(type=clustertype, name='Cluster 1') + + virtual_machines = ( + VirtualMachine(name='Virtual Machine 1', cluster=cluster), + VirtualMachine(name='Virtual Machine 2', cluster=cluster), + VirtualMachine(name='Virtual Machine 3', cluster=cluster), + ) + VirtualMachine.objects.bulk_create(virtual_machines) + + services = ( + Service(device=devices[0], name='Service 1', protocol=IP_PROTOCOL_TCP, port=1001), + Service(device=devices[1], name='Service 2', protocol=IP_PROTOCOL_TCP, port=1002), + Service(device=devices[2], name='Service 3', protocol=IP_PROTOCOL_UDP, port=1003), + Service(virtual_machine=virtual_machines[0], name='Service 4', protocol=IP_PROTOCOL_TCP, port=2001), + Service(virtual_machine=virtual_machines[1], name='Service 5', protocol=IP_PROTOCOL_TCP, port=2002), + Service(virtual_machine=virtual_machines[2], name='Service 6', protocol=IP_PROTOCOL_UDP, port=2003), + ) + Service.objects.bulk_create(services) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 3) + + def test_name(self): + params = {'name': ['Service 1', 'Service 2']} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + + def test_protocol(self): + params = {'protocol': IP_PROTOCOL_TCP} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 4) + + def test_port(self): + params = {'port': ['1001', '1002', '1003']} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 3) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + + def test_virtual_machine(self): + vms = VirtualMachine.objects.all()[:2] + params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + params = {'virtual_machine': [vms[0].name, vms[1].name]} + self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) From 8314c607e4d40f78a217e86b4ee59acbd1310bee Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 6 Jan 2020 20:18:44 +0000 Subject: [PATCH 30/98] Move toggles js code to static --- netbox/project-static/js/interface_toggles.js | 30 +++++++++++++++++ netbox/templates/dcim/device.html | 29 +---------------- .../virtualization/virtualmachine.html | 32 ++----------------- 3 files changed, 33 insertions(+), 58 deletions(-) create mode 100644 netbox/project-static/js/interface_toggles.js diff --git a/netbox/project-static/js/interface_toggles.js b/netbox/project-static/js/interface_toggles.js new file mode 100644 index 000000000..a3649558a --- /dev/null +++ b/netbox/project-static/js/interface_toggles.js @@ -0,0 +1,30 @@ +// Toggle the display of IP addresses under interfaces +$('button.toggle-ips').click(function() { + var selected = $(this).attr('selected'); + if (selected) { + $('#interfaces_table tr.ipaddresses').hide(); + } else { + $('#interfaces_table tr.ipaddresses').show(); + } + $(this).attr('selected', !selected); + $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); + return false; +}); + +// Inteface filtering +$('input.interface-filter').on('input', function() { + var filter = new RegExp(this.value); + + for (interface of $(this).closest('form').find('tbody > tr')) { + // Slice off 'interface_' at the start of the ID + if (filter && filter.test(interface.id.slice(10))) { + // Match the toggle in case the filter now matches the interface + $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked')); + $(interface).show(); + } else { + // Uncheck to prevent actions from including it when it doesn't match + $(interface).find('input:checkbox[name=pk]').prop('checked', false); + $(interface).hide(); + } + } +}); diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 3b3beb3e0..f88838028 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -903,35 +903,8 @@ function toggleConnection(elem) { $(".cable-toggle").click(function() { return toggleConnection($(this)); }); -// Toggle the display of IP addresses under interfaces -$('button.toggle-ips').click(function() { - var selected = $(this).attr('selected'); - if (selected) { - $('#interfaces_table tr.ipaddresses').hide(); - } else { - $('#interfaces_table tr.ipaddresses').show(); - } - $(this).attr('selected', !selected); - $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); - return false; -}); -$('input.interface-filter').on('input', function() { - var filter = new RegExp(this.value); - - for (interface of $(this).closest('form').find('tbody > tr')) { - // Slice off 'interface_' at the start of the ID - if (filter && filter.test(interface.id.slice(10))) { - // Match the toggle in case the filter now matches the interface - $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked')); - $(interface).show(); - } else { - // Uncheck to prevent actions from including it when it doesn't match - $(interface).find('input:checkbox[name=pk]').prop('checked', false); - $(interface).hide(); - } - } -}); + {% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 298daf94f..6477c71ad 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -1,5 +1,6 @@ {% extends '_base.html' %} {% load custom_links %} +{% load static %} {% load helpers %} {% block header %} @@ -315,34 +316,5 @@ {% endblock %} {% block javascript %} - + {% endblock %} From 0e8ffc3b3f03579d712687ab07a9f31d97cf5a69 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 6 Jan 2020 23:33:18 +0000 Subject: [PATCH 31/98] Moved regex note to tooltip --- netbox/project-static/js/forms.js | 6 +++--- netbox/templates/dcim/device.html | 2 +- netbox/templates/virtualization/virtualmachine.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 0b8c7d719..b7dbb1cfa 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -400,8 +400,8 @@ $(document).ready(function() { window.addEventListener('hashchange', headerOffsetScroll); // Offset between the preview window and the window edges - const IMAGE_PREVIEW_OFFSET_X = 20 - const IMAGE_PREVIEW_OFFSET_Y = 10 + const IMAGE_PREVIEW_OFFSET_X = 20; + const IMAGE_PREVIEW_OFFSET_Y = 10; // Preview an image attachment when the link is hovered over $('a.image-preview').on('mouseover', function(e) { @@ -435,6 +435,6 @@ $(document).ready(function() { // Fade the image out; it will be deleted when another one is previewed $('a.image-preview').on('mouseout', function() { - $('#image-preview-window').fadeOut('fast') + $('#image-preview-window').fadeOut('fast'); }); }); diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index f88838028..74d469a12 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -557,7 +557,7 @@
- +
- {{ attachment }} + {{ attachment }} {{ attachment.size|filesizeformat }} {{ attachment.created }}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 6477c71ad..5e73935d0 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -255,7 +255,7 @@
- +
From 49ffcb8075333fa4c51a2ae1990553757aba7ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 09:08:31 -0500 Subject: [PATCH 32/98] Fixes #3853: Fix device role link on config context view --- docs/release-notes/version-2.6.md | 1 + netbox/templates/extras/configcontext.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 4b4160d95..510e4253a 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -8,6 +8,7 @@ ## Bug Fixes * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface +* [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view --- diff --git a/netbox/templates/extras/configcontext.html b/netbox/templates/extras/configcontext.html index 3631122c3..f1ff4fa1f 100644 --- a/netbox/templates/extras/configcontext.html +++ b/netbox/templates/extras/configcontext.html @@ -112,7 +112,7 @@ {% if configcontext.roles.all %} {% else %} From 928cd780887bd4b2cfefaeb34e4f0b25bb4d3a18 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 09:34:50 -0500 Subject: [PATCH 33/98] Add filter tests for secrets --- netbox/secrets/tests/test_filters.py | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 netbox/secrets/tests/test_filters.py diff --git a/netbox/secrets/tests/test_filters.py b/netbox/secrets/tests/test_filters.py new file mode 100644 index 000000000..2c99747ff --- /dev/null +++ b/netbox/secrets/tests/test_filters.py @@ -0,0 +1,90 @@ +from django.test import TestCase + +from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site +from secrets.filters import SecretFilter, SecretRoleFilter +from secrets.models import Secret, SecretRole + + +class SecretRoleTestCase(TestCase): + queryset = SecretRole.objects.all() + + @classmethod + def setUpTestData(cls): + + roles = ( + SecretRole(name='Secret Role 1', slug='secret-role-1'), + SecretRole(name='Secret Role 2', slug='secret-role-2'), + SecretRole(name='Secret Role 3', slug='secret-role-3'), + ) + SecretRole.objects.bulk_create(roles) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Secret Role 1', 'Secret Role 2']} + self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['secret-role-1', 'secret-role-2']} + self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + + +class SecretTestCase(TestCase): + queryset = Secret.objects.all() + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site-1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(device_type=device_type, name='Device 1', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 2', site=site, device_role=device_role), + Device(device_type=device_type, name='Device 3', site=site, device_role=device_role), + ) + Device.objects.bulk_create(devices) + + roles = ( + SecretRole(name='Secret Role 1', slug='secret-role-1'), + SecretRole(name='Secret Role 2', slug='secret-role-2'), + SecretRole(name='Secret Role 3', slug='secret-role-3'), + ) + SecretRole.objects.bulk_create(roles) + + secrets = ( + Secret(device=devices[0], role=roles[0], name='Secret 1', plaintext='SECRET DATA'), + Secret(device=devices[1], role=roles[1], name='Secret 2', plaintext='SECRET DATA'), + Secret(device=devices[2], role=roles[2], name='Secret 3', plaintext='SECRET DATA'), + ) + # Must call save() to encrypt Secrets + for s in secrets: + s.save() + + def test_name(self): + params = {'name': ['Secret 1', 'Secret 2']} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + + def test_role(self): + roles = SecretRole.objects.all()[:2] + params = {'role_id': [roles[0].pk, roles[1].pk]} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + params = {'role': [roles[0].slug, roles[1].slug]} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) From 95bdb8be933c1e4708de55dd46529ae511048ad9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 09:41:43 -0500 Subject: [PATCH 34/98] Add tests for tenancy filters --- netbox/tenancy/tests/test_filters.py | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 netbox/tenancy/tests/test_filters.py diff --git a/netbox/tenancy/tests/test_filters.py b/netbox/tenancy/tests/test_filters.py new file mode 100644 index 000000000..70db8f627 --- /dev/null +++ b/netbox/tenancy/tests/test_filters.py @@ -0,0 +1,72 @@ +from django.test import TestCase + +from tenancy.filters import TenantFilter, TenantGroupFilter +from tenancy.models import Tenant, TenantGroup + + +class TenantGroupTestCase(TestCase): + queryset = TenantGroup.objects.all() + + @classmethod + def setUpTestData(cls): + + groups = ( + TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), + TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), + TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), + ) + TenantGroup.objects.bulk_create(groups) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Tenant Group 1', 'Tenant Group 2']} + self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['tenant-group-1', 'tenant-group-2']} + self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + + +class TenantTestCase(TestCase): + queryset = Tenant.objects.all() + + @classmethod + def setUpTestData(cls): + + groups = ( + TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), + TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), + TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), + ) + TenantGroup.objects.bulk_create(groups) + + tenants = ( + Tenant(name='Tenant 1', slug='tenant-1', group=groups[0]), + Tenant(name='Tenant 2', slug='tenant-2', group=groups[1]), + Tenant(name='Tenant 3', slug='tenant-3', group=groups[2]), + ) + Tenant.objects.bulk_create(tenants) + + def test_name(self): + params = {'name': ['Tenant 1', 'Tenant 2']} + self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['tenant-1', 'tenant-2']} + self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + + def test_group(self): + group = TenantGroup.objects.all()[:2] + params = {'group_id': [group[0].pk, group[1].pk]} + self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + params = {'group': [group[0].slug, group[1].slug]} + self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) From ff0662708e1e35a30cfa2d0684df577ded20a2c3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 10:19:21 -0500 Subject: [PATCH 35/98] Add tests for virtualization filters --- netbox/virtualization/tests/test_filters.py | 365 ++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 netbox/virtualization/tests/test_filters.py diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filters.py new file mode 100644 index 000000000..b0eb279ea --- /dev/null +++ b/netbox/virtualization/tests/test_filters.py @@ -0,0 +1,365 @@ +from django.test import TestCase + +from dcim.models import DeviceRole, Interface, Platform, Region, Site +from virtualization.constants import * +from virtualization.filters import ( + ClusterFilter, ClusterGroupFilter, ClusterTypeFilter, InterfaceFilter, VirtualMachineFilter, +) +from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine + + +class ClusterTypeTestCase(TestCase): + queryset = ClusterType.objects.all() + + @classmethod + def setUpTestData(cls): + + cluster_types = ( + ClusterType(name='Cluster Type 1', slug='cluster-type-1'), + ClusterType(name='Cluster Type 2', slug='cluster-type-2'), + ClusterType(name='Cluster Type 3', slug='cluster-type-3'), + ) + ClusterType.objects.bulk_create(cluster_types) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Cluster Type 1', 'Cluster Type 2']} + self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['cluster-type-1', 'cluster-type-2']} + self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + + +class ClusterGroupTestCase(TestCase): + queryset = ClusterGroup.objects.all() + + @classmethod + def setUpTestData(cls): + + cluster_groups = ( + ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), + ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'), + ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'), + ) + ClusterGroup.objects.bulk_create(cluster_groups) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Cluster Group 1', 'Cluster Group 2']} + self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['cluster-group-1', 'cluster-group-2']} + self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + + +class ClusterTestCase(TestCase): + queryset = Cluster.objects.all() + + @classmethod + def setUpTestData(cls): + + cluster_types = ( + ClusterType(name='Cluster Type 1', slug='cluster-type-1'), + ClusterType(name='Cluster Type 2', slug='cluster-type-2'), + ClusterType(name='Cluster Type 3', slug='cluster-type-3'), + ) + ClusterType.objects.bulk_create(cluster_types) + + cluster_groups = ( + ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), + ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'), + ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'), + ) + ClusterGroup.objects.bulk_create(cluster_groups) + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + clusters = ( + Cluster(name='Cluster 1', type=cluster_types[0], group=cluster_groups[0], site=sites[0]), + Cluster(name='Cluster 2', type=cluster_types[1], group=cluster_groups[1], site=sites[1]), + Cluster(name='Cluster 3', type=cluster_types[2], group=cluster_groups[2], site=sites[2]), + ) + Cluster.objects.bulk_create(clusters) + + def test_name(self): + params = {'name': ['Cluster 1', 'Cluster 2']} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + def test_group(self): + groups = ClusterGroup.objects.all()[:2] + params = {'group_id': [groups[0].pk, groups[1].pk]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + params = {'group': [groups[0].slug, groups[1].slug]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + def test_type(self): + types = ClusterType.objects.all()[:2] + params = {'type_id': [types[0].pk, types[1].pk]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + params = {'type': [types[0].slug, types[1].slug]} + self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + + +class VirtualMachineTestCase(TestCase): + queryset = VirtualMachine.objects.all() + + @classmethod + def setUpTestData(cls): + + cluster_types = ( + ClusterType(name='Cluster Type 1', slug='cluster-type-1'), + ClusterType(name='Cluster Type 2', slug='cluster-type-2'), + ClusterType(name='Cluster Type 3', slug='cluster-type-3'), + ) + ClusterType.objects.bulk_create(cluster_types) + + cluster_groups = ( + ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'), + ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'), + ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'), + ) + ClusterGroup.objects.bulk_create(cluster_groups) + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1', region=regions[0]), + Site(name='Test Site 2', slug='test-site-2', region=regions[1]), + Site(name='Test Site 3', slug='test-site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + clusters = ( + Cluster(name='Cluster 1', type=cluster_types[0], group=cluster_groups[0], site=sites[0]), + Cluster(name='Cluster 2', type=cluster_types[1], group=cluster_groups[1], site=sites[1]), + Cluster(name='Cluster 3', type=cluster_types[2], group=cluster_groups[2], site=sites[2]), + ) + Cluster.objects.bulk_create(clusters) + + platforms = ( + Platform(name='Platform 1', slug='platform-1'), + Platform(name='Platform 2', slug='platform-2'), + Platform(name='Platform 3', slug='platform-3'), + ) + Platform.objects.bulk_create(platforms) + + roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(roles) + + vms = ( + VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], status=DEVICE_STATUS_ACTIVE, vcpus=1, memory=1, disk=1), + VirtualMachine(name='Virtual Machine 2', cluster=clusters[1], platform=platforms[1], role=roles[1], status=DEVICE_STATUS_STAGED, vcpus=2, memory=2, disk=2), + VirtualMachine(name='Virtual Machine 3', cluster=clusters[2], platform=platforms[2], role=roles[2], status=DEVICE_STATUS_OFFLINE, vcpus=3, memory=3, disk=3), + ) + VirtualMachine.objects.bulk_create(vms) + + interfaces = ( + Interface(virtual_machine=vms[0], name='Interface 1', mac_address='00-00-00-00-00-01'), + Interface(virtual_machine=vms[1], name='Interface 2', mac_address='00-00-00-00-00-02'), + Interface(virtual_machine=vms[2], name='Interface 3', mac_address='00-00-00-00-00-03'), + ) + Interface.objects.bulk_create(interfaces) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Virtual Machine 1', 'Virtual Machine 2']} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_vcpus(self): + params = {'vcpus': [1, 2]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_memory(self): + params = {'memory': [1, 2]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_disk(self): + params = {'disk': [1, 2]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [DEVICE_STATUS_ACTIVE, DEVICE_STATUS_STAGED]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_cluster_group(self): + groups = ClusterGroup.objects.all()[:2] + params = {'cluster_group_id': [groups[0].pk, groups[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'cluster_group': [groups[0].slug, groups[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_cluster_type(self): + types = ClusterType.objects.all()[:2] + params = {'cluster_type_id': [types[0].pk, types[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'cluster_type': [types[0].slug, types[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_cluster(self): + clusters = Cluster.objects.all()[:2] + params = {'cluster_id': [clusters[0].pk, clusters[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + # TODO: 'cluster' should match on name + # params = {'cluster': [clusters[0].name, clusters[1].name]} + # self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_role(self): + roles = DeviceRole.objects.all()[:2] + params = {'role_id': [roles[0].pk, roles[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'role': [roles[0].slug, roles[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_platform(self): + platforms = Platform.objects.all()[:2] + params = {'platform_id': [platforms[0].pk, platforms[1].pk]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + params = {'platform': [platforms[0].slug, platforms[1].slug]} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + def test_mac_address(self): + params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} + self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + + +class InterfaceTestCase(TestCase): + queryset = Interface.objects.all() + + @classmethod + def setUpTestData(cls): + + cluster_types = ( + ClusterType(name='Cluster Type 1', slug='cluster-type-1'), + ClusterType(name='Cluster Type 2', slug='cluster-type-2'), + ClusterType(name='Cluster Type 3', slug='cluster-type-3'), + ) + ClusterType.objects.bulk_create(cluster_types) + + clusters = ( + Cluster(name='Cluster 1', type=cluster_types[0]), + Cluster(name='Cluster 2', type=cluster_types[1]), + Cluster(name='Cluster 3', type=cluster_types[2]), + ) + Cluster.objects.bulk_create(clusters) + + vms = ( + VirtualMachine(name='Virtual Machine 1', cluster=clusters[0]), + VirtualMachine(name='Virtual Machine 2', cluster=clusters[1]), + VirtualMachine(name='Virtual Machine 3', cluster=clusters[2]), + ) + VirtualMachine.objects.bulk_create(vms) + + interfaces = ( + Interface(virtual_machine=vms[0], name='Interface 1', enabled=True, mtu=100, mac_address='00-00-00-00-00-01'), + Interface(virtual_machine=vms[1], name='Interface 2', enabled=True, mtu=200, mac_address='00-00-00-00-00-02'), + Interface(virtual_machine=vms[2], name='Interface 3', enabled=False, mtu=300, mac_address='00-00-00-00-00-03'), + ) + Interface.objects.bulk_create(interfaces) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Interface 1', 'Interface 2']} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + + def test_assigned_to_interface(self): + params = {'enabled': 'true'} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + params = {'enabled': 'false'} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 1) + + def test_mtu(self): + params = {'mtu': [100, 200]} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + + def test_virtual_machine(self): + vms = VirtualMachine.objects.all()[:2] + params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + params = {'virtual_machine': [vms[0].name, vms[1].name]} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + + # TODO: Test for multiple values + def test_mac_address(self): + params = {'mac_address': '00-00-00-00-00-01'} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 1) From 7357b1c7e5384afb5e7bc2a8003c4ac547959351 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 10:31:44 -0500 Subject: [PATCH 36/98] Fixes #3856: Allow filtering VM interfaces by multiple MAC addresses --- docs/release-notes/version-2.6.md | 1 + netbox/virtualization/filters.py | 13 +------------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 510e4253a..f3ff8798a 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -9,6 +9,7 @@ * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view +* [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses --- diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 6c75c78fc..8ce97f9e2 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -208,8 +208,7 @@ class InterfaceFilter(django_filters.FilterSet): to_field_name='name', label='Virtual machine', ) - mac_address = django_filters.CharFilter( - method='_mac_address', + mac_address = MultiValueMACAddressFilter( label='MAC address', ) @@ -217,16 +216,6 @@ class InterfaceFilter(django_filters.FilterSet): model = Interface fields = ['id', 'name', 'enabled', 'mtu'] - def _mac_address(self, queryset, name, value): - value = value.strip() - if not value: - return queryset - try: - mac = EUI(value.strip()) - return queryset.filter(mac_address=mac) - except AddrFormatError: - return queryset.none() - def search(self, queryset, name, value): if not value.strip(): return queryset From 43295824638a928f1ac1e067ce64be86ff86c994 Mon Sep 17 00:00:00 2001 From: hSaria <34197532+hSaria@users.noreply.github.com> Date: Tue, 7 Jan 2020 17:18:36 +0000 Subject: [PATCH 37/98] Removed cookie-based storage; now based on request --- netbox/ipam/views.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 1ea9a717d..5f727a5ac 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -334,12 +334,8 @@ class AggregateView(PermissionRequiredMixin, View): limit=0 ) - # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available'): - request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' - - # Add available prefixes to the table if the cookie requested it - if request.session.get('ipam_show_available'): + # Add available prefixes to the table if requested + if request.GET.get('show_available', False): child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) @@ -363,7 +359,7 @@ class AggregateView(PermissionRequiredMixin, View): 'aggregate': aggregate, 'prefix_table': prefix_table, 'permissions': permissions, - 'show_available': request.session.get('ipam_show_available', False), + 'show_available': request.GET.get('show_available', False), }) @@ -519,12 +515,8 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'site', 'vlan', 'role', ).annotate_depth(limit=0) - # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available'): - request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' - - # Add available prefixes to the table if the cookie requested it - if child_prefixes and request.session.get('ipam_show_available'): + # Add available prefixes to the table if requested + if child_prefixes and request.GET.get('show_available', False): child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) @@ -551,7 +543,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'prefixes', - 'show_available': request.session.get('ipam_show_available', False), + 'show_available': request.GET.get('show_available', False), }) @@ -567,12 +559,8 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): 'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for' ) - # Update the ipam_show_available cookie if request specifies it - if request.GET.get('show_available'): - request.session['ipam_show_available'] = request.GET.get('show_available') == 'true' - - # Add available IP addresses to the table if the cookie requested it - if request.session.get('ipam_show_available'): + # Add available IP addresses to the table if requested + if request.GET.get('show_available', False): ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) ip_table = tables.IPAddressTable(ipaddresses) @@ -599,7 +587,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'ip-addresses', - 'show_available': request.session.get('ipam_show_available', False), + 'show_available': request.GET.get('show_available', False), }) From 0bb0d5b7ba590bb3368062ad3ae23caa71dd3781 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Tue, 7 Jan 2020 17:58:30 +0000 Subject: [PATCH 38/98] Changed default to showing available --- netbox/ipam/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 5f727a5ac..26df90c93 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -335,7 +335,7 @@ class AggregateView(PermissionRequiredMixin, View): ) # Add available prefixes to the table if requested - if request.GET.get('show_available', False): + if request.GET.get('show_available', 'true') == 'true': child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) @@ -359,7 +359,7 @@ class AggregateView(PermissionRequiredMixin, View): 'aggregate': aggregate, 'prefix_table': prefix_table, 'permissions': permissions, - 'show_available': request.GET.get('show_available', False), + 'show_available': request.GET.get('show_available', 'true') == 'true', }) @@ -516,7 +516,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): ).annotate_depth(limit=0) # Add available prefixes to the table if requested - if child_prefixes and request.GET.get('show_available', False): + if child_prefixes and request.GET.get('show_available', 'true') == 'true': child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes) prefix_table = tables.PrefixDetailTable(child_prefixes) @@ -543,7 +543,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'prefixes', - 'show_available': request.GET.get('show_available', False), + 'show_available': request.GET.get('show_available', 'true') == 'true', }) @@ -560,7 +560,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): ) # Add available IP addresses to the table if requested - if request.GET.get('show_available', False): + if request.GET.get('show_available', 'true') == 'true': ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) ip_table = tables.IPAddressTable(ipaddresses) @@ -587,7 +587,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): 'permissions': permissions, 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), 'active_tab': 'ip-addresses', - 'show_available': request.GET.get('show_available', False), + 'show_available': request.GET.get('show_available', 'true') == 'true', }) From 6274554d1950db369486607e3dd9f6033ce72a6e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 12:48:20 -0500 Subject: [PATCH 39/98] Add tests for DCIM filters --- netbox/dcim/tests/test_filters.py | 427 ++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 netbox/dcim/tests/test_filters.py diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py new file mode 100644 index 000000000..46e1048d5 --- /dev/null +++ b/netbox/dcim/tests/test_filters.py @@ -0,0 +1,427 @@ +from django.contrib.auth.models import User +from django.test import TestCase + +from dcim.constants import * +from dcim.filters import ( + RackFilter, RackGroupFilter, RackReservationFilter, RackRoleFilter, RegionFilter, SiteFilter, +) +from dcim.models import ( + Rack, RackGroup, RackReservation, RackRole, Region, Site, +) + + +class RegionTestCase(TestCase): + queryset = Region.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + child_regions = ( + Region(name='Region 1A', slug='region-1a', parent=regions[0]), + Region(name='Region 1B', slug='region-1b', parent=regions[0]), + Region(name='Region 2A', slug='region-2a', parent=regions[1]), + Region(name='Region 2B', slug='region-2b', parent=regions[1]), + Region(name='Region 3A', slug='region-3a', parent=regions[2]), + Region(name='Region 3B', slug='region-3b', parent=regions[2]), + ) + for region in child_regions: + region.save() + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Region 1', 'Region 2']} + self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['region-1', 'region-2']} + self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2) + + def test_parent(self): + parent_regions = Region.objects.filter(parent__isnull=True)[:2] + params = {'parent_id': [parent_regions[0].pk, parent_regions[1].pk]} + self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 4) + params = {'parent': [parent_regions[0].slug, parent_regions[1].slug]} + self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 4) + + +class SiteTestCase(TestCase): + queryset = Site.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0], status=SITE_STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10, contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'), + Site(name='Site 2', slug='site-2', region=regions[1], status=SITE_STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20, contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'), + Site(name='Site 3', slug='site-3', region=regions[2], status=SITE_STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30, contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'), + ) + Site.objects.bulk_create(sites) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Site 1', 'Site 2']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['site-1', 'site-2']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_facility(self): + params = {'facility': ['Facility 1', 'Facility 2']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_asn(self): + params = {'asn': [65001, 65002]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_latitude(self): + params = {'latitude': [10, 20]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_longitude(self): + params = {'longitude': [10, 20]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_contact_name(self): + params = {'contact_name': ['Contact 1', 'Contact 2']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_contact_phone(self): + params = {'contact_phone': ['123-555-0001', '123-555-0002']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_contact_email(self): + params = {'contact_email': ['contact1@example.com', 'contact2@example.com']} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [SITE_STATUS_ACTIVE, SITE_STATUS_PLANNED]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + + +class RackGroupTestCase(TestCase): + queryset = RackGroup.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + rack_groups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), + RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), + ) + RackGroup.objects.bulk_create(rack_groups) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Rack Group 1', 'Rack Group 2']} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['rack-group-1', 'rack-group-2']} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + + +class RackRoleTestCase(TestCase): + queryset = RackRole.objects.all() + + @classmethod + def setUpTestData(cls): + + rack_roles = ( + RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'), + RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'), + RackRole(name='Rack Role 3', slug='rack-role-3', color='0000ff'), + ) + RackRole.objects.bulk_create(rack_roles) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Rack Role 1', 'Rack Role 2']} + self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['rack-role-1', 'rack-role-2']} + self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + + def test_color(self): + params = {'color': ['ff0000', '00ff00']} + self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + + +class RackTestCase(TestCase): + queryset = Rack.objects.all() + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + rack_groups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), + RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), + ) + RackGroup.objects.bulk_create(rack_groups) + + rack_roles = ( + RackRole(name='Rack Role 1', slug='rack-role-1'), + RackRole(name='Rack Role 2', slug='rack-role-2'), + RackRole(name='Rack Role 3', slug='rack-role-3'), + ) + RackRole.objects.bulk_create(rack_roles) + + racks = ( + Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], status=RACK_STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RACK_TYPE_2POST, width=RACK_WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=LENGTH_UNIT_MILLIMETER), + Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], status=RACK_STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RACK_TYPE_4POST, width=RACK_WIDTH_19IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=LENGTH_UNIT_MILLIMETER), + Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], status=RACK_STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RACK_TYPE_CABINET, width=RACK_WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=LENGTH_UNIT_INCH), + ) + Rack.objects.bulk_create(racks) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Rack 1', 'Rack 2']} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_facility_id(self): + params = {'facility_id': ['rack-1', 'rack-2']} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_asset_tag(self): + params = {'asset_tag': ['1001', '1002']} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Test for multiple values + params = {'type': RACK_TYPE_2POST} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + + def test_width(self): + # TODO: Test for multiple values + params = {'width': RACK_WIDTH_19IN} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_u_height(self): + params = {'u_height': [42, 43]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_desc_units(self): + params = {'desc_units': 'true'} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + params = {'desc_units': 'false'} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_outer_width(self): + params = {'outer_width': [100, 200]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_outer_depth(self): + params = {'outer_depth': [100, 200]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_outer_unit(self): + self.assertEqual(Rack.objects.filter(outer_unit__isnull=False).count(), 3) + params = {'outer_unit': LENGTH_UNIT_MILLIMETER} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_group(self): + groups = RackGroup.objects.all()[:2] + params = {'group_id': [groups[0].pk, groups[1].pk]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + params = {'group': [groups[0].slug, groups[1].slug]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [RACK_STATUS_ACTIVE, RACK_STATUS_PLANNED]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_role(self): + roles = RackRole.objects.all()[:2] + params = {'role_id': [roles[0].pk, roles[1].pk]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + params = {'role': [roles[0].slug, roles[1].slug]} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + + def test_serial(self): + params = {'serial': 'ABC'} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + params = {'serial': 'abc'} + self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + + +class RackReservationTestCase(TestCase): + queryset = RackReservation.objects.all() + + @classmethod + def setUpTestData(cls): + + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + Site(name='Site 3', slug='site-3'), + ) + Site.objects.bulk_create(sites) + + rack_groups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), + RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), + ) + RackGroup.objects.bulk_create(rack_groups) + + racks = ( + Rack(name='Rack 1', site=sites[0], group=rack_groups[0]), + Rack(name='Rack 2', site=sites[1], group=rack_groups[1]), + Rack(name='Rack 3', site=sites[2], group=rack_groups[2]), + ) + Rack.objects.bulk_create(racks) + + users = ( + User(username='User 1'), + User(username='User 2'), + User(username='User 3'), + ) + User.objects.bulk_create(users) + + reservations = ( + RackReservation(rack=racks[0], units=[1, 2, 3], user=users[0]), + RackReservation(rack=racks[1], units=[4, 5, 6], user=users[1]), + RackReservation(rack=racks[2], units=[7, 8, 9], user=users[2]), + ) + RackReservation.objects.bulk_create(reservations) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + + def test_group(self): + groups = RackGroup.objects.all()[:2] + params = {'group_id': [groups[0].pk, groups[1].pk]} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + params = {'group': [groups[0].slug, groups[1].slug]} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + + def test_user(self): + users = User.objects.all()[:2] + params = {'user_id': [users[0].pk, users[1].pk]} + self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + # TODO: Filtering by username is broken + # params = {'user': [users[0].username, users[1].username]} + # self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) From e8dd278c77f9d60a935e41caddd912177dec48f2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 7 Jan 2020 17:13:05 -0500 Subject: [PATCH 40/98] Add DeviceType filter tests --- netbox/dcim/tests/test_filters.py | 505 +++++++++++++++++++++++++++++- 1 file changed, 501 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 46e1048d5..e6b6531af 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -2,11 +2,11 @@ from django.contrib.auth.models import User from django.test import TestCase from dcim.constants import * -from dcim.filters import ( - RackFilter, RackGroupFilter, RackReservationFilter, RackRoleFilter, RegionFilter, SiteFilter, -) +from dcim.filters import * from dcim.models import ( - Rack, RackGroup, RackReservation, RackRole, Region, Site, + ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceType, FrontPortTemplate, InterfaceTemplate, + Manufacturer, PowerPortTemplate, PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPortTemplate, + Region, Site, ) @@ -425,3 +425,500 @@ class RackReservationTestCase(TestCase): # TODO: Filtering by username is broken # params = {'user': [users[0].username, users[1].username]} # self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + + +class ManufacturerTestCase(TestCase): + queryset = Manufacturer.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Manufacturer 1', 'Manufacturer 2']} + self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['manufacturer-1', 'manufacturer-2']} + self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + + +class DeviceTypeTestCase(TestCase): + queryset = DeviceType.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + device_types = ( + DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, subdevice_role=None), + DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SUBDEVICE_ROLE_PARENT), + DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SUBDEVICE_ROLE_CHILD), + ) + DeviceType.objects.bulk_create(device_types) + + # Add component templates for filtering + ConsolePortTemplate.objects.bulk_create(( + ConsolePortTemplate(device_type=device_types[0], name='Console Port 1'), + ConsolePortTemplate(device_type=device_types[1], name='Console Port 2'), + )) + ConsoleServerPortTemplate.objects.bulk_create(( + ConsoleServerPortTemplate(device_type=device_types[0], name='Console Server Port 1'), + ConsoleServerPortTemplate(device_type=device_types[1], name='Console Server Port 2'), + )) + PowerPortTemplate.objects.bulk_create(( + PowerPortTemplate(device_type=device_types[0], name='Power Port 1'), + PowerPortTemplate(device_type=device_types[1], name='Power Port 2'), + )) + PowerOutletTemplate.objects.bulk_create(( + PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1'), + PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2'), + )) + InterfaceTemplate.objects.bulk_create(( + InterfaceTemplate(device_type=device_types[0], name='Interface 1'), + InterfaceTemplate(device_type=device_types[1], name='Interface 2'), + )) + rear_ports = ( + RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C), + RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_8P8C), + ) + RearPortTemplate.objects.bulk_create(rear_ports) + FrontPortTemplate.objects.bulk_create(( + FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0]), + FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=rear_ports[1]), + )) + DeviceBayTemplate.objects.bulk_create(( + DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'), + DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'), + )) + + def test_model(self): + params = {'model': ['Model 1', 'Model 2']} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['model-1', 'model-2']} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_part_number(self): + params = {'part_number': ['Part Number 1', 'Part Number 2']} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_u_height(self): + params = {'u_height': [1, 2]} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_is_full_depth(self): + params = {'is_full_depth': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'is_full_depth': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_subdevice_role(self): + params = {'subdevice_role': SUBDEVICE_ROLE_PARENT} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_manufacturer(self): + manufacturers = Manufacturer.objects.all()[:2] + params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + + def test_console_ports(self): + params = {'console_ports': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'console_ports': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_console_server_ports(self): + params = {'console_server_ports': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'console_server_ports': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_power_ports(self): + params = {'power_ports': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'power_ports': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_power_outlets(self): + params = {'power_outlets': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'power_outlets': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_interfaces(self): + params = {'interfaces': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'interfaces': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + def test_pass_through_ports(self): + params = {'pass_through_ports': 'true'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + params = {'pass_through_ports': 'false'} + self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + # TODO: Add device_bay filter + # def test_device_bays(self): + # params = {'device_bays': 'true'} + # self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + # params = {'device_bays': 'false'} + # self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + + +class ConsolePortTemplateTestCase(TestCase): + queryset = ConsolePortTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + ConsolePortTemplate.objects.bulk_create(( + ConsolePortTemplate(device_type=device_types[0], name='Console Port 1'), + ConsolePortTemplate(device_type=device_types[1], name='Console Port 2'), + ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Console Port 1', 'Console Port 2']} + self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + + +class ConsoleServerPortTemplateTestCase(TestCase): + queryset = ConsoleServerPortTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + ConsoleServerPortTemplate.objects.bulk_create(( + ConsoleServerPortTemplate(device_type=device_types[0], name='Console Server Port 1'), + ConsoleServerPortTemplate(device_type=device_types[1], name='Console Server Port 2'), + ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Console Server Port 1', 'Console Server Port 2']} + self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + +class PowerPortTemplateTestCase(TestCase): + queryset = PowerPortTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + PowerPortTemplate.objects.bulk_create(( + PowerPortTemplate(device_type=device_types[0], name='Power Port 1', maximum_draw=100, allocated_draw=50), + PowerPortTemplate(device_type=device_types[1], name='Power Port 2', maximum_draw=200, allocated_draw=100), + PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Power Port 1', 'Power Port 2']} + self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_maximum_draw(self): + params = {'maximum_draw': [100, 200]} + self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_allocated_draw(self): + params = {'allocated_draw': [50, 100]} + self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + + +class PowerOutletTemplateTestCase(TestCase): + queryset = PowerOutletTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + PowerOutletTemplate.objects.bulk_create(( + PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1', feed_leg=POWERFEED_LEG_A), + PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2', feed_leg=POWERFEED_LEG_B), + PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=POWERFEED_LEG_C), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Power Outlet 1', 'Power Outlet 2']} + self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_feed_leg(self): + # TODO: Support filtering for multiple values + params = {'feed_leg': POWERFEED_LEG_A} + self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 1) + + +class InterfaceTemplateTestCase(TestCase): + queryset = InterfaceTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + InterfaceTemplate.objects.bulk_create(( + InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=IFACE_TYPE_1GE_FIXED, mgmt_only=True), + InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=IFACE_TYPE_1GE_GBIC, mgmt_only=False), + InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=IFACE_TYPE_1GE_SFP, mgmt_only=False), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Interface 1', 'Interface 2']} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Support filtering for multiple values + params = {'type': IFACE_TYPE_1GE_FIXED} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 1) + + def test_mgmt_only(self): + params = {'mgmt_only': 'true'} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 1) + params = {'mgmt_only': 'false'} + self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + + +class FrontPortTemplateTestCase(TestCase): + queryset = FrontPortTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + rear_ports = ( + RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C), + RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_8P8C), + RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PORT_TYPE_8P8C), + ) + RearPortTemplate.objects.bulk_create(rear_ports) + + FrontPortTemplate.objects.bulk_create(( + FrontPortTemplate(device_type=device_types[0], name='Front Port 1', rear_port=rear_ports[0], type=PORT_TYPE_8P8C), + FrontPortTemplate(device_type=device_types[1], name='Front Port 2', rear_port=rear_ports[1], type=PORT_TYPE_110_PUNCH), + FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PORT_TYPE_BNC), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Front Port 1', 'Front Port 2']} + self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Support filtering for multiple values + params = {'type': PORT_TYPE_8P8C} + self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 1) + + +class RearPortTemplateTestCase(TestCase): + queryset = RearPortTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + RearPortTemplate.objects.bulk_create(( + RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=1), + RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_110_PUNCH, positions=2), + RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PORT_TYPE_BNC, positions=3), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Rear Port 1', 'Rear Port 2']} + self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Support filtering for multiple values + params = {'type': PORT_TYPE_8P8C} + self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 1) + + def test_positions(self): + params = {'positions': [1, 2]} + self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + + +class DeviceBayTemplateTestCase(TestCase): + queryset = DeviceBayTemplate.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + + device_types = ( + DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'), + DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'), + DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'), + ) + DeviceType.objects.bulk_create(device_types) + + DeviceBayTemplate.objects.bulk_create(( + DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'), + DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'), + DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'), + )) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Device Bay 1', 'Device Bay 2']} + self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype_id(self): + device_types = DeviceType.objects.all()[:2] + params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) From 7ddc8962fc0b2f9decb336217d9aca968f531136 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 10:14:48 +0000 Subject: [PATCH 41/98] Fixes #3857: Fix group custom links rendering --- docs/release-notes/version-2.6.md | 1 + netbox/extras/templatetags/custom_links.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..7597de931 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -10,6 +10,7 @@ * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses +* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering --- diff --git a/netbox/extras/templatetags/custom_links.py b/netbox/extras/templatetags/custom_links.py index 8c927a0ae..7dae81a1f 100644 --- a/netbox/extras/templatetags/custom_links.py +++ b/netbox/extras/templatetags/custom_links.py @@ -68,8 +68,9 @@ def custom_links(obj): text_rendered = render_jinja2(cl.text, context) if text_rendered: link_target = ' target="_blank"' if cl.new_window else '' + link_rendered = render_jinja2(cl.url, context) links_rendered.append( - GROUP_LINK.format(cl.url, link_target, cl.text) + GROUP_LINK.format(link_rendered, link_target, text_rendered) ) except Exception as e: links_rendered.append( From 6d5342619f72c9af96de2ae48017104981305dcd Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 10:49:58 +0000 Subject: [PATCH 42/98] Fixes #3440: Total cable trace length --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/models.py | 2 ++ netbox/dcim/views.py | 5 ++++- netbox/templates/dcim/cable_trace.html | 5 ++++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..86bdc44c7 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations +* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace ## Bug Fixes diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 8f95fa19a..2c7105a80 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2950,6 +2950,8 @@ class Cable(ChangeLoggedModel): # Store the given length (if any) in meters for use in database ordering if self.length and self.length_unit: self._abs_length = to_meters(self.length, self.length_unit) + else: + self._abs_length = None # Store the parent Device for the A and B terminations (if applicable) to enable filtering if hasattr(self.termination_a, 'device'): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2d98515cf..55a08fdb8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1754,10 +1754,13 @@ class CableTraceView(PermissionRequiredMixin, View): def get(self, request, model, pk): obj = get_object_or_404(model, pk=pk) + trace = obj.trace(follow_circuits=True) + total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length]) return render(request, 'dcim/cable_trace.html', { 'obj': obj, - 'trace': obj.trace(follow_circuits=True), + 'trace': trace, + 'total_length': total_length, }) diff --git a/netbox/templates/dcim/cable_trace.html b/netbox/templates/dcim/cable_trace.html index c9da88c46..4dd145058 100644 --- a/netbox/templates/dcim/cable_trace.html +++ b/netbox/templates/dcim/cable_trace.html @@ -10,7 +10,10 @@

Near End

-
+
+ {% if total_length %}
Total length: {{ total_length|floatformat:"-2" }} Meters
{% endif %} +
+

Far End

From 02d8a4547f7f34eedff6ae35a71c30ac4d354304 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 13:34:46 +0000 Subject: [PATCH 43/98] Fixes #1982: Swagger NAPALM documentation --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/api/serializers.py | 4 ++++ netbox/dcim/api/views.py | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..90eba7327 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -2,6 +2,7 @@ ## Enhancements +* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 495709268..db5fe992f 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -370,6 +370,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): return obj.get_config_context() +class DeviceNAPALMSerializer(serializers.Serializer): + method = serializers.DictField() + + class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer): device = NestedDeviceSerializer() cable = NestedCableSerializer(read_only=True) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 12774e4be..9bfe0f421 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -327,6 +327,13 @@ class DeviceViewSet(CustomFieldModelViewSet): ) filterset_class = filters.DeviceFilter + _method = Parameter( + name='method', + in_='query', + required=True, + type=openapi.TYPE_STRING + ) + def get_serializer_class(self): """ Select the specific serializer based on the request context. @@ -358,11 +365,15 @@ class DeviceViewSet(CustomFieldModelViewSet): return Response(serializer.data) + @swagger_auto_schema(manual_parameters=[_method], responses={'200': serializers.DeviceNAPALMSerializer}) @action(detail=True, url_path='napalm') def napalm(self, request, pk): """ Execute a NAPALM method on a Device """ + if not request.GET.get('method'): + raise ServiceUnavailable('No NAPALM methods were specified.') + device = get_object_or_404(Device, pk=pk) if not device.primary_ip: raise ServiceUnavailable("This device does not have a primary IP address configured.") From 4865ff20fd71e875f48df6e91f5d193214f446aa Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 09:50:22 -0500 Subject: [PATCH 44/98] Add device filter tests --- netbox/dcim/tests/test_filters.py | 391 +++++++++++++++++++++++++++++- 1 file changed, 388 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index e6b6531af..f91ff3cfd 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -4,10 +4,12 @@ from django.test import TestCase from dcim.constants import * from dcim.filters import * from dcim.models import ( - ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceType, FrontPortTemplate, InterfaceTemplate, - Manufacturer, PowerPortTemplate, PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPortTemplate, - Region, Site, + ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPortTemplate, + InterfaceTemplate, Manufacturer, Platform, PowerPortTemplate, PowerOutletTemplate, Rack, RackGroup, RackReservation, + RackRole, RearPortTemplate, Region, Site, VirtualChassis, ) +from ipam.models import IPAddress +from virtualization.models import Cluster, ClusterType class RegionTestCase(TestCase): @@ -922,3 +924,386 @@ class DeviceBayTemplateTestCase(TestCase): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + + +class DeviceRoleTestCase(TestCase): + queryset = DeviceRole.objects.all() + + @classmethod + def setUpTestData(cls): + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True), + DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True), + DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff', vm_role=False), + ) + DeviceRole.objects.bulk_create(device_roles) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Device Role 1', 'Device Role 2']} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['device-role-1', 'device-role-2']} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + + def test_color(self): + params = {'color': ['ff0000', '00ff00']} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + + def test_vm_role(self): + params = {'vm_role': 'true'} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + params = {'vm_role': 'false'} + self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 1) + + +class PlatformTestCase(TestCase): + queryset = Platform.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + platforms = ( + Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0], napalm_driver='driver-1'), + Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1], napalm_driver='driver-2'), + Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2], napalm_driver='driver-3'), + ) + Platform.objects.bulk_create(platforms) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Platform 1', 'Platform 2']} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + + def test_slug(self): + params = {'slug': ['platform-1', 'platform-2']} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + + def test_napalm_driver(self): + params = {'napalm_driver': ['driver-1', 'driver-2']} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + + def test_manufacturer(self): + manufacturers = Manufacturer.objects.all()[:2] + params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} + self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + + +class DeviceTestCase(TestCase): + queryset = Device.objects.all() + + @classmethod + def setUpTestData(cls): + + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + device_types = ( + DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', is_full_depth=True), + DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', is_full_depth=True), + DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', is_full_depth=False), + ) + DeviceType.objects.bulk_create(device_types) + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) + + platforms = ( + Platform(name='Platform 1', slug='platform-1'), + Platform(name='Platform 2', slug='platform-2'), + Platform(name='Platform 3', slug='platform-3'), + ) + Platform.objects.bulk_create(platforms) + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + rack_groups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), + RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), + ) + RackGroup.objects.bulk_create(rack_groups) + + racks = ( + Rack(name='Rack 1', site=sites[0], group=rack_groups[0]), + Rack(name='Rack 2', site=sites[1], group=rack_groups[1]), + Rack(name='Rack 3', site=sites[2], group=rack_groups[2]), + ) + Rack.objects.bulk_create(racks) + + cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + clusters = ( + Cluster(name='Cluster 1', type=cluster_type), + Cluster(name='Cluster 2', type=cluster_type), + Cluster(name='Cluster 3', type=cluster_type), + ) + Cluster.objects.bulk_create(clusters) + + devices = ( + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=RACK_FACE_FRONT, status=DEVICE_STATUS_STAGED), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=RACK_FACE_REAR, status=DEVICE_STATUS_FAILED), + ) + Device.objects.bulk_create(devices) + + # Add components for filtering + ConsolePort.objects.bulk_create(( + ConsolePort(device=devices[0], name='Console Port 1'), + ConsolePort(device=devices[1], name='Console Port 2'), + )) + ConsoleServerPort.objects.bulk_create(( + ConsoleServerPort(device=devices[0], name='Console Server Port 1'), + ConsoleServerPort(device=devices[1], name='Console Server Port 2'), + )) + PowerPort.objects.bulk_create(( + PowerPort(device=devices[0], name='Power Port 1'), + PowerPort(device=devices[1], name='Power Port 2'), + )) + PowerOutlet.objects.bulk_create(( + PowerOutlet(device=devices[0], name='Power Outlet 1'), + PowerOutlet(device=devices[1], name='Power Outlet 2'), + )) + interfaces = ( + Interface(device=devices[0], name='Interface 1', mac_address='00-00-00-00-00-01'), + Interface(device=devices[1], name='Interface 2', mac_address='00-00-00-00-00-02'), + ) + Interface.objects.bulk_create(interfaces) + rear_ports = ( + RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C), + RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_8P8C), + ) + RearPort.objects.bulk_create(rear_ports) + FrontPort.objects.bulk_create(( + FrontPort(device=devices[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0]), + FrontPort(device=devices[1], name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=rear_ports[1]), + )) + DeviceBay.objects.bulk_create(( + DeviceBay(device=devices[0], name='Device Bay 1'), + DeviceBay(device=devices[1], name='Device Bay 2'), + )) + + # Assign primary IPs for filtering + ipaddresses = ( + IPAddress(family=4, address='192.0.2.1/24', interface=interfaces[0]), + IPAddress(family=4, address='192.0.2.2/24', interface=interfaces[1]), + ) + IPAddress.objects.bulk_create(ipaddresses) + Device.objects.filter(pk=devices[0].pk).update(primary_ip4=ipaddresses[0]) + Device.objects.filter(pk=devices[1].pk).update(primary_ip4=ipaddresses[1]) + + # VirtualChassis assignment for filtering + virtual_chassis = VirtualChassis.objects.create(master=devices[0]) + Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1) + Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Device 1', 'Device 2']} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_asset_tag(self): + params = {'asset_tag': ['1001', '1002']} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_face(self): + params = {'face': RACK_FACE_FRONT} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_position(self): + params = {'position': [1, 2]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_vc_position(self): + params = {'vc_position': [1, 2]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_vc_priority(self): + params = {'vc_priority': [1, 2]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_id__in(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id__in': ','.join([str(id) for id in id_list])} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_manufacturer(self): + manufacturers = Manufacturer.objects.all()[:2] + params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_devicetype(self): + device_types = DeviceType.objects.all()[:2] + params = {'device_type_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_devicerole(self): + device_roles = DeviceRole.objects.all()[:2] + params = {'role_id': [device_roles[0].pk, device_roles[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'role': [device_roles[0].slug, device_roles[1].slug]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_platform(self): + platforms = Platform.objects.all()[:2] + params = {'platform_id': [platforms[0].pk, platforms[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'platform': [platforms[0].slug, platforms[1].slug]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_rackgroup(self): + rack_groups = RackGroup.objects.all()[:2] + params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_rack(self): + racks = Rack.objects.all()[:2] + params = {'rack_id': [racks[0].pk, racks[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_cluster(self): + clusters = Cluster.objects.all()[:2] + params = {'rack_id': [clusters[0].pk, clusters[1].pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_model(self): + params = {'model': ['model-1', 'model-2']} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_status(self): + params = {'status': [DEVICE_STATUS_ACTIVE, DEVICE_STATUS_STAGED]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_is_full_depth(self): + params = {'is_full_depth': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'is_full_depth': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_mac_address(self): + params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_serial(self): + params = {'serial': 'ABC'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + params = {'serial': 'abc'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_has_primary_ip(self): + params = {'has_primary_ip': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'has_primary_ip': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_virtual_chassis_id(self): + params = {'virtual_chassis_id': [VirtualChassis.objects.first().pk]} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + + def test_virtual_chassis_member(self): + params = {'virtual_chassis_member': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'virtual_chassis_member': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_console_ports(self): + params = {'console_ports': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'console_ports': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_console_server_ports(self): + params = {'console_server_ports': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'console_server_ports': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_power_ports(self): + params = {'power_ports': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'power_ports': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_power_outlets(self): + params = {'power_outlets': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'power_outlets': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_interfaces(self): + params = {'interfaces': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'interfaces': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + def test_pass_through_ports(self): + params = {'pass_through_ports': 'true'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + params = {'pass_through_ports': 'false'} + self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + # TODO: Add device_bay filter + # def test_device_bays(self): + # params = {'device_bays': 'true'} + # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + # params = {'device_bays': 'false'} + # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) From 42c2dbe61bfc3f199bcadf350f21b90483b8c697 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 15:53:48 +0000 Subject: [PATCH 45/98] Fixes #2113: Adjust NAPALM settings with headers --- netbox/dcim/api/views.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 12774e4be..205bc0af0 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -396,13 +396,29 @@ class DeviceViewSet(CustomFieldModelViewSet): napalm_methods = request.GET.getlist('method') response = OrderedDict([(m, None) for m in napalm_methods]) ip_address = str(device.primary_ip.address.ip) + username = settings.NAPALM_USERNAME + password = settings.NAPALM_PASSWORD optional_args = settings.NAPALM_ARGS.copy() if device.platform.napalm_args is not None: optional_args.update(device.platform.napalm_args) + + # Update NAPALM parameters according to the provided headers + for header in request.headers: + if header[:7].lower() != 'napalm-': + continue + + key = header[7:] + if key.lower() == 'username': + username = request.headers[header] + elif key.lower() == 'password': + password = request.headers[header] + elif key: + optional_args[key.lower()] == request.headers[header] + d = driver( hostname=ip_address, - username=settings.NAPALM_USERNAME, - password=settings.NAPALM_PASSWORD, + username=username, + password=password, timeout=settings.NAPALM_TIMEOUT, optional_args=optional_args ) From 558f5869338cc808b900a4dfdcbfd375f1c9bbea Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 15:54:09 +0000 Subject: [PATCH 46/98] Added NAPALM documentation --- docs/additional-features/napalm.md | 65 ++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 66 insertions(+) create mode 100644 docs/additional-features/napalm.md diff --git a/docs/additional-features/napalm.md b/docs/additional-features/napalm.md new file mode 100644 index 000000000..a7b91128b --- /dev/null +++ b/docs/additional-features/napalm.md @@ -0,0 +1,65 @@ +# NAPALM + +NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to fetch live data from devices and return it to a requester via its REST API. + +!!! info + To enable the integration, the NAPALM library must be installed. See [installation steps](../../installation/2-netbox/#napalm-automation-optional) for more information. + +``` +GET /api/dcim/devices/1/napalm/?method=get_environment + +{ + "get_environment": { + ... + } +} +``` + +## Authentication + +By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `NAPALM-Username` and `NAPALM-Password` headers. + +``` +$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \ +-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \ +-H "Content-Type: application/json" \ +-H "Accept: application/json; indent=4" \ +-H "NAPALM-Username: foo" \ +-H "NAPALM-Password: bar" +``` + +## Method Support + +The list of supported NAPALM methods depends on the [NAPALM driver](https://napalm.readthedocs.io/en/latest/support/index.html#general-support-matrix) configured for the platform of a device. NetBox only supports [get](https://napalm.readthedocs.io/en/latest/support/index.html#getters-support-matrix) methods. + +## Multiple Methods + +More than one method in an API call can be invoked by adding multiple `method` parameters. For example: + +``` +GET /api/dcim/devices/1/napalm/?method=get_ntp_servers&method=get_ntp_peers + +{ + "get_ntp_servers": { + ... + }, + "get_ntp_peers": { + ... + } +} +``` + +## Optional Arguments + +The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `NAPALM-`. + + +For instance, the SSH port is changed to 2222 in this API call: + +``` +$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \ +-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \ +-H "Content-Type: application/json" \ +-H "Accept: application/json; indent=4" \ +-H "NAPALM-port: 2222" +``` diff --git a/mkdocs.yml b/mkdocs.yml index cc44921b6..b493a799b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ pages: - Custom Scripts: 'additional-features/custom-scripts.md' - Export Templates: 'additional-features/export-templates.md' - Graphs: 'additional-features/graphs.md' + - NAPALM: 'additional-features/napalm.md' - Prometheus Metrics: 'additional-features/prometheus-metrics.md' - Reports: 'additional-features/reports.md' - Tags: 'additional-features/tags.md' From e7a6473709a9f67ede0fad485ef36748255b60f4 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 15:55:36 +0000 Subject: [PATCH 47/98] NAPALM settings changelog --- docs/release-notes/version-2.6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..61683fc75 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -3,6 +3,7 @@ ## Enhancements * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link +* [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations ## Bug Fixes From cbda0c0c5dca478b1dadc0ddcd5447129aa71090 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 16:01:18 +0000 Subject: [PATCH 48/98] Corrected optional arg assignment --- netbox/dcim/api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 205bc0af0..18da2f9a3 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -402,7 +402,7 @@ class DeviceViewSet(CustomFieldModelViewSet): if device.platform.napalm_args is not None: optional_args.update(device.platform.napalm_args) - # Update NAPALM parameters according to the provided headers + # Update NAPALM parameters according to the request headers for header in request.headers: if header[:7].lower() != 'napalm-': continue @@ -413,7 +413,7 @@ class DeviceViewSet(CustomFieldModelViewSet): elif key.lower() == 'password': password = request.headers[header] elif key: - optional_args[key.lower()] == request.headers[header] + optional_args[key.lower()] = request.headers[header] d = driver( hostname=ip_address, From 0e00ce6f514eebe72d52147c27f95950f2cf3175 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 11:04:50 -0500 Subject: [PATCH 49/98] Add console port, console server port filter tests --- netbox/dcim/tests/test_filters.py | 140 +++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index f91ff3cfd..04c3e78c4 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -4,7 +4,7 @@ from django.test import TestCase from dcim.constants import * from dcim.filters import * from dcim.models import ( - ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPortTemplate, + Cable, ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPortTemplate, InterfaceTemplate, Manufacturer, Platform, PowerPortTemplate, PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPortTemplate, Region, Site, VirtualChassis, ) @@ -1307,3 +1307,141 @@ class DeviceTestCase(TestCase): # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) # params = {'device_bays': 'false'} # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + + +class ConsolePortTestCase(TestCase): + queryset = ConsolePort.objects.all() + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + console_server_ports = ( + ConsoleServerPort(device=devices[3], name='Console Server Port 1'), + ConsoleServerPort(device=devices[3], name='Console Server Port 2'), + ) + ConsoleServerPort.objects.bulk_create(console_server_ports) + + console_ports = ( + ConsolePort(device=devices[0], name='Console Port 1', description='First'), + ConsolePort(device=devices[1], name='Console Port 2', description='Second'), + ConsolePort(device=devices[2], name='Console Port 3', description='Third'), + ) + ConsolePort.objects.bulk_create(console_ports) + + # Cables + Cable(termination_a=console_ports[0], termination_b=console_server_ports[0]).save() + Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Console Port 1', 'Console Port 2']} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + + # TODO: Fix boolean value + def test_connection_status(self): + params = {'connection_status': 'True'} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + params = {'cabled': 'false'} + self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 1) + + +class ConsoleServerPortTestCase(TestCase): + queryset = ConsoleServerPort.objects.all() + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + console_ports = ( + ConsolePort(device=devices[3], name='Console Server Port 1'), + ConsolePort(device=devices[3], name='Console Server Port 2'), + ) + ConsolePort.objects.bulk_create(console_ports) + + console_server_ports = ( + ConsoleServerPort(device=devices[0], name='Console Server Port 1', description='First'), + ConsoleServerPort(device=devices[1], name='Console Server Port 2', description='Second'), + ConsoleServerPort(device=devices[2], name='Console Server Port 3', description='Third'), + ) + ConsoleServerPort.objects.bulk_create(console_server_ports) + + # Cables + Cable(termination_a=console_server_ports[0], termination_b=console_ports[0]).save() + Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Console Server Port 1', 'Console Server Port 2']} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + + # TODO: Fix boolean value + def test_connection_status(self): + params = {'connection_status': 'True'} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + params = {'cabled': 'false'} + self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 1) From 94af70d2d10998b4cff8f63c9ae7f4f0db29358e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 11:12:44 -0500 Subject: [PATCH 50/98] Fixes #3862: Allow filtering device components by multiple device names --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/filters.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..62d732a9b 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -10,6 +10,7 @@ * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses +* [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names --- diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 638313507..ffed7fa3d 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -646,7 +646,8 @@ class DeviceComponentFilterSet(django_filters.FilterSet): queryset=Device.objects.all(), label='Device (ID)', ) - device = django_filters.ModelChoiceFilter( + device = django_filters.ModelMultipleChoiceFilter( + field_name='device__name', queryset=Device.objects.all(), to_field_name='name', label='Device (name)', From ff25a38459b72fb8330e77ff7d768189b0e4f8e1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 11:19:13 -0500 Subject: [PATCH 51/98] Fix InterfaceTestCase.test_mac_address --- netbox/virtualization/tests/test_filters.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filters.py index b0eb279ea..ed1422a61 100644 --- a/netbox/virtualization/tests/test_filters.py +++ b/netbox/virtualization/tests/test_filters.py @@ -359,7 +359,6 @@ class InterfaceTestCase(TestCase): params = {'virtual_machine': [vms[0].name, vms[1].name]} self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) - # TODO: Test for multiple values def test_mac_address(self): - params = {'mac_address': '00-00-00-00-00-01'} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 1) + params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} + self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) From 7621fc50be487c7ddb2291054b11e9352fb540d9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 11:20:37 -0500 Subject: [PATCH 52/98] Fix DeviceTestCase.test_cluster --- netbox/dcim/tests/test_filters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 04c3e78c4..efe86496b 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -1080,9 +1080,9 @@ class DeviceTestCase(TestCase): Cluster.objects.bulk_create(clusters) devices = ( - Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE), - Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=RACK_FACE_FRONT, status=DEVICE_STATUS_STAGED), - Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=RACK_FACE_REAR, status=DEVICE_STATUS_FAILED), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE, cluster=clusters[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=RACK_FACE_FRONT, status=DEVICE_STATUS_STAGED, cluster=clusters[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=RACK_FACE_REAR, status=DEVICE_STATUS_FAILED, cluster=clusters[2]), ) Device.objects.bulk_create(devices) @@ -1222,7 +1222,7 @@ class DeviceTestCase(TestCase): def test_cluster(self): clusters = Cluster.objects.all()[:2] - params = {'rack_id': [clusters[0].pk, clusters[1].pk]} + params = {'cluster_id': [clusters[0].pk, clusters[1].pk]} self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) def test_model(self): From b514d7c087af400f92d1e6d1398106a140958e53 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 17:23:09 +0000 Subject: [PATCH 53/98] Fixes #3623: Word expansion for interfaces --- docs/release-notes/version-2.6.md | 1 + netbox/utilities/forms.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..f5fc4beba 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations +* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation ## Bug Fixes diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index eeee719ae..4cd92ca12 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -60,8 +60,13 @@ def parse_alphanumeric_range(string): for n in list(range(int(begin), int(end) + 1)): values.append(n) else: - for n in list(range(ord(begin), ord(end) + 1)): - values.append(chr(n)) + # Value-based + if begin == end: + values.append(begin) + # Range-based + else: + for n in list(range(ord(begin), ord(end) + 1)): + values.append(chr(n)) return values From d751c853b8e315380616a47994c76a709904898c Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Wed, 8 Jan 2020 17:28:31 +0000 Subject: [PATCH 54/98] Added example and handled invalid ranges gracefully --- netbox/utilities/forms.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 4cd92ca12..39422c265 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -65,6 +65,9 @@ def parse_alphanumeric_range(string): values.append(begin) # Range-based else: + # Not a valid range (more than a single character) + if not len(begin) == len(end) == 1: + raise forms.ValidationError('Range "{}" is invalid.'.format(dash_range)) for n in list(range(ord(begin), ord(end) + 1)): values.append(chr(n)) return values @@ -486,6 +489,7 @@ class ExpandableNameField(forms.CharField): 'Mixed cases and types within a single range are not supported.
' \ 'Examples:
  • ge-0/0/[0-23,25,30]
  • ' \ '
  • e[0-3][a-d,f]
  • ' \ + '
  • [xe,ge]-0/0/0
  • ' \ '
  • e[0-3,a-d,f]
' def to_python(self, value): From d1e513d56d54405be058157e8675e7cc02071bc4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 14:01:31 -0500 Subject: [PATCH 55/98] Add remaining tests for device component filters --- netbox/dcim/tests/test_filters.py | 450 ++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index efe86496b..a18b8b946 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -1445,3 +1445,453 @@ class ConsoleServerPortTestCase(TestCase): self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) params = {'cabled': 'false'} self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 1) + + +class PowerPortTestCase(TestCase): + queryset = PowerPort.objects.all() + filter = PowerPortFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + power_outlets = ( + PowerOutlet(device=devices[3], name='Power Outlet 1'), + PowerOutlet(device=devices[3], name='Power Outlet 2'), + ) + PowerOutlet.objects.bulk_create(power_outlets) + + power_ports = ( + PowerPort(device=devices[0], name='Power Port 1', maximum_draw=100, allocated_draw=50, description='First'), + PowerPort(device=devices[1], name='Power Port 2', maximum_draw=200, allocated_draw=100, description='Second'), + PowerPort(device=devices[2], name='Power Port 3', maximum_draw=300, allocated_draw=150, description='Third'), + ) + PowerPort.objects.bulk_create(power_ports) + + # Cables + Cable(termination_a=power_ports[0], termination_b=power_outlets[0]).save() + Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Power Port 1', 'Power Port 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_maximum_draw(self): + params = {'maximum_draw': [100, 200]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_allocated_draw(self): + params = {'allocated_draw': [50, 100]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + # TODO: Fix boolean value + def test_connection_status(self): + params = {'connection_status': 'True'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'cabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + +class PowerOutletTestCase(TestCase): + queryset = PowerOutlet.objects.all() + filter = PowerOutletFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + power_ports = ( + PowerPort(device=devices[3], name='Power Outlet 1'), + PowerPort(device=devices[3], name='Power Outlet 2'), + ) + PowerPort.objects.bulk_create(power_ports) + + power_outlets = ( + PowerOutlet(device=devices[0], name='Power Outlet 1', feed_leg=POWERFEED_LEG_A, description='First'), + PowerOutlet(device=devices[1], name='Power Outlet 2', feed_leg=POWERFEED_LEG_B, description='Second'), + PowerOutlet(device=devices[2], name='Power Outlet 3', feed_leg=POWERFEED_LEG_C, description='Third'), + ) + PowerOutlet.objects.bulk_create(power_outlets) + + # Cables + Cable(termination_a=power_outlets[0], termination_b=power_ports[0]).save() + Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Power Outlet 1', 'Power Outlet 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_feed_leg(self): + # TODO: Support filtering for multiple values + params = {'feed_leg': POWERFEED_LEG_A} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + # TODO: Fix boolean value + def test_connection_status(self): + params = {'connection_status': 'True'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'cabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + +class InterfaceTestCase(TestCase): + queryset = Interface.objects.all() + filter = InterfaceFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + interfaces = ( + Interface(device=devices[0], name='Interface 1', type=IFACE_TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=IFACE_MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'), + Interface(device=devices[1], name='Interface 2', type=IFACE_TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=IFACE_MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'), + Interface(device=devices[2], name='Interface 3', type=IFACE_TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=IFACE_MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'), + Interface(device=devices[3], name='Interface 4', type=IFACE_TYPE_OTHER, enabled=True, mgmt_only=True), + Interface(device=devices[3], name='Interface 5', type=IFACE_TYPE_OTHER, enabled=True, mgmt_only=True), + Interface(device=devices[3], name='Interface 6', type=IFACE_TYPE_OTHER, enabled=False, mgmt_only=False), + ) + Interface.objects.bulk_create(interfaces) + + # Cables + Cable(termination_a=interfaces[0], termination_b=interfaces[3]).save() + Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save() + # Third pair is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:3] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + + def test_name(self): + params = {'name': ['Interface 1', 'Interface 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + # TODO: Fix boolean value + def test_connection_status(self): + params = {'connection_status': 'True'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_enabled(self): + params = {'enabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'enabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_mtu(self): + params = {'mtu': [100, 200]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_mgmt_only(self): + params = {'mgmt_only': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'mgmt_only': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_mode(self): + params = {'mode': IFACE_MODE_ACCESS} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'cabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_kind(self): + params = {'kind': 'physical'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 6) + params = {'kind': 'virtual'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 0) + + def test_mac_address(self): + params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_type(self): + params = {'type': [IFACE_TYPE_1GE_FIXED, IFACE_TYPE_1GE_GBIC]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class FrontPortTestCase(TestCase): + queryset = FrontPort.objects.all() + filter = FrontPortFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + rear_ports = ( + RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=6), + RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_8P8C, positions=6), + RearPort(device=devices[2], name='Rear Port 3', type=PORT_TYPE_8P8C, positions=6), + RearPort(device=devices[3], name='Rear Port 4', type=PORT_TYPE_8P8C, positions=6), + RearPort(device=devices[3], name='Rear Port 5', type=PORT_TYPE_8P8C, positions=6), + RearPort(device=devices[3], name='Rear Port 6', type=PORT_TYPE_8P8C, positions=6), + ) + RearPort.objects.bulk_create(rear_ports) + + front_ports = ( + FrontPort(device=devices[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0], rear_port_position=1, description='First'), + FrontPort(device=devices[1], name='Front Port 2', type=PORT_TYPE_110_PUNCH, rear_port=rear_ports[1], rear_port_position=2, description='Second'), + FrontPort(device=devices[2], name='Front Port 3', type=PORT_TYPE_BNC, rear_port=rear_ports[2], rear_port_position=3, description='Third'), + FrontPort(device=devices[3], name='Front Port 4', type=PORT_TYPE_FC, rear_port=rear_ports[3], rear_port_position=1), + FrontPort(device=devices[3], name='Front Port 5', type=PORT_TYPE_FC, rear_port=rear_ports[4], rear_port_position=1), + FrontPort(device=devices[3], name='Front Port 6', type=PORT_TYPE_FC, rear_port=rear_ports[5], rear_port_position=1), + ) + FrontPort.objects.bulk_create(front_ports) + + # Cables + Cable(termination_a=front_ports[0], termination_b=front_ports[3]).save() + Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Front Port 1', 'Front Port 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Test for multiple values + params = {'type': PORT_TYPE_8P8C} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'cabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class RearPortTestCase(TestCase): + queryset = RearPort.objects.all() + filter = RearPortFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + rear_ports = ( + RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=1, description='First'), + RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_110_PUNCH, positions=2, description='Second'), + RearPort(device=devices[2], name='Rear Port 3', type=PORT_TYPE_BNC, positions=3, description='Third'), + RearPort(device=devices[3], name='Rear Port 4', type=PORT_TYPE_FC, positions=4), + RearPort(device=devices[3], name='Rear Port 5', type=PORT_TYPE_FC, positions=5), + RearPort(device=devices[3], name='Rear Port 6', type=PORT_TYPE_FC, positions=6), + ) + RearPort.objects.bulk_create(rear_ports) + + # Cables + Cable(termination_a=rear_ports[0], termination_b=rear_ports[3]).save() + Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save() + # Third port is not connected + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Rear Port 1', 'Rear Port 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_type(self): + # TODO: Test for multiple values + params = {'type': PORT_TYPE_8P8C} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + def test_positions(self): + params = {'positions': [1, 2]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_cabled(self): + params = {'cabled': 'true'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'cabled': 'false'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class DeviceBayTestCase(TestCase): + queryset = DeviceBay.objects.all() + filter = DeviceBayFilter + + @classmethod + def setUpTestData(cls): + + site = Site.objects.create(name='Site 1', slug='site1') + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), + Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections + ) + Device.objects.bulk_create(devices) + + device_bays = ( + DeviceBay(device=devices[0], name='Device Bay 1', description='First'), + DeviceBay(device=devices[1], name='Device Bay 2', description='Second'), + DeviceBay(device=devices[2], name='Device Bay 3', description='Third'), + ) + DeviceBay.objects.bulk_create(device_bays) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Device Bay 1', 'Device Bay 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_description(self): + params = {'description': ['First', 'Second']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) From 23348fa7bf55d5e30d1cc00f64241a48bcf74199 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 14:24:43 -0500 Subject: [PATCH 56/98] Add tests for InventoryItem, VirtualChassis filters --- netbox/dcim/tests/test_filters.py | 190 +++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index a18b8b946..bfc345e73 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -1865,7 +1865,6 @@ class DeviceBayTestCase(TestCase): Device(name='Device 1', device_type=device_type, device_role=device_role, site=site), Device(name='Device 2', device_type=device_type, device_role=device_role, site=site), Device(name='Device 3', device_type=device_type, device_role=device_role, site=site), - Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections ) Device.objects.bulk_create(devices) @@ -1895,3 +1894,192 @@ class DeviceBayTestCase(TestCase): self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class InventoryItemTestCase(TestCase): + queryset = InventoryItem.objects.all() + filter = InventoryItemFilter + + @classmethod + def setUpTestData(cls): + + manufacturers = ( + Manufacturer(name='Manufacturer 1', slug='manufacturer-1'), + Manufacturer(name='Manufacturer 2', slug='manufacturer-2'), + Manufacturer(name='Manufacturer 3', slug='manufacturer-3'), + ) + Manufacturer.objects.bulk_create(manufacturers) + + device_type = DeviceType.objects.create(manufacturer=manufacturers[0], model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0]), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1]), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2]), + ) + Device.objects.bulk_create(devices) + + inventory_items = ( + InventoryItem(device=devices[0], manufacturer=manufacturers[0], name='Inventory Item 1', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First'), + InventoryItem(device=devices[1], manufacturer=manufacturers[1], name='Inventory Item 2', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second'), + InventoryItem(device=devices[2], manufacturer=manufacturers[2], name='Inventory Item 3', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third'), + ) + InventoryItem.objects.bulk_create(inventory_items) + + child_inventory_items = ( + InventoryItem(device=devices[0], name='Inventory Item 1A', parent=inventory_items[0]), + InventoryItem(device=devices[1], name='Inventory Item 2A', parent=inventory_items[1]), + InventoryItem(device=devices[2], name='Inventory Item 3A', parent=inventory_items[2]), + ) + InventoryItem.objects.bulk_create(child_inventory_items) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_name(self): + params = {'name': ['Inventory Item 1', 'Inventory Item 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_part_id(self): + params = {'part_id': ['1001', '1002']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_asset_tag(self): + params = {'asset_tag': ['1001', '1002']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_discovered(self): + # TODO: Fix boolean value + params = {'discovered': True} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'discovered': False} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_device(self): + # TODO: Allow multiple values + device = Device.objects.first() + params = {'device_id': device.pk} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'device': device.name} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_parent_id(self): + parent_items = InventoryItem.objects.filter(parent__isnull=True)[:2] + params = {'parent_id': [parent_items[0].pk, parent_items[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_manufacturer(self): + manufacturers = Manufacturer.objects.all()[:2] + params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_serial(self): + params = {'serial': 'ABC'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + params = {'serial': 'abc'} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + +class VirtualChassisTestCase(TestCase): + queryset = VirtualChassis.objects.all() + filter = VirtualChassisFilter + + @classmethod + def setUpTestData(cls): + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], vc_position=1), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], vc_position=2), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], vc_position=1), + Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], vc_position=2), + Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], vc_position=1), + Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], vc_position=2), + ) + Device.objects.bulk_create(devices) + + virtual_chassis = ( + VirtualChassis(master=devices[0], domain='Domain 1'), + VirtualChassis(master=devices[2], domain='Domain 2'), + VirtualChassis(master=devices[4], domain='Domain 3'), + ) + VirtualChassis.objects.bulk_create(virtual_chassis) + + Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis[0]) + Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1]) + Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[2]) + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_domain(self): + params = {'domain': ['Domain 1', 'Domain 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) From 03cc79750ccaba6710d7d4de3df4a4730e5a5f38 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 15:30:56 -0500 Subject: [PATCH 57/98] Add CableFilter test --- netbox/dcim/tests/test_filters.py | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index bfc345e73..5e467b95e 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -2083,3 +2083,113 @@ class VirtualChassisTestCase(TestCase): self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class CableTestCase(TestCase): + queryset = Cable.objects.all() + filter = CableFilter + + @classmethod + def setUpTestData(cls): + + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + Site(name='Site 3', slug='site-3'), + ) + Site.objects.bulk_create(sites) + + racks = ( + Rack(name='Rack 1', site=sites[0]), + Rack(name='Rack 2', site=sites[1]), + Rack(name='Rack 3', site=sites[2]), + ) + Rack.objects.bulk_create(racks) + + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') + device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') + device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + devices = ( + Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1), + Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2), + Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1), + Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2), + ) + Device.objects.bulk_create(devices) + + interfaces = ( + Interface(device=devices[0], name='Interface 1', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[0], name='Interface 2', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[1], name='Interface 3', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[1], name='Interface 4', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[2], name='Interface 5', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[2], name='Interface 6', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 7', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 8', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 9', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[4], name='Interface 10', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 11', type=IFACE_TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 12', type=IFACE_TYPE_1GE_FIXED), + ) + Interface.objects.bulk_create(interfaces) + + # Cables + Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CABLE_TYPE_CAT3, status=CONNECTION_STATUS_CONNECTED, color='aa1409', length=10, length_unit=LENGTH_UNIT_FOOT).save() + Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CABLE_TYPE_CAT3, status=CONNECTION_STATUS_CONNECTED, color='aa1409', length=20, length_unit=LENGTH_UNIT_FOOT).save() + Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CABLE_TYPE_CAT5E, status=CONNECTION_STATUS_CONNECTED, color='f44336', length=30, length_unit=LENGTH_UNIT_FOOT).save() + Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CABLE_TYPE_CAT5E, status=CONNECTION_STATUS_PLANNED, color='f44336', length=40, length_unit=LENGTH_UNIT_FOOT).save() + Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CABLE_TYPE_CAT6, status=CONNECTION_STATUS_PLANNED, color='e91e63', length=10, length_unit=LENGTH_UNIT_METER).save() + Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CABLE_TYPE_CAT6, status=CONNECTION_STATUS_PLANNED, color='e91e63', length=20, length_unit=LENGTH_UNIT_METER).save() + + def test_id(self): + id_list = self.queryset.values_list('id', flat=True)[:2] + params = {'id': [str(id) for id in id_list]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_label(self): + params = {'label': ['Cable 1', 'Cable 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_length(self): + params = {'length': [10, 20]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_length_unit(self): + params = {'length_unit': LENGTH_UNIT_FOOT} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_type(self): + params = {'type': [CABLE_TYPE_CAT3, CABLE_TYPE_CAT5E]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_status(self): + params = {'status': [CONNECTION_STATUS_CONNECTED]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + + def test_color(self): + params = {'color': ['aa1409', 'f44336']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + + def test_device(self): + devices = Device.objects.all()[:2] + params = {'device_id': [devices[0].pk, devices[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + params = {'device': [devices[0].name, devices[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + + def test_rack(self): + racks = Rack.objects.all()[:2] + params = {'rack_id': [racks[0].pk, racks[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + params = {'rack': [racks[0].name, racks[1].name]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + + def test_site(self): + site = Site.objects.all()[:2] + params = {'site_id': [site[0].pk, site[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + params = {'site': [site[0].slug, site[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) From 575c65e09101c5bc4e6b00d410586433b0c22851 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 15:56:42 -0500 Subject: [PATCH 58/98] Add tests for PowerPanel and PowerFeed filters --- netbox/dcim/tests/test_filters.py | 168 +++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 5e467b95e..999493aa3 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -5,8 +5,8 @@ from dcim.constants import * from dcim.filters import * from dcim.models import ( Cable, ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPortTemplate, - InterfaceTemplate, Manufacturer, Platform, PowerPortTemplate, PowerOutletTemplate, Rack, RackGroup, RackReservation, - RackRole, RearPortTemplate, Region, Site, VirtualChassis, + InterfaceTemplate, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPortTemplate, PowerOutletTemplate, Rack, + RackGroup, RackReservation, RackRole, RearPortTemplate, Region, Site, VirtualChassis, ) from ipam.models import IPAddress from virtualization.models import Cluster, ClusterType @@ -2193,3 +2193,167 @@ class CableTestCase(TestCase): self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) params = {'site': [site[0].slug, site[1].slug]} self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + + +class PowerPanelTestCase(TestCase): + queryset = PowerPanel.objects.all() + filter = PowerPanelFilter + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + rack_groups = ( + RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), + RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), + RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), + ) + RackGroup.objects.bulk_create(rack_groups) + + power_panels = ( + PowerPanel(name='Power Panel 1', site=sites[0], rack_group=rack_groups[0]), + PowerPanel(name='Power Panel 2', site=sites[1], rack_group=rack_groups[1]), + PowerPanel(name='Power Panel 3', site=sites[2], rack_group=rack_groups[2]), + ) + PowerPanel.objects.bulk_create(power_panels) + + def test_name(self): + params = {'name': ['Power Panel 1', 'Power Panel 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_rack_group(self): + rack_groups = RackGroup.objects.all()[:2] + params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +class PowerFeedTestCase(TestCase): + queryset = PowerFeed.objects.all() + filter = PowerFeedFilter + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Region 1', slug='region-1'), + Region(name='Region 2', slug='region-2'), + Region(name='Region 3', slug='region-3'), + ) + for region in regions: + region.save() + + sites = ( + Site(name='Site 1', slug='site-1', region=regions[0]), + Site(name='Site 2', slug='site-2', region=regions[1]), + Site(name='Site 3', slug='site-3', region=regions[2]), + ) + Site.objects.bulk_create(sites) + + racks = ( + Rack(name='Rack 1', site=sites[0]), + Rack(name='Rack 2', site=sites[1]), + Rack(name='Rack 3', site=sites[2]), + ) + Rack.objects.bulk_create(racks) + + power_panels = ( + PowerPanel(name='Power Panel 1', site=sites[0]), + PowerPanel(name='Power Panel 2', site=sites[1]), + PowerPanel(name='Power Panel 3', site=sites[2]), + ) + PowerPanel.objects.bulk_create(power_panels) + + power_feeds = ( + PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1', status=POWERFEED_STATUS_ACTIVE, type=POWERFEED_TYPE_PRIMARY, supply=POWERFEED_SUPPLY_AC, phase=POWERFEED_PHASE_3PHASE, voltage=100, amperage=100, max_utilization=10), + PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2', status=POWERFEED_STATUS_FAILED, type=POWERFEED_TYPE_PRIMARY, supply=POWERFEED_SUPPLY_AC, phase=POWERFEED_PHASE_3PHASE, voltage=200, amperage=200, max_utilization=20), + PowerFeed(power_panel=power_panels[2], rack=racks[2], name='Power Feed 3', status=POWERFEED_STATUS_OFFLINE, type=POWERFEED_TYPE_REDUNDANT, supply=POWERFEED_SUPPLY_DC, phase=POWERFEED_PHASE_SINGLE, voltage=300, amperage=300, max_utilization=30), + ) + PowerFeed.objects.bulk_create(power_feeds) + + def test_name(self): + params = {'name': ['Power Feed 1', 'Power Feed 2']} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_status(self): + # TODO: Test for multiple values + params = {'status': POWERFEED_STATUS_ACTIVE} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + + def test_type(self): + params = {'type': POWERFEED_TYPE_PRIMARY} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_supply(self): + params = {'supply': POWERFEED_SUPPLY_AC} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_phase(self): + params = {'phase': POWERFEED_PHASE_3PHASE} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_voltage(self): + params = {'voltage': [100, 200]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_amperage(self): + params = {'amperage': [100, 200]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_max_utilization(self): + params = {'max_utilization': [10, 20]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_power_panel_id(self): + power_panels = PowerPanel.objects.all()[:2] + params = {'power_panel_id': [power_panels[0].pk, power_panels[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + def test_rack_id(self): + racks = Rack.objects.all()[:2] + params = {'rack_id': [racks[0].pk, racks[1].pk]} + self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + + +# TODO: Connection filters From d27fea289e0467cb58b7fb638d3a458dd913c4bc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 17:06:39 -0500 Subject: [PATCH 59/98] Standardize usage of self.filterset for test cases --- netbox/circuits/tests/test_filters.py | 68 +- netbox/dcim/tests/test_filters.py | 656 ++++++++++---------- netbox/ipam/tests/test_filters.py | 188 +++--- netbox/secrets/tests/test_filters.py | 22 +- netbox/tenancy/tests/test_filters.py | 20 +- netbox/virtualization/tests/test_filters.py | 101 +-- 6 files changed, 546 insertions(+), 509 deletions(-) diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filters.py index f932766d9..3077b9c8f 100644 --- a/netbox/circuits/tests/test_filters.py +++ b/netbox/circuits/tests/test_filters.py @@ -8,6 +8,7 @@ from dcim.models import Region, Site class ProviderTestCase(TestCase): queryset = Provider.objects.all() + filterset = ProviderFilter @classmethod def setUpTestData(cls): @@ -54,42 +55,43 @@ class ProviderTestCase(TestCase): def test_name(self): params = {'name': ['Provider 1', 'Provider 2']} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['provider-1', 'provider-2']} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_asn(self): params = {'asn': ['65001', '65002']} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_account(self): params = {'account': ['1234', '2345']} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(ProviderFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class CircuitTypeTestCase(TestCase): queryset = CircuitType.objects.all() + filterset = CircuitTypeFilter @classmethod def setUpTestData(cls): @@ -102,19 +104,20 @@ class CircuitTypeTestCase(TestCase): def test_id(self): params = {'id': [self.queryset.first().pk]} - self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_name(self): params = {'name': ['Circuit Type 1']} - self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_slug(self): params = {'slug': ['circuit-type-1']} - self.assertEqual(CircuitTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class CircuitTestCase(TestCase): queryset = Circuit.objects.all() + filterset = CircuitFilter @classmethod def setUpTestData(cls): @@ -166,56 +169,57 @@ class CircuitTestCase(TestCase): def test_cid(self): params = {'cid': ['Test Circuit 1', 'Test Circuit 2']} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_install_date(self): params = {'install_date': ['2020-01-01', '2020-01-02']} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_commit_rate(self): params = {'commit_rate': ['1000', '2000']} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_provider(self): provider = Provider.objects.first() params = {'provider_id': [provider.pk]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) params = {'provider': [provider.slug]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_type(self): circuit_type = CircuitType.objects.first() params = {'type_id': [circuit_type.pk]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) params = {'type': [circuit_type.slug]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_status(self): params = {'status': [CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_PLANNED]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(CircuitFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class CircuitTerminationTestCase(TestCase): queryset = CircuitTermination.objects.all() + filterset = CircuitTerminationFilter @classmethod def setUpTestData(cls): @@ -256,28 +260,28 @@ class CircuitTerminationTestCase(TestCase): def test_term_side(self): params = {'term_side': 'A'} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_port_speed(self): params = {'port_speed': ['1000', '2000']} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_upstream_speed(self): params = {'upstream_speed': ['1000', '2000']} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_xconnect_id(self): params = {'xconnect_id': ['ABC', 'DEF']} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_circuit_id(self): circuits = Circuit.objects.all()[:2] params = {'circuit_id': [circuits[0].pk, circuits[1].pk]} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(CircuitTerminationFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 999493aa3..47bcbe0d4 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -60,6 +60,7 @@ class RegionTestCase(TestCase): class SiteTestCase(TestCase): queryset = Site.objects.all() + filterset = SiteFilter @classmethod def setUpTestData(cls): @@ -82,63 +83,64 @@ class SiteTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Site 1', 'Site 2']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['site-1', 'site-2']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_facility(self): params = {'facility': ['Facility 1', 'Facility 2']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_asn(self): params = {'asn': [65001, 65002]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_latitude(self): params = {'latitude': [10, 20]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_longitude(self): params = {'longitude': [10, 20]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_contact_name(self): params = {'contact_name': ['Contact 1', 'Contact 2']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_contact_phone(self): params = {'contact_phone': ['123-555-0001', '123-555-0002']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_contact_email(self): params = {'contact_email': ['contact1@example.com', 'contact2@example.com']} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): params = {'status': [SITE_STATUS_ACTIVE, SITE_STATUS_PLANNED]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(SiteFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class RackGroupTestCase(TestCase): queryset = RackGroup.objects.all() + filterset = RackGroupFilter @classmethod def setUpTestData(cls): @@ -168,33 +170,34 @@ class RackGroupTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Rack Group 1', 'Rack Group 2']} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['rack-group-1', 'rack-group-2']} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(RackGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class RackRoleTestCase(TestCase): queryset = RackRole.objects.all() + filterset = RackRoleFilter @classmethod def setUpTestData(cls): @@ -209,23 +212,24 @@ class RackRoleTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Rack Role 1', 'Rack Role 2']} - self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['rack-role-1', 'rack-role-2']} - self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_color(self): params = {'color': ['ff0000', '00ff00']} - self.assertEqual(RackRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class RackTestCase(TestCase): queryset = Rack.objects.all() + filterset = RackFilter @classmethod def setUpTestData(cls): @@ -269,99 +273,100 @@ class RackTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Rack 1', 'Rack 2']} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_facility_id(self): params = {'facility_id': ['rack-1', 'rack-2']} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_asset_tag(self): params = {'asset_tag': ['1001', '1002']} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Test for multiple values params = {'type': RACK_TYPE_2POST} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_width(self): # TODO: Test for multiple values params = {'width': RACK_WIDTH_19IN} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_u_height(self): params = {'u_height': [42, 43]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_desc_units(self): params = {'desc_units': 'true'} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'desc_units': 'false'} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_outer_width(self): params = {'outer_width': [100, 200]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_outer_depth(self): params = {'outer_depth': [100, 200]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_outer_unit(self): self.assertEqual(Rack.objects.filter(outer_unit__isnull=False).count(), 3) params = {'outer_unit': LENGTH_UNIT_MILLIMETER} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_group(self): groups = RackGroup.objects.all()[:2] params = {'group_id': [groups[0].pk, groups[1].pk]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'group': [groups[0].slug, groups[1].slug]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): params = {'status': [RACK_STATUS_ACTIVE, RACK_STATUS_PLANNED]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_role(self): roles = RackRole.objects.all()[:2] params = {'role_id': [roles[0].pk, roles[1].pk]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'role': [roles[0].slug, roles[1].slug]} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_serial(self): params = {'serial': 'ABC'} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'serial': 'abc'} - self.assertEqual(RackFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class RackReservationTestCase(TestCase): queryset = RackReservation.objects.all() + filterset = RackReservationFilter @classmethod def setUpTestData(cls): @@ -404,33 +409,34 @@ class RackReservationTestCase(TestCase): def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_group(self): groups = RackGroup.objects.all()[:2] params = {'group_id': [groups[0].pk, groups[1].pk]} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'group': [groups[0].slug, groups[1].slug]} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_user(self): users = User.objects.all()[:2] params = {'user_id': [users[0].pk, users[1].pk]} - self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Filtering by username is broken # params = {'user': [users[0].username, users[1].username]} - # self.assertEqual(RackReservationFilter(params, self.queryset).qs.count(), 2) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class ManufacturerTestCase(TestCase): queryset = Manufacturer.objects.all() + filterset = ManufacturerFilter @classmethod def setUpTestData(cls): @@ -445,19 +451,20 @@ class ManufacturerTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Manufacturer 1', 'Manufacturer 2']} - self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['manufacturer-1', 'manufacturer-2']} - self.assertEqual(ManufacturerFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class DeviceTypeTestCase(TestCase): queryset = DeviceType.objects.all() + filterset = DeviceTypeFilter @classmethod def setUpTestData(cls): @@ -513,88 +520,89 @@ class DeviceTypeTestCase(TestCase): def test_model(self): params = {'model': ['Model 1', 'Model 2']} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['model-1', 'model-2']} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_part_number(self): params = {'part_number': ['Part Number 1', 'Part Number 2']} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_u_height(self): params = {'u_height': [1, 2]} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_is_full_depth(self): params = {'is_full_depth': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'is_full_depth': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_subdevice_role(self): params = {'subdevice_role': SUBDEVICE_ROLE_PARENT} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_manufacturer(self): manufacturers = Manufacturer.objects.all()[:2] params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_console_ports(self): params = {'console_ports': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'console_ports': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_console_server_ports(self): params = {'console_server_ports': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'console_server_ports': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_power_ports(self): params = {'power_ports': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'power_ports': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_power_outlets(self): params = {'power_outlets': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'power_outlets': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_interfaces(self): params = {'interfaces': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'interfaces': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_pass_through_ports(self): params = {'pass_through_ports': 'true'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'pass_through_ports': 'false'} - self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) # TODO: Add device_bay filter # def test_device_bays(self): # params = {'device_bays': 'true'} - # self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 2) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # params = {'device_bays': 'false'} - # self.assertEqual(DeviceTypeFilter(params, self.queryset).qs.count(), 1) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class ConsolePortTemplateTestCase(TestCase): queryset = ConsolePortTemplate.objects.all() + filterset = ConsolePortTemplateFilter @classmethod def setUpTestData(cls): @@ -617,20 +625,21 @@ class ConsolePortTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Console Port 1', 'Console Port 2']} - self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(ConsolePortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class ConsoleServerPortTemplateTestCase(TestCase): queryset = ConsoleServerPortTemplate.objects.all() + filterset = ConsoleServerPortTemplateFilter @classmethod def setUpTestData(cls): @@ -653,20 +662,21 @@ class ConsoleServerPortTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Console Server Port 1', 'Console Server Port 2']} - self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(ConsoleServerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class PowerPortTemplateTestCase(TestCase): queryset = PowerPortTemplate.objects.all() + filterset = PowerPortTemplateFilter @classmethod def setUpTestData(cls): @@ -689,28 +699,29 @@ class PowerPortTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Power Port 1', 'Power Port 2']} - self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_maximum_draw(self): params = {'maximum_draw': [100, 200]} - self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_allocated_draw(self): params = {'allocated_draw': [50, 100]} - self.assertEqual(PowerPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class PowerOutletTemplateTestCase(TestCase): queryset = PowerOutletTemplate.objects.all() + filterset = PowerOutletTemplateFilter @classmethod def setUpTestData(cls): @@ -733,25 +744,26 @@ class PowerOutletTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Power Outlet 1', 'Power Outlet 2']} - self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_feed_leg(self): # TODO: Support filtering for multiple values params = {'feed_leg': POWERFEED_LEG_A} - self.assertEqual(PowerOutletTemplateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class InterfaceTemplateTestCase(TestCase): queryset = InterfaceTemplate.objects.all() + filterset = InterfaceTemplateFilter @classmethod def setUpTestData(cls): @@ -774,31 +786,32 @@ class InterfaceTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Interface 1', 'Interface 2']} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Support filtering for multiple values params = {'type': IFACE_TYPE_1GE_FIXED} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_mgmt_only(self): params = {'mgmt_only': 'true'} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'mgmt_only': 'false'} - self.assertEqual(InterfaceTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class FrontPortTemplateTestCase(TestCase): queryset = FrontPortTemplate.objects.all() + filterset = FrontPortTemplateFilter @classmethod def setUpTestData(cls): @@ -828,25 +841,26 @@ class FrontPortTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Front Port 1', 'Front Port 2']} - self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Support filtering for multiple values params = {'type': PORT_TYPE_8P8C} - self.assertEqual(FrontPortTemplateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class RearPortTemplateTestCase(TestCase): queryset = RearPortTemplate.objects.all() + filterset = RearPortTemplateFilter @classmethod def setUpTestData(cls): @@ -869,29 +883,30 @@ class RearPortTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Rear Port 1', 'Rear Port 2']} - self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Support filtering for multiple values params = {'type': PORT_TYPE_8P8C} - self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_positions(self): params = {'positions': [1, 2]} - self.assertEqual(RearPortTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class DeviceBayTemplateTestCase(TestCase): queryset = DeviceBayTemplate.objects.all() + filterset = DeviceBayTemplateFilter @classmethod def setUpTestData(cls): @@ -914,20 +929,21 @@ class DeviceBayTemplateTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Device Bay 1', 'Device Bay 2']} - self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype_id(self): device_types = DeviceType.objects.all()[:2] params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(DeviceBayTemplateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class DeviceRoleTestCase(TestCase): queryset = DeviceRole.objects.all() + filterset = DeviceRoleFilter @classmethod def setUpTestData(cls): @@ -942,29 +958,30 @@ class DeviceRoleTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Device Role 1', 'Device Role 2']} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['device-role-1', 'device-role-2']} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_color(self): params = {'color': ['ff0000', '00ff00']} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_vm_role(self): params = {'vm_role': 'true'} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'vm_role': 'false'} - self.assertEqual(DeviceRoleFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class PlatformTestCase(TestCase): queryset = Platform.objects.all() + filterset = PlatformFilter @classmethod def setUpTestData(cls): @@ -986,30 +1003,31 @@ class PlatformTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Platform 1', 'Platform 2']} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['platform-1', 'platform-2']} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_napalm_driver(self): params = {'napalm_driver': ['driver-1', 'driver-2']} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_manufacturer(self): manufacturers = Manufacturer.objects.all()[:2] params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} - self.assertEqual(PlatformFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class DeviceTestCase(TestCase): queryset = Device.objects.all() + filterset = DeviceFilter @classmethod def setUpTestData(cls): @@ -1139,178 +1157,179 @@ class DeviceTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Device 1', 'Device 2']} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_asset_tag(self): params = {'asset_tag': ['1001', '1002']} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_face(self): params = {'face': RACK_FACE_FRONT} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_position(self): params = {'position': [1, 2]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_vc_position(self): params = {'vc_position': [1, 2]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_vc_priority(self): params = {'vc_priority': [1, 2]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_manufacturer(self): manufacturers = Manufacturer.objects.all()[:2] params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicetype(self): device_types = DeviceType.objects.all()[:2] params = {'device_type_id': [device_types[0].pk, device_types[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_devicerole(self): device_roles = DeviceRole.objects.all()[:2] params = {'role_id': [device_roles[0].pk, device_roles[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'role': [device_roles[0].slug, device_roles[1].slug]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_platform(self): platforms = Platform.objects.all()[:2] params = {'platform_id': [platforms[0].pk, platforms[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'platform': [platforms[0].slug, platforms[1].slug]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rackgroup(self): rack_groups = RackGroup.objects.all()[:2] params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rack(self): racks = Rack.objects.all()[:2] params = {'rack_id': [racks[0].pk, racks[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cluster(self): clusters = Cluster.objects.all()[:2] params = {'cluster_id': [clusters[0].pk, clusters[1].pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_model(self): params = {'model': ['model-1', 'model-2']} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): params = {'status': [DEVICE_STATUS_ACTIVE, DEVICE_STATUS_STAGED]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_is_full_depth(self): params = {'is_full_depth': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'is_full_depth': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_mac_address(self): params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_serial(self): params = {'serial': 'ABC'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'serial': 'abc'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_has_primary_ip(self): params = {'has_primary_ip': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'has_primary_ip': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_virtual_chassis_id(self): params = {'virtual_chassis_id': [VirtualChassis.objects.first().pk]} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_virtual_chassis_member(self): params = {'virtual_chassis_member': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'virtual_chassis_member': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_console_ports(self): params = {'console_ports': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'console_ports': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_console_server_ports(self): params = {'console_server_ports': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'console_server_ports': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_power_ports(self): params = {'power_ports': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'power_ports': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_power_outlets(self): params = {'power_outlets': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'power_outlets': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_interfaces(self): params = {'interfaces': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'interfaces': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_pass_through_ports(self): params = {'pass_through_ports': 'true'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'pass_through_ports': 'false'} - self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) # TODO: Add device_bay filter # def test_device_bays(self): # params = {'device_bays': 'true'} - # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 2) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # params = {'device_bays': 'false'} - # self.assertEqual(DeviceFilter(params, self.queryset).qs.count(), 1) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class ConsolePortTestCase(TestCase): queryset = ConsolePort.objects.all() + filterset = ConsolePortFilter @classmethod def setUpTestData(cls): @@ -1349,37 +1368,38 @@ class ConsolePortTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Console Port 1', 'Console Port 2']} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Fix boolean value def test_connection_status(self): params = {'connection_status': 'True'} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cabled': 'false'} - self.assertEqual(ConsolePortFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class ConsoleServerPortTestCase(TestCase): queryset = ConsoleServerPort.objects.all() + filterset = ConsoleServerPortFilter @classmethod def setUpTestData(cls): @@ -1418,38 +1438,38 @@ class ConsoleServerPortTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Console Server Port 1', 'Console Server Port 2']} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Fix boolean value def test_connection_status(self): params = {'connection_status': 'True'} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cabled': 'false'} - self.assertEqual(ConsoleServerPortFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class PowerPortTestCase(TestCase): queryset = PowerPort.objects.all() - filter = PowerPortFilter + filterset = PowerPortFilter @classmethod def setUpTestData(cls): @@ -1488,46 +1508,46 @@ class PowerPortTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Power Port 1', 'Power Port 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_maximum_draw(self): params = {'maximum_draw': [100, 200]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_allocated_draw(self): params = {'allocated_draw': [50, 100]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Fix boolean value def test_connection_status(self): params = {'connection_status': 'True'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class PowerOutletTestCase(TestCase): queryset = PowerOutlet.objects.all() - filter = PowerOutletFilter + filterset = PowerOutletFilter @classmethod def setUpTestData(cls): @@ -1566,43 +1586,43 @@ class PowerOutletTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Power Outlet 1', 'Power Outlet 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_feed_leg(self): # TODO: Support filtering for multiple values params = {'feed_leg': POWERFEED_LEG_A} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) # TODO: Fix boolean value def test_connection_status(self): params = {'connection_status': 'True'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class InterfaceTestCase(TestCase): queryset = Interface.objects.all() - filter = InterfaceFilter + filterset = InterfaceFilter @classmethod def setUpTestData(cls): @@ -1638,72 +1658,72 @@ class InterfaceTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_name(self): params = {'name': ['Interface 1', 'Interface 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Fix boolean value def test_connection_status(self): params = {'connection_status': 'True'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_enabled(self): params = {'enabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'enabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mtu(self): params = {'mtu': [100, 200]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mgmt_only(self): params = {'mgmt_only': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'mgmt_only': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mode(self): params = {'mode': IFACE_MODE_ACCESS} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'cabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_kind(self): params = {'kind': 'physical'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 6) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'kind': 'virtual'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 0) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) def test_mac_address(self): params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): params = {'type': [IFACE_TYPE_1GE_FIXED, IFACE_TYPE_1GE_GBIC]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class FrontPortTestCase(TestCase): queryset = FrontPort.objects.all() - filter = FrontPortFilter + filterset = FrontPortFilter @classmethod def setUpTestData(cls): @@ -1749,38 +1769,38 @@ class FrontPortTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Front Port 1', 'Front Port 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Test for multiple values params = {'type': PORT_TYPE_8P8C} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'cabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class RearPortTestCase(TestCase): queryset = RearPort.objects.all() - filter = RearPortFilter + filterset = RearPortFilter @classmethod def setUpTestData(cls): @@ -1816,42 +1836,42 @@ class RearPortTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Rear Port 1', 'Rear Port 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): # TODO: Test for multiple values params = {'type': PORT_TYPE_8P8C} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_positions(self): params = {'positions': [1, 2]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cabled(self): params = {'cabled': 'true'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'cabled': 'false'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class DeviceBayTestCase(TestCase): queryset = DeviceBay.objects.all() - filter = DeviceBayFilter + filterset = DeviceBayFilter @classmethod def setUpTestData(cls): @@ -1878,27 +1898,27 @@ class DeviceBayTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Device Bay 1', 'Device Bay 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_description(self): params = {'description': ['First', 'Second']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class InventoryItemTestCase(TestCase): queryset = InventoryItem.objects.all() - filter = InventoryItemFilter + filterset = InventoryItemFilter @classmethod def setUpTestData(cls): @@ -1952,71 +1972,71 @@ class InventoryItemTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Inventory Item 1', 'Inventory Item 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_part_id(self): params = {'part_id': ['1001', '1002']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_asset_tag(self): params = {'asset_tag': ['1001', '1002']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_discovered(self): # TODO: Fix boolean value params = {'discovered': True} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'discovered': False} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_device(self): # TODO: Allow multiple values device = Device.objects.first() params = {'device_id': device.pk} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': device.name} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_parent_id(self): parent_items = InventoryItem.objects.filter(parent__isnull=True)[:2] params = {'parent_id': [parent_items[0].pk, parent_items[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_manufacturer(self): manufacturers = Manufacturer.objects.all()[:2] params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_serial(self): params = {'serial': 'ABC'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'serial': 'abc'} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) class VirtualChassisTestCase(TestCase): queryset = VirtualChassis.objects.all() - filter = VirtualChassisFilter + filterset = VirtualChassisFilter @classmethod def setUpTestData(cls): @@ -2064,30 +2084,30 @@ class VirtualChassisTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_domain(self): params = {'domain': ['Domain 1', 'Domain 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class CableTestCase(TestCase): queryset = Cable.objects.all() - filter = CableFilter + filterset = CableFilter @classmethod def setUpTestData(cls): @@ -2147,57 +2167,57 @@ class CableTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_length(self): params = {'length': [10, 20]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_length_unit(self): params = {'length_unit': LENGTH_UNIT_FOOT} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_type(self): params = {'type': [CABLE_TYPE_CAT3, CABLE_TYPE_CAT5E]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_status(self): params = {'status': [CONNECTION_STATUS_CONNECTED]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_color(self): params = {'color': ['aa1409', 'f44336']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_rack(self): racks = Rack.objects.all()[:2] params = {'rack_id': [racks[0].pk, racks[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) params = {'rack': [racks[0].name, racks[1].name]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) def test_site(self): site = Site.objects.all()[:2] params = {'site_id': [site[0].pk, site[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) params = {'site': [site[0].slug, site[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) class PowerPanelTestCase(TestCase): queryset = PowerPanel.objects.all() - filter = PowerPanelFilter + filterset = PowerPanelFilter @classmethod def setUpTestData(cls): @@ -2233,31 +2253,31 @@ class PowerPanelTestCase(TestCase): def test_name(self): params = {'name': ['Power Panel 1', 'Power Panel 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rack_group(self): rack_groups = RackGroup.objects.all()[:2] params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class PowerFeedTestCase(TestCase): queryset = PowerFeed.objects.all() - filter = PowerFeedFilter + filterset = PowerFeedFilter @classmethod def setUpTestData(cls): @@ -2300,60 +2320,60 @@ class PowerFeedTestCase(TestCase): def test_name(self): params = {'name': ['Power Feed 1', 'Power Feed 2']} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): # TODO: Test for multiple values params = {'status': POWERFEED_STATUS_ACTIVE} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_type(self): params = {'type': POWERFEED_TYPE_PRIMARY} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_supply(self): params = {'supply': POWERFEED_SUPPLY_AC} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_phase(self): params = {'phase': POWERFEED_PHASE_3PHASE} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_voltage(self): params = {'voltage': [100, 200]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_amperage(self): params = {'amperage': [100, 200]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_max_utilization(self): params = {'max_utilization': [10, 20]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_power_panel_id(self): power_panels = PowerPanel.objects.all()[:2] params = {'power_panel_id': [power_panels[0].pk, power_panels[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rack_id(self): racks = Rack.objects.all()[:2] params = {'rack_id': [racks[0].pk, racks[1].pk]} - self.assertEqual(self.filter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Connection filters diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py index b44b2a70e..5ae912bc6 100644 --- a/netbox/ipam/tests/test_filters.py +++ b/netbox/ipam/tests/test_filters.py @@ -2,16 +2,14 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site from ipam.constants import * -from ipam.filters import ( - AggregateFilter, IPAddressFilter, PrefixFilter, RIRFilter, RoleFilter, ServiceFilter, VLANFilter, VLANGroupFilter, - VRFFilter, -) +from ipam.filters import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from virtualization.models import Cluster, ClusterType, VirtualMachine class VRFTestCase(TestCase): queryset = VRF.objects.all() + filterset = VRFFilter @classmethod def setUpTestData(cls): @@ -28,26 +26,27 @@ class VRFTestCase(TestCase): def test_name(self): params = {'name': ['VRF 1', 'VRF 2']} - self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rd(self): params = {'rd': ['65000:100', '65000:200']} - self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_enforce_unique(self): params = {'enforce_unique': 'true'} - self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) params = {'enforce_unique': 'false'} - self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(VRFFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) class RIRTestCase(TestCase): queryset = RIR.objects.all() + filterset = RIRFilter @classmethod def setUpTestData(cls): @@ -64,26 +63,27 @@ class RIRTestCase(TestCase): def test_name(self): params = {'name': ['RIR 1', 'RIR 2']} - self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['rir-1', 'rir-2']} - self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_is_private(self): params = {'is_private': 'true'} - self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) params = {'is_private': 'false'} - self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(RIRFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) class AggregateTestCase(TestCase): queryset = Aggregate.objects.all() + filterset = AggregateFilter @classmethod def setUpTestData(cls): @@ -107,27 +107,28 @@ class AggregateTestCase(TestCase): def test_family(self): params = {'family': '4'} - self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_date_added(self): params = {'date_added': ['2020-01-01', '2020-01-02']} - self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: Test for multiple values def test_prefix(self): params = {'prefix': '10.1.0.0/16'} - self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_rir(self): rirs = RIR.objects.all()[:2] params = {'rir_id': [rirs[0].pk, rirs[1].pk]} - self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'rir': [rirs[0].slug, rirs[1].slug]} - self.assertEqual(AggregateFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) class RoleTestCase(TestCase): queryset = Role.objects.all() + filterset = RoleFilter @classmethod def setUpTestData(cls): @@ -142,19 +143,20 @@ class RoleTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Role 1', 'Role 2']} - self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['role-1', 'role-2']} - self.assertEqual(RoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class PrefixTestCase(TestCase): queryset = Prefix.objects.all() + filterset = PrefixFilter @classmethod def setUpTestData(cls): @@ -212,80 +214,81 @@ class PrefixTestCase(TestCase): def test_family(self): params = {'family': '6'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) def test_is_pool(self): params = {'is_pool': 'true'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'is_pool': 'false'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 8) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_within(self): params = {'within': '10.0.0.0/16'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_within_include(self): params = {'within_include': '10.0.0.0/16'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) def test_contains(self): params = {'contains': '10.0.1.0/24'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'contains': '2001:db8:0:1::/64'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mask_length(self): params = {'mask_length': '24'} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_vrf(self): vrfs = VRF.objects.all()[:2] params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'vrf': [vrfs[0].rd, vrfs[1].rd]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_vlan(self): vlans = VLAN.objects.all()[:2] params = {'vlan_id': [vlans[0].pk, vlans[1].pk]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) # TODO: Test for multiple values params = {'vlan_vid': vlans[0].vid} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_role(self): roles = Role.objects.all()[:2] params = {'role_id': [roles[0].pk, roles[1].pk]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'role': [roles[0].slug, roles[1].slug]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_status(self): params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) class IPAddressTestCase(TestCase): queryset = IPAddress.objects.all() + filterset = IPAddressFilter @classmethod def setUpTestData(cls): @@ -343,84 +346,85 @@ class IPAddressTestCase(TestCase): def test_family(self): params = {'family': '6'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_dns_name(self): params = {'dns_name': ['ipaddress-a', 'ipaddress-b']} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_parent(self): params = {'parent': '10.0.0.0/24'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'parent': '2001:db8::/64'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def filter_address(self): # Check IPv4 and IPv6, with and without a mask params = {'address': '10.0.0.1/24'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'address': '10.0.0.1'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'address': '2001:db8::1/64'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'address': '2001:db8::1'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_mask_length(self): params = {'mask_length': '24'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_vrf(self): vrfs = VRF.objects.all()[:2] params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'vrf': [vrfs[0].rd, vrfs[1].rd]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) # TODO: Test for multiple values def test_device(self): device = Device.objects.first() params = {'device_id': device.pk} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) params = {'device': device.name} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_virtual_machine(self): vms = VirtualMachine.objects.all()[:2] params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'virtual_machine': [vms[0].name, vms[1].name]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_interface(self): interfaces = Interface.objects.all()[:2] params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'interface': ['Interface 1', 'Interface 2']} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_assigned_to_interface(self): params = {'assigned_to_interface': 'true'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 6) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) params = {'assigned_to_interface': 'false'} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_role(self): params = {'role': [IPADDRESS_ROLE_SECONDARY, IPADDRESS_ROLE_VIP]} - self.assertEqual(IPAddressFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) class VLANGroupTestCase(TestCase): queryset = VLANGroup.objects.all() + filterset = VLANGroupFilter @classmethod def setUpTestData(cls): @@ -452,33 +456,34 @@ class VLANGroupTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['VLAN Group 1', 'VLAN Group 2']} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['vlan-group-1', 'vlan-group-2']} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(VLANGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class VLANTestCase(TestCase): queryset = VLAN.objects.all() + filterset = VLANFilter @classmethod def setUpTestData(cls): @@ -525,52 +530,53 @@ class VLANTestCase(TestCase): def test_name(self): params = {'name': ['VLAN 101', 'VLAN 102']} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_rd(self): params = {'vid': ['101', '201', '301']} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_group(self): groups = VLANGroup.objects.all()[:2] params = {'group_id': [groups[0].pk, groups[1].pk]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'group': [groups[0].slug, groups[1].slug]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_role(self): roles = Role.objects.all()[:2] params = {'role_id': [roles[0].pk, roles[1].pk]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) params = {'role': [roles[0].slug, roles[1].slug]} - self.assertEqual(VLANFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_status(self): params = {'status': [VLAN_STATUS_ACTIVE, VLAN_STATUS_DEPRECATED]} - self.assertEqual(PrefixFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) class ServiceTestCase(TestCase): queryset = Service.objects.all() + filterset = ServiceFilter @classmethod def setUpTestData(cls): @@ -610,30 +616,30 @@ class ServiceTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:3] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_name(self): params = {'name': ['Service 1', 'Service 2']} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_protocol(self): params = {'protocol': IP_PROTOCOL_TCP} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 4) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_port(self): params = {'port': ['1001', '1002', '1003']} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_virtual_machine(self): vms = VirtualMachine.objects.all()[:2] params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'virtual_machine': [vms[0].name, vms[1].name]} - self.assertEqual(ServiceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/secrets/tests/test_filters.py b/netbox/secrets/tests/test_filters.py index 2c99747ff..c378147ff 100644 --- a/netbox/secrets/tests/test_filters.py +++ b/netbox/secrets/tests/test_filters.py @@ -1,12 +1,13 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from secrets.filters import SecretFilter, SecretRoleFilter +from secrets.filters import * from secrets.models import Secret, SecretRole class SecretRoleTestCase(TestCase): queryset = SecretRole.objects.all() + filterset = SecretRoleFilter @classmethod def setUpTestData(cls): @@ -21,19 +22,20 @@ class SecretRoleTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Secret Role 1', 'Secret Role 2']} - self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['secret-role-1', 'secret-role-2']} - self.assertEqual(SecretRoleFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class SecretTestCase(TestCase): queryset = Secret.objects.all() + filterset = SecretFilter @classmethod def setUpTestData(cls): @@ -68,23 +70,23 @@ class SecretTestCase(TestCase): def test_name(self): params = {'name': ['Secret 1', 'Secret 2']} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_role(self): roles = SecretRole.objects.all()[:2] params = {'role_id': [roles[0].pk, roles[1].pk]} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'role': [roles[0].slug, roles[1].slug]} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'device': [devices[0].name, devices[1].name]} - self.assertEqual(SecretFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/tenancy/tests/test_filters.py b/netbox/tenancy/tests/test_filters.py index 70db8f627..9bf58d452 100644 --- a/netbox/tenancy/tests/test_filters.py +++ b/netbox/tenancy/tests/test_filters.py @@ -1,11 +1,12 @@ from django.test import TestCase -from tenancy.filters import TenantFilter, TenantGroupFilter +from tenancy.filters import * from tenancy.models import Tenant, TenantGroup class TenantGroupTestCase(TestCase): queryset = TenantGroup.objects.all() + filterset = TenantGroupFilter @classmethod def setUpTestData(cls): @@ -20,19 +21,20 @@ class TenantGroupTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Tenant Group 1', 'Tenant Group 2']} - self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['tenant-group-1', 'tenant-group-2']} - self.assertEqual(TenantGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class TenantTestCase(TestCase): queryset = Tenant.objects.all() + filterset = TenantFilter @classmethod def setUpTestData(cls): @@ -53,20 +55,20 @@ class TenantTestCase(TestCase): def test_name(self): params = {'name': ['Tenant 1', 'Tenant 2']} - self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['tenant-1', 'tenant-2']} - self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_group(self): group = TenantGroup.objects.all()[:2] params = {'group_id': [group[0].pk, group[1].pk]} - self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'group': [group[0].slug, group[1].slug]} - self.assertEqual(TenantFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filters.py index ed1422a61..6804a9a70 100644 --- a/netbox/virtualization/tests/test_filters.py +++ b/netbox/virtualization/tests/test_filters.py @@ -2,14 +2,13 @@ from django.test import TestCase from dcim.models import DeviceRole, Interface, Platform, Region, Site from virtualization.constants import * -from virtualization.filters import ( - ClusterFilter, ClusterGroupFilter, ClusterTypeFilter, InterfaceFilter, VirtualMachineFilter, -) +from virtualization.filters import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine class ClusterTypeTestCase(TestCase): queryset = ClusterType.objects.all() + filterset = ClusterTypeFilter @classmethod def setUpTestData(cls): @@ -24,19 +23,20 @@ class ClusterTypeTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Cluster Type 1', 'Cluster Type 2']} - self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['cluster-type-1', 'cluster-type-2']} - self.assertEqual(ClusterTypeFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class ClusterGroupTestCase(TestCase): queryset = ClusterGroup.objects.all() + filterset = ClusterGroupFilter @classmethod def setUpTestData(cls): @@ -51,19 +51,20 @@ class ClusterGroupTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Cluster Group 1', 'Cluster Group 2']} - self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_slug(self): params = {'slug': ['cluster-group-1', 'cluster-group-2']} - self.assertEqual(ClusterGroupFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class ClusterTestCase(TestCase): queryset = Cluster.objects.all() + filterset = ClusterFilter @classmethod def setUpTestData(cls): @@ -107,44 +108,45 @@ class ClusterTestCase(TestCase): def test_name(self): params = {'name': ['Cluster 1', 'Cluster 2']} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_group(self): groups = ClusterGroup.objects.all()[:2] params = {'group_id': [groups[0].pk, groups[1].pk]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'group': [groups[0].slug, groups[1].slug]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_type(self): types = ClusterType.objects.all()[:2] params = {'type_id': [types[0].pk, types[1].pk]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'type': [types[0].slug, types[1].slug]} - self.assertEqual(ClusterFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class VirtualMachineTestCase(TestCase): queryset = VirtualMachine.objects.all() + filterset = VirtualMachineFilter @classmethod def setUpTestData(cls): @@ -217,90 +219,91 @@ class VirtualMachineTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Virtual Machine 1', 'Virtual Machine 2']} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_vcpus(self): params = {'vcpus': [1, 2]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_memory(self): params = {'memory': [1, 2]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_disk(self): params = {'disk': [1, 2]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_id__in(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id__in': ','.join([str(id) for id in id_list])} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_status(self): params = {'status': [DEVICE_STATUS_ACTIVE, DEVICE_STATUS_STAGED]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cluster_group(self): groups = ClusterGroup.objects.all()[:2] params = {'cluster_group_id': [groups[0].pk, groups[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cluster_group': [groups[0].slug, groups[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cluster_type(self): types = ClusterType.objects.all()[:2] params = {'cluster_type_id': [types[0].pk, types[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'cluster_type': [types[0].slug, types[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_cluster(self): clusters = Cluster.objects.all()[:2] params = {'cluster_id': [clusters[0].pk, clusters[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) # TODO: 'cluster' should match on name # params = {'cluster': [clusters[0].name, clusters[1].name]} - # self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_region(self): regions = Region.objects.all()[:2] params = {'region_id': [regions[0].pk, regions[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'region': [regions[0].slug, regions[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_site(self): sites = Site.objects.all()[:2] params = {'site_id': [sites[0].pk, sites[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'site': [sites[0].slug, sites[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_role(self): roles = DeviceRole.objects.all()[:2] params = {'role_id': [roles[0].pk, roles[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'role': [roles[0].slug, roles[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_platform(self): platforms = Platform.objects.all()[:2] params = {'platform_id': [platforms[0].pk, platforms[1].pk]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'platform': [platforms[0].slug, platforms[1].slug]} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mac_address(self): params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} - self.assertEqual(VirtualMachineFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) class InterfaceTestCase(TestCase): queryset = Interface.objects.all() + filterset = InterfaceFilter @classmethod def setUpTestData(cls): @@ -336,29 +339,29 @@ class InterfaceTestCase(TestCase): def test_id(self): id_list = self.queryset.values_list('id', flat=True)[:2] params = {'id': [str(id) for id in id_list]} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_name(self): params = {'name': ['Interface 1', 'Interface 2']} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_assigned_to_interface(self): params = {'enabled': 'true'} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'enabled': 'false'} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 1) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_mtu(self): params = {'mtu': [100, 200]} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_virtual_machine(self): vms = VirtualMachine.objects.all()[:2] params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'virtual_machine': [vms[0].name, vms[1].name]} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mac_address(self): params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} - self.assertEqual(InterfaceFilter(params, self.queryset).qs.count(), 2) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) From fb554b99f3fd918c8ce12b66506419783cef5d26 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 17:20:31 -0500 Subject: [PATCH 60/98] Clean up filter imports --- netbox/circuits/filters.py | 7 +++++ netbox/circuits/tests/test_filters.py | 2 +- netbox/dcim/filters.py | 39 +++++++++++++++++++++++++++ netbox/dcim/tests/test_filters.py | 8 +++--- netbox/ipam/filters.py | 13 +++++++++ netbox/secrets/filters.py | 6 +++++ netbox/tenancy/filters.py | 6 +++++ netbox/virtualization/filters.py | 11 ++++++-- 8 files changed, 86 insertions(+), 6 deletions(-) diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 0ac5ec170..1f1d078f6 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -8,6 +8,13 @@ from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilte from .constants import * from .models import Circuit, CircuitTermination, CircuitType, Provider +__all__ = ( + 'CircuitFilter', + 'CircuitTerminationFilter', + 'CircuitTypeFilter', + 'ProviderFilter', +) + class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet): id__in = NumericInFilter( diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filters.py index 3077b9c8f..a715ad757 100644 --- a/netbox/circuits/tests/test_filters.py +++ b/netbox/circuits/tests/test_filters.py @@ -1,7 +1,7 @@ from django.test import TestCase from circuits.constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_OFFLINE, CIRCUIT_STATUS_PLANNED -from circuits.filters import CircuitFilter, CircuitTerminationFilter, CircuitTypeFilter, ProviderFilter +from circuits.filters import * from circuits.models import Circuit, CircuitTermination, CircuitType, Provider from dcim.models import Region, Site diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index ffed7fa3d..9d6a0ae6a 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -21,6 +21,45 @@ from .models import ( ) +__all__ = ( + 'CableFilter', + 'ConsoleConnectionFilter', + 'ConsolePortFilter', + 'ConsolePortTemplateFilter', + 'ConsoleServerPortFilter', + 'ConsoleServerPortTemplateFilter', + 'DeviceBayFilter', + 'DeviceBayTemplateFilter', + 'DeviceFilter', + 'DeviceRoleFilter', + 'DeviceTypeFilter', + 'FrontPortFilter', + 'FrontPortTemplateFilter', + 'InterfaceConnectionFilter', + 'InterfaceFilter', + 'InterfaceTemplateFilter', + 'InventoryItemFilter', + 'ManufacturerFilter', + 'PlatformFilter', + 'PowerConnectionFilter', + 'PowerFeedFilter', + 'PowerOutletFilter', + 'PowerOutletTemplateFilter', + 'PowerPanelFilter', + 'PowerPortFilter', + 'PowerPortTemplateFilter', + 'RackFilter', + 'RackGroupFilter', + 'RackReservationFilter', + 'RackRoleFilter', + 'RearPortFilter', + 'RearPortTemplateFilter', + 'RegionFilter', + 'SiteFilter', + 'VirtualChassisFilter', +) + + class RegionFilter(NameSlugSearchFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 47bcbe0d4..1feacc5c5 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -4,9 +4,11 @@ from django.test import TestCase from dcim.constants import * from dcim.filters import * from dcim.models import ( - Cable, ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPortTemplate, - InterfaceTemplate, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPortTemplate, PowerOutletTemplate, Rack, - RackGroup, RackReservation, RackRole, RearPortTemplate, Region, Site, VirtualChassis, + Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, + DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, + InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet, + PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, + VirtualChassis, ) from ipam.models import IPAddress from virtualization.models import Cluster, ClusterType diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index bf14b80d6..9d1b1d650 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -13,6 +13,19 @@ from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF +__all__ = ( + 'AggregateFilter', + 'IPAddressFilter', + 'PrefixFilter', + 'RIRFilter', + 'RoleFilter', + 'ServiceFilter', + 'VLANFilter', + 'VLANGroupFilter', + 'VRFFilter', +) + + class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): id__in = NumericInFilter( field_name='id', diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index bdc643e71..2998b9c18 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -7,6 +7,12 @@ from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilte from .models import Secret, SecretRole +__all__ = ( + 'SecretFilter', + 'SecretRoleFilter', +) + + class SecretRoleFilter(NameSlugSearchFilterSet): class Meta: diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index ac7e6fabb..357c47b29 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -6,6 +6,12 @@ from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilte from .models import Tenant, TenantGroup +__all__ = ( + 'TenantFilter', + 'TenantGroupFilter', +) + + class TenantGroupFilter(NameSlugSearchFilterSet): class Meta: diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 8ce97f9e2..9c8de86ce 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -1,7 +1,5 @@ import django_filters from django.db.models import Q -from netaddr import EUI -from netaddr.core import AddrFormatError from dcim.models import DeviceRole, Interface, Platform, Region, Site from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet @@ -13,6 +11,15 @@ from .constants import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine +__all__ = ( + 'ClusterFilter', + 'ClusterGroupFilter', + 'ClusterTypeFilter', + 'InterfaceFilter', + 'VirtualMachineFilter', +) + + class ClusterTypeFilter(NameSlugSearchFilterSet): class Meta: From 02d759a481657a6987c89e25059379b15de97967 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 8 Jan 2020 21:41:10 -0500 Subject: [PATCH 61/98] Add filter tests for extras --- netbox/extras/filters.py | 14 +++ netbox/extras/tests/test_filters.py | 181 ++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 netbox/extras/tests/test_filters.py diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 8c805ebdf..cd1306bc4 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -8,6 +8,20 @@ from .constants import * from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag, TopologyMap +__all__ = ( + 'ConfigContextFilter', + 'CreatedUpdatedFilterSet', + 'CustomFieldFilter', + 'CustomFieldFilterSet', + 'ExportTemplateFilter', + 'GraphFilter', + 'LocalConfigContextFilter', + 'ObjectChangeFilter', + 'TagFilter', + 'TopologyMapFilter', +) + + class CustomFieldFilter(django_filters.Filter): """ Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name. diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filters.py new file mode 100644 index 000000000..6c6b8145b --- /dev/null +++ b/netbox/extras/tests/test_filters.py @@ -0,0 +1,181 @@ +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from dcim.models import DeviceRole, Platform, Region, Site +from extras.constants import * +from extras.filters import * +from extras.models import ConfigContext, ExportTemplate, Graph +from tenancy.models import Tenant, TenantGroup + + +class GraphTestCase(TestCase): + queryset = Graph.objects.all() + filterset = GraphFilter + + @classmethod + def setUpTestData(cls): + + graphs = ( + Graph(name='Graph 1', type=GRAPH_TYPE_DEVICE, source='http://example.com/1'), + Graph(name='Graph 2', type=GRAPH_TYPE_INTERFACE, source='http://example.com/2'), + Graph(name='Graph 3', type=GRAPH_TYPE_SITE, source='http://example.com/3'), + ) + Graph.objects.bulk_create(graphs) + + def test_name(self): + params = {'name': 'Graph 1'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_type(self): + params = {'type': GRAPH_TYPE_DEVICE} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + +class ExportTemplateTestCase(TestCase): + queryset = ExportTemplate.objects.all() + filterset = ExportTemplateFilter + + @classmethod + def setUpTestData(cls): + + content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device']) + + export_templates = ( + ExportTemplate(name='Export Template 1', content_type=content_types[0], template_language=TEMPLATE_LANGUAGE_DJANGO, template_code='TESTING'), + ExportTemplate(name='Export Template 2', content_type=content_types[1], template_language=TEMPLATE_LANGUAGE_JINJA2, template_code='TESTING'), + ExportTemplate(name='Export Template 3', content_type=content_types[2], template_language=TEMPLATE_LANGUAGE_JINJA2, template_code='TESTING'), + ) + ExportTemplate.objects.bulk_create(export_templates) + + def test_name(self): + params = {'name': 'Export Template 1'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_content_type(self): + params = {'content_type': ContentType.objects.get(model='site').pk} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_template_language(self): + params = {'template_language': TEMPLATE_LANGUAGE_JINJA2} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + +class ConfigContextTestCase(TestCase): + queryset = ConfigContext.objects.all() + filterset = ConfigContextFilter + + @classmethod + def setUpTestData(cls): + + regions = ( + Region(name='Test Region 1', slug='test-region-1'), + Region(name='Test Region 2', slug='test-region-2'), + Region(name='Test Region 3', slug='test-region-3'), + ) + # Can't use bulk_create for models with MPTT fields + for r in regions: + r.save() + + sites = ( + Site(name='Test Site 1', slug='test-site-1'), + Site(name='Test Site 2', slug='test-site-2'), + Site(name='Test Site 3', slug='test-site-3'), + ) + Site.objects.bulk_create(sites) + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) + + platforms = ( + Platform(name='Platform 1', slug='platform-1'), + Platform(name='Platform 2', slug='platform-2'), + Platform(name='Platform 3', slug='platform-3'), + ) + Platform.objects.bulk_create(platforms) + + tenant_groups = ( + TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), + TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), + TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), + ) + TenantGroup.objects.bulk_create(tenant_groups) + + tenants = ( + Tenant(name='Tenant 1', slug='tenant-1'), + Tenant(name='Tenant 2', slug='tenant-2'), + Tenant(name='Tenant 3', slug='tenant-3'), + ) + Tenant.objects.bulk_create(tenants) + + for i in range(0, 3): + is_active = bool(i % 2) + c = ConfigContext.objects.create( + name='Config Context {}'.format(i + 1), + is_active=is_active, + data='{"foo": 123}' + ) + c.regions.set([regions[i]]) + c.sites.set([sites[i]]) + c.roles.set([device_roles[i]]) + c.platforms.set([platforms[i]]) + c.tenant_groups.set([tenant_groups[i]]) + c.tenants.set([tenants[i]]) + + def test_name(self): + params = {'name': 'Config Context 1'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_is_active(self): + params = {'is_active': True} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'is_active': False} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_region(self): + regions = Region.objects.all()[:2] + params = {'region_id': [regions[0].pk, regions[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'region': [regions[0].slug, regions[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_site(self): + sites = Site.objects.all()[:2] + params = {'site_id': [sites[0].pk, sites[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'site': [sites[0].slug, sites[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_role(self): + device_roles = DeviceRole.objects.all()[:2] + params = {'role_id': [device_roles[0].pk, device_roles[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'role': [device_roles[0].slug, device_roles[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_platform(self): + platforms = Platform.objects.all()[:2] + params = {'platform_id': [platforms[0].pk, platforms[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'platform': [platforms[0].slug, platforms[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_tenant_group(self): + tenant_groups = TenantGroup.objects.all()[:2] + params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_tenant_(self): + tenants = Tenant.objects.all()[:2] + params = {'tenant_id': [tenants[0].pk, tenants[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'tenant': [tenants[0].slug, tenants[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + +# TODO: ObjectChangeFilter test From b931b2b272b5228a2529d7821c52ad988b24bd62 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 10:00:02 +0000 Subject: [PATCH 62/98] Fixes #3668: search address by DNS name when assigning --- docs/release-notes/version-2.6.md | 1 + netbox/ipam/forms.py | 7 ++++++- netbox/ipam/tables.py | 2 +- netbox/ipam/views.py | 1 + netbox/templates/ipam/ipaddress_assign.html | 1 + 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..341e62a79 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations +* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address ## Bug Fixes diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 413e72eaf..f4bcfb2e6 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -943,7 +943,12 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): ) ) address = forms.CharField( - label='IP Address' + label='IP Address', + required=False, + ) + dns_name = forms.CharField( + label='DNS Name', + required=False, ) diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index e4d2bf8b4..d6e26f6a1 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -373,7 +373,7 @@ class IPAddressAssignTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress - fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description') + fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description') orderable = False diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 2cc1a0ea8..d38366d54 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -745,6 +745,7 @@ class IPAddressAssignView(PermissionRequiredMixin, View): ).filter( vrf=form.cleaned_data['vrf'], address__istartswith=form.cleaned_data['address'], + dns_name__icontains=form.cleaned_data['dns_name'], )[:100] # Limit to 100 results table = tables.IPAddressAssignTable(queryset) diff --git a/netbox/templates/ipam/ipaddress_assign.html b/netbox/templates/ipam/ipaddress_assign.html index 579f2d98b..349c06919 100644 --- a/netbox/templates/ipam/ipaddress_assign.html +++ b/netbox/templates/ipam/ipaddress_assign.html @@ -26,6 +26,7 @@
{% render_field form.vrf %} {% render_field form.address %} + {% render_field form.dns_name %}
From efba4c4f744280a18c06c2ab6627b27cec23e0cd Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 14:33:49 +0000 Subject: [PATCH 63/98] Fixes #3864: Disallow /0 masks --- docs/release-notes/version-2.6.md | 1 + netbox/ipam/models.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index f3ff8798a..2cd240e15 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -10,6 +10,7 @@ * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses +* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks --- diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 8f9b64b59..a67ff4a86 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -177,6 +177,12 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel): # Clear host bits from prefix self.prefix = self.prefix.cidr + # /0 masks are not acceptable + if self.prefix.prefixlen == 0: + raise ValidationError({ + 'prefix': "Cannot create aggregate with /0 mask." + }) + # Ensure that the aggregate being added is not covered by an existing aggregate covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix)) if self.pk: @@ -347,6 +353,12 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): if self.prefix: + # /0 masks are not acceptable + if self.prefix.prefixlen == 0: + raise ValidationError({ + 'prefix': "Cannot create prefix with /0 mask." + }) + # Disallow host masks if self.prefix.version == 4 and self.prefix.prefixlen == 32: raise ValidationError({ @@ -622,6 +634,12 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): if self.address: + # /0 masks are not acceptable + if self.address.prefixlen == 0: + raise ValidationError({ + 'address': "Cannot create IP address with /0 mask." + }) + # Enforce unique IP space (if applicable) if self.role not in IPADDRESS_ROLES_NONUNIQUE and (( self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE From 924843fb9af1d87c1300f90e0e3bb8f422d623d4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Jan 2020 09:41:10 -0500 Subject: [PATCH 64/98] Fixes #3851: Allow passing initial data to custom script forms --- docs/release-notes/version-2.6.md | 1 + netbox/extras/scripts.py | 4 ++-- netbox/extras/views.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index cde5b3e80..3149f3cb9 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations +* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms ## Bug Fixes diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index 28238b008..4876595a3 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -263,12 +263,12 @@ class BaseScript: def run(self, data): raise NotImplementedError("The script must define a run() method.") - def as_form(self, data=None, files=None): + def as_form(self, data=None, files=None, initial=None): """ Return a Django form suitable for populating the context data required to run this Script. """ vars = self._get_vars() - form = ScriptForm(vars, data, files, commit_default=getattr(self.Meta, 'commit_default', True)) + form = ScriptForm(vars, data, files, initial=initial, commit_default=getattr(self.Meta, 'commit_default', True)) return form diff --git a/netbox/extras/views.py b/netbox/extras/views.py index eb17a65ab..b9ef7f9b4 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -392,7 +392,7 @@ class ScriptView(PermissionRequiredMixin, View): def get(self, request, module, name): script = self._get_script(module, name) - form = script.as_form() + form = script.as_form(initial=request.GET) return render(request, 'extras/script.html', { 'module': module, From 92ee2b205fec28ab576776203070451622d743ef Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 14:48:21 +0000 Subject: [PATCH 65/98] Fixes #3872: Limit related IPs table --- docs/release-notes/version-2.6.md | 1 + netbox/ipam/views.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 3149f3cb9..e1715887b 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -13,6 +13,7 @@ * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses * [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering * [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names +* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Limit number of related IPs --- diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 2cc1a0ea8..a8a6cd846 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -676,7 +676,7 @@ class IPAddressView(PermissionRequiredMixin, View): address=str(ipaddress.address) ).filter( vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address) - ) + )[:50] related_ips_table = tables.IPAddressTable(list(related_ips), orderable=False) return render(request, 'ipam/ipaddress.html', { From 202c2dcb5ae5b7d83aaa4b5a0d4d8d2e13300e48 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:26:11 +0000 Subject: [PATCH 66/98] Changed to `q` filter --- netbox/ipam/forms.py | 10 +++------- netbox/ipam/views.py | 12 +++++------- netbox/templates/ipam/ipaddress_assign.html | 5 ++--- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index f4bcfb2e6..c3387a5aa 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -933,7 +933,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd class IPAddressAssignForm(BootstrapMixin, forms.Form): - vrf = forms.ModelChoiceField( + vrf_id = forms.ModelChoiceField( queryset=VRF.objects.all(), required=False, label='VRF', @@ -942,13 +942,9 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): api_url="/api/ipam/vrfs/" ) ) - address = forms.CharField( - label='IP Address', - required=False, - ) - dns_name = forms.CharField( - label='DNS Name', + q = forms.CharField( required=False, + label='Search', ) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index d6cada398..084cf1205 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -749,14 +749,12 @@ class IPAddressAssignView(PermissionRequiredMixin, View): if form.is_valid(): - queryset = IPAddress.objects.prefetch_related( + addresses = IPAddress.objects.prefetch_related( 'vrf', 'tenant', 'interface__device', 'interface__virtual_machine' - ).filter( - vrf=form.cleaned_data['vrf'], - address__istartswith=form.cleaned_data['address'], - dns_name__icontains=form.cleaned_data['dns_name'], - )[:100] # Limit to 100 results - table = tables.IPAddressAssignTable(queryset) + ) + # Limit to 100 results + addresses = filters.IPAddressFilter(request.POST, addresses).qs[:100] + table = tables.IPAddressAssignTable(addresses) return render(request, 'ipam/ipaddress_assign.html', { 'form': form, diff --git a/netbox/templates/ipam/ipaddress_assign.html b/netbox/templates/ipam/ipaddress_assign.html index 349c06919..ab163533f 100644 --- a/netbox/templates/ipam/ipaddress_assign.html +++ b/netbox/templates/ipam/ipaddress_assign.html @@ -24,9 +24,8 @@
Select IP Address
- {% render_field form.vrf %} - {% render_field form.address %} - {% render_field form.dns_name %} + {% render_field form.vrf_id %} + {% render_field form.q %}
From 3195219ed40563c6bbaef5bd0ad5a09209bc5dbe Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:30:13 +0000 Subject: [PATCH 67/98] Added changelog for 3009 --- docs/release-notes/version-2.6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 1f3e9a430..afc9b87c1 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses +* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations * [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms From 01de358d4e08bf29d2c8a5fca064732b1e2f9b9a Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:32:01 +0000 Subject: [PATCH 68/98] Added changelog for 3009 again as I accidentally removed it while merging --- docs/release-notes/version-2.6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index a6e54cbef..e054e7684 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -4,6 +4,7 @@ * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses +* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations * [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace From 813262ff88d25bd3bdd0e883dce7b4d5c5b73423 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:39:13 +0000 Subject: [PATCH 69/98] Moved NAPALM parameter to decorator --- netbox/dcim/api/views.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 9bfe0f421..3c953bbfc 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -327,13 +327,6 @@ class DeviceViewSet(CustomFieldModelViewSet): ) filterset_class = filters.DeviceFilter - _method = Parameter( - name='method', - in_='query', - required=True, - type=openapi.TYPE_STRING - ) - def get_serializer_class(self): """ Select the specific serializer based on the request context. @@ -365,7 +358,17 @@ class DeviceViewSet(CustomFieldModelViewSet): return Response(serializer.data) - @swagger_auto_schema(manual_parameters=[_method], responses={'200': serializers.DeviceNAPALMSerializer}) + @swagger_auto_schema( + manual_parameters=[ + Parameter( + name='method', + in_='query', + required=True, + type=openapi.TYPE_STRING + ) + ], + responses={'200': serializers.DeviceNAPALMSerializer} + ) @action(detail=True, url_path='napalm') def napalm(self, request, pk): """ From b6fb10e5c2b2d560716308ffad813c0f2c56d2e5 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:40:13 +0000 Subject: [PATCH 70/98] Removed exception for empty methods I'll create a seperate ticket for that --- netbox/dcim/api/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 3c953bbfc..e345210fd 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -374,9 +374,6 @@ class DeviceViewSet(CustomFieldModelViewSet): """ Execute a NAPALM method on a Device """ - if not request.GET.get('method'): - raise ServiceUnavailable('No NAPALM methods were specified.') - device = get_object_or_404(Device, pk=pk) if not device.primary_ip: raise ServiceUnavailable("This device does not have a primary IP address configured.") From 8690ba0c6730785a89f6b6c74c5bef1df15a3d10 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 16:48:26 +0000 Subject: [PATCH 71/98] Changed `NAPALM-` prefix to `X-NAPALM-` --- docs/additional-features/napalm.md | 10 +++++----- netbox/dcim/api/views.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/additional-features/napalm.md b/docs/additional-features/napalm.md index a7b91128b..c8e8b8b3a 100644 --- a/docs/additional-features/napalm.md +++ b/docs/additional-features/napalm.md @@ -17,15 +17,15 @@ GET /api/dcim/devices/1/napalm/?method=get_environment ## Authentication -By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `NAPALM-Username` and `NAPALM-Password` headers. +By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `X-NAPALM-Username` and `X-NAPALM-Password` headers. ``` $ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \ -H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \ -H "Content-Type: application/json" \ -H "Accept: application/json; indent=4" \ --H "NAPALM-Username: foo" \ --H "NAPALM-Password: bar" +-H "X-NAPALM-Username: foo" \ +-H "X-NAPALM-Password: bar" ``` ## Method Support @@ -51,7 +51,7 @@ GET /api/dcim/devices/1/napalm/?method=get_ntp_servers&method=get_ntp_peers ## Optional Arguments -The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `NAPALM-`. +The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `X-NAPALM-`. For instance, the SSH port is changed to 2222 in this API call: @@ -61,5 +61,5 @@ $ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \ -H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \ -H "Content-Type: application/json" \ -H "Accept: application/json; indent=4" \ --H "NAPALM-port: 2222" +-H "X-NAPALM-port: 2222" ``` diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 18da2f9a3..a7dfb39ff 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -404,10 +404,10 @@ class DeviceViewSet(CustomFieldModelViewSet): # Update NAPALM parameters according to the request headers for header in request.headers: - if header[:7].lower() != 'napalm-': + if header[:9].lower() != 'x-napalm-': continue - key = header[7:] + key = header[9:] if key.lower() == 'username': username = request.headers[header] elif key.lower() == 'password': From c1f999320d1853d448b35b618721081618983b25 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 17:16:58 +0000 Subject: [PATCH 72/98] Replaced with pagination --- netbox/ipam/views.py | 11 +++++++++-- netbox/templates/ipam/ipaddress.html | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a8a6cd846..7a1a2901c 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -676,8 +676,15 @@ class IPAddressView(PermissionRequiredMixin, View): address=str(ipaddress.address) ).filter( vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address) - )[:50] - related_ips_table = tables.IPAddressTable(list(related_ips), orderable=False) + ) + + related_ips_table = tables.IPAddressTable(related_ips, orderable=False) + + paginate = { + 'paginator_class': EnhancedPaginator, + 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) + } + RequestConfig(request, paginate).configure(related_ips_table) return render(request, 'ipam/ipaddress.html', { 'ipaddress': ipaddress, diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index cb04e14d5..aae819ceb 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -160,7 +160,7 @@ {% if duplicate_ips_table.rows %} {% include 'panel_table.html' with table=duplicate_ips_table heading='Duplicate IP Addresses' panel_class='danger' %} {% endif %} - {% include 'panel_table.html' with table=related_ips_table heading='Related IP Addresses' panel_class='default noprint' %} + {% include 'utilities/obj_table.html' with table=related_ips_table table_template='panel_table.html' heading='Related IP Addresses' panel_class='default noprint' %} {% endblock %} From 7e37ffab80d43992565c8e3d8b54c0f08bae9933 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 18:26:10 +0000 Subject: [PATCH 73/98] Added tests for IPv4 --- netbox/utilities/tests/test_forms.py | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 netbox/utilities/tests/test_forms.py diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py new file mode 100644 index 000000000..0434ff137 --- /dev/null +++ b/netbox/utilities/tests/test_forms.py @@ -0,0 +1,69 @@ +from django.test import TestCase + +from utilities.forms import * + + +class ExpandIPAddress(TestCase): + """ + Validate the operation of expand_ipaddress_pattern(). + """ + def test_ipv4_range(self): + input = '1.2.3.[9-10]/32' + output = sorted([ + '1.2.3.9/32', + '1.2.3.10/32', + ]) + + self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + + def test_ipv4_set(self): + input = '1.2.3.[4,44]/32' + output = sorted([ + '1.2.3.4/32', + '1.2.3.44/32', + ]) + + self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + + def test_ipv4_multiple_ranges(self): + input = '1.[9-10].3.[9-11]/32' + output = sorted([ + '1.9.3.9/32', + '1.9.3.10/32', + '1.9.3.11/32', + '1.10.3.9/32', + '1.10.3.10/32', + '1.10.3.11/32', + ]) + + self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + + def test_ipv4_multiple_sets(self): + input = '1.[2,22].3.[4,44]/32' + output = sorted([ + '1.2.3.4/32', + '1.2.3.44/32', + '1.22.3.4/32', + '1.22.3.44/32', + ]) + + self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + + + def test_ipv4_set_and_range(self): + input = '1.[2,22].3.[9-11]/32' + output = sorted([ + '1.2.3.9/32', + '1.2.3.10/32', + '1.2.3.11/32', + '1.22.3.9/32', + '1.22.3.10/32', + '1.22.3.11/32', + ]) + + self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + + # TODO: IPv6 + # TODO: negative tests + +# TODO: alphanumeric From 71f739d901d28eb2ca4b0225fd21abc6d6ed7c0f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Jan 2020 13:28:39 -0500 Subject: [PATCH 74/98] Fixes #3849: Fix ordering of models when dumping data to JSON --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/models.py | 362 +++++++++++++++--------------- 2 files changed, 182 insertions(+), 181 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 57807ea4d..bd9503008 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -14,6 +14,7 @@ ## Bug Fixes * [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface +* [#3849](https://github.com/netbox-community/netbox/issues/3849) - Fix ordering of models when dumping data to JSON * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses * [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 2c7105a80..69c3c3475 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2755,6 +2755,187 @@ class VirtualChassis(ChangeLoggedModel): ) +# +# Power +# + +class PowerPanel(ChangeLoggedModel): + """ + A distribution point for electrical power; e.g. a data center RPP. + """ + site = models.ForeignKey( + to='Site', + on_delete=models.PROTECT + ) + rack_group = models.ForeignKey( + to='RackGroup', + on_delete=models.PROTECT, + blank=True, + null=True + ) + name = models.CharField( + max_length=50 + ) + + csv_headers = ['site', 'rack_group_name', 'name'] + + class Meta: + ordering = ['site', 'name'] + unique_together = ['site', 'name'] + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse('dcim:powerpanel', args=[self.pk]) + + def to_csv(self): + return ( + self.site.name, + self.rack_group.name if self.rack_group else None, + self.name, + ) + + def clean(self): + + # RackGroup must belong to assigned Site + if self.rack_group and self.rack_group.site != self.site: + raise ValidationError("Rack group {} ({}) is in a different site than {}".format( + self.rack_group, self.rack_group.site, self.site + )) + + +class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): + """ + An electrical circuit delivered from a PowerPanel. + """ + power_panel = models.ForeignKey( + to='PowerPanel', + on_delete=models.PROTECT, + related_name='powerfeeds' + ) + rack = models.ForeignKey( + to='Rack', + on_delete=models.PROTECT, + blank=True, + null=True + ) + connected_endpoint = models.OneToOneField( + to='dcim.PowerPort', + on_delete=models.SET_NULL, + related_name='+', + blank=True, + null=True + ) + connection_status = models.NullBooleanField( + choices=CONNECTION_STATUS_CHOICES, + blank=True + ) + name = models.CharField( + max_length=50 + ) + status = models.PositiveSmallIntegerField( + choices=POWERFEED_STATUS_CHOICES, + default=POWERFEED_STATUS_ACTIVE + ) + type = models.PositiveSmallIntegerField( + choices=POWERFEED_TYPE_CHOICES, + default=POWERFEED_TYPE_PRIMARY + ) + supply = models.PositiveSmallIntegerField( + choices=POWERFEED_SUPPLY_CHOICES, + default=POWERFEED_SUPPLY_AC + ) + phase = models.PositiveSmallIntegerField( + choices=POWERFEED_PHASE_CHOICES, + default=POWERFEED_PHASE_SINGLE + ) + voltage = models.PositiveSmallIntegerField( + validators=[MinValueValidator(1)], + default=120 + ) + amperage = models.PositiveSmallIntegerField( + validators=[MinValueValidator(1)], + default=20 + ) + max_utilization = models.PositiveSmallIntegerField( + validators=[MinValueValidator(1), MaxValueValidator(100)], + default=80, + help_text="Maximum permissible draw (percentage)" + ) + available_power = models.PositiveSmallIntegerField( + default=0, + editable=False + ) + comments = models.TextField( + blank=True + ) + custom_field_values = GenericRelation( + to='extras.CustomFieldValue', + content_type_field='obj_type', + object_id_field='obj_id' + ) + + tags = TaggableManager(through=TaggedItem) + + csv_headers = [ + 'site', 'panel_name', 'rack_group', 'rack_name', 'name', 'status', 'type', 'supply', 'phase', 'voltage', + 'amperage', 'max_utilization', 'comments', + ] + + class Meta: + ordering = ['power_panel', 'name'] + unique_together = ['power_panel', 'name'] + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse('dcim:powerfeed', args=[self.pk]) + + def to_csv(self): + return ( + self.power_panel.site.name, + self.power_panel.name, + self.rack.group.name if self.rack and self.rack.group else None, + self.rack.name if self.rack else None, + self.name, + self.get_status_display(), + self.get_type_display(), + self.get_supply_display(), + self.get_phase_display(), + self.voltage, + self.amperage, + self.max_utilization, + self.comments, + ) + + def clean(self): + + # Rack must belong to same Site as PowerPanel + if self.rack and self.rack.site != self.power_panel.site: + raise ValidationError("Rack {} ({}) and power panel {} ({}) are in different sites".format( + self.rack, self.rack.site, self.power_panel, self.power_panel.site + )) + + def save(self, *args, **kwargs): + + # Cache the available_power property on the instance + kva = self.voltage * self.amperage * (self.max_utilization / 100) + if self.phase == POWERFEED_PHASE_3PHASE: + self.available_power = round(kva * 1.732) + else: + self.available_power = round(kva) + + super().save(*args, **kwargs) + + def get_type_class(self): + return STATUS_CLASSES[self.type] + + def get_status_class(self): + return STATUS_CLASSES[self.status] + + # # Cables # @@ -3008,184 +3189,3 @@ class Cable(ChangeLoggedModel): b_endpoint = b_path[-1][2] return a_endpoint, b_endpoint, path_status - - -# -# Power -# - -class PowerPanel(ChangeLoggedModel): - """ - A distribution point for electrical power; e.g. a data center RPP. - """ - site = models.ForeignKey( - to='Site', - on_delete=models.PROTECT - ) - rack_group = models.ForeignKey( - to='RackGroup', - on_delete=models.PROTECT, - blank=True, - null=True - ) - name = models.CharField( - max_length=50 - ) - - csv_headers = ['site', 'rack_group_name', 'name'] - - class Meta: - ordering = ['site', 'name'] - unique_together = ['site', 'name'] - - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse('dcim:powerpanel', args=[self.pk]) - - def to_csv(self): - return ( - self.site.name, - self.rack_group.name if self.rack_group else None, - self.name, - ) - - def clean(self): - - # RackGroup must belong to assigned Site - if self.rack_group and self.rack_group.site != self.site: - raise ValidationError("Rack group {} ({}) is in a different site than {}".format( - self.rack_group, self.rack_group.site, self.site - )) - - -class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): - """ - An electrical circuit delivered from a PowerPanel. - """ - power_panel = models.ForeignKey( - to='PowerPanel', - on_delete=models.PROTECT, - related_name='powerfeeds' - ) - rack = models.ForeignKey( - to='Rack', - on_delete=models.PROTECT, - blank=True, - null=True - ) - connected_endpoint = models.OneToOneField( - to='dcim.PowerPort', - on_delete=models.SET_NULL, - related_name='+', - blank=True, - null=True - ) - connection_status = models.NullBooleanField( - choices=CONNECTION_STATUS_CHOICES, - blank=True - ) - name = models.CharField( - max_length=50 - ) - status = models.PositiveSmallIntegerField( - choices=POWERFEED_STATUS_CHOICES, - default=POWERFEED_STATUS_ACTIVE - ) - type = models.PositiveSmallIntegerField( - choices=POWERFEED_TYPE_CHOICES, - default=POWERFEED_TYPE_PRIMARY - ) - supply = models.PositiveSmallIntegerField( - choices=POWERFEED_SUPPLY_CHOICES, - default=POWERFEED_SUPPLY_AC - ) - phase = models.PositiveSmallIntegerField( - choices=POWERFEED_PHASE_CHOICES, - default=POWERFEED_PHASE_SINGLE - ) - voltage = models.PositiveSmallIntegerField( - validators=[MinValueValidator(1)], - default=120 - ) - amperage = models.PositiveSmallIntegerField( - validators=[MinValueValidator(1)], - default=20 - ) - max_utilization = models.PositiveSmallIntegerField( - validators=[MinValueValidator(1), MaxValueValidator(100)], - default=80, - help_text="Maximum permissible draw (percentage)" - ) - available_power = models.PositiveSmallIntegerField( - default=0, - editable=False - ) - comments = models.TextField( - blank=True - ) - custom_field_values = GenericRelation( - to='extras.CustomFieldValue', - content_type_field='obj_type', - object_id_field='obj_id' - ) - - tags = TaggableManager(through=TaggedItem) - - csv_headers = [ - 'site', 'panel_name', 'rack_group', 'rack_name', 'name', 'status', 'type', 'supply', 'phase', 'voltage', - 'amperage', 'max_utilization', 'comments', - ] - - class Meta: - ordering = ['power_panel', 'name'] - unique_together = ['power_panel', 'name'] - - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse('dcim:powerfeed', args=[self.pk]) - - def to_csv(self): - return ( - self.power_panel.site.name, - self.power_panel.name, - self.rack.group.name if self.rack and self.rack.group else None, - self.rack.name if self.rack else None, - self.name, - self.get_status_display(), - self.get_type_display(), - self.get_supply_display(), - self.get_phase_display(), - self.voltage, - self.amperage, - self.max_utilization, - self.comments, - ) - - def clean(self): - - # Rack must belong to same Site as PowerPanel - if self.rack and self.rack.site != self.power_panel.site: - raise ValidationError("Rack {} ({}) and power panel {} ({}) are in different sites".format( - self.rack, self.rack.site, self.power_panel, self.power_panel.site - )) - - def save(self, *args, **kwargs): - - # Cache the available_power property on the instance - kva = self.voltage * self.amperage * (self.max_utilization / 100) - if self.phase == POWERFEED_PHASE_3PHASE: - self.available_power = round(kva * 1.732) - else: - self.available_power = round(kva) - - super().save(*args, **kwargs) - - def get_type_class(self): - return STATUS_CLASSES[self.type] - - def get_status_class(self): - return STATUS_CLASSES[self.status] From 98706bb9272257f82319533b6eed1cd309b7a206 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 20:10:51 +0000 Subject: [PATCH 75/98] Fixes #3393: Paginate circuits at the provider details view --- docs/release-notes/version-2.6.md | 1 + netbox/circuits/views.py | 12 +++++- netbox/templates/circuits/provider.html | 54 +------------------------ 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index ee6ea2e4d..7d6609863 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -9,6 +9,7 @@ * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations * [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace +* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms ## Bug Fixes diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 655b714d7..73b3e5d3e 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin @@ -5,9 +6,11 @@ from django.db import transaction from django.db.models import Count, OuterRef, Subquery from django.shortcuts import get_object_or_404, redirect, render from django.views.generic import View +from django_tables2 import RequestConfig from extras.models import Graph, GRAPH_TYPE_PROVIDER from utilities.forms import ConfirmationForm +from utilities.paginator import EnhancedPaginator from utilities.views import ( BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) @@ -36,11 +39,18 @@ class ProviderView(PermissionRequiredMixin, View): provider = get_object_or_404(Provider, slug=slug) circuits = Circuit.objects.filter(provider=provider).prefetch_related('type', 'tenant', 'terminations__site') + circuits_table = tables.CircuitTable(circuits, orderable=False) show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists() + paginate = { + 'paginator_class': EnhancedPaginator, + 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) + } + RequestConfig(request, paginate).configure(circuits_table) + return render(request, 'circuits/provider.html', { 'provider': provider, - 'circuits': circuits, + 'circuits_table': circuits_table, 'show_graphs': show_graphs, }) diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index a83a5337a..178e488d8 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -125,58 +125,7 @@
Circuits
-
- - - - - - - - - {% for c in circuits %} - - - - - - - - - {% empty %} - - - - {% endfor %} -
Circuit IDTypeTenantA SideZ SideDescription
- {{ c.cid }} - - {{ c.type }} - - {% if c.tenant %} - {{ c.tenant }} - {% else %} - - {% endif %} - - {% if c.termination_a %} - {{ c.termination_a.site }} - {% else %} - - {% endif %} - - {% if c.termination_z %} - {{ c.termination_z.site }} - {% else %} - - {% endif %} - - {% if c.description %} - {{ c.description }} - {% else %} - - {% endif %} -
None
+ {% include 'inc/table.html' with table=circuits_table %} {% if perms.circuits.add_circuit %} {% endif %} + {% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %} {% include 'inc/modal.html' with modal_name='graphs' %} From 6440dd6211c0e73b6ecc92ce2b7f8e2ba020e5e8 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 20:15:22 +0000 Subject: [PATCH 76/98] Hid the provider column --- netbox/circuits/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 73b3e5d3e..5d76e38ee 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -39,9 +39,11 @@ class ProviderView(PermissionRequiredMixin, View): provider = get_object_or_404(Provider, slug=slug) circuits = Circuit.objects.filter(provider=provider).prefetch_related('type', 'tenant', 'terminations__site') - circuits_table = tables.CircuitTable(circuits, orderable=False) show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists() + circuits_table = tables.CircuitTable(circuits, orderable=False) + circuits_table.columns.hide('provider') + paginate = { 'paginator_class': EnhancedPaginator, 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) From d564de0b8eed4908bce9456282df2dbda14fd0bc Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 20:23:16 +0000 Subject: [PATCH 77/98] Corrected placement of changelog --- docs/release-notes/version-2.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 7d6609863..f4691f34e 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -8,8 +8,8 @@ * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations -* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace * [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view +* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms ## Bug Fixes From 6f9fcc741e1fc054ff19cf028f9a005959bfe875 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 21:12:35 +0000 Subject: [PATCH 78/98] Fixes #3876: set min and max values for ASN field --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/fields.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index ee6ea2e4d..abc65f12c 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -21,6 +21,7 @@ * [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names * [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks * [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address +* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page --- diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index 9624ce0a3..d8fef0d53 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -11,6 +11,11 @@ class ASNField(models.BigIntegerField): MaxValueValidator(4294967295), ] + def formfield(self, **kwargs): + defaults = {'min_value': 1, 'max_value': 4294967295} + defaults.update(**kwargs) + return super().formfield(**defaults) + class mac_unix_expanded_uppercase(mac_unix_expanded): word_fmt = '%.2X' From 6f89c9a54d36738dffc9af88bae65bf292ba2642 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Thu, 9 Jan 2020 21:58:38 +0000 Subject: [PATCH 79/98] Replaced ASN bounds with constants --- netbox/dcim/constants.py | 4 ++++ netbox/dcim/fields.py | 8 +++++--- netbox/dcim/forms.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index ccaa48636..f325e34d4 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -1,4 +1,8 @@ +# BGP ASN bounds +BGP_ASN_MIN = 1 +BGP_ASN_MAX = 2**32 - 1 + # Rack types RACK_TYPE_2POST = 100 RACK_TYPE_4POST = 200 diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index d8fef0d53..719b6755a 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -3,16 +3,18 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from netaddr import AddrFormatError, EUI, mac_unix_expanded +from .constants import * + class ASNField(models.BigIntegerField): description = "32-bit ASN field" default_validators = [ - MinValueValidator(1), - MaxValueValidator(4294967295), + MinValueValidator(BGP_ASN_MIN), + MaxValueValidator(BGP_ASN_MAX), ] def formfield(self, **kwargs): - defaults = {'min_value': 1, 'max_value': 4294967295} + defaults = {'min_value': BGP_ASN_MIN, 'max_value': BGP_ASN_MAX} defaults.update(**kwargs) return super().formfield(**defaults) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index dbb9cff15..6086491d0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -292,8 +292,8 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor ) ) asn = forms.IntegerField( - min_value=1, - max_value=4294967295, + min_value=BGP_ASN_MIN, + max_value=BGP_ASN_MAX, required=False, label='ASN' ) From bd95503ffaba40675520e501c6f8d949101fa9b5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Jan 2020 20:13:21 -0500 Subject: [PATCH 80/98] Add configuration file for GitHub Stale bot --- .github/lock.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/lock.yml diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 000000000..36a41b04e --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,23 @@ +# Configuration for Lock (https://github.com/apps/lock) + +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 90 + +# Skip issues and pull requests created before a given timestamp. Timestamp must +# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable +skipCreatedBefore: 2020-01-01 + +# Issues and pull requests with these labels will be ignored. Set to `[]` to disable +exemptLabels: [] + +# Label to add before locking, such as `outdated`. Set to `false` to disable +lockLabel: false + +# Comment to post before locking. Set to `false` to disable +lockComment: false + +# Assign `resolved` as the reason for locking. Set to `false` to disable +setLockReason: true + +# Limit to only `issues` or `pulls` +# only: issues From bc86a994a6f44f53e8985436e501a31a09bc98e0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Jan 2020 20:14:31 -0500 Subject: [PATCH 81/98] Clean up Stale bot config formatting --- .github/stale.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/stale.yml b/.github/stale.yml index 7c8d03f12..61201cc4e 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,20 +1,27 @@ +# Configuration for Stale (https://github.com/apps/stale) + # Number of days of inactivity before an issue becomes stale daysUntilStale: 14 + # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 + # Issues with these labels will never be considered stale exemptLabels: - "status: accepted" - "status: gathering feedback" - "status: blocked" + # Label to use when marking an issue as stale staleLabel: wontfix + # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md). + # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > This issue has been automatically closed due to lack of activity. In an From 14d0b741d5840267f9ee8c0dfbe6db5347afcd07 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 10:26:46 +0000 Subject: [PATCH 82/98] Removed redundant list call --- netbox/utilities/tests/test_forms.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index 0434ff137..1fd09341c 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -14,7 +14,7 @@ class ExpandIPAddress(TestCase): '1.2.3.10/32', ]) - self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) def test_ipv4_set(self): input = '1.2.3.[4,44]/32' @@ -23,7 +23,7 @@ class ExpandIPAddress(TestCase): '1.2.3.44/32', ]) - self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) def test_ipv4_multiple_ranges(self): input = '1.[9-10].3.[9-11]/32' @@ -36,7 +36,7 @@ class ExpandIPAddress(TestCase): '1.10.3.11/32', ]) - self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) def test_ipv4_multiple_sets(self): input = '1.[2,22].3.[4,44]/32' @@ -47,8 +47,7 @@ class ExpandIPAddress(TestCase): '1.22.3.44/32', ]) - self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) - + self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) def test_ipv4_set_and_range(self): input = '1.[2,22].3.[9-11]/32' @@ -61,7 +60,7 @@ class ExpandIPAddress(TestCase): '1.22.3.11/32', ]) - self.assertEqual(sorted(list(expand_ipaddress_pattern(input, 4))), output) + self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) # TODO: IPv6 # TODO: negative tests From 260a26e1b0b3a3c5589e211962c26af904d19492 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 11:06:01 +0000 Subject: [PATCH 83/98] Added tests for IPv6 --- netbox/utilities/tests/test_forms.py | 67 +++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index 1fd09341c..ea693f42c 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -62,7 +62,72 @@ class ExpandIPAddress(TestCase): self.assertEqual(sorted(expand_ipaddress_pattern(input, 4)), output) - # TODO: IPv6 + def test_ipv6_range(self): + input = 'fec::abcd:[9-b]/64' + output = sorted([ + 'fec::abcd:9/64', + 'fec::abcd:a/64', + 'fec::abcd:b/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + + def test_ipv6_range_multichar_field(self): + input = 'fec::abcd:[f-11]/64' + output = sorted([ + 'fec::abcd:f/64', + 'fec::abcd:10/64', + 'fec::abcd:11/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + + def test_ipv6_set(self): + input = 'fec::abcd:[9,ab]/64' + output = sorted([ + 'fec::abcd:9/64', + 'fec::abcd:ab/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + + def test_ipv6_multiple_ranges(self): + input = 'fec::[1-2]bcd:[9-b]/64' + output = sorted([ + 'fec::1bcd:9/64', + 'fec::1bcd:a/64', + 'fec::1bcd:b/64', + 'fec::2bcd:9/64', + 'fec::2bcd:a/64', + 'fec::2bcd:b/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + + def test_ipv6_multiple_sets(self): + input = 'fec::[a,f]bcd:[9,ab]/64' + output = sorted([ + 'fec::abcd:9/64', + 'fec::abcd:ab/64', + 'fec::fbcd:9/64', + 'fec::fbcd:ab/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + + def test_ipv6_set_and_range(self): + input = 'fec::[dead,beaf]:[9-b]/64' + output = sorted([ + 'fec::dead:9/64', + 'fec::dead:a/64', + 'fec::dead:b/64', + 'fec::beaf:9/64', + 'fec::beaf:a/64', + 'fec::beaf:b/64', + ]) + + self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) + # TODO: negative tests # TODO: alphanumeric From c31309a013bedca63fb8f54ad4d649b1feb5f88f Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 11:21:37 +0000 Subject: [PATCH 84/98] Negative tests for expand_ipaddress_pattern --- netbox/utilities/tests/test_forms.py | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index ea693f42c..7bad34e3c 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -128,6 +128,38 @@ class ExpandIPAddress(TestCase): self.assertEqual(sorted(expand_ipaddress_pattern(input, 6)), output) - # TODO: negative tests + def test_invalid_address_family(self): + with self.assertRaisesRegex(Exception, 'Invalid IP address family: 5'): + sorted(expand_ipaddress_pattern(None, 5)) + + def test_invalid_non_pattern(self): + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.4/32', 4)) + + def test_invalid_range(self): + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[4-]/32', 4)) + + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[-4]/32', 4)) + + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[4--5]/32', 4)) + + def test_invalid_range_bounds(self): + self.assertEqual(sorted(expand_ipaddress_pattern('1.2.3.[4-3]/32', 6)), []) + + def test_invalid_set(self): + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[4]/32', 4)) + + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[4,]/32', 4)) + + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[,4]/32', 4)) + + with self.assertRaises(ValueError): + sorted(expand_ipaddress_pattern('1.2.3.[4,,5]/32', 4)) # TODO: alphanumeric From 86433215a4379ef7e541f8f1c3caadda244ccde1 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 11:54:43 +0000 Subject: [PATCH 85/98] Added tests for alphanumeric --- netbox/utilities/tests/test_forms.py | 120 ++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index 7bad34e3c..2d7235505 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -1,3 +1,4 @@ +from django import forms from django.test import TestCase from utilities.forms import * @@ -162,4 +163,121 @@ class ExpandIPAddress(TestCase): with self.assertRaises(ValueError): sorted(expand_ipaddress_pattern('1.2.3.[4,,5]/32', 4)) -# TODO: alphanumeric + +class ExpandAlphanumeric(TestCase): + """ + Validate the operation of expand_alphanumeric_pattern(). + """ + def test_range_numberic(self): + input = 'r[9-11]a' + output = sorted([ + 'r9a', + 'r10a', + 'r11a', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_range_alpha(self): + input = '[r-t]1a' + output = sorted([ + 'r1a', + 's1a', + 't1a', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_set(self): + input = '[r,t]1a' + output = sorted([ + 'r1a', + 't1a', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_set_multichar(self): + input = '[ra,tb]1a' + output = sorted([ + 'ra1a', + 'tb1a', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_multiple_ranges(self): + input = '[r-t]1[a-b]' + output = sorted([ + 'r1a', + 'r1b', + 's1a', + 's1b', + 't1a', + 't1b', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_multiple_sets(self): + input = '[ra,tb]1[ax,by]' + output = sorted([ + 'ra1ax', + 'ra1by', + 'tb1ax', + 'tb1by', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_set_and_range(self): + input = '[ra,tb]1[a-c]' + output = sorted([ + 'ra1a', + 'ra1b', + 'ra1c', + 'tb1a', + 'tb1b', + 'tb1c', + ]) + + self.assertEqual(sorted(expand_alphanumeric_pattern(input)), output) + + def test_invalid_non_pattern(self): + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r9a')) + + def test_invalid_range(self): + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[8-]a')) + + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[-8]a')) + + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[8--9]a')) + + def test_invalid_range_alphanumeric(self): + self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-a]a')), []) + self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), []) + + def test_invalid_range_bounds(self): + self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), []) + self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), []) + + def test_invalid_range_len(self): + with self.assertRaises(forms.ValidationError): + sorted(expand_alphanumeric_pattern('r[a-bb]a')) + + def test_invalid_set(self): + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[a]a')) + + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[a,]a')) + + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[,a]a')) + + with self.assertRaises(ValueError): + sorted(expand_alphanumeric_pattern('r[a,,b]a')) From df9b175d559d7bf65738aab0f1819589e18a3df9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Jan 2020 10:21:11 -0500 Subject: [PATCH 86/98] Fixes #3882: Fix filtering of devices by rack group --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/forms.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 025994856..d7832a823 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -26,6 +26,7 @@ * [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks * [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address * [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page +* [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group --- diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 6086491d0..f0b91c2f5 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -739,7 +739,7 @@ class RackElevationFilterForm(RackFilterForm): # Filter the rack field based on the site and group self.fields['site'].widget.add_filter_for('id', 'site') - self.fields['group_id'].widget.add_filter_for('id', 'group_id') + self.fields['rack_group_id'].widget.add_filter_for('id', 'group_id') # @@ -1791,7 +1791,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm): model = Device field_order = [ - 'q', 'region', 'site', 'group_id', 'rack_id', 'status', 'role', 'tenant_group', 'tenant', + 'q', 'region', 'site', 'rack_group_id', 'rack_id', 'status', 'role', 'tenant_group', 'tenant', 'manufacturer_id', 'device_type_id', 'mac_address', 'has_primary_ip', ] q = forms.CharField( @@ -1817,12 +1817,12 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt api_url="/api/dcim/sites/", value_field="slug", filter_for={ - 'group_id': 'site', + 'rack_group_id': 'site', 'rack_id': 'site', } ) ) - group_id = FilterChoiceField( + rack_group_id = FilterChoiceField( queryset=RackGroup.objects.prefetch_related( 'site' ), From 6554f66a2091f8e558c4b2733080cffbba02f58a Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 15:59:56 +0000 Subject: [PATCH 87/98] Fixes #3021: Added tenancy filter to cables --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/filters.py | 8 ++++++++ netbox/dcim/forms.py | 11 +++++++++++ netbox/dcim/tests/test_filters.py | 20 +++++++++++++++++--- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 025994856..b6caff051 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -7,6 +7,7 @@ * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers * [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses * [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address +* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add tenant filter field for cables * [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations * [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 9d6a0ae6a..29604491d 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1050,6 +1050,14 @@ class CableFilter(django_filters.FilterSet): method='filter_device', field_name='device__site__slug' ) + tenant_id = MultiValueNumberFilter( + method='filter_device', + field_name='device__tenant_id' + ) + tenant = MultiValueNumberFilter( + method='filter_device', + field_name='device__tenant__slug' + ) class Meta: model = Cable diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 6086491d0..42b8796a9 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -3119,6 +3119,17 @@ class CableFilterForm(BootstrapMixin, forms.Form): } ) ) + tenant = FilterChoiceField( + queryset=Tenant.objects.all(), + to_field_name='slug', + widget=APISelectMultiple( + api_url="/api/tenancy/tenants/", + value_field='slug', + filter_for={ + 'device_id': 'tenant', + } + ) + ) rack_id = FilterChoiceField( queryset=Rack.objects.all(), label='Rack', diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 1feacc5c5..9b8551ab4 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -11,6 +11,7 @@ from dcim.models import ( VirtualChassis, ) from ipam.models import IPAddress +from tenancy.models import Tenant from virtualization.models import Cluster, ClusterType @@ -2121,6 +2122,12 @@ class CableTestCase(TestCase): ) Site.objects.bulk_create(sites) + tenants = ( + Tenant(name='Tenant 1', slug='tenant-1'), + Tenant(name='Tenant 2', slug='tenant-2'), + ) + Tenant.objects.bulk_create(tenants) + racks = ( Rack(name='Rack 1', site=sites[0]), Rack(name='Rack 2', site=sites[1]), @@ -2133,9 +2140,9 @@ class CableTestCase(TestCase): device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1), + Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1, tenant=tenants[0]), + Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2, tenant=tenants[0]), + Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1, tenant=tenants[1]), Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2), Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1), Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2), @@ -2216,6 +2223,13 @@ class CableTestCase(TestCase): params = {'site': [site[0].slug, site[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + def test_tenant(self): + tenant = Tenant.objects.all()[:2] + params = {'tenant_id': [tenant[0].pk, tenant[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'tenant': [tenant[0].slug, tenant[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + class PowerPanelTestCase(TestCase): queryset = PowerPanel.objects.all() From 692ad7b57a36fe631b3885ff68b05cb678ba0d73 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Fri, 10 Jan 2020 16:42:02 +0000 Subject: [PATCH 88/98] Fixes #3491: include content of webhook error response --- docs/release-notes/version-2.6.md | 1 + netbox/extras/webhooks_worker.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 025994856..325ac2234 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -11,6 +11,7 @@ * [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations * [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view * [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace +* [#3491](https://github.com/netbox-community/netbox/issues/3491) - Include content of response on webhook error * [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation * [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms diff --git a/netbox/extras/webhooks_worker.py b/netbox/extras/webhooks_worker.py index 9a637e852..d41d795c6 100644 --- a/netbox/extras/webhooks_worker.py +++ b/netbox/extras/webhooks_worker.py @@ -60,5 +60,5 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque return 'Status {} returned, webhook successfully processed.'.format(response.status_code) else: raise requests.exceptions.RequestException( - "Status {} returned, webhook FAILED to process.".format(response.status_code) + "Status {} returned with content '{}', webhook FAILED to process.".format(response.status_code, response.content) ) From d0139e064c8b2425708a4d33d58706acdb3c7156 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Jan 2020 12:24:47 -0500 Subject: [PATCH 89/98] Extend section regarding test adaptation --- docs/development/extending-models.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/development/extending-models.md b/docs/development/extending-models.md index 0070c5545..dd44bb2ab 100644 --- a/docs/development/extending-models.md +++ b/docs/development/extending-models.md @@ -69,6 +69,14 @@ If the new field will be included in the object list view, add a column to the m Edit the object's view template to display the new field. There may also be a custom add/edit form template that needs to be updated. -### 11. Adjust API and model tests +### 11. Create/extend test cases -Extend the model and/or API tests to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields. +Create or extend the relevant test cases to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields. NetBox incorporates various test suites, including: + +* API serializer/view tests +* Filter tests +* Form tests +* Model tests +* View tests + +Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality. From 4dcbde76fca4bae30283ae2296508f58cfaf35d3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Jan 2020 15:34:38 -0500 Subject: [PATCH 90/98] Closes #3891: Add local_context_data filter for virtual machines --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/tests/test_filters.py | 8 +++++++- netbox/virtualization/filters.py | 4 ++-- netbox/virtualization/tests/test_filters.py | 8 +++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index d7832a823..88cd9c120 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -14,6 +14,7 @@ * [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation * [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms +* [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines ## Bug Fixes diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 1feacc5c5..84b357d27 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -1100,7 +1100,7 @@ class DeviceTestCase(TestCase): Cluster.objects.bulk_create(clusters) devices = ( - Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE, cluster=clusters[0]), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE, cluster=clusters[0], local_context_data={"foo": 123}), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=RACK_FACE_FRONT, status=DEVICE_STATUS_STAGED, cluster=clusters[1]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=RACK_FACE_REAR, status=DEVICE_STATUS_FAILED, cluster=clusters[2]), ) @@ -1328,6 +1328,12 @@ class DeviceTestCase(TestCase): # params = {'device_bays': 'false'} # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + def test_local_context_data(self): + params = {'local_context_data': 'true'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'local_context_data': 'false'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ConsolePortTestCase(TestCase): queryset = ConsolePort.objects.all() diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 9c8de86ce..c75de93e0 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -2,7 +2,7 @@ import django_filters from django.db.models import Q from dcim.models import DeviceRole, Interface, Platform, Region, Site -from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet +from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilter from tenancy.filtersets import TenancyFilterSet from utilities.filters import ( MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, @@ -99,7 +99,7 @@ class ClusterFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet): ) -class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): +class VirtualMachineFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet): id__in = NumericInFilter( field_name='id', lookup_expr='in' diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filters.py index 6804a9a70..c892be2da 100644 --- a/netbox/virtualization/tests/test_filters.py +++ b/netbox/virtualization/tests/test_filters.py @@ -203,7 +203,7 @@ class VirtualMachineTestCase(TestCase): DeviceRole.objects.bulk_create(roles) vms = ( - VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], status=DEVICE_STATUS_ACTIVE, vcpus=1, memory=1, disk=1), + VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], status=DEVICE_STATUS_ACTIVE, vcpus=1, memory=1, disk=1, local_context_data={"foo": 123}), VirtualMachine(name='Virtual Machine 2', cluster=clusters[1], platform=platforms[1], role=roles[1], status=DEVICE_STATUS_STAGED, vcpus=2, memory=2, disk=2), VirtualMachine(name='Virtual Machine 3', cluster=clusters[2], platform=platforms[2], role=roles[2], status=DEVICE_STATUS_OFFLINE, vcpus=3, memory=3, disk=3), ) @@ -300,6 +300,12 @@ class VirtualMachineTestCase(TestCase): params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_local_context_data(self): + params = {'local_context_data': 'true'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'local_context_data': 'false'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class InterfaceTestCase(TestCase): queryset = Interface.objects.all() From 2d5819eca3b4fbc365bb0ed6a2ef72e89193bd24 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sat, 11 Jan 2020 15:36:58 +0000 Subject: [PATCH 91/98] Fixes #3895: Elevations filter regression --- netbox/dcim/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index f0b91c2f5..e85468445 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -739,7 +739,7 @@ class RackElevationFilterForm(RackFilterForm): # Filter the rack field based on the site and group self.fields['site'].widget.add_filter_for('id', 'site') - self.fields['rack_group_id'].widget.add_filter_for('id', 'group_id') + self.fields['group_id'].widget.add_filter_for('id', 'group_id') # From 35995967f9b0b0338a56cc08c3d9b09db6b14656 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sun, 12 Jan 2020 11:08:13 +0000 Subject: [PATCH 92/98] Fixes #3898: Call str of cable on delete to save PK in id_string --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/models.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 88cd9c120..c4d8f1414 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -28,6 +28,7 @@ * [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address * [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group +* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix deleted message being set to None for cable --- diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 69c3c3475..89ff8bc15 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -3040,6 +3040,11 @@ class Cable(ChangeLoggedModel): def get_absolute_url(self): return reverse('dcim:cable', args=[self.pk]) + def delete(self, *args, **kwargs): + # Trigger the __str__ method to save the pk into `self.id_string` + str(self) + super().delete(*args, **kwargs) + def clean(self): # Validate that termination A exists From 863a919a631a9ec2c11eca73c3d6dae83ea04ebb Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Sun, 12 Jan 2020 11:21:02 +0000 Subject: [PATCH 93/98] Added post-delete cable ID test --- netbox/dcim/tests/test_models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 2b5bed283..eba81b136 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -325,9 +325,12 @@ class CableTestCase(TestCase): def test_cable_deletion(self): """ - When a Cable is deleted, the `cable` field on its termination points must be nullified. + When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method + should still return the PK of the string even after being nullified. """ self.cable.delete() + self.assertIsNone(self.cable.pk) + self.assertNotEqual(str(self.cable), '#None') interface1 = Interface.objects.get(pk=self.interface1.pk) self.assertIsNone(interface1.cable) interface2 = Interface.objects.get(pk=self.interface2.pk) From 6fc0c6e866169e0369fa77079ea0e15a98a4bfc7 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 12:05:06 +0000 Subject: [PATCH 94/98] Fixes #3902: relax non-essential required fields --- docs/release-notes/version-2.6.md | 1 + netbox/dcim/forms.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 88cd9c120..c83fc8173 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -15,6 +15,7 @@ * [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms * [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines +* [#3902](https://github.com/netbox-community/netbox/issues/3902) - Relax the non-essential required fields when connecting cable to circuit or power feed ## Bug Fixes diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index f0b91c2f5..14c997b30 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2804,6 +2804,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f termination_b_provider = forms.ModelChoiceField( queryset=Provider.objects.all(), label='Provider', + required=False, widget=APISelect( api_url='/api/circuits/providers/', filter_for={ @@ -2857,6 +2858,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode termination_b_site = forms.ModelChoiceField( queryset=Site.objects.all(), label='Site', + required=False, widget=APISelect( api_url='/api/dcim/sites/', display_field='cid', @@ -2888,6 +2890,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode ('rack_group', 'termination_b_rackgroup'), ), label='Power Panel', + required=False, widget=APISelect( api_url='/api/dcim/power-panels/', filter_for={ From c82de22ca6174894a3edf1f25a2d05b0ec62d891 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 14:57:21 +0000 Subject: [PATCH 95/98] Store a private copy of the pk during init and use that with __str__ --- netbox/dcim/models.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 89ff8bc15..d7c85ff00 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -3027,24 +3027,18 @@ class Cable(ChangeLoggedModel): ('termination_b_type', 'termination_b_id'), ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # A copy of the PK to be used by __str__ in case the object is deleted + self._pk = self.pk + def __str__(self): - if self.label: - return self.label - - # Save a copy of the PK on the instance since it's nullified if .delete() is called - if not hasattr(self, 'id_string'): - self.id_string = '#{}'.format(self.pk) - - return self.id_string + return self.label or '#{}'.format(self._pk) def get_absolute_url(self): return reverse('dcim:cable', args=[self.pk]) - def delete(self, *args, **kwargs): - # Trigger the __str__ method to save the pk into `self.id_string` - str(self) - super().delete(*args, **kwargs) - def clean(self): # Validate that termination A exists From 724b49865315e5f5752c0f3a407ca8db6a19c9d1 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 15:21:37 +0000 Subject: [PATCH 96/98] Set the private pk after super save --- netbox/dcim/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index d7c85ff00..833fb483b 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -3141,6 +3141,9 @@ class Cable(ChangeLoggedModel): super().save(*args, **kwargs) + # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) + self._pk = self.pk + def to_csv(self): return ( '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model), From 195c6447173b32d9e158b08e4d2881ddddd5afe5 Mon Sep 17 00:00:00 2001 From: Saria Hajjar Date: Mon, 13 Jan 2020 15:31:35 +0000 Subject: [PATCH 97/98] Fixes #3905: divide by zero on power feeds with low values --- docs/release-notes/version-2.6.md | 1 + netbox/templates/dcim/powerfeed.html | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 88cd9c120..cffaa1a41 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -28,6 +28,7 @@ * [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address * [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group +* [#3905](https://github.com/netbox-community/netbox/issues/3905) - Fix divide-by-zero on power feeds with low power values --- diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index a8ab302eb..4e88f09c9 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -112,7 +112,9 @@ {% if utilization %} {{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA - {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %} + {% if powerfeed.available_power > 0 %} + {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %} + {% endif %} {% else %} N/A From 0f1fc6cb52f5318d32ebf2372b9a8fed16598a95 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 13 Jan 2020 13:17:43 -0500 Subject: [PATCH 98/98] Release v2.6.12 --- docs/release-notes/version-2.6.md | 35 +++++++++++++++---------------- netbox/netbox/settings.py | 2 +- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md index 8565609c6..d77fbd365 100644 --- a/docs/release-notes/version-2.6.md +++ b/docs/release-notes/version-2.6.md @@ -1,23 +1,22 @@ -# v2.6.12 (FUTURE) +# v2.6.12 (2020-01-13) ## Enhancements -* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger -* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link +* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger (OpenAPI) +* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering over the link * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers -* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses -* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address -* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add tenant filter field for cables -* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces -* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations -* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view -* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace +* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle the display of child prefixes/IP addresses +* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address to interfaces +* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add `tenant` filter field for cables +* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Enable filtering of interfaces by name on the device view +* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations view +* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate assigned circuits at the provider details view +* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total path length to cable trace * [#3491](https://github.com/netbox-community/netbox/issues/3491) - Include content of response on webhook error -* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation -* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address +* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Enable word expansion during interface creation +* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Enable searching by DNS name when assigning IP address * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms * [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines -* [#3902](https://github.com/netbox-community/netbox/issues/3902) - Relax the non-essential required fields when connecting cable to circuit or power feed ## Bug Fixes @@ -25,13 +24,13 @@ * [#3849](https://github.com/netbox-community/netbox/issues/3849) - Fix ordering of models when dumping data to JSON * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses -* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering +* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix rendering of grouped custom links * [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names -* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks -* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address -* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page +* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks for prefixes and IP addresses +* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs on the IP address view +* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fix minimum/maximum value rendering for site ASN field * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group -* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix deleted message being set to None for cable +* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix references to deleted cables without a label * [#3905](https://github.com/netbox-community/netbox/issues/3905) - Fix divide-by-zero on power feeds with low power values --- diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 75f67e2a6..21a9dbae5 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured # Environment setup # -VERSION = '2.6.12-dev' +VERSION = '2.6.12' # Hostname HOSTNAME = platform.node()