diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 6919ff16f..3c52c973c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -5,21 +5,25 @@ labels: ["type: bug"] body: - type: markdown attributes: - value: "**NOTE:** This form is only for reporting _reproducible bugs_ in a - current NetBox installation. If you're having trouble with installation or just - looking for assistance with using NetBox, please visit our - [discussion forum](https://github.com/netbox-community/netbox/discussions) instead." + value: > + **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox + installation. If you're having trouble with installation or just looking for + assistance with using NetBox, please visit our + [discussion forum](https://github.com/netbox-community/netbox/discussions) instead. - type: input attributes: label: NetBox version - description: "What version of NetBox are you currently running?" - placeholder: v2.10.4 + description: > + What version of NetBox are you currently running? (If you don't have access to the most + recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/) + before opening a bug report to see if your issue has already been addressed.) + placeholder: v2.11.3 validations: required: true - type: dropdown attributes: label: Python version - description: "What version of Python are you currently running?" + description: What version of Python are you currently running? options: - 3.6 - 3.7 @@ -30,12 +34,13 @@ body: - type: textarea attributes: label: Steps to Reproduce - description: "Describe in detail the exact steps that someone else can take to - reproduce this bug using the current stable release of NetBox. Begin with the - creation of any necessary database objects and call out every operation being - performed explicitly. If reporting a bug in the REST API, be sure to reconstruct - the raw HTTP request(s) being made: Don't rely on a client library such as - pynetbox." + description: > + Describe in detail the exact steps that someone else can take to + reproduce this bug using the current stable release of NetBox. Begin with the + creation of any necessary database objects and call out every operation being + performed explicitly. If reporting a bug in the REST API, be sure to reconstruct + the raw HTTP request(s) being made: Don't rely on a client library such as + pynetbox." placeholder: | 1. Click on "create widget" 2. Set foo to 12 and bar to G @@ -45,14 +50,14 @@ body: - type: textarea attributes: label: Expected Behavior - description: "What did you expect to happen?" - placeholder: "A new widget should have been created with the specified attributes" + description: What did you expect to happen? + placeholder: A new widget should have been created with the specified attributes validations: required: true - type: textarea attributes: label: Observed Behavior - description: "What happened instead?" - placeholder: "A TypeError exception was raised" + description: What happened instead? + placeholder: A TypeError exception was raised validations: required: true diff --git a/.github/ISSUE_TEMPLATE/documentation_change.yaml b/.github/ISSUE_TEMPLATE/documentation_change.yaml index 19d9696ad..0f87115fc 100644 --- a/.github/ISSUE_TEMPLATE/documentation_change.yaml +++ b/.github/ISSUE_TEMPLATE/documentation_change.yaml @@ -30,6 +30,6 @@ body: - type: textarea attributes: label: Proposed Changes - description: "Describe the proposed changes and why they are necessary" + description: Describe the proposed changes and why they are necessary. validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 7d7bde225..9181f7ce4 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -5,15 +5,16 @@ labels: ["type: feature"] body: - type: markdown attributes: - value: "**NOTE:** This form is only for submitting well-formed proposals to extend or - modify NetBox in some way. If you're trying to solve a problem but can't figure out how, - or if you still need time to work on the details of a proposed new feature, please start - a [discussion](https://github.com/netbox-community/netbox/discussions) instead." + value: > + **NOTE:** This form is only for submitting well-formed proposals to extend or modify + NetBox in some way. If you're trying to solve a problem but can't figure out how, or if + you still need time to work on the details of a proposed new feature, please start a + [discussion](https://github.com/netbox-community/netbox/discussions) instead. - type: input attributes: label: NetBox version - description: "What version of NetBox are you currently running?" - placeholder: v2.10.4 + description: What version of NetBox are you currently running? + placeholder: v2.11.3 validations: required: true - type: dropdown @@ -28,26 +29,29 @@ body: - type: textarea attributes: label: Proposed functionality - description: "Describe in detail the new feature or behavior you'd like to propose. - Include any specific changes to work flows, data models, or the user interface." + description: > + Describe in detail the new feature or behavior you'd like to propose. Include any specific + changes to work flows, data models, or the user interface. validations: required: true - type: textarea attributes: label: Use case - description: "Explain how adding this functionality would benefit NetBox users. What - need does it address?" + description: > + Explain how adding this functionality would benefit NetBox users. What need does it address? validations: required: true - type: textarea attributes: label: Database changes - description: "Note any changes to the database schema necessary to support the new - feature. For example, does the proposal require adding a new model or field? (Not - all new features require database changes.)" + description: > + Note any changes to the database schema necessary to support the new feature. For example, + does the proposal require adding a new model or field? (Not all new features require database + changes.) - type: textarea attributes: label: External dependencies - description: "List any new dependencies on external libraries or services that this - new feature would introduce. For example, does the proposal require the installation - of a new Python package? (Not all new features introduce new dependencies.)" + description: > + List any new dependencies on external libraries or services that this new feature would + introduce. For example, does the proposal require the installation of a new Python package? + (Not all new features introduce new dependencies.) diff --git a/.github/ISSUE_TEMPLATE/housekeeping.yaml b/.github/ISSUE_TEMPLATE/housekeeping.yaml index 5e675583e..777871395 100644 --- a/.github/ISSUE_TEMPLATE/housekeeping.yaml +++ b/.github/ISSUE_TEMPLATE/housekeeping.yaml @@ -5,18 +5,20 @@ labels: ["type: housekeeping"] body: - type: markdown attributes: - value: "**NOTE:** This template is for use by maintainers only. Please do not submit - an issue using this template unless you have been specifically asked to do so." + value: > + **NOTE:** This template is for use by maintainers only. Please do not submit + an issue using this template unless you have been specifically asked to do so. - type: textarea attributes: label: Proposed Changes - description: "Describe in detail the new feature or behavior you'd like to propose. - Include any specific changes to work flows, data models, or the user interface." + description: > + Describe in detail the new feature or behavior you'd like to propose. + Include any specific changes to work flows, data models, or the user interface. validations: required: true - type: textarea attributes: label: Justification - description: "Please provide justification for the proposed change(s)." + description: Please provide justification for the proposed change(s). validations: required: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8fc85ead6..0f617e8aa 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,9 +17,10 @@ jobs: necessary. close-pr-message: > This PR has been automatically closed due to lack of activity. - days-before-stale: 45 - days-before-close: 15 + days-before-stale: 60 + days-before-close: 30 exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone' + operations-per-run: 100 remove-stale-when-updated: false stale-issue-label: 'pending closure' stale-issue-message: > diff --git a/README.md b/README.md index 237673e6b..877d8b515 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -![NetBox](docs/netbox_logo.svg "NetBox logo") +
+ NetBox logo +
NetBox is an IP address management (IPAM) and data center infrastructure management (DCIM) tool. Initially conceived by the network engineering team at @@ -10,7 +12,21 @@ NetBox runs as a web application atop the [Django](https://www.djangoproject.com Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox). -The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/). +The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/). A public demo instance is available at https://demo.netbox.dev. + +| | status | +|-------------|------------| +| **master** | ![Build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) | +| **develop** | ![Build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=develop) | + +
+

Thank you to our sponsors!

+ + [![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com/) +            + [![Stellar Technologies](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/stellar.png)](https://stellar.tech/) + +
### Discussion @@ -18,37 +34,14 @@ The complete documentation for NetBox can be found at [Read the Docs](https://ne * [Slack](https://slack.netbox.dev/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out * [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being replaced by GitHub discussions -### Build Status - -| | status | -| ----------- | ------------------------------------------------------------------------------------------------- | -| **master** | ![Build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) | -| **develop** | ![Build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=develop) | - -### Screenshots - -![Screenshot of Main Page](docs/media/home-light.png "Main Page") - ---- - -![Screenshot of Rack Elevation](docs/media/rack-dark.png "Rack Elevation") - ---- - -![Screenshot of Prefix Hierarchy](docs/media/prefixes-light.png "Prefix Hierarchy") - ---- - -![Screenshot of Cable Tracing](docs/media/cable-dark.png "Cable Tracing") - -## Installation +### Installation Please see [the documentation](https://netbox.readthedocs.io/en/stable/) for instructions on installing NetBox. To upgrade NetBox, please download the [latest release](https://github.com/netbox-community/netbox/releases) and run `upgrade.sh`. -## Providing Feedback +### Providing Feedback The best platform for general feedback, assistance, and other discussion is our [GitHub discussions](https://github.com/netbox-community/netbox/discussions). @@ -58,7 +51,17 @@ the [appropriate template](https://github.com/netbox-community/netbox/issues/new If you are interested in contributing to the development of NetBox, please read our [contributing guide](CONTRIBUTING.md) prior to beginning any work. -## Related projects +### Screenshots + +![Screenshot of Main Page](docs/media/home-light.png "Main Page") + +![Screenshot of Rack Elevation](docs/media/rack-dark.png "Rack Elevation") + +![Screenshot of Prefix Hierarchy](docs/media/prefixes-light.png "Prefix Hierarchy") + +![Screenshot of Cable Tracing](docs/media/cable-dark.png "Cable Tracing") + +### Related projects Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for a list of relevant community projects. diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index 4ed3d946e..927bf9f37 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -515,6 +515,14 @@ The file path to the location where custom scripts will be kept. By default, thi --- +## SESSION_COOKIE_NAME + +Default: `sessionid` + +The name used for the session cookie. See the [Django documentation](https://docs.djangoproject.com/en/stable/ref/settings/#session-cookie-name) for more detail. + +--- + ## SESSION_FILE_PATH Default: None diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md index d8cb671f6..91d5ab2ab 100644 --- a/docs/development/release-checklist.md +++ b/docs/development/release-checklist.md @@ -70,7 +70,11 @@ Ensure that continuous integration testing on the `develop` branch is completing ### Update Version and Changelog -Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version. Commit these changes to the `develop` branch. +* Update the `VERSION` constant in `settings.py` to the new release version. +* Update the example version numbers in the feature request and bug report templates under `.github/ISSUE_TEMPLATES/`. +* Replace the "FUTURE" placeholder in the release notes with the current date. + +Commit these changes to the `develop` branch. ### Submit a Pull Request diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index a43e354de..0827d5434 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -1,5 +1,32 @@ # NetBox v2.11 +## v2.11.3 (2021-05-07) + +### Enhancements + +* [#6197](https://github.com/netbox-community/netbox/issues/6197) - Introduced `SESSION_COOKIE_NAME` config parameter +* [#6318](https://github.com/netbox-community/netbox/issues/6318) - Add OM5 MMF cable type +* [#6351](https://github.com/netbox-community/netbox/issues/6351) - Add aggregates count to tenant view +* [#6359](https://github.com/netbox-community/netbox/issues/6359) - Enable custom links for organizational and nested group models + +### Bug Fixes + +* [#6240](https://github.com/netbox-community/netbox/issues/6240) - Fix display of available VLAN ranges under VLAN group view +* [#6308](https://github.com/netbox-community/netbox/issues/6308) - Fix linking of available VLANs in VLAN group view +* [#6309](https://github.com/netbox-community/netbox/issues/6309) - Restrict parent VM interface assignment to the parent VM +* [#6312](https://github.com/netbox-community/netbox/issues/6312) - Interface device filter should return all virtual chassis interfaces only if device is master +* [#6313](https://github.com/netbox-community/netbox/issues/6313) - Fix device type instance count under manufacturer view +* [#6321](https://github.com/netbox-community/netbox/issues/6321) - Restore "add an IP" button under prefix IPs view +* [#6333](https://github.com/netbox-community/netbox/issues/6333) - Fix filtering of circuit terminations by primary key +* [#6339](https://github.com/netbox-community/netbox/issues/6339) - Improve ordering of interfaces when viewing virtual chassis master +* [#6350](https://github.com/netbox-community/netbox/issues/6350) - Include first & last IP addresses when allocating available IPv6 addresses via the REST API +* [#6355](https://github.com/netbox-community/netbox/issues/6355) - Fix caching error when swapping A/Z circuit terminations +* [#6357](https://github.com/netbox-community/netbox/issues/6357) - Fix ProviderNetwork nested API serializer +* [#6363](https://github.com/netbox-community/netbox/issues/6363) - Correct pre-population of cluster group when creating a cluster +* [#6369](https://github.com/netbox-community/netbox/issues/6369) - Fix interface assignment for VLANs in non-scoped groups + +--- + ## v2.11.2 (2021-04-27) ### Enhancements diff --git a/docs/release-notes/version-2.12.md b/docs/release-notes/version-2.12.md index 548868a3d..598237e76 100644 --- a/docs/release-notes/version-2.12.md +++ b/docs/release-notes/version-2.12.md @@ -6,3 +6,18 @@ * [#5532](https://github.com/netbox-community/netbox/issues/5532) - Drop support for Python 3.6 * [#5994](https://github.com/netbox-community/netbox/issues/5994) - Drop support for `display_field` argument on ObjectVar + +### REST API Changes + +* dcim.Device + * Removed the `display_name` attribute (use `display` instead) +* dcim.DeviceType + * Removed the `display_name` attribute (use `display` instead) +* dcim.Rack + * Removed the `display_name` attribute (use `display` instead) +* extras.ContentType + * Removed the `display_name` attribute (use `display` instead) +* ipam.VLAN + * Removed the `display_name` attribute (use `display` instead) +* ipam.VRF + * Removed the `display_name` attribute (use `display` instead) diff --git a/netbox/circuits/api/nested_serializers.py b/netbox/circuits/api/nested_serializers.py index fccf4a8b6..6f7cb4f21 100644 --- a/netbox/circuits/api/nested_serializers.py +++ b/netbox/circuits/api/nested_serializers.py @@ -20,7 +20,7 @@ class NestedProviderNetworkSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail') class Meta: - model = Provider + model = ProviderNetwork fields = ['id', 'url', 'display', 'name'] diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 0ea8d1973..3bceb2de0 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -1,6 +1,6 @@ from rest_framework.routers import APIRootView -from circuits import filters +from circuits import filtersets from circuits.models import * from dcim.api.views import PassThroughPortMixin from extras.api.views import CustomFieldModelViewSet @@ -26,7 +26,7 @@ class ProviderViewSet(CustomFieldModelViewSet): circuit_count=count_related(Circuit, 'provider') ) serializer_class = serializers.ProviderSerializer - filterset_class = filters.ProviderFilterSet + filterset_class = filtersets.ProviderFilterSet # @@ -38,7 +38,7 @@ class CircuitTypeViewSet(CustomFieldModelViewSet): circuit_count=count_related(Circuit, 'type') ) serializer_class = serializers.CircuitTypeSerializer - filterset_class = filters.CircuitTypeFilterSet + filterset_class = filtersets.CircuitTypeFilterSet # @@ -50,7 +50,7 @@ class CircuitViewSet(CustomFieldModelViewSet): 'type', 'tenant', 'provider', 'termination_a', 'termination_z' ).prefetch_related('tags') serializer_class = serializers.CircuitSerializer - filterset_class = filters.CircuitFilterSet + filterset_class = filtersets.CircuitFilterSet # @@ -62,7 +62,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet): 'circuit', 'site', 'provider_network', 'cable' ) serializer_class = serializers.CircuitTerminationSerializer - filterset_class = filters.CircuitTerminationFilterSet + filterset_class = filtersets.CircuitTerminationFilterSet brief_prefetch_fields = ['circuit'] @@ -73,4 +73,4 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet): class ProviderNetworkViewSet(CustomFieldModelViewSet): queryset = ProviderNetwork.objects.prefetch_related('tags') serializer_class = serializers.ProviderNetworkSerializer - filterset_class = filters.ProviderNetworkFilterSet + filterset_class = filtersets.ProviderNetworkFilterSet diff --git a/netbox/circuits/filters.py b/netbox/circuits/filtersets.py similarity index 89% rename from netbox/circuits/filters.py rename to netbox/circuits/filtersets.py index 034a99ac9..066178685 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filtersets.py @@ -1,13 +1,12 @@ import django_filters from django.db.models import Q -from dcim.filters import CableTerminationFilterSet +from dcim.filtersets import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet -from tenancy.filters import TenancyFilterSet -from utilities.filters import ( - BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter -) +from extras.filters import TagFilter +from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet +from tenancy.filtersets import TenancyFilterSet +from utilities.filters import TreeNodeMultipleChoiceFilter from .choices import * from .models import * @@ -20,7 +19,7 @@ __all__ = ( ) -class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class ProviderFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -80,7 +79,7 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated ) -class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class ProviderNetworkFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -110,14 +109,14 @@ class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, Created ).distinct() -class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class CircuitTypeFilterSet(OrganizationalModelFilterSet): class Meta: model = CircuitType fields = ['id', 'name', 'slug'] -class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet): +class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -207,7 +206,7 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe ).distinct() -class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableTerminationFilterSet): +class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -233,7 +232,7 @@ class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableT class Meta: model = CircuitTermination - fields = ['term_side', 'port_speed', 'upstream_speed', 'xconnect_id'] + fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index b2ffb3c09..31d08537e 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -149,7 +149,7 @@ class ProviderNetwork(PrimaryModel): ) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class CircuitType(OrganizationalModel): """ Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index 0a000fb2e..a12cef671 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -1,9 +1,8 @@ from django.db.models.signals import post_delete, post_save from django.dispatch import receiver -from django.utils import timezone from dcim.signals import rebuild_paths -from .models import Circuit, CircuitTermination +from .models import CircuitTermination @receiver(post_save, sender=CircuitTermination) @@ -11,11 +10,9 @@ def update_circuit(instance, **kwargs): """ When a CircuitTermination has been modified, update its parent Circuit. """ - fields = { - 'last_updated': timezone.now(), - f'termination_{instance.term_side.lower()}': instance.pk, - } - Circuit.objects.filter(pk=instance.circuit_id).update(**fields) + termination_name = f'termination_{instance.term_side.lower()}' + setattr(instance.circuit, termination_name, instance) + instance.circuit.save() @receiver((post_save, post_delete), sender=CircuitTermination) diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filtersets.py similarity index 95% rename from netbox/circuits/tests/test_filters.py rename to netbox/circuits/tests/test_filtersets.py index 448e42368..4880a8388 100644 --- a/netbox/circuits/tests/test_filters.py +++ b/netbox/circuits/tests/test_filtersets.py @@ -1,13 +1,14 @@ from django.test import TestCase from circuits.choices import * -from circuits.filters import * +from circuits.filtersets import * from circuits.models import * from dcim.models import Cable, Region, Site, SiteGroup from tenancy.models import Tenant, TenantGroup +from utilities.testing import ChangeLoggedFilterSetTests -class ProviderTestCase(TestCase): +class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Provider.objects.all() filterset = ProviderFilterSet @@ -61,10 +62,6 @@ class ProviderTestCase(TestCase): CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A'), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Provider 1', 'Provider 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -103,7 +100,7 @@ class ProviderTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class CircuitTypeTestCase(TestCase): +class CircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = CircuitType.objects.all() filterset = CircuitTypeFilterSet @@ -116,10 +113,6 @@ class CircuitTypeTestCase(TestCase): CircuitType(name='Circuit Type 3', slug='circuit-type-3'), )) - def test_id(self): - params = {'id': [self.queryset.first().pk]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) - def test_name(self): params = {'name': ['Circuit Type 1']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) @@ -129,7 +122,7 @@ class CircuitTypeTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class CircuitTestCase(TestCase): +class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Circuit.objects.all() filterset = CircuitFilterSet @@ -213,10 +206,6 @@ class CircuitTestCase(TestCase): )) CircuitTermination.objects.bulk_create(circuit_terminations) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_cid(self): params = {'cid': ['Test Circuit 1', 'Test Circuit 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -288,7 +277,7 @@ class CircuitTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class CircuitTerminationTestCase(TestCase): +class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = CircuitTermination.objects.all() filterset = CircuitTerminationFilterSet @@ -382,7 +371,7 @@ class CircuitTerminationTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ProviderNetworkTestCase(TestCase): +class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ProviderNetwork.objects.all() filterset = ProviderNetworkFilterSet @@ -403,10 +392,6 @@ class ProviderNetworkTestCase(TestCase): ) ProviderNetwork.objects.bulk_create(provider_networks) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Provider Network 1', 'Provider Network 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index c05ea4622..b4bb0155e 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -7,7 +7,7 @@ from netbox.views import generic from utilities.forms import ConfirmationForm from utilities.tables import paginate_table from utilities.utils import count_related -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import CircuitTerminationSideChoices from .models import * @@ -20,7 +20,7 @@ class ProviderListView(generic.ObjectListView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet filterset_form = forms.ProviderFilterForm table = tables.ProviderTable @@ -63,7 +63,7 @@ class ProviderBulkEditView(generic.BulkEditView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet table = tables.ProviderTable form = forms.ProviderBulkEditForm @@ -72,7 +72,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet table = tables.ProviderTable @@ -82,7 +82,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): class ProviderNetworkListView(generic.ObjectListView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet filterset_form = forms.ProviderNetworkFilterForm table = tables.ProviderNetworkTable @@ -125,14 +125,14 @@ class ProviderNetworkBulkImportView(generic.BulkImportView): class ProviderNetworkBulkEditView(generic.BulkEditView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet table = tables.ProviderNetworkTable form = forms.ProviderNetworkBulkEditForm class ProviderNetworkBulkDeleteView(generic.BulkDeleteView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet table = tables.ProviderNetworkTable @@ -183,7 +183,7 @@ class CircuitTypeBulkEditView(generic.BulkEditView): queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) - filterset = filters.CircuitTypeFilterSet + filterset = filtersets.CircuitTypeFilterSet table = tables.CircuitTypeTable form = forms.CircuitTypeBulkEditForm @@ -203,7 +203,7 @@ class CircuitListView(generic.ObjectListView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'termination_a', 'termination_z' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet filterset_form = forms.CircuitFilterForm table = tables.CircuitTable @@ -211,27 +211,6 @@ class CircuitListView(generic.ObjectListView): class CircuitView(generic.ObjectView): queryset = Circuit.objects.all() - def get_extra_context(self, request, instance): - - # A-side termination - termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related( - 'site__region' - ).filter( - circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_A - ).first() - - # Z-side termination - termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related( - 'site__region' - ).filter( - circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_Z - ).first() - - return { - 'termination_a': termination_a, - 'termination_z': termination_z, - } - class CircuitEditView(generic.ObjectEditView): queryset = Circuit.objects.all() @@ -252,7 +231,7 @@ class CircuitBulkEditView(generic.BulkEditView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'terminations' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet table = tables.CircuitTable form = forms.CircuitBulkEditForm @@ -261,7 +240,7 @@ class CircuitBulkDeleteView(generic.BulkDeleteView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'terminations' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet table = tables.CircuitTable @@ -296,16 +275,11 @@ class CircuitSwapTerminations(generic.ObjectEditView): if form.is_valid(): - termination_a = CircuitTermination.objects.filter( - circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A - ).first() - termination_z = CircuitTermination.objects.filter( - circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z - ).first() + termination_a = CircuitTermination.objects.filter(pk=circuit.termination_a_id).first() + termination_z = CircuitTermination.objects.filter(pk=circuit.termination_z_id).first() if termination_a and termination_z: # Use a placeholder to avoid an IntegrityError on the (circuit, term_side) unique constraint - print('swapping') with transaction.atomic(): termination_a.term_side = '_' termination_a.save() @@ -316,11 +290,20 @@ class CircuitSwapTerminations(generic.ObjectEditView): elif termination_a: termination_a.term_side = 'Z' termination_a.save() + circuit.refresh_from_db() + circuit.termination_a = None + circuit.save() else: termination_z.term_side = 'A' termination_z.save() + circuit.refresh_from_db() + circuit.termination_z = None + circuit.save() - messages.success(request, "Swapped terminations for circuit {}.".format(circuit)) + print(f'term A: {circuit.termination_a}') + print(f'term Z: {circuit.termination_z}') + + messages.success(request, f"Swapped terminations for circuit {circuit}.") return redirect('circuits:circuit', pk=circuit.pk) return render(request, 'circuits/circuit_terminations_swap.html', { diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index 80e003efc..67ae9b046 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -101,7 +101,7 @@ class NestedRackSerializer(WritableNestedSerializer): class Meta: model = models.Rack - fields = ['id', 'url', 'display', 'name', 'display_name', 'device_count'] + fields = ['id', 'url', 'display', 'name', 'device_count'] class NestedRackReservationSerializer(WritableNestedSerializer): @@ -136,7 +136,7 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer): class Meta: model = models.DeviceType - fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'device_count'] + fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'device_count'] class NestedConsolePortTemplateSerializer(WritableNestedSerializer): @@ -232,7 +232,7 @@ class NestedDeviceSerializer(WritableNestedSerializer): class Meta: model = models.Device - fields = ['id', 'url', 'display', 'name', 'display_name'] + fields = ['id', 'url', 'display', 'name'] class NestedConsoleServerPortSerializer(WritableNestedSerializer): diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 377449140..d2ecad608 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -172,10 +172,9 @@ class RackSerializer(PrimaryModelSerializer): class Meta: model = Rack fields = [ - 'id', 'url', 'display', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status', - 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', - 'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', - 'powerfeed_count', + 'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial', + 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', + 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count', ] # Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This # prevents facility_id from being interpreted as a required field. @@ -284,9 +283,9 @@ class DeviceTypeSerializer(PrimaryModelSerializer): class Meta: model = DeviceType fields = [ - 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height', - 'is_full_depth', 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', - 'created', 'last_updated', 'device_count', + 'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', + 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', 'device_count', ] @@ -465,10 +464,10 @@ class DeviceSerializer(PrimaryModelSerializer): class Meta: model = Device fields = [ - 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', - 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', - 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', - 'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', + 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', + 'tags', 'custom_fields', 'created', 'last_updated', ] validators = [] @@ -501,10 +500,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): class Meta(DeviceSerializer.Meta): fields = [ - 'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', - 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', - 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', - 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', + 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', + 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', + 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', ] @swagger_serializer_method(serializer_or_field=serializers.DictField) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index cb46c1eca..9d402227f 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -16,7 +16,7 @@ from rest_framework.routers import APIRootView from rest_framework.viewsets import GenericViewSet, ViewSet from circuits.models import Circuit -from dcim import filters +from dcim import filtersets from dcim.models import * from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet from ipam.models import Prefix, VLAN @@ -103,7 +103,7 @@ class RegionViewSet(CustomFieldModelViewSet): cumulative=True ) serializer_class = serializers.RegionSerializer - filterset_class = filters.RegionFilterSet + filterset_class = filtersets.RegionFilterSet # @@ -119,7 +119,7 @@ class SiteGroupViewSet(CustomFieldModelViewSet): cumulative=True ) serializer_class = serializers.SiteGroupSerializer - filterset_class = filters.SiteGroupFilterSet + filterset_class = filtersets.SiteGroupFilterSet # @@ -138,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'cluster__site') ) serializer_class = serializers.SiteSerializer - filterset_class = filters.SiteFilterSet + filterset_class = filtersets.SiteFilterSet # @@ -160,7 +160,7 @@ class LocationViewSet(CustomFieldModelViewSet): cumulative=True ).prefetch_related('site') serializer_class = serializers.LocationSerializer - filterset_class = filters.LocationFilterSet + filterset_class = filtersets.LocationFilterSet # @@ -172,7 +172,7 @@ class RackRoleViewSet(CustomFieldModelViewSet): rack_count=count_related(Rack, 'role') ) serializer_class = serializers.RackRoleSerializer - filterset_class = filters.RackRoleFilterSet + filterset_class = filtersets.RackRoleFilterSet # @@ -187,7 +187,7 @@ class RackViewSet(CustomFieldModelViewSet): powerfeed_count=count_related(PowerFeed, 'rack') ) serializer_class = serializers.RackSerializer - filterset_class = filters.RackFilterSet + filterset_class = filtersets.RackFilterSet @swagger_auto_schema( responses={200: serializers.RackUnitSerializer(many=True)}, @@ -244,7 +244,7 @@ class RackViewSet(CustomFieldModelViewSet): class RackReservationViewSet(ModelViewSet): queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant') serializer_class = serializers.RackReservationSerializer - filterset_class = filters.RackReservationFilterSet + filterset_class = filtersets.RackReservationFilterSet # Assign user from request def perform_create(self, serializer): @@ -262,7 +262,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet): platform_count=count_related(Platform, 'manufacturer') ) serializer_class = serializers.ManufacturerSerializer - filterset_class = filters.ManufacturerFilterSet + filterset_class = filtersets.ManufacturerFilterSet # @@ -274,7 +274,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet): device_count=count_related(Device, 'device_type') ) serializer_class = serializers.DeviceTypeSerializer - filterset_class = filters.DeviceTypeFilterSet + filterset_class = filtersets.DeviceTypeFilterSet brief_prefetch_fields = ['manufacturer'] @@ -285,49 +285,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet): class ConsolePortTemplateViewSet(ModelViewSet): queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsolePortTemplateSerializer - filterset_class = filters.ConsolePortTemplateFilterSet + filterset_class = filtersets.ConsolePortTemplateFilterSet class ConsoleServerPortTemplateViewSet(ModelViewSet): queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsoleServerPortTemplateSerializer - filterset_class = filters.ConsoleServerPortTemplateFilterSet + filterset_class = filtersets.ConsoleServerPortTemplateFilterSet class PowerPortTemplateViewSet(ModelViewSet): queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerPortTemplateSerializer - filterset_class = filters.PowerPortTemplateFilterSet + filterset_class = filtersets.PowerPortTemplateFilterSet class PowerOutletTemplateViewSet(ModelViewSet): queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerOutletTemplateSerializer - filterset_class = filters.PowerOutletTemplateFilterSet + filterset_class = filtersets.PowerOutletTemplateFilterSet class InterfaceTemplateViewSet(ModelViewSet): queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.InterfaceTemplateSerializer - filterset_class = filters.InterfaceTemplateFilterSet + filterset_class = filtersets.InterfaceTemplateFilterSet class FrontPortTemplateViewSet(ModelViewSet): queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.FrontPortTemplateSerializer - filterset_class = filters.FrontPortTemplateFilterSet + filterset_class = filtersets.FrontPortTemplateFilterSet class RearPortTemplateViewSet(ModelViewSet): queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.RearPortTemplateSerializer - filterset_class = filters.RearPortTemplateFilterSet + filterset_class = filtersets.RearPortTemplateFilterSet class DeviceBayTemplateViewSet(ModelViewSet): queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.DeviceBayTemplateSerializer - filterset_class = filters.DeviceBayTemplateFilterSet + filterset_class = filtersets.DeviceBayTemplateFilterSet # @@ -340,7 +340,7 @@ class DeviceRoleViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'role') ) serializer_class = serializers.DeviceRoleSerializer - filterset_class = filters.DeviceRoleFilterSet + filterset_class = filtersets.DeviceRoleFilterSet # @@ -353,7 +353,7 @@ class PlatformViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'platform') ) serializer_class = serializers.PlatformSerializer - filterset_class = filters.PlatformFilterSet + filterset_class = filtersets.PlatformFilterSet # @@ -365,7 +365,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet): 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags', ) - filterset_class = filters.DeviceFilterSet + filterset_class = filtersets.DeviceFilterSet def get_serializer_class(self): """ @@ -510,7 +510,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet): class ConsolePortViewSet(PathEndpointMixin, ModelViewSet): queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.ConsolePortSerializer - filterset_class = filters.ConsolePortFilterSet + filterset_class = filtersets.ConsolePortFilterSet brief_prefetch_fields = ['device'] @@ -519,21 +519,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet): 'device', '_path__destination', 'cable', '_cable_peer', 'tags' ) serializer_class = serializers.ConsoleServerPortSerializer - filterset_class = filters.ConsoleServerPortFilterSet + filterset_class = filtersets.ConsoleServerPortFilterSet brief_prefetch_fields = ['device'] class PowerPortViewSet(PathEndpointMixin, ModelViewSet): queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.PowerPortSerializer - filterset_class = filters.PowerPortFilterSet + filterset_class = filtersets.PowerPortFilterSet brief_prefetch_fields = ['device'] class PowerOutletViewSet(PathEndpointMixin, ModelViewSet): queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.PowerOutletSerializer - filterset_class = filters.PowerOutletFilterSet + filterset_class = filtersets.PowerOutletFilterSet brief_prefetch_fields = ['device'] @@ -542,35 +542,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet): 'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags' ) serializer_class = serializers.InterfaceSerializer - filterset_class = filters.InterfaceFilterSet + filterset_class = filtersets.InterfaceFilterSet brief_prefetch_fields = ['device'] class FrontPortViewSet(PassThroughPortMixin, ModelViewSet): queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags') serializer_class = serializers.FrontPortSerializer - filterset_class = filters.FrontPortFilterSet + filterset_class = filtersets.FrontPortFilterSet brief_prefetch_fields = ['device'] class RearPortViewSet(PassThroughPortMixin, ModelViewSet): queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags') serializer_class = serializers.RearPortSerializer - filterset_class = filters.RearPortFilterSet + filterset_class = filtersets.RearPortFilterSet brief_prefetch_fields = ['device'] class DeviceBayViewSet(ModelViewSet): queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags') serializer_class = serializers.DeviceBaySerializer - filterset_class = filters.DeviceBayFilterSet + filterset_class = filtersets.DeviceBayFilterSet brief_prefetch_fields = ['device'] class InventoryItemViewSet(ModelViewSet): queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags') serializer_class = serializers.InventoryItemSerializer - filterset_class = filters.InventoryItemFilterSet + filterset_class = filtersets.InventoryItemFilterSet brief_prefetch_fields = ['device'] @@ -583,7 +583,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): _path__destination_id__isnull=False ) serializer_class = serializers.ConsolePortSerializer - filterset_class = filters.ConsoleConnectionFilterSet + filterset_class = filtersets.ConsoleConnectionFilterSet class PowerConnectionViewSet(ListModelMixin, GenericViewSet): @@ -591,7 +591,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): _path__destination_id__isnull=False ) serializer_class = serializers.PowerPortSerializer - filterset_class = filters.PowerConnectionFilterSet + filterset_class = filtersets.PowerConnectionFilterSet class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): @@ -603,7 +603,7 @@ class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): pk__lt=F('_path__destination_id') ) serializer_class = serializers.InterfaceConnectionSerializer - filterset_class = filters.InterfaceConnectionFilterSet + filterset_class = filtersets.InterfaceConnectionFilterSet # @@ -616,7 +616,7 @@ class CableViewSet(ModelViewSet): 'termination_a', 'termination_b' ) serializer_class = serializers.CableSerializer - filterset_class = filters.CableFilterSet + filterset_class = filtersets.CableFilterSet # @@ -628,7 +628,7 @@ class VirtualChassisViewSet(ModelViewSet): member_count=count_related(Device, 'virtual_chassis') ) serializer_class = serializers.VirtualChassisSerializer - filterset_class = filters.VirtualChassisFilterSet + filterset_class = filtersets.VirtualChassisFilterSet brief_prefetch_fields = ['master'] @@ -643,7 +643,7 @@ class PowerPanelViewSet(ModelViewSet): powerfeed_count=count_related(PowerFeed, 'power_panel') ) serializer_class = serializers.PowerPanelSerializer - filterset_class = filters.PowerPanelFilterSet + filterset_class = filtersets.PowerPanelFilterSet # @@ -655,7 +655,7 @@ class PowerFeedViewSet(PathEndpointMixin, CustomFieldModelViewSet): 'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags' ) serializer_class = serializers.PowerFeedSerializer - filterset_class = filters.PowerFeedFilterSet + filterset_class = filtersets.PowerFeedFilterSet # diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 2483c8d12..c5646cf2b 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1001,6 +1001,7 @@ class CableTypeChoices(ChoiceSet): TYPE_MMF_OM2 = 'mmf-om2' TYPE_MMF_OM3 = 'mmf-om3' TYPE_MMF_OM4 = 'mmf-om4' + TYPE_MMF_OM5 = 'mmf-om5' TYPE_SMF = 'smf' TYPE_SMF_OS1 = 'smf-os1' TYPE_SMF_OS2 = 'smf-os2' @@ -1031,6 +1032,7 @@ class CableTypeChoices(ChoiceSet): (TYPE_MMF_OM2, 'Multimode Fiber (OM2)'), (TYPE_MMF_OM3, 'Multimode Fiber (OM3)'), (TYPE_MMF_OM4, 'Multimode Fiber (OM4)'), + (TYPE_MMF_OM5, 'Multimode Fiber (OM5)'), (TYPE_SMF, 'Singlemode Fiber'), (TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'), (TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'), diff --git a/netbox/dcim/elevations.py b/netbox/dcim/elevations.py index 8571c6684..473b9608f 100644 --- a/netbox/dcim/elevations.py +++ b/netbox/dcim/elevations.py @@ -34,10 +34,11 @@ class RackElevationSVG: @staticmethod def _get_device_description(device): - return '{} ({}) — {} ({}U) {} {}'.format( + return '{} ({}) — {} {} ({}U) {} {}'.format( device.name, device.device_role, - device.device_type.display_name, + device.device_type.manufacturer.name, + device.device_type.model, device.device_type.u_height, device.asset_tag or '', device.serial or '' diff --git a/netbox/dcim/filters.py b/netbox/dcim/filtersets.py similarity index 91% rename from netbox/dcim/filters.py rename to netbox/dcim/filtersets.py index 29c4281ba..b04c14ba9 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filtersets.py @@ -1,13 +1,16 @@ import django_filters from django.contrib.auth.models import User -from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet -from tenancy.filters import TenancyFilterSet +from extras.filters import TagFilter +from extras.filtersets import LocalConfigContextFilterSet +from netbox.filtersets import ( + BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, +) +from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( - BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, - NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter, + MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter, ) from virtualization.models import Cluster from .choices import * @@ -57,7 +60,7 @@ __all__ = ( ) -class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RegionFilterSet(OrganizationalModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), label='Parent region (ID)', @@ -74,7 +77,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilt fields = ['id', 'name', 'slug', 'description'] -class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class SiteGroupFilterSet(OrganizationalModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=SiteGroup.objects.all(), label='Parent site group (ID)', @@ -91,7 +94,7 @@ class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF fields = ['id', 'name', 'slug', 'description'] -class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -154,7 +157,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, return queryset.filter(qs_filter) -class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class LocationFilterSet(OrganizationalModelFilterSet): region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', @@ -218,14 +221,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi ) -class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RackRoleFilterSet(OrganizationalModelFilterSet): class Meta: model = RackRole fields = ['id', 'name', 'slug', 'color'] -class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -323,7 +326,7 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, ) -class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -383,14 +386,14 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel ) -class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class ManufacturerFilterSet(OrganizationalModelFilterSet): class Meta: model = Manufacturer fields = ['id', 'name', 'slug', 'description'] -class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class DeviceTypeFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -476,7 +479,7 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat return queryset.exclude(devicebaytemplates__isnull=value) -class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class DeviceTypeComponentFilterSet(django_filters.FilterSet): devicetype_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceType.objects.all(), field_name='device_type_id', @@ -484,28 +487,28 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilter ) -class ConsolePortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = ConsolePortTemplate fields = ['id', 'name', 'type'] -class ConsoleServerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = ConsoleServerPortTemplate fields = ['id', 'name', 'type'] -class PowerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = PowerPortTemplate fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw'] -class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): feed_leg = django_filters.MultipleChoiceFilter( choices=PowerOutletFeedLegChoices, null_value=None @@ -516,7 +519,7 @@ class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'feed_leg'] -class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=InterfaceTypeChoices, null_value=None @@ -527,7 +530,7 @@ class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'mgmt_only'] -class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -538,7 +541,7 @@ class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type'] -class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -549,21 +552,21 @@ class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'positions'] -class DeviceBayTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = DeviceBayTemplate fields = ['id', 'name'] -class DeviceRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class DeviceRoleFilterSet(OrganizationalModelFilterSet): class Meta: model = DeviceRole fields = ['id', 'name', 'slug', 'color', 'vm_role'] -class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class PlatformFilterSet(OrganizationalModelFilterSet): manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer', queryset=Manufacturer.objects.all(), @@ -581,13 +584,7 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi fields = ['id', 'name', 'slug', 'napalm_driver', 'description'] -class DeviceFilterSet( - BaseFilterSet, - TenancyFilterSet, - LocalConfigContextFilterSet, - CustomFieldModelFilterSet, - CreatedUpdatedFilterSet -): +class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -792,7 +789,7 @@ class DeviceFilterSet( return queryset.exclude(devicebays__isnull=value) -class DeviceComponentFilterSet(CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class DeviceComponentFilterSet(django_filters.FilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -876,7 +873,7 @@ class PathEndpointFilterSet(django_filters.FilterSet): return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False)) -class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=ConsolePortTypeChoices, null_value=None @@ -887,12 +884,7 @@ class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina fields = ['id', 'name', 'label', 'description'] -class ConsoleServerPortFilterSet( - BaseFilterSet, - DeviceComponentFilterSet, - CableTerminationFilterSet, - PathEndpointFilterSet -): +class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=ConsolePortTypeChoices, null_value=None @@ -903,7 +895,7 @@ class ConsoleServerPortFilterSet( fields = ['id', 'name', 'label', 'description'] -class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=PowerPortTypeChoices, null_value=None @@ -914,7 +906,7 @@ class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description'] -class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=PowerOutletTypeChoices, null_value=None @@ -929,7 +921,7 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina fields = ['id', 'name', 'label', 'feed_leg', 'description'] -class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1027,7 +1019,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati }.get(value, queryset.none()) -class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): +class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1038,7 +1030,7 @@ class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati fields = ['id', 'name', 'label', 'type', 'description'] -class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): +class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1049,14 +1041,14 @@ class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminatio fields = ['id', 'name', 'label', 'type', 'positions', 'description'] -class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet): +class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): class Meta: model = DeviceBay fields = ['id', 'name', 'label', 'description'] -class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): +class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1129,7 +1121,7 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): return queryset.filter(qs_filter) -class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VirtualChassisFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1209,7 +1201,7 @@ class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedU return queryset.filter(qs_filter).distinct() -class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class CableFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1273,7 +1265,7 @@ class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFil return queryset -class ConnectionFilterSet: +class ConnectionFilterSet(BaseFilterSet): def filter_site(self, queryset, name, value): if not value.strip(): @@ -1286,7 +1278,7 @@ class ConnectionFilterSet: return queryset.filter(**{f'{name}__in': value}) -class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class ConsoleConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1304,7 +1296,7 @@ class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = ['name'] -class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class PowerConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1322,7 +1314,7 @@ class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = ['name'] -class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class InterfaceConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1340,7 +1332,7 @@ class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = [] -class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class PowerPanelFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1402,13 +1394,7 @@ class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat return queryset.filter(qs_filter) -class PowerFeedFilterSet( - BaseFilterSet, - CableTerminationFilterSet, - PathEndpointFilterSet, - CustomFieldModelFilterSet, - CreatedUpdatedFilterSet -): +class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 16e909895..ab8475431 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2153,7 +2153,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): ip_choices = [(None, '---------')] # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member - interface_ids = self.instance.vc_interfaces().values_list('pk', flat=True) + interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True) # Collect interface IPs interface_ips = IPAddress.objects.filter( diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 2fe7c28e5..ff3da7ca6 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -36,7 +36,7 @@ __all__ = ( # Device Types # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Manufacturer(OrganizationalModel): """ A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell. @@ -320,10 +320,6 @@ class DeviceType(PrimaryModel): if self.rear_image: self.rear_image.delete(save=False) - @property - def display_name(self): - return f'{self.manufacturer.name} {self.model}' - @property def is_parent_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT @@ -337,7 +333,7 @@ class DeviceType(PrimaryModel): # Devices # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class DeviceRole(OrganizationalModel): """ Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a @@ -388,7 +384,7 @@ class DeviceRole(OrganizationalModel): ) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Platform(OrganizationalModel): """ Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". @@ -622,7 +618,13 @@ class Device(PrimaryModel, ConfigContextModel): ) def __str__(self): - return self.display_name or super().__str__() + if self.name: + return self.name + elif self.virtual_chassis: + return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' + elif self.device_type: + return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' + return super().__str__() def get_absolute_url(self): return reverse('dcim:device', args=[self.pk]) @@ -716,7 +718,7 @@ class Device(PrimaryModel, ConfigContextModel): pass # Validate primary IP addresses - vc_interfaces = self.vc_interfaces() + vc_interfaces = self.vc_interfaces(if_master=False) if self.primary_ip4: if self.primary_ip4.family != 4: raise ValidationError({ @@ -823,17 +825,6 @@ class Device(PrimaryModel, ConfigContextModel): self.comments, ) - @property - def display_name(self): - if self.name: - return self.name - elif self.virtual_chassis: - return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' - elif self.device_type: - return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' - else: - return '' # Device has not yet been created - @property def identifier(self): """ @@ -856,9 +847,7 @@ class Device(PrimaryModel, ConfigContextModel): @property def interfaces_count(self): - if self.virtual_chassis and self.virtual_chassis.master == self: - return self.vc_interfaces().count() - return self.interfaces.count() + return self.vc_interfaces().count() def get_vc_master(self): """ @@ -866,7 +855,7 @@ class Device(PrimaryModel, ConfigContextModel): """ return self.virtual_chassis.master if self.virtual_chassis else None - def vc_interfaces(self, if_master=False): + def vc_interfaces(self, if_master=True): """ Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another Device belonging to the same VirtualChassis. @@ -874,7 +863,7 @@ class Device(PrimaryModel, ConfigContextModel): :param if_master: If True, return VC member interfaces only if this Device is the VC master. """ filter = Q(device=self) - if self.virtual_chassis and (not if_master or self.virtual_chassis.master == self): + if self.virtual_chassis and (self.virtual_chassis.master == self or not if_master): filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False) return Interface.objects.filter(filter) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 2869c4265..3c63c1a3c 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -35,7 +35,7 @@ __all__ = ( # Racks # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class RackRole(OrganizationalModel): """ Racks can be organized by functional role, similar to Devices. @@ -209,7 +209,9 @@ class Rack(PrimaryModel): ) def __str__(self): - return self.display_name or super().__str__() + if self.facility_id: + return f'{self.name} ({self.facility_id})' + return self.name def get_absolute_url(self): return reverse('dcim:rack', args=[self.pk]) @@ -277,12 +279,6 @@ class Rack(PrimaryModel): else: return reversed(range(1, self.u_height + 1)) - @property - def display_name(self): - if self.facility_id: - return f'{self.name} ({self.facility_id})' - return self.name - def get_status_class(self): return RackStatusChoices.CSS_CLASSES.get(self.status) diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 225a8e749..1e5165088 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -26,7 +26,7 @@ __all__ = ( # Regions # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Region(NestedGroupModel): """ A region represents a geographic collection of sites. For example, you might create regions representing countries, @@ -78,7 +78,7 @@ class Region(NestedGroupModel): # Site groups # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class SiteGroup(NestedGroupModel): """ A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and @@ -285,7 +285,7 @@ class Site(PrimaryModel): # Locations # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Location(NestedGroupModel): """ A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 4f7c05c71..5f275f1eb 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -520,6 +520,7 @@ class DeviceInterfaceTable(InterfaceTable): 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions', ) + order_by = ('name',) default_columns = ( 'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable', 'connection', 'actions', diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 24130c649..92ffd1207 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -251,7 +251,7 @@ class RackRoleTest(APIViewTestCases.APIViewTestCase): class RackTest(APIViewTestCases.APIViewTestCase): model = Rack - brief_fields = ['device_count', 'display', 'display_name', 'id', 'name', 'url'] + brief_fields = ['device_count', 'display', 'id', 'name', 'url'] bulk_update_data = { 'status': 'planned', } @@ -422,7 +422,7 @@ class ManufacturerTest(APIViewTestCases.APIViewTestCase): class DeviceTypeTest(APIViewTestCases.APIViewTestCase): model = DeviceType - brief_fields = ['device_count', 'display', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url'] + brief_fields = ['device_count', 'display', 'id', 'manufacturer', 'model', 'slug', 'url'] bulk_update_data = { 'part_number': 'ABC123', } @@ -870,7 +870,7 @@ class PlatformTest(APIViewTestCases.APIViewTestCase): class DeviceTest(APIViewTestCases.APIViewTestCase): model = Device - brief_fields = ['display', 'display_name', 'id', 'name', 'url'] + brief_fields = ['display', 'id', 'name', 'url'] bulk_update_data = { 'status': 'failed', } diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filtersets.py similarity index 95% rename from netbox/dcim/tests/test_filters.py rename to netbox/dcim/tests/test_filtersets.py index 632a10c46..154ec0847 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2,14 +2,15 @@ from django.contrib.auth.models import User from django.test import TestCase from dcim.choices import * -from dcim.filters import * +from dcim.filtersets import * from dcim.models import * from ipam.models import IPAddress from tenancy.models import Tenant, TenantGroup +from utilities.testing import ChangeLoggedFilterSetTests from virtualization.models import Cluster, ClusterType -class RegionTestCase(TestCase): +class RegionTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Region.objects.all() filterset = RegionFilterSet @@ -35,10 +36,6 @@ class RegionTestCase(TestCase): for region in child_regions: region.save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Region 1', 'Region 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -59,7 +56,7 @@ class RegionTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class SiteGroupTestCase(TestCase): +class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = SiteGroup.objects.all() filterset = SiteGroupFilterSet @@ -85,10 +82,6 @@ class SiteGroupTestCase(TestCase): for sitegroup in child_sitegroups: sitegroup.save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Site Group 1', 'Site Group 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -109,7 +102,7 @@ class SiteGroupTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class SiteTestCase(TestCase): +class SiteTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Site.objects.all() filterset = SiteFilterSet @@ -154,10 +147,6 @@ class SiteTestCase(TestCase): ) Site.objects.bulk_create(sites) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Site 1', 'Site 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -227,7 +216,7 @@ class SiteTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class LocationTestCase(TestCase): +class LocationTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Location.objects.all() filterset = LocationFilterSet @@ -273,10 +262,6 @@ class LocationTestCase(TestCase): for location in locations: location.save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Location 1', 'Location 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -318,7 +303,7 @@ class LocationTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RackRoleTestCase(TestCase): +class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RackRole.objects.all() filterset = RackRoleFilterSet @@ -332,10 +317,6 @@ class RackRoleTestCase(TestCase): ) RackRole.objects.bulk_create(rack_roles) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -349,7 +330,7 @@ class RackRoleTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RackTestCase(TestCase): +class RackTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Rack.objects.all() filterset = RackFilterSet @@ -416,10 +397,6 @@ class RackTestCase(TestCase): ) Rack.objects.bulk_create(racks) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Rack 1', 'Rack 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -523,7 +500,7 @@ class RackTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RackReservationTestCase(TestCase): +class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RackReservation.objects.all() filterset = RackReservationFilterSet @@ -581,10 +558,6 @@ class RackReservationTestCase(TestCase): ) RackReservation.objects.bulk_create(reservations) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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]} @@ -621,7 +594,7 @@ class RackReservationTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ManufacturerTestCase(TestCase): +class ManufacturerTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Manufacturer.objects.all() filterset = ManufacturerFilterSet @@ -635,10 +608,6 @@ class ManufacturerTestCase(TestCase): ) Manufacturer.objects.bulk_create(manufacturers) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Manufacturer 1', 'Manufacturer 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -652,7 +621,7 @@ class ManufacturerTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceTypeTestCase(TestCase): +class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = DeviceType.objects.all() filterset = DeviceTypeFilterSet @@ -708,10 +677,6 @@ class DeviceTypeTestCase(TestCase): DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_model(self): params = {'model': ['Model 1', 'Model 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -788,7 +753,7 @@ class DeviceTypeTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class ConsolePortTemplateTestCase(TestCase): +class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConsolePortTemplate.objects.all() filterset = ConsolePortTemplateFilterSet @@ -810,10 +775,6 @@ class ConsolePortTemplateTestCase(TestCase): ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -824,7 +785,7 @@ class ConsolePortTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ConsoleServerPortTemplateTestCase(TestCase): +class ConsoleServerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConsoleServerPortTemplate.objects.all() filterset = ConsoleServerPortTemplateFilterSet @@ -846,10 +807,6 @@ class ConsoleServerPortTemplateTestCase(TestCase): ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -860,7 +817,7 @@ class ConsoleServerPortTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class PowerPortTemplateTestCase(TestCase): +class PowerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerPortTemplate.objects.all() filterset = PowerPortTemplateFilterSet @@ -882,10 +839,6 @@ class PowerPortTemplateTestCase(TestCase): PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -904,7 +857,7 @@ class PowerPortTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class PowerOutletTemplateTestCase(TestCase): +class PowerOutletTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerOutletTemplate.objects.all() filterset = PowerOutletTemplateFilterSet @@ -926,10 +879,6 @@ class PowerOutletTemplateTestCase(TestCase): PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -944,7 +893,7 @@ class PowerOutletTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class InterfaceTemplateTestCase(TestCase): +class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = InterfaceTemplate.objects.all() filterset = InterfaceTemplateFilterSet @@ -966,10 +915,6 @@ class InterfaceTemplateTestCase(TestCase): InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Interface 1', 'Interface 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -990,7 +935,7 @@ class InterfaceTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class FrontPortTemplateTestCase(TestCase): +class FrontPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = FrontPortTemplate.objects.all() filterset = FrontPortTemplateFilterSet @@ -1019,10 +964,6 @@ class FrontPortTemplateTestCase(TestCase): FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PortTypeChoices.TYPE_BNC), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -1037,7 +978,7 @@ class FrontPortTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RearPortTemplateTestCase(TestCase): +class RearPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RearPortTemplate.objects.all() filterset = RearPortTemplateFilterSet @@ -1059,10 +1000,6 @@ class RearPortTemplateTestCase(TestCase): RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -1081,7 +1018,7 @@ class RearPortTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceBayTemplateTestCase(TestCase): +class DeviceBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = DeviceBayTemplate.objects.all() filterset = DeviceBayTemplateFilterSet @@ -1103,10 +1040,6 @@ class DeviceBayTemplateTestCase(TestCase): DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'), )) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -1117,7 +1050,7 @@ class DeviceBayTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceRoleTestCase(TestCase): +class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = DeviceRole.objects.all() filterset = DeviceRoleFilterSet @@ -1131,10 +1064,6 @@ class DeviceRoleTestCase(TestCase): ) DeviceRole.objects.bulk_create(device_roles) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -1154,7 +1083,7 @@ class DeviceRoleTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class PlatformTestCase(TestCase): +class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Platform.objects.all() filterset = PlatformFilterSet @@ -1175,10 +1104,6 @@ class PlatformTestCase(TestCase): ) Platform.objects.bulk_create(platforms) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Platform 1', 'Platform 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -1203,7 +1128,7 @@ class PlatformTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceTestCase(TestCase): +class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Device.objects.all() filterset = DeviceFilterSet @@ -1356,10 +1281,6 @@ class DeviceTestCase(TestCase): 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): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Device 1', 'Device 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -1549,7 +1470,7 @@ class DeviceTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ConsolePortTestCase(TestCase): +class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConsolePort.objects.all() filterset = ConsolePortFilterSet @@ -1608,10 +1529,6 @@ class ConsolePortTestCase(TestCase): Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -1665,7 +1582,7 @@ class ConsolePortTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class ConsoleServerPortTestCase(TestCase): +class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConsoleServerPort.objects.all() filterset = ConsoleServerPortFilterSet @@ -1724,10 +1641,6 @@ class ConsoleServerPortTestCase(TestCase): Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -1781,7 +1694,7 @@ class ConsoleServerPortTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class PowerPortTestCase(TestCase): +class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerPort.objects.all() filterset = PowerPortFilterSet @@ -1840,10 +1753,6 @@ class PowerPortTestCase(TestCase): Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -1905,7 +1814,7 @@ class PowerPortTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class PowerOutletTestCase(TestCase): +class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerOutlet.objects.all() filterset = PowerOutletFilterSet @@ -1964,10 +1873,6 @@ class PowerOutletTestCase(TestCase): Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -2025,7 +1930,7 @@ class PowerOutletTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class InterfaceTestCase(TestCase): +class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Interface.objects.all() filterset = InterfaceFilterSet @@ -2081,10 +1986,6 @@ class InterfaceTestCase(TestCase): Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save() # Third pair is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Interface 1', 'Interface 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -2200,7 +2101,7 @@ class InterfaceTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class FrontPortTestCase(TestCase): +class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = FrontPort.objects.all() filterset = FrontPortFilterSet @@ -2266,10 +2167,6 @@ class FrontPortTestCase(TestCase): Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -2321,7 +2218,7 @@ class FrontPortTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RearPortTestCase(TestCase): +class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RearPort.objects.all() filterset = RearPortFilterSet @@ -2377,10 +2274,6 @@ class RearPortTestCase(TestCase): Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save() # Third port is not connected - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -2436,7 +2329,7 @@ class RearPortTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceBayTestCase(TestCase): +class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = DeviceBay.objects.all() filterset = DeviceBayFilterSet @@ -2483,10 +2376,6 @@ class DeviceBayTestCase(TestCase): ) DeviceBay.objects.bulk_create(device_bays) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -2528,7 +2417,7 @@ class DeviceBayTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class InventoryItemTestCase(TestCase): +class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = InventoryItem.objects.all() filterset = InventoryItemFilterSet @@ -2591,10 +2480,6 @@ class InventoryItemTestCase(TestCase): for i in child_inventory_items: i.save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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.filterset(params, self.queryset).qs.count(), 2) @@ -2666,7 +2551,7 @@ class InventoryItemTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class VirtualChassisTestCase(TestCase): +class VirtualChassisTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = VirtualChassis.objects.all() filterset = VirtualChassisFilterSet @@ -2721,10 +2606,6 @@ class VirtualChassisTestCase(TestCase): 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): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_domain(self): params = {'domain': ['Domain 1', 'Domain 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -2762,7 +2643,7 @@ class VirtualChassisTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class CableTestCase(TestCase): +class CableTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Cable.objects.all() filterset = CableFilterSet @@ -2827,10 +2708,6 @@ class CableTestCase(TestCase): Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save() Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -2886,7 +2763,7 @@ class CableTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class PowerPanelTestCase(TestCase): +class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerPanel.objects.all() filterset = PowerPanelFilterSet @@ -2931,10 +2808,6 @@ class PowerPanelTestCase(TestCase): ) PowerPanel.objects.bulk_create(power_panels) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Power Panel 1', 'Power Panel 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -2966,7 +2839,7 @@ class PowerPanelTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class PowerFeedTestCase(TestCase): +class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerFeed.objects.all() filterset = PowerFeedFilterSet @@ -3029,10 +2902,6 @@ class PowerFeedTestCase(TestCase): Cable(termination_a=power_feeds[0], termination_b=power_ports[0]).save() Cable(termination_a=power_feeds[1], termination_b=power_ports[1]).save() - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Power Feed 1', 'Power Feed 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 75d570df9..7082d9e39 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -27,7 +27,7 @@ from utilities.tables import paginate_table from utilities.utils import csv_format, count_related from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin from virtualization.models import VirtualMachine -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES from .models import ( @@ -110,7 +110,7 @@ class RegionListView(generic.ObjectListView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet filterset_form = forms.RegionFilterForm table = tables.RegionTable @@ -166,7 +166,7 @@ class RegionBulkEditView(generic.BulkEditView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet table = tables.RegionTable form = forms.RegionBulkEditForm @@ -179,7 +179,7 @@ class RegionBulkDeleteView(generic.BulkDeleteView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet table = tables.RegionTable @@ -195,7 +195,7 @@ class SiteGroupListView(generic.ObjectListView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet filterset_form = forms.SiteGroupFilterForm table = tables.SiteGroupTable @@ -251,7 +251,7 @@ class SiteGroupBulkEditView(generic.BulkEditView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable form = forms.SiteGroupBulkEditForm @@ -264,7 +264,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable @@ -274,7 +274,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): class SiteListView(generic.ObjectListView): queryset = Site.objects.all() - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet filterset_form = forms.SiteFilterForm table = tables.SiteTable @@ -329,14 +329,14 @@ class SiteBulkImportView(generic.BulkImportView): class SiteBulkEditView(generic.BulkEditView): queryset = Site.objects.prefetch_related('region', 'tenant') - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet table = tables.SiteTable form = forms.SiteBulkEditForm class SiteBulkDeleteView(generic.BulkDeleteView): queryset = Site.objects.prefetch_related('region', 'tenant') - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet table = tables.SiteTable @@ -358,7 +358,7 @@ class LocationListView(generic.ObjectListView): 'rack_count', cumulative=True ) - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet filterset_form = forms.LocationFilterForm table = tables.LocationTable @@ -417,7 +417,7 @@ class LocationBulkEditView(generic.BulkEditView): 'rack_count', cumulative=True ).prefetch_related('site') - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet table = tables.LocationTable form = forms.LocationBulkEditForm @@ -430,7 +430,7 @@ class LocationBulkDeleteView(generic.BulkDeleteView): 'rack_count', cumulative=True ).prefetch_related('site') - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet table = tables.LocationTable @@ -481,7 +481,7 @@ class RackRoleBulkEditView(generic.BulkEditView): queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) - filterset = filters.RackRoleFilterSet + filterset = filtersets.RackRoleFilterSet table = tables.RackRoleTable form = forms.RackRoleBulkEditForm @@ -503,7 +503,7 @@ class RackListView(generic.ObjectListView): ).annotate( device_count=count_related(Device, 'rack') ) - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet filterset_form = forms.RackFilterForm table = tables.RackDetailTable @@ -516,7 +516,7 @@ class RackElevationListView(generic.ObjectListView): def get(self, request): - racks = filters.RackFilterSet(request.GET, self.queryset).qs + racks = filtersets.RackFilterSet(request.GET, self.queryset).qs total_count = racks.count() # Determine ordering @@ -605,14 +605,14 @@ class RackBulkImportView(generic.BulkImportView): class RackBulkEditView(generic.BulkEditView): queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role') - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet table = tables.RackTable form = forms.RackBulkEditForm class RackBulkDeleteView(generic.BulkDeleteView): queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role') - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet table = tables.RackTable @@ -622,7 +622,7 @@ class RackBulkDeleteView(generic.BulkDeleteView): class RackReservationListView(generic.ObjectListView): queryset = RackReservation.objects.all() - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet filterset_form = forms.RackReservationFilterForm table = tables.RackReservationTable @@ -665,14 +665,14 @@ class RackReservationImportView(generic.BulkImportView): class RackReservationBulkEditView(generic.BulkEditView): queryset = RackReservation.objects.prefetch_related('rack', 'user') - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet table = tables.RackReservationTable form = forms.RackReservationBulkEditForm class RackReservationBulkDeleteView(generic.BulkDeleteView): queryset = RackReservation.objects.prefetch_related('rack', 'user') - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet table = tables.RackReservationTable @@ -695,6 +695,8 @@ class ManufacturerView(generic.ObjectView): def get_extra_context(self, request, instance): devicetypes = DeviceType.objects.restrict(request.user, 'view').filter( manufacturer=instance + ).annotate( + instance_count=count_related(Device, 'device_type') ) devicetypes_table = tables.DeviceTypeTable(devicetypes) @@ -725,7 +727,7 @@ class ManufacturerBulkEditView(generic.BulkEditView): queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer') ) - filterset = filters.ManufacturerFilterSet + filterset = filtersets.ManufacturerFilterSet table = tables.ManufacturerTable form = forms.ManufacturerBulkEditForm @@ -745,7 +747,7 @@ class DeviceTypeListView(generic.ObjectListView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet filterset_form = forms.DeviceTypeFilterForm table = tables.DeviceTypeTable @@ -851,7 +853,7 @@ class DeviceTypeBulkEditView(generic.BulkEditView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet table = tables.DeviceTypeTable form = forms.DeviceTypeBulkEditForm @@ -860,7 +862,7 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet table = tables.DeviceTypeTable @@ -1193,7 +1195,7 @@ class DeviceRoleBulkEditView(generic.BulkEditView): device_count=count_related(Device, 'device_role'), vm_count=count_related(VirtualMachine, 'role') ) - filterset = filters.DeviceRoleFilterSet + filterset = filtersets.DeviceRoleFilterSet table = tables.DeviceRoleTable form = forms.DeviceRoleBulkEditForm @@ -1252,7 +1254,7 @@ class PlatformBulkImportView(generic.BulkImportView): class PlatformBulkEditView(generic.BulkEditView): queryset = Platform.objects.all() - filterset = filters.PlatformFilterSet + filterset = filtersets.PlatformFilterSet table = tables.PlatformTable form = forms.PlatformBulkEditForm @@ -1268,7 +1270,7 @@ class PlatformBulkDeleteView(generic.BulkDeleteView): class DeviceListView(generic.ObjectListView): queryset = Device.objects.all() - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet filterset_form = forms.DeviceFilterForm table = tables.DeviceTable template_name = 'dcim/device_list.html' @@ -1408,7 +1410,7 @@ class DeviceInterfacesView(generic.ObjectView): template_name = 'dcim/device/interfaces.html' def get_extra_context(self, request, instance): - interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related( + interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related( Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)), Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)), 'lag', 'cable', '_path__destination', 'tags', @@ -1530,7 +1532,7 @@ class DeviceLLDPNeighborsView(generic.ObjectView): template_name = 'dcim/device/lldp_neighbors.html' def get_extra_context(self, request, instance): - interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related( + interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related( '_path__destination' ).exclude( type__in=NONCONNECTABLE_IFACE_TYPES @@ -1603,14 +1605,14 @@ class ChildDeviceBulkImportView(generic.BulkImportView): class DeviceBulkEditView(generic.BulkEditView): queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable form = forms.DeviceBulkEditForm class DeviceBulkDeleteView(generic.BulkDeleteView): queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable @@ -1620,7 +1622,7 @@ class DeviceBulkDeleteView(generic.BulkDeleteView): class ConsolePortListView(generic.ObjectListView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm table = tables.ConsolePortTable action_buttons = ('import', 'export') @@ -1655,7 +1657,7 @@ class ConsolePortBulkImportView(generic.BulkImportView): class ConsolePortBulkEditView(generic.BulkEditView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet table = tables.ConsolePortTable form = forms.ConsolePortBulkEditForm @@ -1670,7 +1672,7 @@ class ConsolePortBulkDisconnectView(BulkDisconnectView): class ConsolePortBulkDeleteView(generic.BulkDeleteView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet table = tables.ConsolePortTable @@ -1680,7 +1682,7 @@ class ConsolePortBulkDeleteView(generic.BulkDeleteView): class ConsoleServerPortListView(generic.ObjectListView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm table = tables.ConsoleServerPortTable action_buttons = ('import', 'export') @@ -1715,7 +1717,7 @@ class ConsoleServerPortBulkImportView(generic.BulkImportView): class ConsoleServerPortBulkEditView(generic.BulkEditView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet table = tables.ConsoleServerPortTable form = forms.ConsoleServerPortBulkEditForm @@ -1730,7 +1732,7 @@ class ConsoleServerPortBulkDisconnectView(BulkDisconnectView): class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet table = tables.ConsoleServerPortTable @@ -1740,7 +1742,7 @@ class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView): class PowerPortListView(generic.ObjectListView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm table = tables.PowerPortTable action_buttons = ('import', 'export') @@ -1775,7 +1777,7 @@ class PowerPortBulkImportView(generic.BulkImportView): class PowerPortBulkEditView(generic.BulkEditView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet table = tables.PowerPortTable form = forms.PowerPortBulkEditForm @@ -1790,7 +1792,7 @@ class PowerPortBulkDisconnectView(BulkDisconnectView): class PowerPortBulkDeleteView(generic.BulkDeleteView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet table = tables.PowerPortTable @@ -1800,7 +1802,7 @@ class PowerPortBulkDeleteView(generic.BulkDeleteView): class PowerOutletListView(generic.ObjectListView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm table = tables.PowerOutletTable action_buttons = ('import', 'export') @@ -1835,7 +1837,7 @@ class PowerOutletBulkImportView(generic.BulkImportView): class PowerOutletBulkEditView(generic.BulkEditView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet table = tables.PowerOutletTable form = forms.PowerOutletBulkEditForm @@ -1850,7 +1852,7 @@ class PowerOutletBulkDisconnectView(BulkDisconnectView): class PowerOutletBulkDeleteView(generic.BulkDeleteView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet table = tables.PowerOutletTable @@ -1860,7 +1862,7 @@ class PowerOutletBulkDeleteView(generic.BulkDeleteView): class InterfaceListView(generic.ObjectListView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm table = tables.InterfaceTable action_buttons = ('import', 'export') @@ -1997,7 +1999,7 @@ class InterfaceBulkImportView(generic.BulkImportView): class InterfaceBulkEditView(generic.BulkEditView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet table = tables.InterfaceTable form = forms.InterfaceBulkEditForm @@ -2012,7 +2014,7 @@ class InterfaceBulkDisconnectView(BulkDisconnectView): class InterfaceBulkDeleteView(generic.BulkDeleteView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet table = tables.InterfaceTable @@ -2022,7 +2024,7 @@ class InterfaceBulkDeleteView(generic.BulkDeleteView): class FrontPortListView(generic.ObjectListView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm table = tables.FrontPortTable action_buttons = ('import', 'export') @@ -2057,7 +2059,7 @@ class FrontPortBulkImportView(generic.BulkImportView): class FrontPortBulkEditView(generic.BulkEditView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet table = tables.FrontPortTable form = forms.FrontPortBulkEditForm @@ -2072,7 +2074,7 @@ class FrontPortBulkDisconnectView(BulkDisconnectView): class FrontPortBulkDeleteView(generic.BulkDeleteView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet table = tables.FrontPortTable @@ -2082,7 +2084,7 @@ class FrontPortBulkDeleteView(generic.BulkDeleteView): class RearPortListView(generic.ObjectListView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet filterset_form = forms.RearPortFilterForm table = tables.RearPortTable action_buttons = ('import', 'export') @@ -2117,7 +2119,7 @@ class RearPortBulkImportView(generic.BulkImportView): class RearPortBulkEditView(generic.BulkEditView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet table = tables.RearPortTable form = forms.RearPortBulkEditForm @@ -2132,7 +2134,7 @@ class RearPortBulkDisconnectView(BulkDisconnectView): class RearPortBulkDeleteView(generic.BulkDeleteView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet table = tables.RearPortTable @@ -2142,7 +2144,7 @@ class RearPortBulkDeleteView(generic.BulkDeleteView): class DeviceBayListView(generic.ObjectListView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm table = tables.DeviceBayTable action_buttons = ('import', 'export') @@ -2242,7 +2244,7 @@ class DeviceBayBulkImportView(generic.BulkImportView): class DeviceBayBulkEditView(generic.BulkEditView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet table = tables.DeviceBayTable form = forms.DeviceBayBulkEditForm @@ -2253,7 +2255,7 @@ class DeviceBayBulkRenameView(generic.BulkRenameView): class DeviceBayBulkDeleteView(generic.BulkDeleteView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet table = tables.DeviceBayTable @@ -2263,7 +2265,7 @@ class DeviceBayBulkDeleteView(generic.BulkDeleteView): class InventoryItemListView(generic.ObjectListView): queryset = InventoryItem.objects.all() - filterset = filters.InventoryItemFilterSet + filterset = filtersets.InventoryItemFilterSet filterset_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable action_buttons = ('import', 'export') @@ -2297,7 +2299,7 @@ class InventoryItemBulkImportView(generic.BulkImportView): class InventoryItemBulkEditView(generic.BulkEditView): queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer') - filterset = filters.InventoryItemFilterSet + filterset = filtersets.InventoryItemFilterSet table = tables.InventoryItemTable form = forms.InventoryItemBulkEditForm @@ -2322,7 +2324,7 @@ class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView): form = forms.ConsolePortBulkCreateForm queryset = ConsolePort.objects.all() model_form = forms.ConsolePortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2333,7 +2335,7 @@ class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView): form = forms.ConsoleServerPortBulkCreateForm queryset = ConsoleServerPort.objects.all() model_form = forms.ConsoleServerPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2344,7 +2346,7 @@ class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView): form = forms.PowerPortBulkCreateForm queryset = PowerPort.objects.all() model_form = forms.PowerPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2355,7 +2357,7 @@ class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView): form = forms.PowerOutletBulkCreateForm queryset = PowerOutlet.objects.all() model_form = forms.PowerOutletForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2366,7 +2368,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView): form = forms.InterfaceBulkCreateForm queryset = Interface.objects.all() model_form = forms.InterfaceForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2377,7 +2379,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView): # form = forms.FrontPortBulkCreateForm # queryset = FrontPort.objects.all() # model_form = forms.FrontPortForm -# filterset = filters.DeviceFilterSet +# filterset = filtersets.DeviceFilterSet # table = tables.DeviceTable # default_return_url = 'dcim:device_list' @@ -2388,7 +2390,7 @@ class DeviceBulkAddRearPortView(generic.BulkComponentCreateView): form = forms.RearPortBulkCreateForm queryset = RearPort.objects.all() model_form = forms.RearPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2399,7 +2401,7 @@ class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView): form = forms.DeviceBayBulkCreateForm queryset = DeviceBay.objects.all() model_form = forms.DeviceBayForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2410,7 +2412,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView): form = forms.InventoryItemBulkCreateForm queryset = InventoryItem.objects.all() model_form = forms.InventoryItemForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2421,7 +2423,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView): class CableListView(generic.ObjectListView): queryset = Cable.objects.all() - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet filterset_form = forms.CableFilterForm table = tables.CableTable action_buttons = ('import', 'export') @@ -2554,14 +2556,14 @@ class CableBulkImportView(generic.BulkImportView): class CableBulkEditView(generic.BulkEditView): queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet table = tables.CableTable form = forms.CableBulkEditForm class CableBulkDeleteView(generic.BulkDeleteView): queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet table = tables.CableTable @@ -2571,7 +2573,7 @@ class CableBulkDeleteView(generic.BulkDeleteView): class ConsoleConnectionsListView(generic.ObjectListView): queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device') - filterset = filters.ConsoleConnectionFilterSet + filterset = filtersets.ConsoleConnectionFilterSet filterset_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable template_name = 'dcim/connections_list.html' @@ -2601,7 +2603,7 @@ class ConsoleConnectionsListView(generic.ObjectListView): class PowerConnectionsListView(generic.ObjectListView): queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device') - filterset = filters.PowerConnectionFilterSet + filterset = filtersets.PowerConnectionFilterSet filterset_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable template_name = 'dcim/connections_list.html' @@ -2635,7 +2637,7 @@ class InterfaceConnectionsListView(generic.ObjectListView): _path__isnull=False, pk__lt=F('_path__destination_id') ).order_by('device') - filterset = filters.InterfaceConnectionFilterSet + filterset = filtersets.InterfaceConnectionFilterSet filterset_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable template_name = 'dcim/connections_list.html' @@ -2674,7 +2676,7 @@ class VirtualChassisListView(generic.ObjectListView): member_count=count_related(Device, 'virtual_chassis') ) table = tables.VirtualChassisTable - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet filterset_form = forms.VirtualChassisFilterForm @@ -2882,14 +2884,14 @@ class VirtualChassisBulkImportView(generic.BulkImportView): class VirtualChassisBulkEditView(generic.BulkEditView): queryset = VirtualChassis.objects.all() - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet table = tables.VirtualChassisTable form = forms.VirtualChassisBulkEditForm class VirtualChassisBulkDeleteView(generic.BulkDeleteView): queryset = VirtualChassis.objects.all() - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet table = tables.VirtualChassisTable @@ -2903,7 +2905,7 @@ class PowerPanelListView(generic.ObjectListView): ).annotate( powerfeed_count=count_related(PowerFeed, 'power_panel') ) - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet filterset_form = forms.PowerPanelFilterForm table = tables.PowerPanelTable @@ -2943,7 +2945,7 @@ class PowerPanelBulkImportView(generic.BulkImportView): class PowerPanelBulkEditView(generic.BulkEditView): queryset = PowerPanel.objects.prefetch_related('site', 'location') - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet table = tables.PowerPanelTable form = forms.PowerPanelBulkEditForm @@ -2954,7 +2956,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView): ).annotate( powerfeed_count=count_related(PowerFeed, 'power_panel') ) - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet table = tables.PowerPanelTable @@ -2964,7 +2966,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView): class PowerFeedListView(generic.ObjectListView): queryset = PowerFeed.objects.all() - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet filterset_form = forms.PowerFeedFilterForm table = tables.PowerFeedTable @@ -2990,7 +2992,7 @@ class PowerFeedBulkImportView(generic.BulkImportView): class PowerFeedBulkEditView(generic.BulkEditView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet table = tables.PowerFeedTable form = forms.PowerFeedBulkEditForm @@ -3001,5 +3003,5 @@ class PowerFeedBulkDisconnectView(BulkDisconnectView): class PowerFeedBulkDeleteView(generic.BulkDeleteView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet table = tables.PowerFeedTable diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 66627bfbc..536df1c75 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -453,12 +453,7 @@ class ObjectChangeSerializer(BaseModelSerializer): class ContentTypeSerializer(BaseModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail') - display_name = serializers.SerializerMethodField() class Meta: model = ContentType - fields = ['id', 'url', 'display', 'app_label', 'model', 'display_name'] - - @swagger_serializer_method(serializer_or_field=serializers.CharField) - def get_display_name(self, obj): - return obj.app_labeled_name + fields = ['id', 'url', 'display', 'app_label', 'model'] diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index cee5146a6..7e6c97782 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -9,7 +9,7 @@ from rest_framework.routers import APIRootView from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet from rq import Worker -from extras import filters +from extras import filtersets from extras.choices import JobResultStatusChoices from extras.models import * from extras.models import CustomField @@ -61,7 +61,7 @@ class WebhookViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = Webhook.objects.all() serializer_class = serializers.WebhookSerializer - filterset_class = filters.WebhookFilterSet + filterset_class = filtersets.WebhookFilterSet # @@ -72,7 +72,7 @@ class CustomFieldViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = CustomField.objects.all() serializer_class = serializers.CustomFieldSerializer - filterset_class = filters.CustomFieldFilterSet + filterset_class = filtersets.CustomFieldFilterSet class CustomFieldModelViewSet(ModelViewSet): @@ -101,7 +101,7 @@ class CustomLinkViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = CustomLink.objects.all() serializer_class = serializers.CustomLinkSerializer - filterset_class = filters.CustomLinkFilterSet + filterset_class = filtersets.CustomLinkFilterSet # @@ -112,7 +112,7 @@ class ExportTemplateViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = ExportTemplate.objects.all() serializer_class = serializers.ExportTemplateSerializer - filterset_class = filters.ExportTemplateFilterSet + filterset_class = filtersets.ExportTemplateFilterSet # @@ -124,7 +124,7 @@ class TagViewSet(ModelViewSet): tagged_items=count_related(TaggedItem, 'tag') ) serializer_class = serializers.TagSerializer - filterset_class = filters.TagFilterSet + filterset_class = filtersets.TagFilterSet # @@ -135,7 +135,7 @@ class ImageAttachmentViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = ImageAttachment.objects.all() serializer_class = serializers.ImageAttachmentSerializer - filterset_class = filters.ImageAttachmentFilterSet + filterset_class = filtersets.ImageAttachmentFilterSet # @@ -146,7 +146,7 @@ class JournalEntryViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = JournalEntry.objects.all() serializer_class = serializers.JournalEntrySerializer - filterset_class = filters.JournalEntryFilterSet + filterset_class = filtersets.JournalEntryFilterSet # @@ -158,7 +158,7 @@ class ConfigContextViewSet(ModelViewSet): 'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants', ) serializer_class = serializers.ConfigContextSerializer - filterset_class = filters.ConfigContextFilterSet + filterset_class = filtersets.ConfigContextFilterSet # @@ -358,7 +358,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): metadata_class = ContentTypeMetadata queryset = ObjectChange.objects.prefetch_related('user') serializer_class = serializers.ObjectChangeSerializer - filterset_class = filters.ObjectChangeFilterSet + filterset_class = filtersets.ObjectChangeFilterSet # @@ -371,7 +371,7 @@ class JobResultViewSet(ReadOnlyModelViewSet): """ queryset = JobResult.objects.prefetch_related('user') serializer_class = serializers.JobResultSerializer - filterset_class = filters.JobResultFilterSet + filterset_class = filtersets.JobResultFilterSet # @@ -384,4 +384,4 @@ class ContentTypeViewSet(ReadOnlyModelViewSet): """ queryset = ContentType.objects.order_by('app_label', 'model') serializer_class = serializers.ContentTypeSerializer - filterset_class = filters.ContentTypeFilterSet + filterset_class = filtersets.ContentTypeFilterSet diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index aacdbda6b..aef2046fd 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,31 +1,12 @@ import django_filters -from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType -from django.db.models import Q from django.forms import DateField, IntegerField, NullBooleanField -from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup -from tenancy.models import Tenant, TenantGroup -from utilities.filters import BaseFilterSet, ContentTypeFilter -from virtualization.models import Cluster, ClusterGroup +from .models import Tag from .choices import * -from .models import * - __all__ = ( - 'ConfigContextFilterSet', - 'ContentTypeFilterSet', - 'CreatedUpdatedFilterSet', 'CustomFieldFilter', - 'CustomLinkFilterSet', - 'CustomFieldModelFilterSet', - 'ExportTemplateFilterSet', - 'ImageAttachmentFilterSet', - 'JournalEntryFilterSet', - 'LocalConfigContextFilterSet', - 'ObjectChangeFilterSet', - 'TagFilterSet', - 'WebhookFilterSet', + 'TagFilter', ) EXACT_FILTER_TYPES = ( @@ -36,41 +17,6 @@ EXACT_FILTER_TYPES = ( ) -class CreatedUpdatedFilterSet(django_filters.FilterSet): - created = django_filters.DateFilter() - created__gte = django_filters.DateFilter( - field_name='created', - lookup_expr='gte' - ) - created__lte = django_filters.DateFilter( - field_name='created', - lookup_expr='lte' - ) - last_updated = django_filters.DateTimeFilter() - last_updated__gte = django_filters.DateTimeFilter( - field_name='last_updated', - lookup_expr='gte' - ) - last_updated__lte = django_filters.DateTimeFilter( - field_name='last_updated', - lookup_expr='lte' - ) - - -class WebhookFilterSet(BaseFilterSet): - content_types = ContentTypeFilter() - http_method = django_filters.MultipleChoiceFilter( - choices=WebhookHttpMethodChoices - ) - - class Meta: - model = Webhook - fields = [ - 'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', - 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', - ] - - class CustomFieldFilter(django_filters.Filter): """ Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name. @@ -94,310 +40,16 @@ class CustomFieldFilter(django_filters.Filter): self.lookup_expr = 'icontains' -class CustomFieldModelFilterSet(django_filters.FilterSet): +class TagFilter(django_filters.ModelMultipleChoiceFilter): """ - Dynamically add a Filter for each CustomField applicable to the parent model. + Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered + to objects matching all tags. """ def __init__(self, *args, **kwargs): + + kwargs.setdefault('field_name', 'tags__slug') + kwargs.setdefault('to_field_name', 'slug') + kwargs.setdefault('conjoined', True) + kwargs.setdefault('queryset', Tag.objects.all()) + super().__init__(*args, **kwargs) - - custom_fields = CustomField.objects.filter( - content_types=ContentType.objects.get_for_model(self._meta.model) - ).exclude( - filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED - ) - for cf in custom_fields: - self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf) - - -class CustomFieldFilterSet(django_filters.FilterSet): - content_types = ContentTypeFilter() - - class Meta: - model = CustomField - fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight'] - - -class CustomLinkFilterSet(BaseFilterSet): - - class Meta: - model = CustomLink - fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window'] - - -class ExportTemplateFilterSet(BaseFilterSet): - - class Meta: - model = ExportTemplate - fields = ['id', 'content_type', 'name'] - - -class ImageAttachmentFilterSet(BaseFilterSet): - content_type = ContentTypeFilter() - - class Meta: - model = ImageAttachment - fields = ['id', 'content_type_id', 'object_id', 'name'] - - -class JournalEntryFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - created = django_filters.DateTimeFromToRangeFilter() - assigned_object_type = ContentTypeFilter() - created_by_id = django_filters.ModelMultipleChoiceFilter( - queryset=User.objects.all(), - label='User (ID)', - ) - created_by = django_filters.ModelMultipleChoiceFilter( - field_name='created_by__username', - queryset=User.objects.all(), - to_field_name='username', - label='User (name)', - ) - kind = django_filters.MultipleChoiceFilter( - choices=JournalEntryKindChoices - ) - - class Meta: - model = JournalEntry - fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter(comments__icontains=value) - - -class TagFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - - class Meta: - model = Tag - fields = ['id', 'name', 'slug', 'color'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) - ) - - -class ConfigContextFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - region_id = django_filters.ModelMultipleChoiceFilter( - field_name='regions', - queryset=Region.objects.all(), - label='Region', - ) - region = django_filters.ModelMultipleChoiceFilter( - field_name='regions__slug', - queryset=Region.objects.all(), - to_field_name='slug', - label='Region (slug)', - ) - site_group = django_filters.ModelMultipleChoiceFilter( - field_name='site_groups__slug', - queryset=SiteGroup.objects.all(), - to_field_name='slug', - label='Site group (slug)', - ) - site_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='site_groups', - queryset=SiteGroup.objects.all(), - label='Site group', - ) - site_id = django_filters.ModelMultipleChoiceFilter( - field_name='sites', - queryset=Site.objects.all(), - label='Site', - ) - site = django_filters.ModelMultipleChoiceFilter( - field_name='sites__slug', - queryset=Site.objects.all(), - to_field_name='slug', - label='Site (slug)', - ) - device_type_id = django_filters.ModelMultipleChoiceFilter( - field_name='device_types', - queryset=DeviceType.objects.all(), - label='Device type', - ) - role_id = django_filters.ModelMultipleChoiceFilter( - field_name='roles', - queryset=DeviceRole.objects.all(), - label='Role', - ) - role = django_filters.ModelMultipleChoiceFilter( - field_name='roles__slug', - queryset=DeviceRole.objects.all(), - to_field_name='slug', - label='Role (slug)', - ) - platform_id = django_filters.ModelMultipleChoiceFilter( - field_name='platforms', - queryset=Platform.objects.all(), - label='Platform', - ) - platform = django_filters.ModelMultipleChoiceFilter( - field_name='platforms__slug', - queryset=Platform.objects.all(), - to_field_name='slug', - label='Platform (slug)', - ) - cluster_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='cluster_groups', - queryset=ClusterGroup.objects.all(), - label='Cluster group', - ) - cluster_group = django_filters.ModelMultipleChoiceFilter( - field_name='cluster_groups__slug', - queryset=ClusterGroup.objects.all(), - to_field_name='slug', - label='Cluster group (slug)', - ) - cluster_id = django_filters.ModelMultipleChoiceFilter( - field_name='clusters', - queryset=Cluster.objects.all(), - label='Cluster', - ) - tenant_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='tenant_groups', - queryset=TenantGroup.objects.all(), - label='Tenant group', - ) - tenant_group = django_filters.ModelMultipleChoiceFilter( - field_name='tenant_groups__slug', - queryset=TenantGroup.objects.all(), - to_field_name='slug', - label='Tenant group (slug)', - ) - tenant_id = django_filters.ModelMultipleChoiceFilter( - field_name='tenants', - queryset=Tenant.objects.all(), - label='Tenant', - ) - tenant = django_filters.ModelMultipleChoiceFilter( - field_name='tenants__slug', - queryset=Tenant.objects.all(), - to_field_name='slug', - label='Tenant (slug)', - ) - tag = django_filters.ModelMultipleChoiceFilter( - field_name='tags__slug', - queryset=Tag.objects.all(), - to_field_name='slug', - label='Tag (slug)', - ) - - class Meta: - model = ConfigContext - fields = ['id', 'name', 'is_active'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(description__icontains=value) | - Q(data__icontains=value) - ) - - -# -# Filter for Local Config Context Data -# - -class LocalConfigContextFilterSet(django_filters.FilterSet): - local_context_data = django_filters.BooleanFilter( - method='_local_context_data', - label='Has local config context data', - ) - - def _local_context_data(self, queryset, name, value): - return queryset.exclude(local_context_data__isnull=value) - - -class ObjectChangeFilterSet(BaseFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - time = django_filters.DateTimeFromToRangeFilter() - changed_object_type = ContentTypeFilter() - user_id = django_filters.ModelMultipleChoiceFilter( - queryset=User.objects.all(), - label='User (ID)', - ) - user = django_filters.ModelMultipleChoiceFilter( - field_name='user__username', - queryset=User.objects.all(), - to_field_name='username', - label='User name', - ) - - class Meta: - model = ObjectChange - fields = [ - 'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id', - 'object_repr', - ] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(user_name__icontains=value) | - Q(object_repr__icontains=value) - ) - - -# -# Job Results -# - -class JobResultFilterSet(BaseFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - created = django_filters.DateTimeFilter() - completed = django_filters.DateTimeFilter() - status = django_filters.MultipleChoiceFilter( - choices=JobResultStatusChoices, - null_value=None - ) - - class Meta: - model = JobResult - fields = [ - 'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name' - ] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(user__username__icontains=value) - ) - - -# -# ContentTypes -# - -class ContentTypeFilterSet(django_filters.FilterSet): - - class Meta: - model = ContentType - fields = ['id', 'app_label', 'model'] diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py new file mode 100644 index 000000000..92c0dc9a6 --- /dev/null +++ b/netbox/extras/filtersets.py @@ -0,0 +1,341 @@ +import django_filters +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.db.models import Q + +from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup +from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet +from tenancy.models import Tenant, TenantGroup +from utilities.filters import ContentTypeFilter +from virtualization.models import Cluster, ClusterGroup +from .choices import * +from .models import * + + +__all__ = ( + 'ConfigContextFilterSet', + 'ContentTypeFilterSet', + 'CustomLinkFilterSet', + 'ExportTemplateFilterSet', + 'ImageAttachmentFilterSet', + 'JournalEntryFilterSet', + 'LocalConfigContextFilterSet', + 'ObjectChangeFilterSet', + 'TagFilterSet', + 'WebhookFilterSet', +) + +EXACT_FILTER_TYPES = ( + CustomFieldTypeChoices.TYPE_BOOLEAN, + CustomFieldTypeChoices.TYPE_DATE, + CustomFieldTypeChoices.TYPE_INTEGER, + CustomFieldTypeChoices.TYPE_SELECT, +) + + +class WebhookFilterSet(BaseFilterSet): + content_types = ContentTypeFilter() + http_method = django_filters.MultipleChoiceFilter( + choices=WebhookHttpMethodChoices + ) + + class Meta: + model = Webhook + fields = [ + 'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', + 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', + ] + + +class CustomFieldFilterSet(django_filters.FilterSet): + content_types = ContentTypeFilter() + + class Meta: + model = CustomField + fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight'] + + +class CustomLinkFilterSet(BaseFilterSet): + + class Meta: + model = CustomLink + fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window'] + + +class ExportTemplateFilterSet(BaseFilterSet): + + class Meta: + model = ExportTemplate + fields = ['id', 'content_type', 'name'] + + +class ImageAttachmentFilterSet(BaseFilterSet): + created = django_filters.DateTimeFilter() + content_type = ContentTypeFilter() + + class Meta: + model = ImageAttachment + fields = ['id', 'content_type_id', 'object_id', 'name'] + + +class JournalEntryFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + created = django_filters.DateTimeFromToRangeFilter() + assigned_object_type = ContentTypeFilter() + created_by_id = django_filters.ModelMultipleChoiceFilter( + queryset=User.objects.all(), + label='User (ID)', + ) + created_by = django_filters.ModelMultipleChoiceFilter( + field_name='created_by__username', + queryset=User.objects.all(), + to_field_name='username', + label='User (name)', + ) + kind = django_filters.MultipleChoiceFilter( + choices=JournalEntryKindChoices + ) + + class Meta: + model = JournalEntry + fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter(comments__icontains=value) + + +class TagFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + class Meta: + model = Tag + fields = ['id', 'name', 'slug', 'color'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(slug__icontains=value) + ) + + +class ConfigContextFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + region_id = django_filters.ModelMultipleChoiceFilter( + field_name='regions', + queryset=Region.objects.all(), + label='Region', + ) + region = django_filters.ModelMultipleChoiceFilter( + field_name='regions__slug', + queryset=Region.objects.all(), + to_field_name='slug', + label='Region (slug)', + ) + site_group = django_filters.ModelMultipleChoiceFilter( + field_name='site_groups__slug', + queryset=SiteGroup.objects.all(), + to_field_name='slug', + label='Site group (slug)', + ) + site_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='site_groups', + queryset=SiteGroup.objects.all(), + label='Site group', + ) + site_id = django_filters.ModelMultipleChoiceFilter( + field_name='sites', + queryset=Site.objects.all(), + label='Site', + ) + site = django_filters.ModelMultipleChoiceFilter( + field_name='sites__slug', + queryset=Site.objects.all(), + to_field_name='slug', + label='Site (slug)', + ) + device_type_id = django_filters.ModelMultipleChoiceFilter( + field_name='device_types', + queryset=DeviceType.objects.all(), + label='Device type', + ) + role_id = django_filters.ModelMultipleChoiceFilter( + field_name='roles', + queryset=DeviceRole.objects.all(), + label='Role', + ) + role = django_filters.ModelMultipleChoiceFilter( + field_name='roles__slug', + queryset=DeviceRole.objects.all(), + to_field_name='slug', + label='Role (slug)', + ) + platform_id = django_filters.ModelMultipleChoiceFilter( + field_name='platforms', + queryset=Platform.objects.all(), + label='Platform', + ) + platform = django_filters.ModelMultipleChoiceFilter( + field_name='platforms__slug', + queryset=Platform.objects.all(), + to_field_name='slug', + label='Platform (slug)', + ) + cluster_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='cluster_groups', + queryset=ClusterGroup.objects.all(), + label='Cluster group', + ) + cluster_group = django_filters.ModelMultipleChoiceFilter( + field_name='cluster_groups__slug', + queryset=ClusterGroup.objects.all(), + to_field_name='slug', + label='Cluster group (slug)', + ) + cluster_id = django_filters.ModelMultipleChoiceFilter( + field_name='clusters', + queryset=Cluster.objects.all(), + label='Cluster', + ) + tenant_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='tenant_groups', + queryset=TenantGroup.objects.all(), + label='Tenant group', + ) + tenant_group = django_filters.ModelMultipleChoiceFilter( + field_name='tenant_groups__slug', + queryset=TenantGroup.objects.all(), + to_field_name='slug', + label='Tenant group (slug)', + ) + tenant_id = django_filters.ModelMultipleChoiceFilter( + field_name='tenants', + queryset=Tenant.objects.all(), + label='Tenant', + ) + tenant = django_filters.ModelMultipleChoiceFilter( + field_name='tenants__slug', + queryset=Tenant.objects.all(), + to_field_name='slug', + label='Tenant (slug)', + ) + tag = django_filters.ModelMultipleChoiceFilter( + field_name='tags__slug', + queryset=Tag.objects.all(), + to_field_name='slug', + label='Tag (slug)', + ) + + class Meta: + model = ConfigContext + fields = ['id', 'name', 'is_active'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(description__icontains=value) | + Q(data__icontains=value) + ) + + +# +# Filter for Local Config Context Data +# + +class LocalConfigContextFilterSet(django_filters.FilterSet): + local_context_data = django_filters.BooleanFilter( + method='_local_context_data', + label='Has local config context data', + ) + + def _local_context_data(self, queryset, name, value): + return queryset.exclude(local_context_data__isnull=value) + + +class ObjectChangeFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + time = django_filters.DateTimeFromToRangeFilter() + changed_object_type = ContentTypeFilter() + user_id = django_filters.ModelMultipleChoiceFilter( + queryset=User.objects.all(), + label='User (ID)', + ) + user = django_filters.ModelMultipleChoiceFilter( + field_name='user__username', + queryset=User.objects.all(), + to_field_name='username', + label='User name', + ) + + class Meta: + model = ObjectChange + fields = [ + 'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id', + 'object_repr', + ] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(user_name__icontains=value) | + Q(object_repr__icontains=value) + ) + + +# +# Job Results +# + +class JobResultFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + created = django_filters.DateTimeFilter() + completed = django_filters.DateTimeFilter() + status = django_filters.MultipleChoiceFilter( + choices=JobResultStatusChoices, + null_value=None + ) + + class Meta: + model = JobResult + fields = [ + 'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name' + ] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(user__username__icontains=value) + ) + + +# +# ContentTypes +# + +class ContentTypeFilterSet(django_filters.FilterSet): + + class Meta: + model = ContentType + fields = ['id', 'app_label', 'model'] diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index d1725ac9d..c14424ba6 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from rest_framework import status -from dcim.filters import SiteFilterSet +from dcim.filtersets import SiteFilterSet from dcim.forms import SiteCSVForm from dcim.models import Site, Rack from extras.choices import * diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filtersets.py similarity index 93% rename from netbox/extras/tests/test_filters.py rename to netbox/extras/tests/test_filtersets.py index bb78c4daf..eb08f5930 100644 --- a/netbox/extras/tests/test_filters.py +++ b/netbox/extras/tests/test_filtersets.py @@ -1,4 +1,5 @@ import uuid +from datetime import datetime, timezone from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -6,14 +7,15 @@ from django.test import TestCase from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices -from extras.filters import * +from extras.filtersets import * from extras.models import * from ipam.models import IPAddress from tenancy.models import Tenant, TenantGroup +from utilities.testing import BaseFilterSetTests, ChangeLoggedFilterSetTests from virtualization.models import Cluster, ClusterGroup, ClusterType -class WebhookTestCase(TestCase): +class WebhookTestCase(TestCase, BaseFilterSetTests): queryset = Webhook.objects.all() filterset = WebhookFilterSet @@ -52,10 +54,6 @@ class WebhookTestCase(TestCase): webhooks[1].content_types.add(content_types[1]) webhooks[2].content_types.add(content_types[2]) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Webhook 1', 'Webhook 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -89,7 +87,7 @@ class WebhookTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class CustomLinkTestCase(TestCase): +class CustomLinkTestCase(TestCase, BaseFilterSetTests): queryset = CustomLink.objects.all() filterset = CustomLinkFilterSet @@ -125,10 +123,6 @@ class CustomLinkTestCase(TestCase): ) CustomLink.objects.bulk_create(custom_links) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Custom Link 1', 'Custom Link 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -148,7 +142,7 @@ class CustomLinkTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class ExportTemplateTestCase(TestCase): +class ExportTemplateTestCase(TestCase, BaseFilterSetTests): queryset = ExportTemplate.objects.all() filterset = ExportTemplateFilterSet @@ -164,10 +158,6 @@ class ExportTemplateTestCase(TestCase): ) ExportTemplate.objects.bulk_create(export_templates) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Export Template 1', 'Export Template 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -177,7 +167,7 @@ class ExportTemplateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class ImageAttachmentTestCase(TestCase): +class ImageAttachmentTestCase(TestCase, BaseFilterSetTests): queryset = ImageAttachment.objects.all() filterset = ImageAttachmentFilterSet @@ -235,10 +225,6 @@ class ImageAttachmentTestCase(TestCase): ) ImageAttachment.objects.bulk_create(image_attachments) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Image Attachment 1', 'Image Attachment 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -254,8 +240,14 @@ class ImageAttachmentTestCase(TestCase): } self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + def test_created(self): + pk_list = self.queryset.values_list('pk', flat=True)[:2] + self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc)) + params = {'created': '2021-01-01T00:00:00'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class JournalEntryTestCase(TestCase): + +class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = JournalEntry.objects.all() filterset = JournalEntryFilterSet @@ -320,10 +312,6 @@ class JournalEntryTestCase(TestCase): ) JournalEntry.objects.bulk_create(journal_entries) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_created_by(self): users = User.objects.filter(username__in=['Alice', 'Bob']) params = {'created_by': [users[0].username, users[1].username]} @@ -348,8 +336,17 @@ class JournalEntryTestCase(TestCase): params = {'kind': [JournalEntryKindChoices.KIND_INFO, JournalEntryKindChoices.KIND_SUCCESS]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_created(self): + pk_list = self.queryset.values_list('pk', flat=True)[:2] + self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc)) + params = { + 'created_after': '2020-12-31T00:00:00', + 'created_before': '2021-01-02T00:00:00', + } + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ConfigContextTestCase(TestCase): + +class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = ConfigContext.objects.all() filterset = ConfigContextFilterSet @@ -449,10 +446,6 @@ class ConfigContextTestCase(TestCase): c.tenant_groups.set([tenant_groups[i]]) c.tenants.set([tenants[i]]) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Config Context 1', 'Config Context 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -530,7 +523,7 @@ class ConfigContextTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class TagTestCase(TestCase): +class TagTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Tag.objects.all() filterset = TagFilterSet @@ -544,10 +537,6 @@ class TagTestCase(TestCase): ) Tag.objects.bulk_create(tags) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Tag 1', 'Tag 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -561,7 +550,7 @@ class TagTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ObjectChangeTestCase(TestCase): +class ObjectChangeTestCase(TestCase, BaseFilterSetTests): queryset = ObjectChange.objects.all() filterset = ObjectChangeFilterSet @@ -635,10 +624,6 @@ class ObjectChangeTestCase(TestCase): ) ObjectChange.objects.bulk_create(object_changes) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:3]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) - def test_user(self): params = {'user_id': User.objects.filter(username__in=['user1', 'user2']).values_list('pk', flat=True)} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 4cda84d99..3f86c98d2 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -13,7 +13,7 @@ from utilities.forms import ConfirmationForm from utilities.tables import paginate_table from utilities.utils import copy_safe_request, count_related, shallow_compare_dict from utilities.views import ContentTypePermissionRequiredMixin -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import JobResultStatusChoices from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem from .reports import get_report, get_reports, run_report @@ -28,7 +28,7 @@ class TagListView(generic.ObjectListView): queryset = Tag.objects.annotate( items=count_related(TaggedItem, 'tag') ) - filterset = filters.TagFilterSet + filterset = filtersets.TagFilterSet filterset_form = forms.TagFilterForm table = tables.TagTable @@ -94,7 +94,7 @@ class TagBulkDeleteView(generic.BulkDeleteView): class ConfigContextListView(generic.ObjectListView): queryset = ConfigContext.objects.all() - filterset = filters.ConfigContextFilterSet + filterset = filtersets.ConfigContextFilterSet filterset_form = forms.ConfigContextFilterForm table = tables.ConfigContextTable action_buttons = ('add',) @@ -127,7 +127,7 @@ class ConfigContextEditView(generic.ObjectEditView): class ConfigContextBulkEditView(generic.BulkEditView): queryset = ConfigContext.objects.all() - filterset = filters.ConfigContextFilterSet + filterset = filtersets.ConfigContextFilterSet table = tables.ConfigContextTable form = forms.ConfigContextBulkEditForm @@ -173,7 +173,7 @@ class ObjectConfigContextView(generic.ObjectView): class ObjectChangeListView(generic.ObjectListView): queryset = ObjectChange.objects.all() - filterset = filters.ObjectChangeFilterSet + filterset = filtersets.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable template_name = 'extras/objectchange_list.html' @@ -300,7 +300,7 @@ class ImageAttachmentDeleteView(generic.ObjectDeleteView): class JournalEntryListView(generic.ObjectListView): queryset = JournalEntry.objects.all() - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet filterset_form = forms.JournalEntryFilterForm table = tables.JournalEntryTable action_buttons = ('export',) @@ -338,14 +338,14 @@ class JournalEntryDeleteView(generic.ObjectDeleteView): class JournalEntryBulkEditView(generic.BulkEditView): queryset = JournalEntry.objects.prefetch_related('created_by') - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet table = tables.JournalEntryTable form = forms.JournalEntryBulkEditForm class JournalEntryBulkDeleteView(generic.BulkDeleteView): queryset = JournalEntry.objects.prefetch_related('created_by') - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet table = tables.JournalEntryTable diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index a5eb63911..f2aa5f19e 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -27,7 +27,7 @@ class NestedVRFSerializer(WritableNestedSerializer): class Meta: model = models.VRF - fields = ['id', 'url', 'display', 'name', 'rd', 'display_name', 'prefix_count'] + fields = ['id', 'url', 'display', 'name', 'rd', 'prefix_count'] # @@ -92,7 +92,7 @@ class NestedVLANSerializer(WritableNestedSerializer): class Meta: model = models.VLAN - fields = ['id', 'url', 'display', 'vid', 'name', 'display_name'] + fields = ['id', 'url', 'display', 'vid', 'name'] # diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 931e2cc47..324c4de03 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -44,8 +44,7 @@ class VRFSerializer(PrimaryModelSerializer): model = VRF fields = [ 'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', - 'export_targets', 'tags', 'display_name', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', - 'prefix_count', + 'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', 'prefix_count', ] @@ -167,7 +166,7 @@ class VLANSerializer(PrimaryModelSerializer): model = VLAN fields = [ 'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'tags', - 'display_name', 'custom_fields', 'created', 'last_updated', 'prefix_count', + 'custom_fields', 'created', 'last_updated', 'prefix_count', ] validators = [] diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 1e1177772..f3f1335f7 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -10,7 +10,7 @@ from rest_framework.response import Response from rest_framework.routers import APIRootView from extras.api.views import CustomFieldModelViewSet -from ipam import filters +from ipam import filtersets from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from netbox.api.views import ModelViewSet from utilities.constants import ADVISORY_LOCK_KEYS @@ -38,7 +38,7 @@ class VRFViewSet(CustomFieldModelViewSet): prefix_count=count_related(Prefix, 'vrf') ) serializer_class = serializers.VRFSerializer - filterset_class = filters.VRFFilterSet + filterset_class = filtersets.VRFFilterSet # @@ -48,7 +48,7 @@ class VRFViewSet(CustomFieldModelViewSet): class RouteTargetViewSet(CustomFieldModelViewSet): queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags') serializer_class = serializers.RouteTargetSerializer - filterset_class = filters.RouteTargetFilterSet + filterset_class = filtersets.RouteTargetFilterSet # @@ -60,7 +60,7 @@ class RIRViewSet(CustomFieldModelViewSet): aggregate_count=count_related(Aggregate, 'rir') ) serializer_class = serializers.RIRSerializer - filterset_class = filters.RIRFilterSet + filterset_class = filtersets.RIRFilterSet # @@ -70,7 +70,7 @@ class RIRViewSet(CustomFieldModelViewSet): class AggregateViewSet(CustomFieldModelViewSet): queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags') serializer_class = serializers.AggregateSerializer - filterset_class = filters.AggregateFilterSet + filterset_class = filtersets.AggregateFilterSet # @@ -83,7 +83,7 @@ class RoleViewSet(CustomFieldModelViewSet): vlan_count=count_related(VLAN, 'role') ) serializer_class = serializers.RoleSerializer - filterset_class = filters.RoleFilterSet + filterset_class = filtersets.RoleFilterSet # @@ -95,7 +95,7 @@ class PrefixViewSet(CustomFieldModelViewSet): 'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags' ) serializer_class = serializers.PrefixSerializer - filterset_class = filters.PrefixFilterSet + filterset_class = filtersets.PrefixFilterSet def get_serializer_class(self): if self.action == "available_prefixes" and self.request.method == "POST": @@ -275,7 +275,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): 'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object' ) serializer_class = serializers.IPAddressSerializer - filterset_class = filters.IPAddressFilterSet + filterset_class = filtersets.IPAddressFilterSet # @@ -287,7 +287,7 @@ class VLANGroupViewSet(CustomFieldModelViewSet): vlan_count=count_related(VLAN, 'group') ) serializer_class = serializers.VLANGroupSerializer - filterset_class = filters.VLANGroupFilterSet + filterset_class = filtersets.VLANGroupFilterSet # @@ -301,7 +301,7 @@ class VLANViewSet(CustomFieldModelViewSet): prefix_count=count_related(Prefix, 'vlan') ) serializer_class = serializers.VLANSerializer - filterset_class = filters.VLANFilterSet + filterset_class = filtersets.VLANFilterSet # @@ -313,4 +313,4 @@ class ServiceViewSet(ModelViewSet): 'device', 'virtual_machine', 'tags', 'ipaddresses' ) serializer_class = serializers.ServiceSerializer - filterset_class = filters.ServiceFilterSet + filterset_class = filtersets.ServiceFilterSet diff --git a/netbox/ipam/filters.py b/netbox/ipam/filtersets.py similarity index 94% rename from netbox/ipam/filters.py rename to netbox/ipam/filtersets.py index 8f4030411..5ab4994ea 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filtersets.py @@ -6,11 +6,11 @@ from django.db.models import Q from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site, SiteGroup -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet -from tenancy.filters import TenancyFilterSet +from extras.filters import TagFilter +from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet +from tenancy.filtersets import TenancyFilterSet from utilities.filters import ( - BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, - NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter, + ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, ) from virtualization.models import VirtualMachine, VMInterface from .choices import * @@ -31,7 +31,7 @@ __all__ = ( ) -class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -74,7 +74,7 @@ class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, C fields = ['id', 'name', 'rd', 'enforce_unique'] -class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -116,14 +116,14 @@ class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilt fields = ['id', 'name'] -class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RIRFilterSet(OrganizationalModelFilterSet): class Meta: model = RIR fields = ['id', 'name', 'slug', 'is_private', 'description'] -class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -173,7 +173,7 @@ class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter return queryset.none() -class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RoleFilterSet(OrganizationalModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -184,7 +184,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilter fields = ['id', 'name', 'slug'] -class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -369,7 +369,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet ) -class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -535,7 +535,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter return queryset.exclude(assigned_object_id__isnull=value) -class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class VLANGroupFilterSet(OrganizationalModelFilterSet): scope_type = ContentTypeFilter() region = django_filters.NumberFilter( method='filter_scope' @@ -570,7 +570,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF ) -class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -666,7 +666,7 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, return queryset.get_for_virtualmachine(value) -class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): +class ServiceFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index b11a88d54..2490a0c5a 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -29,7 +29,7 @@ __all__ = ( ) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class RIR(OrganizationalModel): """ A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address @@ -184,7 +184,7 @@ class Aggregate(PrimaryModel): return int(float(child_prefixes.size) / self.prefix.size * 100) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Role(OrganizationalModel): """ A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or @@ -426,19 +426,11 @@ class Prefix(PrimaryModel): child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]) available_ips = prefix - child_ips - # All IP addresses within a pool are considered usable - if self.is_pool: + # IPv6, pool, or IPv4 /31 sets are fully usable + if self.family == 6 or self.is_pool or self.prefix.prefixlen == 31: return available_ips - # All IP addresses within a point-to-point prefix (IPv4 /31 or IPv6 /127) are considered usable - if ( - self.prefix.version == 4 and self.prefix.prefixlen == 31 # RFC 3021 - ) or ( - self.prefix.version == 6 and self.prefix.prefixlen == 127 # RFC 6164 - ): - return available_ips - - # Omit first and last IP address from the available set + # For "normal" IPv4 prefixes, omit first and last addresses available_ips -= netaddr.IPSet([ netaddr.IPAddress(self.prefix.first), netaddr.IPAddress(self.prefix.last), diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index d0f5375e2..b4964e761 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -21,7 +21,7 @@ __all__ = ( ) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class VLANGroup(OrganizationalModel): """ A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique. @@ -172,7 +172,7 @@ class VLAN(PrimaryModel): verbose_name_plural = 'VLANs' def __str__(self): - return self.display_name or super().__str__() + return f'{self.name} ({self.vid})' def get_absolute_url(self): return reverse('ipam:vlan', args=[self.pk]) @@ -199,10 +199,6 @@ class VLAN(PrimaryModel): self.description, ) - @property - def display_name(self): - return f'{self.name} ({self.vid})' - def get_status_class(self): return VLANStatusChoices.CSS_CLASSES.get(self.status) diff --git a/netbox/ipam/models/vrfs.py b/netbox/ipam/models/vrfs.py index 9eb2c6ab6..f674a7f65 100644 --- a/netbox/ipam/models/vrfs.py +++ b/netbox/ipam/models/vrfs.py @@ -71,7 +71,9 @@ class VRF(PrimaryModel): verbose_name_plural = 'VRFs' def __str__(self): - return self.display_name or super().__str__() + if self.rd: + return f'{self.name} ({self.rd})' + return self.name def get_absolute_url(self): return reverse('ipam:vrf', args=[self.pk]) @@ -85,12 +87,6 @@ class VRF(PrimaryModel): self.description, ) - @property - def display_name(self): - if self.rd: - return f'{self.name} ({self.rd})' - return self.name - @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class RouteTarget(PrimaryModel): diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 1a723421d..784d58342 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -64,6 +64,7 @@ class VLANQuerySet(RestrictedQuerySet): return self.filter( Q(group__in=VLANGroup.objects.filter(q)) | Q(site=device.site) | + Q(group__scope_id__isnull=True, site__isnull=True) | # Global group VLANs Q(group__isnull=True, site__isnull=True) # Global VLANs ) @@ -104,6 +105,7 @@ class VLANQuerySet(RestrictedQuerySet): # Return all applicable VLANs q = ( Q(group__in=vlan_groups) | + Q(group__scope_id__isnull=True, site__isnull=True) | # Global group VLANs Q(group__isnull=True, site__isnull=True) # Global VLANs ) if vm.cluster.site: diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index b2b4b9e8f..0de07b06b 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -22,7 +22,7 @@ class AppTest(APITestCase): class VRFTest(APIViewTestCases.APIViewTestCase): model = VRF - brief_fields = ['display', 'display_name', 'id', 'name', 'prefix_count', 'rd', 'url'] + brief_fields = ['display', 'id', 'name', 'prefix_count', 'rd', 'url'] create_data = [ { 'name': 'VRF 4', @@ -421,7 +421,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase): class VLANTest(APIViewTestCases.APIViewTestCase): model = VLAN - brief_fields = ['display', 'display_name', 'id', 'name', 'url', 'vid'] + brief_fields = ['display', 'id', 'name', 'url', 'vid'] bulk_update_data = { 'description': 'New description', } diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filtersets.py similarity index 96% rename from netbox/ipam/tests/test_filters.py rename to netbox/ipam/tests/test_filtersets.py index 3ea54209c..f43a44c62 100644 --- a/netbox/ipam/tests/test_filters.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -2,13 +2,14 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup from ipam.choices import * -from ipam.filters import * +from ipam.filtersets import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF +from utilities.testing import ChangeLoggedFilterSetTests from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from tenancy.models import Tenant, TenantGroup -class VRFTestCase(TestCase): +class VRFTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = VRF.objects.all() filterset = VRFFilterSet @@ -53,10 +54,6 @@ class VRFTestCase(TestCase): vrfs[2].import_targets.add(route_targets[2]) vrfs[2].export_targets.add(route_targets[2]) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['VRF 1', 'VRF 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -100,7 +97,7 @@ class VRFTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class RouteTargetTestCase(TestCase): +class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RouteTarget.objects.all() filterset = RouteTargetFilterSet @@ -149,10 +146,6 @@ class RouteTargetTestCase(TestCase): vrfs[1].import_targets.add(route_targets[4], route_targets[5]) vrfs[1].export_targets.add(route_targets[6], route_targets[7]) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['65000:1001', '65000:1002', '65000:1003']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) @@ -186,7 +179,7 @@ class RouteTargetTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8) -class RIRTestCase(TestCase): +class RIRTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = RIR.objects.all() filterset = RIRFilterSet @@ -203,10 +196,6 @@ class RIRTestCase(TestCase): ) RIR.objects.bulk_create(rirs) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['RIR 1', 'RIR 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -226,7 +215,7 @@ class RIRTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) -class AggregateTestCase(TestCase): +class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Aggregate.objects.all() filterset = AggregateFilterSet @@ -265,10 +254,6 @@ class AggregateTestCase(TestCase): ) Aggregate.objects.bulk_create(aggregates) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_family(self): params = {'family': '4'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) @@ -304,7 +289,7 @@ class AggregateTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class RoleTestCase(TestCase): +class RoleTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Role.objects.all() filterset = RoleFilterSet @@ -318,10 +303,6 @@ class RoleTestCase(TestCase): ) Role.objects.bulk_create(roles) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['Role 1', 'Role 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -331,7 +312,7 @@ class RoleTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class PrefixTestCase(TestCase): +class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Prefix.objects.all() filterset = PrefixFilterSet @@ -421,10 +402,6 @@ class PrefixTestCase(TestCase): ) Prefix.objects.bulk_create(prefixes) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_family(self): params = {'family': '6'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) @@ -528,7 +505,7 @@ class PrefixTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class IPAddressTestCase(TestCase): +class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = IPAddress.objects.all() filterset = IPAddressFilterSet @@ -607,10 +584,6 @@ class IPAddressTestCase(TestCase): ) IPAddress.objects.bulk_create(ipaddresses) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_family(self): params = {'family': '6'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) @@ -708,7 +681,7 @@ class IPAddressTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) -class VLANGroupTestCase(TestCase): +class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = VLANGroup.objects.all() filterset = VLANGroupFilterSet @@ -751,10 +724,6 @@ class VLANGroupTestCase(TestCase): ) VLANGroup.objects.bulk_create(vlan_groups) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[: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(self.filterset(params, self.queryset).qs.count(), 2) @@ -796,7 +765,7 @@ class VLANGroupTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class VLANTestCase(TestCase): +class VLANTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = VLAN.objects.all() filterset = VLANFilterSet @@ -965,10 +934,6 @@ class VLANTestCase(TestCase): ) VLAN.objects.bulk_create(vlans) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:2]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_name(self): params = {'name': ['VLAN 101', 'VLAN 102']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -1041,7 +1006,7 @@ class VLANTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) # 5 scoped + 1 global -class ServiceTestCase(TestCase): +class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Service.objects.all() filterset = ServiceFilterSet @@ -1080,10 +1045,6 @@ class ServiceTestCase(TestCase): ) Service.objects.bulk_create(services) - def test_id(self): - params = {'id': self.queryset.values_list('pk', flat=True)[:3]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) - def test_name(self): params = {'name': ['Service 1', 'Service 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 0339aff07..168933af7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -7,7 +7,7 @@ from netbox.views import generic from utilities.tables import paginate_table from utilities.utils import count_related from virtualization.models import VirtualMachine, VMInterface -from . import filters, forms, tables +from . import filtersets, forms, tables from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans @@ -19,7 +19,7 @@ from .utils import add_available_ipaddresses, add_available_prefixes, add_availa class VRFListView(generic.ObjectListView): queryset = VRF.objects.all() - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet filterset_form = forms.VRFFilterForm table = tables.VRFTable @@ -65,14 +65,14 @@ class VRFBulkImportView(generic.BulkImportView): class VRFBulkEditView(generic.BulkEditView): queryset = VRF.objects.prefetch_related('tenant') - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet table = tables.VRFTable form = forms.VRFBulkEditForm class VRFBulkDeleteView(generic.BulkDeleteView): queryset = VRF.objects.prefetch_related('tenant') - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet table = tables.VRFTable @@ -82,7 +82,7 @@ class VRFBulkDeleteView(generic.BulkDeleteView): class RouteTargetListView(generic.ObjectListView): queryset = RouteTarget.objects.all() - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet filterset_form = forms.RouteTargetFilterForm table = tables.RouteTargetTable @@ -123,14 +123,14 @@ class RouteTargetBulkImportView(generic.BulkImportView): class RouteTargetBulkEditView(generic.BulkEditView): queryset = RouteTarget.objects.prefetch_related('tenant') - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet table = tables.RouteTargetTable form = forms.RouteTargetBulkEditForm class RouteTargetBulkDeleteView(generic.BulkDeleteView): queryset = RouteTarget.objects.prefetch_related('tenant') - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet table = tables.RouteTargetTable @@ -142,7 +142,7 @@ class RIRListView(generic.ObjectListView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet filterset_form = forms.RIRFilterForm table = tables.RIRTable template_name = 'ipam/rir_list.html' @@ -184,7 +184,7 @@ class RIRBulkEditView(generic.BulkEditView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet table = tables.RIRTable form = forms.RIRBulkEditForm @@ -193,7 +193,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet table = tables.RIRTable @@ -205,7 +205,7 @@ class AggregateListView(generic.ObjectListView): queryset = Aggregate.objects.annotate( child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ()) ) - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet filterset_form = forms.AggregateFilterForm table = tables.AggregateDetailTable template_name = 'ipam/aggregate_list.html' @@ -280,14 +280,14 @@ class AggregateBulkImportView(generic.BulkImportView): class AggregateBulkEditView(generic.BulkEditView): queryset = Aggregate.objects.prefetch_related('rir') - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet table = tables.AggregateTable form = forms.AggregateBulkEditForm class AggregateBulkDeleteView(generic.BulkDeleteView): queryset = Aggregate.objects.prefetch_related('rir') - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet table = tables.AggregateTable @@ -337,7 +337,7 @@ class RoleBulkImportView(generic.BulkImportView): class RoleBulkEditView(generic.BulkEditView): queryset = Role.objects.all() - filterset = filters.RoleFilterSet + filterset = filtersets.RoleFilterSet table = tables.RoleTable form = forms.RoleBulkEditForm @@ -353,7 +353,7 @@ class RoleBulkDeleteView(generic.BulkDeleteView): class PrefixListView(generic.ObjectListView): queryset = Prefix.objects.annotate_tree() - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet filterset_form = forms.PrefixFilterForm table = tables.PrefixDetailTable template_name = 'ipam/prefix_list.html' @@ -493,14 +493,14 @@ class PrefixBulkImportView(generic.BulkImportView): class PrefixBulkEditView(generic.BulkEditView): queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet table = tables.PrefixTable form = forms.PrefixBulkEditForm class PrefixBulkDeleteView(generic.BulkDeleteView): queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet table = tables.PrefixTable @@ -510,7 +510,7 @@ class PrefixBulkDeleteView(generic.BulkDeleteView): class IPAddressListView(generic.ObjectListView): queryset = IPAddress.objects.all() - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet filterset_form = forms.IPAddressFilterForm table = tables.IPAddressDetailTable @@ -613,7 +613,7 @@ class IPAddressAssignView(generic.ObjectView): addresses = self.queryset.prefetch_related('vrf', 'tenant') # Limit to 100 results - addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100] + addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100] table = tables.IPAddressAssignTable(addresses) return render(request, 'ipam/ipaddress_assign.html', { @@ -643,14 +643,14 @@ class IPAddressBulkImportView(generic.BulkImportView): class IPAddressBulkEditView(generic.BulkEditView): queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet table = tables.IPAddressTable form = forms.IPAddressBulkEditForm class IPAddressBulkDeleteView(generic.BulkDeleteView): queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet table = tables.IPAddressTable @@ -662,7 +662,7 @@ class VLANGroupListView(generic.ObjectListView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable @@ -673,7 +673,7 @@ class VLANGroupView(generic.ObjectView): def get_extra_context(self, request, instance): vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related( Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user)) - ) + ).order_by('vid') vlans_count = vlans.count() vlans = add_available_vlans(instance, vlans) @@ -684,9 +684,17 @@ class VLANGroupView(generic.ObjectView): vlans_table.columns.hide('group') paginate_table(vlans_table, request) + # Compile permissions list for rendering the object table + permissions = { + 'add': request.user.has_perm('ipam.add_vlan'), + 'change': request.user.has_perm('ipam.change_vlan'), + 'delete': request.user.has_perm('ipam.delete_vlan'), + } + return { 'vlans_count': vlans_count, 'vlans_table': vlans_table, + 'permissions': permissions, } @@ -710,7 +718,7 @@ class VLANGroupBulkEditView(generic.BulkEditView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable form = forms.VLANGroupBulkEditForm @@ -719,7 +727,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable @@ -729,7 +737,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): class VLANListView(generic.ObjectListView): queryset = VLAN.objects.all() - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet filterset_form = forms.VLANFilterForm table = tables.VLANDetailTable @@ -797,14 +805,14 @@ class VLANBulkImportView(generic.BulkImportView): class VLANBulkEditView(generic.BulkEditView): queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet table = tables.VLANTable form = forms.VLANBulkEditForm class VLANBulkDeleteView(generic.BulkDeleteView): queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet table = tables.VLANTable @@ -814,7 +822,7 @@ class VLANBulkDeleteView(generic.BulkDeleteView): class ServiceListView(generic.ObjectListView): queryset = Service.objects.all() - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet filterset_form = forms.ServiceFilterForm table = tables.ServiceTable action_buttons = ('import', 'export') @@ -855,12 +863,12 @@ class ServiceDeleteView(generic.ObjectDeleteView): class ServiceBulkEditView(generic.BulkEditView): queryset = Service.objects.prefetch_related('device', 'virtual_machine') - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet table = tables.ServiceTable form = forms.ServiceBulkEditForm class ServiceBulkDeleteView(generic.BulkDeleteView): queryset = Service.objects.prefetch_related('device', 'virtual_machine') - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet table = tables.ServiceTable diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index c40e280dd..461d7f4cd 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -246,6 +246,9 @@ RQ_DEFAULT_TIMEOUT = 300 # this setting is derived from the installed location. # SCRIPTS_ROOT = '/opt/netbox/netbox/scripts' +# The name to use for the session cookie. +SESSION_COOKIE_NAME = 'sessionid' + # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 5568f4e70..b6da0b2de 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -1,9 +1,9 @@ from collections import OrderedDict -from circuits.filters import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet +from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet from circuits.models import Circuit, ProviderNetwork, Provider from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable -from dcim.filters import ( +from dcim.filtersets import ( CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet, SiteFilterSet, VirtualChassisFilterSet, ) @@ -12,17 +12,17 @@ from dcim.tables import ( CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable, VirtualChassisTable, ) -from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet +from ipam.filtersets import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable -from secrets.filters import SecretFilterSet +from secrets.filtersets import SecretFilterSet from secrets.models import Secret from secrets.tables import SecretTable -from tenancy.filters import TenantFilterSet +from tenancy.filtersets import TenantFilterSet from tenancy.models import Tenant from tenancy.tables import TenantTable from utilities.utils import count_related -from virtualization.filters import ClusterFilterSet, VirtualMachineFilterSet +from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet from virtualization.models import Cluster, VirtualMachine from virtualization.tables import ClusterTable, VirtualMachineDetailTable diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py new file mode 100644 index 000000000..aa9e15385 --- /dev/null +++ b/netbox/netbox/filtersets.py @@ -0,0 +1,238 @@ +import django_filters +from copy import deepcopy +from django.contrib.contenttypes.models import ContentType +from django.db import models +from django_filters.utils import get_model_field, resolve_field + +from dcim.forms import MACAddressField +from extras.choices import CustomFieldFilterLogicChoices +from extras.filters import CustomFieldFilter, TagFilter +from extras.models import CustomField +from utilities.constants import ( + FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP, + FILTER_NUMERIC_BASED_LOOKUP_MAP +) +from utilities import filters + + +__all__ = ( + 'BaseFilterSet', + 'ChangeLoggedModelFilterSet', + 'OrganizationalModelFilterSet', + 'PrimaryModelFilterSet', +) + + +# +# FilterSets +# + +class BaseFilterSet(django_filters.FilterSet): + """ + A base FilterSet which provides common functionality to all NetBox FilterSets + """ + FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS) + FILTER_DEFAULTS.update({ + models.AutoField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.CharField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.DateField: { + 'filter_class': filters.MultiValueDateFilter + }, + models.DateTimeField: { + 'filter_class': filters.MultiValueDateTimeFilter + }, + models.DecimalField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.EmailField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.FloatField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.IntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.PositiveIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.PositiveSmallIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.SlugField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.SmallIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.TimeField: { + 'filter_class': filters.MultiValueTimeFilter + }, + models.URLField: { + 'filter_class': filters.MultiValueCharFilter + }, + MACAddressField: { + 'filter_class': filters.MultiValueMACAddressFilter + }, + }) + + @staticmethod + def _get_filter_lookup_dict(existing_filter): + # Choose the lookup expression map based on the filter type + if isinstance(existing_filter, ( + filters.MultiValueDateFilter, + filters.MultiValueDateTimeFilter, + filters.MultiValueNumberFilter, + filters.MultiValueTimeFilter + )): + lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP + + elif isinstance(existing_filter, ( + filters.TreeNodeMultipleChoiceFilter, + )): + # TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression + lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP + + elif isinstance(existing_filter, ( + django_filters.ModelChoiceFilter, + django_filters.ModelMultipleChoiceFilter, + TagFilter + )) or existing_filter.extra.get('choices'): + # These filter types support only negation + lookup_map = FILTER_NEGATION_LOOKUP_MAP + + elif isinstance(existing_filter, ( + django_filters.filters.CharFilter, + django_filters.MultipleChoiceFilter, + filters.MultiValueCharFilter, + filters.MultiValueMACAddressFilter + )): + lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP + + else: + lookup_map = None + + return lookup_map + + @classmethod + def get_filters(cls): + """ + Override filter generation to support dynamic lookup expressions for certain filter types. + + For specific filter types, new filters are created based on defined lookup expressions in + the form `__` + """ + filters = super().get_filters() + + new_filters = {} + for existing_filter_name, existing_filter in filters.items(): + # Loop over existing filters to extract metadata by which to create new filters + + # If the filter makes use of a custom filter method or lookup expression skip it + # as we cannot sanely handle these cases in a generic mannor + if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']: + continue + + # Choose the lookup expression map based on the filter type + lookup_map = cls._get_filter_lookup_dict(existing_filter) + if lookup_map is None: + # Do not augment this filter type with more lookup expressions + continue + + # Get properties of the existing filter for later use + field_name = existing_filter.field_name + field = get_model_field(cls._meta.model, field_name) + + # Create new filters for each lookup expression in the map + for lookup_name, lookup_expr in lookup_map.items(): + new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name) + + try: + if existing_filter_name in cls.declared_filters: + # The filter field has been explicity defined on the filterset class so we must manually + # create the new filter with the same type because there is no guarantee the defined type + # is the same as the default type for the field + resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid + new_filter = type(existing_filter)( + field_name=field_name, + lookup_expr=lookup_expr, + label=existing_filter.label, + exclude=existing_filter.exclude, + distinct=existing_filter.distinct, + **existing_filter.extra + ) + else: + # The filter field is listed in Meta.fields so we can safely rely on default behaviour + # Will raise FieldLookupError if the lookup is invalid + new_filter = cls.filter_for_field(field, field_name, lookup_expr) + except django_filters.exceptions.FieldLookupError: + # The filter could not be created because the lookup expression is not supported on the field + continue + + if lookup_name.startswith('n'): + # This is a negation filter which requires a queryset.exclude() clause + # Of course setting the negation of the existing filter's exclude attribute handles both cases + new_filter.exclude = not existing_filter.exclude + + new_filters[new_filter_name] = new_filter + + filters.update(new_filters) + return filters + + +class ChangeLoggedModelFilterSet(BaseFilterSet): + created = django_filters.DateFilter() + created__gte = django_filters.DateFilter( + field_name='created', + lookup_expr='gte' + ) + created__lte = django_filters.DateFilter( + field_name='created', + lookup_expr='lte' + ) + last_updated = django_filters.DateTimeFilter() + last_updated__gte = django_filters.DateTimeFilter( + field_name='last_updated', + lookup_expr='gte' + ) + last_updated__lte = django_filters.DateTimeFilter( + field_name='last_updated', + lookup_expr='lte' + ) + + +class PrimaryModelFilterSet(ChangeLoggedModelFilterSet): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Dynamically add a Filter for each CustomField applicable to the parent model + custom_fields = CustomField.objects.filter( + content_types=ContentType.objects.get_for_model(self._meta.model) + ).exclude( + filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED + ) + for cf in custom_fields: + self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf) + + +class OrganizationalModelFilterSet(PrimaryModelFilterSet): + """ + A base class for adding the search method to models which only expose the `name` and `slug` fields + """ + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + models.Q(name__icontains=value) | + models.Q(slug__icontains=value) + ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 7d4fb91da..71edae573 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -114,6 +114,7 @@ REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 're RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None) +SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid') SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d') SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i') SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s') diff --git a/netbox/netbox/views/__init__.py b/netbox/netbox/views/__init__.py index bc1a68569..66f66cf52 100644 --- a/netbox/netbox/views/__init__.py +++ b/netbox/netbox/views/__init__.py @@ -109,12 +109,13 @@ class HomeView(View): for section_label, section_items in sections: stat = {"label": section_label, "items": []} for perm, item_label, description, get_count in section_items: + app, scope = perm.split(".") + url = ":".join((app, scope.replace("view_", "") + "_list")) + item = {"label": item_label, "description": description, "count": None, "url": url, "disabled": True} if perm in perms: - app, scope = perm.split(".") - url = ":".join((app, scope.replace("view_", "") + "_list")) - stat["items"].append( - {"label": item_label, "description": description, "count": get_count(), "url": url} - ) + item["count"] = get_count() + item["disabled"] = False + stat["items"].append(item) stats.append(stat) return stats diff --git a/netbox/project-static/_dark.scss b/netbox/project-static/_dark.scss new file mode 100644 index 000000000..9de45c1e7 --- /dev/null +++ b/netbox/project-static/_dark.scss @@ -0,0 +1,10 @@ +// Entry for netbox-dark.css stylesheet. + +body[data-netbox-color-mode='dark'] { + // Imports are scoped under the body when its data-netbox-color-mode attribute is set to 'dark'. + @import './theme-dark.scss'; + @import './bootstrap.scss'; + @import './select.scss'; + @import './flatpickr-dark.scss'; + @import './netbox.scss'; +} diff --git a/netbox/project-static/rack_elevation.scss b/netbox/project-static/_elevations.scss similarity index 97% rename from netbox/project-static/rack_elevation.scss rename to netbox/project-static/_elevations.scss index 6962e7763..859142a4c 100644 --- a/netbox/project-static/rack_elevation.scss +++ b/netbox/project-static/_elevations.scss @@ -1,4 +1,5 @@ -// Stylesheet for rendering SVG rack elevations +// Entry for rack_elevation.css stylesheet. + @import './theme-light.scss'; * { diff --git a/netbox/project-static/_external.scss b/netbox/project-static/_external.scss new file mode 100644 index 000000000..ae37789c0 --- /dev/null +++ b/netbox/project-static/_external.scss @@ -0,0 +1,4 @@ +// Entry for all 3rd party library imports that do not rely on Bootstrap or NetBox styles. + +@import '@mdi/font/css/materialdesignicons.min.css'; +@import 'flatpickr/dist/flatpickr.css'; diff --git a/netbox/project-static/_light.scss b/netbox/project-static/_light.scss new file mode 100644 index 000000000..af0bbaab8 --- /dev/null +++ b/netbox/project-static/_light.scss @@ -0,0 +1,6 @@ +// Entry for netbox-light.css stylesheet. + +@import './theme-light.scss'; +@import './bootstrap.scss'; +@import './select.scss'; +@import './netbox.scss'; diff --git a/netbox/project-static/bundle.js b/netbox/project-static/bundle.js index e60a8fdfd..b12cb4261 100644 --- a/netbox/project-static/bundle.js +++ b/netbox/project-static/bundle.js @@ -27,8 +27,10 @@ if (args.includes('--no-cache')) { // Style (SCSS) bundle jobs. Generally, everything should be bundled into netbox.css from main.scss // unless there is a specific reason to do otherwise. const styles = [ - ['main.scss', 'netbox.css'], - ['rack_elevation.scss', 'rack_elevation.css'], + ['_external.scss', 'netbox-external.css'], + ['_light.scss', 'netbox-light.css'], + ['_dark.scss', 'netbox-dark.css'], + ['_elevations.scss', 'rack_elevation.css'], ]; // Script (JavaScript) bundle jobs. Generally, everything should be bundled into netbox.js from diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css new file mode 100644 index 000000000..b5e7e841b Binary files /dev/null and b/netbox/project-static/dist/netbox-dark.css differ diff --git a/netbox/project-static/dist/netbox-dark.css.map b/netbox/project-static/dist/netbox-dark.css.map new file mode 100644 index 000000000..e9e7d7459 --- /dev/null +++ b/netbox/project-static/dist/netbox-dark.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["node_modules/bootstrap/scss/_type.scss","node_modules/bootstrap/scss/_root.scss","node_modules/bootstrap/scss/_reboot.scss","node_modules/bootstrap/scss/_variables.scss","node_modules/bootstrap/scss/vendor/_rfs.scss","theme-base.scss","node_modules/bootstrap/scss/mixins/_border-radius.scss","node_modules/bootstrap/scss/mixins/_lists.scss","node_modules/bootstrap/scss/_images.scss","node_modules/bootstrap/scss/mixins/_image.scss","node_modules/bootstrap/scss/_containers.scss","node_modules/bootstrap/scss/mixins/_container.scss","node_modules/bootstrap/scss/mixins/_breakpoints.scss","node_modules/bootstrap/scss/_grid.scss","node_modules/bootstrap/scss/mixins/_grid.scss","node_modules/bootstrap/scss/_tables.scss","node_modules/bootstrap/scss/mixins/_table-variants.scss","node_modules/bootstrap/scss/forms/_labels.scss","node_modules/bootstrap/scss/forms/_form-text.scss","node_modules/bootstrap/scss/forms/_form-control.scss","node_modules/bootstrap/scss/mixins/_transition.scss","theme-dark.scss","node_modules/bootstrap/scss/mixins/_gradients.scss","node_modules/bootstrap/scss/forms/_form-select.scss","node_modules/bootstrap/scss/forms/_form-check.scss","node_modules/bootstrap/scss/forms/_form-range.scss","node_modules/bootstrap/scss/forms/_floating-labels.scss","node_modules/bootstrap/scss/forms/_input-group.scss","node_modules/bootstrap/scss/mixins/_forms.scss","node_modules/bootstrap/scss/_buttons.scss","node_modules/bootstrap/scss/mixins/_buttons.scss","node_modules/bootstrap/scss/_transitions.scss","node_modules/bootstrap/scss/_dropdown.scss","node_modules/bootstrap/scss/mixins/_caret.scss","node_modules/bootstrap/scss/_button-group.scss","node_modules/bootstrap/scss/_nav.scss","node_modules/bootstrap/scss/_navbar.scss","node_modules/bootstrap/scss/_card.scss","node_modules/bootstrap/scss/_accordion.scss","node_modules/bootstrap/scss/_breadcrumb.scss","node_modules/bootstrap/scss/_pagination.scss","node_modules/bootstrap/scss/mixins/_pagination.scss","node_modules/bootstrap/scss/_badge.scss","node_modules/bootstrap/scss/_alert.scss","node_modules/bootstrap/scss/mixins/_alert.scss","node_modules/bootstrap/scss/_progress.scss","node_modules/bootstrap/scss/_list-group.scss","node_modules/bootstrap/scss/mixins/_list-group.scss","node_modules/bootstrap/scss/_close.scss","node_modules/bootstrap/scss/_toasts.scss","node_modules/bootstrap/scss/_modal.scss","node_modules/bootstrap/scss/_tooltip.scss","node_modules/bootstrap/scss/mixins/_reset-text.scss","node_modules/bootstrap/scss/_popover.scss","node_modules/bootstrap/scss/_carousel.scss","node_modules/bootstrap/scss/mixins/_clearfix.scss","node_modules/bootstrap/scss/_spinners.scss","node_modules/bootstrap/scss/helpers/_colored-links.scss","node_modules/bootstrap/scss/helpers/_ratio.scss","node_modules/bootstrap/scss/helpers/_position.scss","node_modules/bootstrap/scss/helpers/_visually-hidden.scss","node_modules/bootstrap/scss/mixins/_visually-hidden.scss","node_modules/bootstrap/scss/helpers/_stretched-link.scss","node_modules/bootstrap/scss/helpers/_text-truncation.scss","node_modules/bootstrap/scss/mixins/_text-truncate.scss","node_modules/bootstrap/scss/mixins/_utilities.scss","node_modules/bootstrap/scss/utilities/_api.scss","select.scss","node_modules/slim-select/src/slim-select/slimselect.scss","flatpickr-dark.scss","netbox.scss"],"names":[],"mappings":"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A,uCoBfN,6CpBgBQ,iBoBPN,mDACE,SAAA,CACA,anC8CO,CmC5CP,wBnCEO,CmCDP,qBAGF,mDACE,SAAA,CACA,anCsCO,CmCrCP,wBnCLO,CmCMP,SrCqgCgC,CqCpgChC,8CAKF,0EACE,iBAGF,+DACE,SAAA,CACA,UrC9BO,CmBJT,wBjB2DS,CmCvBP,qBAGF,iEACE,anCtBO,CmCuBP,mBAAA,CACA,wBnCtBO,CmCuBP,qBC3CF,6CACE,uBAOI,oEnCqCJ,4BAAA,CACA,gCmChCI,mEnCiBJ,6BAAA,CACA,iCmChCF,4DACE,qBAAA,CrCsPE,kBqC/OE,mFnCqCJ,6BAAA,CACA,iCmChCI,kFnCiBJ,8BAAA,CACA,kCmChCF,4DACE,oBAAA,CrCsPE,kBqC/OE,mFnCqCJ,4BAAA,CACA,gCmChCI,kFnCiBJ,6BAAA,CACA,iCoC/BJ,yCACE,oBAAA,CACA,mBAAA,CtCoPI,eALI,CsC7OR,evCya4B,CuCxa5B,aAAA,CACA,UvCHS,CuCIT,iBAAA,CACA,kBAAA,CACA,uBAAA,CpCKE,sBoCAF,+CACE,aAKJ,8CACE,iBAAA,CACA,SCvBF,yCACE,iBAAA,CACA,YAAA,CACA,kBxCmvC8B,CwClvC9B,4BAAA,CrCWE,oBqCNJ,iDAEE,cAIF,8CACE,gBAQF,qDACE,mBAGA,gEACE,iBAAA,CACA,KAAA,CACA,OAAA,CACA,SAAA,CACA,qBAeF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,8CClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,0DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,gDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,4DACE,cD6CF,+CClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,2DACE,cD6CF,8CClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,0DACE,cD6CF,6CClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,yDACE,cD6CF,8CClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,0DACE,cD6CF,gDClDA,aD8Cc,CrB5Cd,qBqB0CmB,CC1CnB,qBAEA,4DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,gDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,4DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,iDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,mDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,mDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,+DACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,oDClDA,aD8Cc,CrB5Cd,wBqB0CmB,CC1CnB,qBAEA,gEACE,cD6CF,iDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,6DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8DACE,cD6CF,kDClDA,aDgDgB,CrB9ChB,wBqB0CmB,CC1CnB,qBAEA,8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e,CC5Jf,yBAGF,wFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,yDACE,aDmKmB,CClKnB,yBAGE,4KAEE,aD6Je,CC5Jf,yBAGF,uFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,wDACE,aDiKiB,CChKjB,yBAGE,0KAEE,aD2Ja,CC1Jb,yBAGF,sFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,uDACE,aDiKiB,CChKjB,yBAGE,wKAEE,aD2Ja,CC1Jb,yBAGF,qFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,wDACE,aDiKiB,CChKjB,yBAGE,0KAEE,aD2Ja,CC1Jb,yBAGF,sFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,0DACE,aDiKiB,CChKjB,sBAGE,8KAEE,aD2Ja,CC1Jb,yBAGF,wFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,0DACE,aDmKmB,CClKnB,yBAGE,8KAEE,aD6Je,CC5Jf,yBAGF,wFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDiKiB,CChKjB,yBAGE,gLAEE,aD2Ja,CC1Jb,yBAGF,yFACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,6DACE,aDiKiB,CChKjB,yBAGE,oLAEE,aD2Ja,CC1Jb,yBAGF,2FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDiKiB,CChKjB,yBAGE,oLAEE,aD2Ja,CC1Jb,yBAGF,2FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDiKiB,CChKjB,yBAGE,oLAEE,aD2Ja,CC1Jb,yBAGF,2FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDiKiB,CChKjB,yBAGE,oLAEE,aD2Ja,CC1Jb,yBAGF,2FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDiKiB,CChKjB,yBAGE,oLAEE,aD2Ja,CC1Jb,yBAGF,2FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,6DACE,aDmKmB,CClKnB,yBAGE,oLAEE,aD6Je,CC5Jf,yBAGF,2FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDmKmB,CClKnB,yBAGE,sLAEE,aD6Je,CC5Jf,yBAGF,4FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,8DACE,aDiKiB,CChKjB,yBAGE,sLAEE,aD2Ja,CC1Jb,yBAGF,4FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,2DACE,aDmKmB,CClKnB,yBAGE,gLAEE,aD6Je,CC5Jf,yBAGF,yFACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDmKmB,CClKnB,yBAGE,kLAEE,aD6Je,CC5Jf,yBAGF,0FACE,U5CRG,C4CSH,wBDuJe,CCtJf,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBAdN,4DACE,aDiKiB,CChKjB,yBAGE,kLAEE,aD2Ja,CC1Jb,yBAGF,0FACE,U5CRG,C4CSH,wBDqJa,CCpJb,qBCbR,6CACE,sBAAA,CACA,S7Cq4C2B,C6Cp4C3B,U7Co4C2B,C6Cn4C3B,aAAA,CACA,U7CFS,C6CGT,qXAAA,CAC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,UlEdK,CkEeL,UAEF,iIACE,mBAGJ,qEACE,WACA,2EACE,qBAEF,khDAkBE,kBhEWK,CgEVL,oBhEUK,CgETL,WAEF,2EACE,oBhEvCK,CgEwCL,kBhExCK,CgEyCL,WAEF,omBAOE,cChEN,wCACE,0BAAA,CACA,0BAAA,CACA,wBAAA,CACA,gCAAA,CACA,mCAAA,CACA,iCAAA,CACA,2BAAA,CACA,kBAAA,CACA,wBAAA,CACA,oBAAA,CACA,8BAAA,CACA,uCAAA,CACA,wCAAA,CACA,2BAAA,CACA,qCAAA,CACA,kCAAA,CACA,4CAAA,CACA,6CAAA,CACA,qCAAA,CACA,4BAEA,0EACE,uBAAA,CACA,0BAAA,CACA,wBAAA,CACA,gCAAA,CACA,kDAAA,CACA,iCAAA,CACA,2BAAA,CACA,qBAAA,CACA,wBAAA,CACA,oBAAA,CACA,8BAAA,CACA,wCAAA,CACA,0CAAA,CACA,2BAAA,CACA,qCAAA,CACA,kCAAA,CACA,4CAAA,CACA,6CAAA,CACA,qCAAA,CACA,4BAIJ,oCACE,mDAGF,2CACE,ejE+Ea,CiE9Eb,iBAIF,yEACE,mBAOE,02BACE,WADF,uGACE,WADF,yGACE,WADF,6GACE,WADF,mjBACE,WADF,qjBACE,WADF,wpBACE,WADF,4bACE,WADF,izBACE,WADF,+VACE,WADF,myBACE,WADF,yVACE,WADF,oqBACE,WADF,ocACE,WADF,qxBACE,WADF,mVACE,WADF,4rBACE,WADF,odACE,WADF,4rBACE,WADF,odACE,WADF,oqBACE,WADF,ocACE,WAMN,qDACE,eAGF,uCACE,mCAAA,CACA,4BACA,uDACE,YAAA,CACA,eAGF,uDACE,YAAA,CACA,eAEF,6DACE,iBAAA,CACA,0CAIA,2QAGE,cAOF,mLACE,cAEF,2EACE,WAIA,+KACE,wBAGJ,sFACE,sCAEF,oFACE,SnEnHK,CmEoHL,YAGF,oFACE,YjE9GK,CiE+GL,eAKF,sHACE,qBACA,kIACE,0BAGJ,kKACE,6BAAA,CACA,+BAEF,gPAEE,ejEZS,CiEaT,gBjETW,CiEUX,sBAGA,4RAEE,iBnEmRsB,CmEl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","file":"netbox-dark.css","sourceRoot":"..","sourcesContent":["//\n// Headings\n//\n.h1 {\n @extend h1;\n}\n\n.h2 {\n @extend h2;\n}\n\n.h3 {\n @extend h3;\n}\n\n.h4 {\n @extend h4;\n}\n\n.h5 {\n @extend h5;\n}\n\n.h6 {\n @extend h6;\n}\n\n\n.lead {\n @include font-size($lead-font-size);\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n@each $display, $font-size in $display-font-sizes {\n .display-#{$display} {\n @include font-size($font-size);\n font-weight: $display-font-weight;\n line-height: $display-line-height;\n }\n}\n\n//\n// Emphasis\n//\n.small {\n @extend small;\n}\n\n.mark {\n @extend mark;\n}\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled();\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled();\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n @include font-size($initialism-font-size);\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $blockquote-margin-y;\n @include font-size($blockquote-font-size);\n\n > :last-child {\n margin-bottom: 0;\n }\n}\n\n.blockquote-footer {\n margin-top: -$blockquote-margin-y;\n margin-bottom: $blockquote-margin-y;\n @include font-size($blockquote-footer-font-size);\n color: $blockquote-footer-color;\n\n &::before {\n content: \"\\2014\\00A0\"; // em dash, nbsp\n }\n}\n",":root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n font-size: $font-size-root;\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: $body-text-align;\n background-color: $body-bg; // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-` diff --git a/netbox/templates/dcim/devicerole.html b/netbox/templates/dcim/devicerole.html index 5edf9cb7a..6811e9036 100644 --- a/netbox/templates/dcim/devicerole.html +++ b/netbox/templates/dcim/devicerole.html @@ -9,7 +9,7 @@ {% block content %}

-
+
Device Role @@ -51,13 +51,13 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% plugin_right_page object %}
-
+
Devices diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index fb1391d04..984fc81ac 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -58,7 +58,7 @@ {% block content %}
-
+
Chassis @@ -133,7 +133,7 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:devicetype_list' %}
@@ -152,12 +152,12 @@
-
+
{% plugin_full_width_page object %}
-
+
diff --git a/netbox/templates/dcim/frontport.html b/netbox/templates/dcim/frontport.html index 726680316..ac9137231 100644 --- a/netbox/templates/dcim/frontport.html +++ b/netbox/templates/dcim/frontport.html @@ -10,7 +10,7 @@ {% block content %}
-
+
Front Port @@ -56,7 +56,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
Connection @@ -126,7 +126,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 942a40567..54cfc2604 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -19,7 +19,7 @@ {% block content %}
-
+
Interface @@ -86,7 +86,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
{% if object.is_connectable %}
@@ -267,7 +267,7 @@
-
+
IP Addresses @@ -290,17 +290,17 @@
-
+
{% include 'panel_table.html' with table=vlan_table heading="VLANs" %}
-
+
{% include 'panel_table.html' with table=child_interfaces_table heading="Child Interfaces" %}
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index 52bb23f06..0317e0323 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -46,8 +46,8 @@ {% block buttons %} Cancel {% if obj.pk %} - - + + {% else %} diff --git a/netbox/templates/dcim/inventoryitem.html b/netbox/templates/dcim/inventoryitem.html index 441fee4e9..509c7f804 100644 --- a/netbox/templates/dcim/inventoryitem.html +++ b/netbox/templates/dcim/inventoryitem.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Inventory Item @@ -73,12 +73,12 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
{% plugin_right_page object %}
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 05bd2170f..f151f08bf 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -12,7 +12,7 @@ {% block content %}
-
+
Location @@ -58,7 +58,7 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %}
@@ -80,7 +80,7 @@
-
+
Locations diff --git a/netbox/templates/dcim/manufacturer.html b/netbox/templates/dcim/manufacturer.html index 2e0e553bd..a4624c42f 100644 --- a/netbox/templates/dcim/manufacturer.html +++ b/netbox/templates/dcim/manufacturer.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Manufacturer @@ -35,13 +35,13 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% plugin_right_page object %}
-
+
Device Types diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html index 820e53c7c..e921fc0b9 100644 --- a/netbox/templates/dcim/platform.html +++ b/netbox/templates/dcim/platform.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Platform @@ -53,13 +53,13 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% plugin_right_page object %}
-
+
Devices diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index add2486ba..8bddc1303 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -16,7 +16,7 @@ {% block content %}
-
+
Power Feed @@ -112,7 +112,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:powerfeed_list' %} {% plugin_left_page object %}
-
+
Connection @@ -199,7 +199,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/poweroutlet.html b/netbox/templates/dcim/poweroutlet.html index fe8f7c002..2ef2fd8dd 100644 --- a/netbox/templates/dcim/poweroutlet.html +++ b/netbox/templates/dcim/poweroutlet.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Power Outlet @@ -53,7 +53,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
Connection @@ -123,7 +123,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index f0f0dda72..6476b3660 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -14,7 +14,7 @@ {% block content %}
-
+
Power Panel @@ -42,14 +42,14 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:powerpanel_list' %} {% plugin_right_page object %}
-
+
{% csrf_token %}
diff --git a/netbox/templates/dcim/powerport.html b/netbox/templates/dcim/powerport.html index de71971ea..e3d94ecfb 100644 --- a/netbox/templates/dcim/powerport.html +++ b/netbox/templates/dcim/powerport.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Power Port @@ -53,7 +53,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
Connection @@ -133,7 +133,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index ee4604717..0cc115ddd 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -33,7 +33,7 @@ {% block content %}
-
+
Rack @@ -310,15 +310,15 @@
{% plugin_left_page object %}
-
+
-
+

