From 05570ae4ade98eca869a07767f932af23d06e3a6 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 aa4f73ffbfa3c23a5d64595753f18a32fc672dfd 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 a110b0badbd8370e06ce8c0edfb1d82640a6bebe 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 d267aeb62112015db2f4b1cdf90afda348bfd700 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 1a57120b789435b1bc27139e0563bfe7a984794a 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 7ad9e8a2fbd5e655143a7a40d2d4b41765dd1f4d 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 37bc17d3a28ca1149cd2bded2213f877daadce99 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 f81641ae961e0a776f8a298285c2d45f0070ec73 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 2a219eff23332bd9e4429340033f10e32cb7bd07 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 8e3a37168848fa7e13c6fd2fba6051d9c905ecc3 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 28ae6849b4fe276e775a0bd745c67be2ebb69ead 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 d3c6caf8a8db5cc643f0f5ed7fab2c78dff97c28 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 feb04f0401edacc687bc5a956df1708853d8e49c 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 9d8ab81e3adb23f50f942d5b1eea523996edbefd 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 e1c61c5019e79fc6f17cfd31a852202d1e67786a 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 04f3e58ab4d1ca920edef902ad5c68c204b9dc4a 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 e1d1f522ff07c573979eb494c02c9befb07cd202 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 3556051d14b71de5785339976b1a992d8c2c115d 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 28eca9a02606e4ee11dde3b6b157ca20c5dc2396 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 4b19073b8becd42d0d3a0622114b1ef9e46f9450 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 dba40cd6bc124629319da89b06bf7c3a839be7f5 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 792f38334a61ab2ee1004148f60dac0dab847939 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 067af26892f4a3a92814150bead550a5964f5ae0 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 c45daca5f2376ce5b061b232f6c3a80b40aa6a0e 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 7dddd4734ce5225aad0bcf1fecbf7d9c7aff1d70 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 07a1baef13e0a4251f7aa28db4a4ce40a5193b16 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 4b5c4b7be5092a26ef321209f1b92f9c14b6d3c0 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 8d0aaa4ec1e19a48b5df21e8da9a81286294c459 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 05bfe94d3e4ef1bfc0014ce1445378440029fe99 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 a7ec0c14f75fd201efb7c408d40aaed78aea94a3 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 6a3cd83efcecc21a157cca164a7913852158597d 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 e7ee4486a5ac99e996ae36e95751297157f02cc5 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 f267a532f6e39da4e15102292a930d61ad691472 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 39d0261d8a325be1c6d50d3a47e0563f3dcdfa8e 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 227921e0a0aee15cf7e550126f2ff49596a66760 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 770f4c962cf4bfb5d44bf7cc6ac5f1e080e0cd7f 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 190e68365468551e729e7c1b0a4830561be19fc0 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 32f39e10c9f94fe079cf31a1f96020e59346bfb5 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 acb2f323043d02195654c48c3f8439a18de29c11 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 832fd49339f32fa2e07a05cc14096b3c7988262a 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 74997a18a54284a38cbcd5b216a870679b73eb0b 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 996d49de670159d62b6ca82c76830901b3f702c2 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 ea05b5b60614940561931d52bd06e5f5735bfcf1 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 a7982bb0e136476d502a284ec21ca9ee9423f526 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 dc475f4755a6ab8d77e60de40e9142c795581317 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 ce8d47086033cee2e707bb27f769eb9d6d1dd36e 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 98a66f7fbe77ef849ce0b27bb9dca80a7edc36c5 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 f49467bcb5370f6bd39cbc786328874981c88c71 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 5cac900380ddf4ef1a91ad6ced08577ec91902a1 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 8fef6edb27416f12f29c67cf0539721e4ef2a831 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 39fba4f05de0618bb5fa55d877719502bd155ba9 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 e9b2ad9f5ca6d5911192d8467da51938aa1ee486 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 eb40275427047b1007b9a73d14c5d087ac6a7021 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 396bb28967b040247eea67c92ab7ea47a67a9c62 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 bca7435a5a0958fcd2973128e3357d190039e293 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 40c30baffa310a40a6652f3eab7dcba675dc4b6a 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 e312c30822e7ddd99d41448bcc89a31b1b6e8283 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 c04d8ca5a7548aa0c42dc0b1ec6155c795f14626 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 b1e8145ffbbb2ec1080b53ca7479b12356cbd2d1 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 4151e528023c1e0f3f9116156f83615f8cc20471 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 4030e5ec24e972fadc63bcad2766d493758e9fb7 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 1cdbfd6d60ab5cefb6cd3f8497fb3f565618b117 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 6537f35176594e57bd3919074ffbecb868af38bd 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 54227ca9c7f6663061a59d9b130cf927b0ca4452 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 73e456495fcdb997a6c4b476480700be12a4547e 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 472486acd60e1e4c37bbba24eb3e967c95d690c0 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 581ed52b2424a3cc9f5b6dc1fd998591f1754344 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 86865b91f8e8b1a684ed5fec688d7aed4945d48a 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 46c712e735702b27e79cf2a6019963212867616b 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 ad565e55f1ee9c1a975202f1d314f78e48a0718a 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 9d085ad83a26aa90eeed4c36c123171bf6ae0c80 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 67f4d8fab51c5ff4ed8452cdf37dec305042244b 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 c13b9d87983a1fc7b3e1434e219bb85545f90513 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 e5c5a1a101373e8dc1dcc42eb0cd69ce7aa2ed59 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 883655ce71cf69237f5f583749ebd2e12a335781 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 94a7d8e49360dd3debefefab9628d0f01e13b5d2 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 1e740a70f766fbde6304246002dfd830dbe06889 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 4eacc57522d3bdb4e5e3c23ffca84494c70c00cd 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 6c19c88e99038bae25d9be46b8499960d0ed502a 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 d88b3456c4acaa3851ef097c2fae40738a727ff7 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 0296aa240ab710751ca74224683a35768d27c251 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 fe89982d4ef663427d91e4efe67b364ccebaa811 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 2eba84dad5e2c385ea3c3553f89ae09af7a31afd 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 acb66c7dc0f99435299bfb81ec7d626dfd632714 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 71120d9899d4f6481a07b9728c10ba3dbc0aa0bd 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 6bc8f2e50bc6e859064f24e4d78c396c70c8e0ca 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 f4514034b8a0a7a52b6686896b85eea9e652a3d8 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 f20d16f188bce3f43bcce616166ebc9dc138e20a 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 509a115f687a1242d0b10ed1d458f176b4a862f3 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 b7e78028ce6e998dc9172bd41bc602045daeb1ea 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 422c6bad5bc6b1f465e3d86195afc8bc5a5f3125 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 a2308b9c9949de742142f3a40600b5178b7216a9 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 49fa243b4f9779d05e2e6e697b685b46484a265c 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 fe0fbeab49ed345f838a74bd61eef4d265a15d90 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 3d78a673431bfe370e95a4f82a90a11e0041932d 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 608006ee776a2f6fe8e7174f196e9eb51757f19e 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 32ffc1b54b128260e627659a7816dfd13ca80e1c 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 c3c3000a5374ad4ccfeee48e4b563ccd72f5dfea 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()