Front

{% include 'dcim/inc/rack_elevation.html' with face='front' %}
-
+

Rear

{% include 'dcim/inc/rack_elevation.html' with face='rear' %} @@ -344,7 +344,7 @@ {{ device }} {{ device.device_role }} - {{ device.device_type.display_name }} + {{ device.device_type }} {% if device.parent_bay %} {{ device.parent_bay.device }} {{ device.parent_bay }} @@ -373,7 +373,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index d0c1a47a6..286cd2bca 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -30,16 +30,16 @@ {% render_field form.width %} {% render_field form.u_height %}
- -
+ +
{{ form.outer_width }}
Width
-
+
{{ form.outer_depth }}
Depth
-
+
{{ form.outer_unit }}
Unit
diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index b13e954b1..363b92779 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -24,8 +24,8 @@ {% block content %}
-
-
+
+
{% include 'inc/search_panel.html' %}
{% if page %} diff --git a/netbox/templates/dcim/rackreservation.html b/netbox/templates/dcim/rackreservation.html index 83ce4be1a..e685ba18e 100644 --- a/netbox/templates/dcim/rackreservation.html +++ b/netbox/templates/dcim/rackreservation.html @@ -12,7 +12,7 @@ {% block content %}
-
+
Rack @@ -87,15 +87,15 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:rackreservation_list' %} {% plugin_left_page object %}
-
+
-
+

Front

{% include 'dcim/inc/rack_elevation.html' with object=object.rack face='front' %}
-
+

Rear

{% include 'dcim/inc/rack_elevation.html' with object=object.rack face='rear' %} @@ -106,7 +106,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/rackrole.html b/netbox/templates/dcim/rackrole.html index fbfd9c0df..85da68a25 100644 --- a/netbox/templates/dcim/rackrole.html +++ b/netbox/templates/dcim/rackrole.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Rack Role @@ -41,13 +41,13 @@
{% plugin_left_page object %}
-
+
{% include 'inc/custom_fields_panel.html' %} {% plugin_right_page object %}
-
+
Racks diff --git a/netbox/templates/dcim/rearport.html b/netbox/templates/dcim/rearport.html index 30dd8a335..31397bf6d 100644 --- a/netbox/templates/dcim/rearport.html +++ b/netbox/templates/dcim/rearport.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Rear Port @@ -49,7 +49,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% plugin_left_page object %}
-
+
Connection @@ -113,7 +113,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/region.html b/netbox/templates/dcim/region.html index 0fe4adf00..2f59f935e 100644 --- a/netbox/templates/dcim/region.html +++ b/netbox/templates/dcim/region.html @@ -12,7 +12,7 @@ {% block content %}
-
+
Region @@ -49,7 +49,7 @@ {% include 'inc/custom_fields_panel.html' %} {% plugin_left_page object %}
-
+
Child Regions @@ -69,7 +69,7 @@
-
+
Sites diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index 1fa77d580..97ac7293d 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -16,7 +16,7 @@ {% block content %}
-
+
Site @@ -164,34 +164,34 @@
{% plugin_left_page object %}
-
+
Stats
-
+ -
+ -
+ -
+ -
+ -
+

{{ stats.vm_count }}

Virtual Machines

@@ -246,7 +246,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/sitegroup.html b/netbox/templates/dcim/sitegroup.html index 1d3b38161..6fe426859 100644 --- a/netbox/templates/dcim/sitegroup.html +++ b/netbox/templates/dcim/sitegroup.html @@ -12,7 +12,7 @@ {% block content %}
-
+
Site Group @@ -49,7 +49,7 @@ {% include 'inc/custom_fields_panel.html' %} {% plugin_left_page object %}
-
+
Child Groups @@ -69,7 +69,7 @@
-
+
Sites diff --git a/netbox/templates/dcim/virtualchassis.html b/netbox/templates/dcim/virtualchassis.html index 3582a806c..6f98559db 100644 --- a/netbox/templates/dcim/virtualchassis.html +++ b/netbox/templates/dcim/virtualchassis.html @@ -22,7 +22,7 @@ {% block content %}
-
+
Virtual Chassis @@ -50,7 +50,7 @@ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:virtualchassis_list' %} {% plugin_left_page object %}
-
+
Members @@ -87,7 +87,7 @@
-
+
{% plugin_full_width_page object %}
diff --git a/netbox/templates/dcim/virtualchassis_add_member.html b/netbox/templates/dcim/virtualchassis_add_member.html index e89f912fa..b7818b47f 100644 --- a/netbox/templates/dcim/virtualchassis_add_member.html +++ b/netbox/templates/dcim/virtualchassis_add_member.html @@ -7,7 +7,7 @@ {% csrf_token %}
-
+
Add New Member
@@ -18,7 +18,7 @@
-
+
Cancel diff --git a/netbox/templates/dcim/virtualchassis_edit.html b/netbox/templates/dcim/virtualchassis_edit.html index 492c45f82..195855620 100644 --- a/netbox/templates/dcim/virtualchassis_edit.html +++ b/netbox/templates/dcim/virtualchassis_edit.html @@ -10,7 +10,7 @@ {{ pk_form.pk }} {{ formset.management_form }}
-
+
Virtual Chassis
@@ -91,10 +91,10 @@
-
+
Cancel {% if vc_form.instance.pk %} - + {% else %} {% endif %} diff --git a/netbox/templates/extras/configcontext.html b/netbox/templates/extras/configcontext.html index 733264df3..dde954a2d 100644 --- a/netbox/templates/extras/configcontext.html +++ b/netbox/templates/extras/configcontext.html @@ -9,7 +9,7 @@ {% block content %}
-
+
Config Context @@ -185,7 +185,7 @@
-
+
Data
diff --git a/netbox/templates/extras/journalentry.html b/netbox/templates/extras/journalentry.html index 4cbfcb729..5ebddd59d 100644 --- a/netbox/templates/extras/journalentry.html +++ b/netbox/templates/extras/journalentry.html @@ -10,7 +10,7 @@ {% block content %}
-
+
Journal Entry @@ -45,7 +45,7 @@
-
+
Comments diff --git a/netbox/templates/extras/object_configcontext.html b/netbox/templates/extras/object_configcontext.html index 4c489b51b..408e0a64a 100644 --- a/netbox/templates/extras/object_configcontext.html +++ b/netbox/templates/extras/object_configcontext.html @@ -6,7 +6,7 @@ {% block content %}
-
+
Rendered Context @@ -17,7 +17,7 @@
-
+
Local Context diff --git a/netbox/templates/extras/object_journal.html b/netbox/templates/extras/object_journal.html index 437ea4105..bcb070b22 100644 --- a/netbox/templates/extras/object_journal.html +++ b/netbox/templates/extras/object_journal.html @@ -8,7 +8,7 @@ {% block content %} {% if perms.extras.add_journalentry %} -
+

New Journal Entry

{% csrf_token %} @@ -18,14 +18,14 @@ {% render_field form.kind %} {% render_field form.comments %}
-
+
Cancel
{% endif %} -
+
{% include 'panel_table.html' %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} diff --git a/netbox/templates/extras/objectchange.html b/netbox/templates/extras/objectchange.html index 23e00f221..30664db9a 100644 --- a/netbox/templates/extras/objectchange.html +++ b/netbox/templates/extras/objectchange.html @@ -21,7 +21,7 @@ {% block header %}
-
+
@@ -38,7 +38,7 @@ {% block content %}
-
+
Change @@ -89,7 +89,7 @@
-
+
Difference
@@ -122,7 +122,7 @@
-
+
Pre-Change Data @@ -139,7 +139,7 @@
-
+
Post-Change Data @@ -158,7 +158,7 @@
-
+
{% include 'panel_table.html' with table=related_changes_table heading='Related Changes' panel_class='default' %} {% if related_changes_count > related_changes_table.rows|length %}
diff --git a/netbox/templates/extras/report.html b/netbox/templates/extras/report.html index a87477b4e..0d7698d76 100644 --- a/netbox/templates/extras/report.html +++ b/netbox/templates/extras/report.html @@ -32,7 +32,7 @@
{% endif %}
-
+
{% if report.result %} Last run: {{ report.result.created }} diff --git a/netbox/templates/extras/report_list.html b/netbox/templates/extras/report_list.html index 3314d3044..8ce3fd931 100644 --- a/netbox/templates/extras/report_list.html +++ b/netbox/templates/extras/report_list.html @@ -5,7 +5,7 @@ {% block content %}
-
+
{% if reports %} {% for module, module_reports in reports %}
@@ -85,7 +85,7 @@
{% endif %}
-
+
{% if reports %}
diff --git a/netbox/templates/extras/report_result.html b/netbox/templates/extras/report_result.html index a5210ecbe..acd1e9f9f 100644 --- a/netbox/templates/extras/report_result.html +++ b/netbox/templates/extras/report_result.html @@ -11,7 +11,7 @@ {% block content %}
-
+

Run: {{ result.created }} {% if result.completed %} diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index 2ef75133c..1078ba427 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -29,7 +29,7 @@

-
+
{% if not perms.extras.run_script %}
diff --git a/netbox/templates/extras/script_list.html b/netbox/templates/extras/script_list.html index b6e468f93..290d7d64f 100644 --- a/netbox/templates/extras/script_list.html +++ b/netbox/templates/extras/script_list.html @@ -5,7 +5,7 @@ {% block content %}
-
+
{% if scripts %} {% for module, module_scripts in scripts.items %}

{{ module|bettertitle }}

@@ -49,7 +49,7 @@
{% endif %}
-
+
{% if scripts %}
diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 9c126fbec..6d036251c 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -13,7 +13,7 @@ {% block content %}
-
+