diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a37c5dfb1a..6919ff16f2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,6 +1,6 @@ --- name: 🐛 Bug Report -about: Report a reproducible bug in the current release of NetBox +description: Report a reproducible bug in the current release of NetBox labels: ["type: bug"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/documentation_change.yaml b/.github/ISSUE_TEMPLATE/documentation_change.yaml index b480e629a5..19d9696add 100644 --- a/.github/ISSUE_TEMPLATE/documentation_change.yaml +++ b/.github/ISSUE_TEMPLATE/documentation_change.yaml @@ -1,6 +1,6 @@ --- name: 📖 Documentation Change -about: Suggest an addition or modification to the NetBox documentation +description: Suggest an addition or modification to the NetBox documentation labels: ["type: documentation"] body: - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 6282eedde5..7d7bde2259 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,6 +1,6 @@ --- name: ✨ Feature Request -about: Propose a new NetBox feature or enhancement +description: Propose a new NetBox feature or enhancement labels: ["type: feature"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/housekeeping.yaml b/.github/ISSUE_TEMPLATE/housekeeping.yaml index 778dca235b..5e675583e5 100644 --- a/.github/ISSUE_TEMPLATE/housekeeping.yaml +++ b/.github/ISSUE_TEMPLATE/housekeeping.yaml @@ -1,6 +1,6 @@ --- name: 🏡 Housekeeping -about: A change pertaining to the codebase itself (developers only) +description: A change pertaining to the codebase itself (developers only) labels: ["type: housekeeping"] body: - type: markdown diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9182457a02..99ba1f9ce7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8] + python-version: [3.7, 3.8] services: redis: image: redis diff --git a/docs/additional-features/custom-scripts.md b/docs/additional-features/custom-scripts.md index 3ed9107918..a27bcab83c 100644 --- a/docs/additional-features/custom-scripts.md +++ b/docs/additional-features/custom-scripts.md @@ -170,14 +170,9 @@ Similar to `ChoiceVar`, but allows for the selection of multiple choices. A particular object within NetBox. Each ObjectVar must specify a particular model, and allows the user to select one of the available instances. ObjectVar accepts several arguments, listed below. * `model` - The model class -* `display_field` - The name of the REST API object field to display in the selection list (default: `'display'`) * `query_params` - A dictionary of query parameters to use when retrieving available options (optional) * `null_option` - A label representing a "null" or empty choice (optional) -!!! warning - The `display_field` parameter is now deprecated, and will be removed in NetBox v2.12. All ObjectVar instances will - instead use the new standard `display` field for all serializers (introduced in NetBox v2.11). - To limit the selections available within the list, additional query parameters can be passed as the `query_params` dictionary. For example, to show only devices with an "active" status: ```python @@ -288,7 +283,6 @@ class NewBranchScript(Script): switch_model = ObjectVar( description="Access switch model", model=DeviceType, - display_field='model', query_params={ 'manufacturer_id': '$manufacturer' } diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index f7f6f36e9d..fb47400f20 120000 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -1 +1 @@ -version-2.11.md \ No newline at end of file +version-2.12.md \ No newline at end of file diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index d356fc2352..9c46d34009 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -300,43 +300,43 @@ All end-to-end cable paths are now cached using the new CablePath backend model. * Removed the `/extras/_custom_field_choices/` endpoint (replaced by new custom fields endpoint) * Added the `/status/` endpoint to convey NetBox's current status * circuits.CircuitTermination: - * Added the `/trace/` endpoint - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * `port_speed` may now be null + * Added the `/trace/` endpoint + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * `port_speed` may now be null * dcim.Cable: Added `custom_fields` * dcim.ConsolePort: - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * Removed `connection_status` from nested serializer + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * Removed `connection_status` from nested serializer * dcim.ConsoleServerPort: - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * Removed `connection_status` from nested serializer + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * Removed `connection_status` from nested serializer * dcim.FrontPort: - * Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths - * Added `cable_peer` and `cable_peer_type` + * Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths + * Added `cable_peer` and `cable_peer_type` * dcim.Interface: - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * Removed `connection_status` from nested serializer + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * Removed `connection_status` from nested serializer * dcim.InventoryItem: The `_depth` field has been added to reflect MPTT positioning * dcim.PowerFeed: - * Added the `/trace/` endpoint - * Added fields `connected_endpoint`, `connected_endpoint_type`, `connected_endpoint_reachable`, `cable_peer`, and `cable_peer_type` + * Added the `/trace/` endpoint + * Added fields `connected_endpoint`, `connected_endpoint_type`, `connected_endpoint_reachable`, `cable_peer`, and `cable_peer_type` * dcim.PowerOutlet: - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * Removed `connection_status` from nested serializer + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * Removed `connection_status` from nested serializer * dcim.PowerPanel: Added `custom_fields` * dcim.PowerPort - * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) - * Added `cable_peer` and `cable_peer_type` - * Removed `connection_status` from nested serializer + * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) + * Added `cable_peer` and `cable_peer_type` + * Removed `connection_status` from nested serializer * dcim.RackReservation: Added `custom_fields` * dcim.RearPort: - * Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths - * Added `cable_peer` and `cable_peer_type` + * Replaced the `/trace/` endpoint with `/paths/`, which returns a list of cable paths + * Added `cable_peer` and `cable_peer_type` * dcim.VirtualChassis: Added `custom_fields` * extras.ExportTemplate: The `template_language` field has been removed * extras.Graph: This API endpoint has been removed (see #4349) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 77bb1d04bd..838022006f 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -1,41 +1,25 @@ # NetBox v2.11 -## v2.11.0 (FUTURE) +## v2.11.1 (2021-04-21) -### Enhancements (from Beta) +### Enhancements -* [#5757](https://github.com/netbox-community/netbox/issues/5757) - Add unique identifier to every object view -* [#5848](https://github.com/netbox-community/netbox/issues/5848) - Filter custom fields by content type in format `.` -* [#6088](https://github.com/netbox-community/netbox/issues/6088) - Improved table configuration form -* [#6097](https://github.com/netbox-community/netbox/issues/6097) - Redirect old slug-based object views -* [#6109](https://github.com/netbox-community/netbox/issues/6109) - Add device counts to locations table -* [#6121](https://github.com/netbox-community/netbox/issues/6121) - Extend parent interface assignment to VM interfaces -* [#6125](https://github.com/netbox-community/netbox/issues/6125) - Add locations count to home page -* [#6146](https://github.com/netbox-community/netbox/issues/6146) - Add bulk disconnect support for power feeds -* [#6149](https://github.com/netbox-community/netbox/issues/6149) - Support image attachments for locations -* [#6150](https://github.com/netbox-community/netbox/issues/6150) - Enable change logging for journal entries +* [#6161](https://github.com/netbox-community/netbox/issues/6161) - Enable ordering of device component tables +* [#6179](https://github.com/netbox-community/netbox/issues/6179) - Enable natural ordering for virtual machines +* [#6189](https://github.com/netbox-community/netbox/issues/6189) - Add ability to search for locations by name or description +* [#6190](https://github.com/netbox-community/netbox/issues/6190) - Allow filtering devices with no location assigned +* [#6210](https://github.com/netbox-community/netbox/issues/6210) - Include child locations on location view -### Bug Fixes (from Beta) +### Bug Fixes -* [#5583](https://github.com/netbox-community/netbox/issues/5583) - Eliminate redundant change records when adding/removing tags -* [#6100](https://github.com/netbox-community/netbox/issues/6100) - Fix VM interfaces table "add interfaces" link -* [#6104](https://github.com/netbox-community/netbox/issues/6104) - Fix location column on racks table -* [#6105](https://github.com/netbox-community/netbox/issues/6105) - Hide checkboxes for VMs under cluster VMs view -* [#6106](https://github.com/netbox-community/netbox/issues/6106) - Allow assigning a virtual interface as the parent of an existing interface -* [#6107](https://github.com/netbox-community/netbox/issues/6107) - Fix rack selection field on device form -* [#6110](https://github.com/netbox-community/netbox/issues/6110) - Fix handling of TemplateColumn values for table export -* [#6123](https://github.com/netbox-community/netbox/issues/6123) - Prevent device from being assigned to mismatched site and location -* [#6124](https://github.com/netbox-community/netbox/issues/6124) - Location `parent` filter should return all child locations (not just those directly assigned) -* [#6130](https://github.com/netbox-community/netbox/issues/6130) - Improve display of assigned models in custom fields list -* [#6155](https://github.com/netbox-community/netbox/issues/6155) - Fix admin links for plugins, background tasks -* [#6171](https://github.com/netbox-community/netbox/issues/6171) - Fix display of horizontally-scrolling object lists -* [#6173](https://github.com/netbox-community/netbox/issues/6173) - Fix assigned device/VM count when bulk editing/deleting device roles +* [#6184](https://github.com/netbox-community/netbox/issues/6184) - Fix parent object table column in prefix IP addresses list +* [#6188](https://github.com/netbox-community/netbox/issues/6188) - Support custom field filtering for regions, site groups, and locations +* [#6196](https://github.com/netbox-community/netbox/issues/6196) - Fix object list display for users with read-only permissions +* [#6215](https://github.com/netbox-community/netbox/issues/6215) - Restore tenancy section in virtual machine form --- -## v2.11-beta1 (2021-04-06) - -**WARNING:** This is a beta release and is not suitable for production use. It is intended for development and evaluation purposes only. No upgrade path to the final v2.11 release will be provided from this beta, and users should assume that all data entered into the application will be lost. +## v2.11.0 (2021-04-16) **Note:** NetBox v2.11 is the last major release that will support Python 3.6. Beginning with NetBox v2.12, Python 3.7 or later will be required. @@ -126,7 +110,9 @@ A new provider network model has been introduced to represent the boundary of a * [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields * [#5608](https://github.com/netbox-community/netbox/issues/5608) - Add REST API endpoint for custom links * [#5610](https://github.com/netbox-community/netbox/issues/5610) - Add REST API endpoint for webhooks +* [#5757](https://github.com/netbox-community/netbox/issues/5757) - Add unique identifier to every object view * [#5830](https://github.com/netbox-community/netbox/issues/5830) - Add `as_attachment` to ExportTemplate to control download behavior +* [#5848](https://github.com/netbox-community/netbox/issues/5848) - Filter custom fields by content type in format `.` * [#5891](https://github.com/netbox-community/netbox/issues/5891) - Add `display` field to all REST API serializers * [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI * [#5895](https://github.com/netbox-community/netbox/issues/5895) - Rename RackGroup to Location @@ -136,6 +122,29 @@ A new provider network model has been introduced to represent the boundary of a * [#5975](https://github.com/netbox-community/netbox/issues/5975) - Allow partial (decimal) vCPU allocations for virtual machines * [#6001](https://github.com/netbox-community/netbox/issues/6001) - Paginate component tables under device views * [#6038](https://github.com/netbox-community/netbox/issues/6038) - Include tagged objects list on tag view +* [#6088](https://github.com/netbox-community/netbox/issues/6088) - Improved table configuration form +* [#6097](https://github.com/netbox-community/netbox/issues/6097) - Redirect old slug-based object views +* [#6125](https://github.com/netbox-community/netbox/issues/6125) - Add locations count to home page +* [#6146](https://github.com/netbox-community/netbox/issues/6146) - Add bulk disconnect support for power feeds +* [#6149](https://github.com/netbox-community/netbox/issues/6149) - Support image attachments for locations + +### Bug Fixes (from v2.11-beta1) + +* [#5583](https://github.com/netbox-community/netbox/issues/5583) - Eliminate redundant change records when adding/removing tags +* [#6100](https://github.com/netbox-community/netbox/issues/6100) - Fix VM interfaces table "add interfaces" link +* [#6104](https://github.com/netbox-community/netbox/issues/6104) - Fix location column on racks table +* [#6105](https://github.com/netbox-community/netbox/issues/6105) - Hide checkboxes for VMs under cluster VMs view +* [#6106](https://github.com/netbox-community/netbox/issues/6106) - Allow assigning a virtual interface as the parent of an existing interface +* [#6107](https://github.com/netbox-community/netbox/issues/6107) - Fix rack selection field on device form +* [#6110](https://github.com/netbox-community/netbox/issues/6110) - Fix handling of TemplateColumn values for table export +* [#6123](https://github.com/netbox-community/netbox/issues/6123) - Prevent device from being assigned to mismatched site and location +* [#6124](https://github.com/netbox-community/netbox/issues/6124) - Location `parent` filter should return all child locations (not just those directly assigned) +* [#6130](https://github.com/netbox-community/netbox/issues/6130) - Improve display of assigned models in custom fields list +* [#6155](https://github.com/netbox-community/netbox/issues/6155) - Fix admin links for plugins, background tasks +* [#6171](https://github.com/netbox-community/netbox/issues/6171) - Fix display of horizontally-scrolling object lists +* [#6173](https://github.com/netbox-community/netbox/issues/6173) - Fix assigned device/VM count when bulk editing/deleting device roles +* [#6176](https://github.com/netbox-community/netbox/issues/6176) - Correct position of MAC address field when creating VM interfaces +* [#6177](https://github.com/netbox-community/netbox/issues/6177) - Prevent VM interface from being assigned as its own parent ### Other Changes @@ -152,51 +161,51 @@ A new provider network model has been introduced to represent the boundary of a * All primary keys are now 64-bit integers * All model serializers now include a `display` field to be used for the presentation of an object to a human user * All device components - * Added support for custom fields - * Added `created` and `last_updated` fields to track object creation and modification + * Added support for custom fields + * Added `created` and `last_updated` fields to track object creation and modification * All device component templates - * Added `created` and `last_updated` fields to track object creation and modification + * Added `created` and `last_updated` fields to track object creation and modification * All organizational models - * Added support for custom fields + * Added support for custom fields * All cable termination models (cabled device components, power feeds, and circuit terminations) - * Added `mark_connected` boolean field to force connection status - * Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied + * Added `mark_connected` boolean field to force connection status + * Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied * Renamed RackGroup to Location - * The `/dcim/rack-groups/` endpoint is now `/dcim/locations/` + * The `/dcim/rack-groups/` endpoint is now `/dcim/locations/` * circuits.CircuitTermination - * Added the `provider_network` field - * Removed the `connected_endpoint`, `connected_endpoint_type`, and `connected_endpoint_reachable` fields + * Added the `provider_network` field + * Removed the `connected_endpoint`, `connected_endpoint_type`, and `connected_endpoint_reachable` fields * circuits.ProviderNetwork - * Added the `/api/circuits/provider-networks/` endpoint + * Added the `/api/circuits/provider-networks/` endpoint * dcim.Device - * Added the `location` field + * Added the `location` field * dcim.Interface - * Added the `parent` field + * Added the `parent` field * dcim.PowerPanel - * Renamed `rack_group` field to `location` + * Renamed `rack_group` field to `location` * dcim.Rack - * Renamed `group` field to `location` + * Renamed `group` field to `location` * dcim.Site - * Added the `group` foreign key field to SiteGroup + * Added the `group` foreign key field to SiteGroup * dcim.SiteGroup - * Added the `/api/dcim/site-groups/` endpoint + * Added the `/api/dcim/site-groups/` endpoint * extras.ConfigContext - * Added the `site_groups` many-to-many field to track the assignment of ConfigContexts to SiteGroups + * Added the `site_groups` many-to-many field to track the assignment of ConfigContexts to SiteGroups * extras.CustomField - * Added new custom field type: `multi-select` + * Added new custom field type: `multi-select` * extras.CustomLink - * Added the `/api/extras/custom-links/` endpoint + * Added the `/api/extras/custom-links/` endpoint * extras.ExportTemplate - * Added the `as_attachment` boolean field + * Added the `as_attachment` boolean field * extras.ObjectChange - * Added the `prechange_data` field - * Renamed `object_data` to `postchange_data` + * Added the `prechange_data` field + * Renamed `object_data` to `postchange_data` * extras.Webhook - * Added the `/api/extras/webhooks/` endpoint + * Added the `/api/extras/webhooks/` endpoint * ipam.VLANGroup - * Added the `scope_type`, `scope_id`, and `scope` fields (`scope` is a generic foreign key) - * Dropped the `site` foreign key field + * Added the `scope_type`, `scope_id`, and `scope` fields (`scope` is a generic foreign key) + * Dropped the `site` foreign key field * virtualization.VirtualMachine - * `vcpus` has been changed from an integer to a decimal value + * `vcpus` has been changed from an integer to a decimal value * virtualization.VMInterface - * Added the `parent` field + * Added the `parent` field diff --git a/docs/release-notes/version-2.12.md b/docs/release-notes/version-2.12.md new file mode 100644 index 0000000000..548868a3d0 --- /dev/null +++ b/docs/release-notes/version-2.12.md @@ -0,0 +1,8 @@ +# NetBox v2.12 + +## v2.12-beta1 (FUTURE) + +### Other Changes + +* [#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 diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 7ae016dc43..2dc4faefba 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -209,6 +209,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet): model = Location fields = ['id', 'name', 'slug', 'description'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(description__icontains=value) + ) + class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet): diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 3d24d2ff8d..9f4916d4b0 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -230,7 +230,7 @@ class RegionBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class RegionFilterForm(BootstrapMixin, forms.Form): +class RegionFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Site q = forms.CharField( required=False, @@ -287,8 +287,8 @@ class SiteGroupBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class SiteGroupFilterForm(BootstrapMixin, forms.Form): - model = Site +class SiteGroupFilterForm(BootstrapMixin, CustomFieldFilterForm): + model = SiteGroup q = forms.CharField( required=False, label=_('Search') @@ -557,7 +557,12 @@ class LocationBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['parent', 'description'] -class LocationFilterForm(BootstrapMixin, forms.Form): +class LocationFilterForm(BootstrapMixin, CustomFieldFilterForm): + model = Location + q = forms.CharField( + required=False, + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -2424,10 +2429,11 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt location_id = DynamicModelMultipleChoiceField( queryset=Location.objects.all(), required=False, - label=_('Location'), + null_option='None', query_params={ 'site_id': '$site_id' - } + }, + label=_('Location') ) rack_id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index cc1d78fa31..4f7c05c717 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -291,6 +291,7 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class DeviceConsolePortTable(ConsolePortTable): name = tables.TemplateColumn( template_code=' {{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -335,6 +336,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): name = tables.TemplateColumn( template_code=' ' '{{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -379,6 +381,7 @@ class DevicePowerPortTable(PowerPortTable): name = tables.TemplateColumn( template_code=' ' '{{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -428,6 +431,7 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable): class DevicePowerOutletTable(PowerOutletTable): name = tables.TemplateColumn( template_code=' {{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -492,6 +496,7 @@ class DeviceInterfaceTable(InterfaceTable): template_code=' {{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) parent = tables.Column( @@ -555,6 +560,7 @@ class DeviceFrontPortTable(FrontPortTable): name = tables.TemplateColumn( template_code=' ' '{{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -602,6 +608,7 @@ class DeviceRearPortTable(RearPortTable): name = tables.TemplateColumn( template_code=' ' '{{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -651,6 +658,7 @@ class DeviceDeviceBayTable(DeviceBayTable): name = tables.TemplateColumn( template_code=' {{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( @@ -698,6 +706,7 @@ class DeviceInventoryItemTable(InventoryItemTable): name = tables.TemplateColumn( template_code='' '{{ value }}', + order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} ) actions = ButtonsColumn( diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 5da50e0db8..a9aee80f16 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -364,16 +364,30 @@ class LocationView(generic.ObjectView): queryset = Location.objects.all() def get_extra_context(self, request, instance): - devices = Device.objects.restrict(request.user, 'view').filter( - location=instance - ) + location_ids = instance.get_descendants(include_self=True).values_list('pk', flat=True) + rack_count = Rack.objects.filter(location__in=location_ids).count() + device_count = Device.objects.filter(location__in=location_ids).count() - devices_table = tables.DeviceTable(devices) - devices_table.columns.hide('location') - paginate_table(devices_table, request) + child_locations = Location.objects.add_related_count( + Location.objects.add_related_count( + Location.objects.all(), + Device, + 'location', + 'device_count', + cumulative=True + ), + Rack, + 'location', + 'rack_count', + cumulative=True + ).filter(pk__in=location_ids).exclude(pk=instance.pk) + child_locations_table = tables.LocationTable(child_locations) + paginate_table(child_locations_table, request) return { - 'devices_table': devices_table, + 'rack_count': rack_count, + 'device_count': device_count, + 'child_locations_table': child_locations_table, } @@ -1305,8 +1319,7 @@ class DeviceConsolePortsView(generic.ObjectView): ) consoleport_table = tables.DeviceConsolePortTable( data=consoleports, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'): consoleport_table.columns.show('pk') @@ -1330,8 +1343,7 @@ class DeviceConsoleServerPortsView(generic.ObjectView): ) consoleserverport_table = tables.DeviceConsoleServerPortTable( data=consoleserverports, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_consoleserverport') or \ request.user.has_perm('dcim.delete_consoleserverport'): @@ -1354,8 +1366,7 @@ class DevicePowerPortsView(generic.ObjectView): ) powerport_table = tables.DevicePowerPortTable( data=powerports, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'): powerport_table.columns.show('pk') @@ -1377,8 +1388,7 @@ class DevicePowerOutletsView(generic.ObjectView): ) poweroutlet_table = tables.DevicePowerOutletTable( data=poweroutlets, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'): poweroutlet_table.columns.show('pk') @@ -1402,8 +1412,7 @@ class DeviceInterfacesView(generic.ObjectView): ) interface_table = tables.DeviceInterfaceTable( data=interfaces, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'): interface_table.columns.show('pk') @@ -1425,8 +1434,7 @@ class DeviceFrontPortsView(generic.ObjectView): ) frontport_table = tables.DeviceFrontPortTable( data=frontports, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'): frontport_table.columns.show('pk') @@ -1446,8 +1454,7 @@ class DeviceRearPortsView(generic.ObjectView): rearports = RearPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related('cable') rearport_table = tables.DeviceRearPortTable( data=rearports, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'): rearport_table.columns.show('pk') @@ -1469,8 +1476,7 @@ class DeviceDeviceBaysView(generic.ObjectView): ) devicebay_table = tables.DeviceDeviceBayTable( data=devicebays, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'): devicebay_table.columns.show('pk') @@ -1492,8 +1498,7 @@ class DeviceInventoryView(generic.ObjectView): ).prefetch_related('manufacturer') inventoryitem_table = tables.DeviceInventoryItemTable( data=inventoryitems, - user=request.user, - orderable=False + user=request.user ) if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'): inventoryitem_table.columns.show('pk') diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index 29ecc3ef37..156b88065d 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -180,27 +180,16 @@ class ObjectVar(ScriptVariable): A single object within NetBox. :param model: The NetBox model being referenced - :param display_field: The attribute of the returned object to display in the selection list (DEPRECATED) :param query_params: A dictionary of additional query parameters to attach when making REST API requests (optional) :param null_option: The label to use as a "null" selection option (optional) """ form_field = DynamicModelChoiceField def __init__(self, model, query_params=None, null_option=None, *args, **kwargs): - - # TODO: Remove display_field in v2.12 - if 'display_field' in kwargs: - warnings.warn( - "The 'display_field' parameter has been deprecated, and will be removed in NetBox v2.12. Object " - "variables will now reference the 'display' attribute available on all model serializers by default." - ) - display_field = kwargs.pop('display_field', 'display') - super().__init__(*args, **kwargs) self.field_attrs.update({ 'queryset': model.objects.all(), - 'display_field': display_field, 'query_params': query_params, 'null_option': null_option, }) diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 54afa700ef..0bbaddb529 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -340,10 +340,10 @@ class IPAddressTable(BaseTable): verbose_name='Interface' ) assigned_object_parent = tables.Column( - accessor='assigned_object__parent', + accessor='assigned_object.parent_object', linkify=True, orderable=False, - verbose_name='Interface Parent' + verbose_name='Device/VM' ) class Meta(BaseTable.Meta): diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 24a6455a52..7d4fb91da2 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '2.11.0-dev' +VERSION = '2.12-beta1' # Hostname HOSTNAME = platform.node() @@ -25,15 +25,9 @@ HOSTNAME = platform.node() BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Validate Python version -if platform.python_version_tuple() < ('3', '6'): - raise RuntimeError( - "NetBox requires Python 3.6 or higher (current: Python {})".format(platform.python_version()) - ) -# TODO: Remove in NetBox v2.12 if platform.python_version_tuple() < ('3', '7'): - warnings.warn( - "Support for Python 3.6 will be dropped in NetBox v2.12. Please upgrade to Python 3.7 or later at your " - "earliest convenience." + raise RuntimeError( + f"NetBox requires Python 3.7 or higher (current: Python {platform.python_version()})" ) diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 8728d7c7c5..05bd2170f5 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -44,13 +44,13 @@ Racks - {{ object.racks.count }} + {{ rack_count }} Devices - {{ devices_table.rows|length }} + {{ device_count }} @@ -83,21 +83,21 @@
- Devices + Locations
- {% include 'inc/table.html' with table=devices_table %} -
- {% if perms.dcim.add_device %} - + {% if perms.dcim.add_location %} + {% endif %} -
- {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} - {% plugin_full_width_page object %} +
+ {% include 'inc/paginator.html' with paginator=child_locations_table.paginator page=child_locations_table.page %} + {% plugin_full_width_page object %} {% endblock %} diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index b89245a399..cd90e27711 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -92,7 +92,9 @@ {% else %} - {% include table_template|default:'responsive_table.html' %} +
+ {% render_table table 'inc/table.html' %} +
{% endif %} {% endwith %} {% include 'inc/paginator.html' with paginator=table.paginator page=table.page %} diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 9bc0e3df7f..88569c6948 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -328,7 +328,6 @@ class ExpandableIPAddressField(forms.CharField): class DynamicModelChoiceMixin: """ - :param display_field: The name of the attribute of an API response object to display in the selection list :param query_params: A dictionary of additional key/value pairs to attach to the API request :param initial_params: A dictionary of child field references to use for selecting a parent field's initial value :param null_option: The string used to represent a null selection (if any) @@ -338,10 +337,8 @@ class DynamicModelChoiceMixin: filter = django_filters.ModelChoiceFilter widget = widgets.APISelect - # TODO: Remove display_field in v2.12 - def __init__(self, display_field='display', query_params=None, initial_params=None, null_option=None, - disabled_indicator=None, *args, **kwargs): - self.display_field = display_field + def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, *args, + **kwargs): self.query_params = query_params or {} self.initial_params = initial_params or {} self.null_option = null_option @@ -354,9 +351,7 @@ class DynamicModelChoiceMixin: super().__init__(*args, **kwargs) def widget_attrs(self, widget): - attrs = { - 'display-field': self.display_field, - } + attrs = {} # Set value-field attribute if the field specifies to_field_name if self.to_field_name: diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 9d2a14abd5..5fb56e2808 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -376,6 +376,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): fieldsets = ( ('Virtual Machine', ('name', 'role', 'status', 'tags')), ('Cluster', ('cluster_group', 'cluster')), + ('Tenancy', ('tenant_group', 'tenant')), ('Management', ('platform', 'primary_ip4', 'primary_ip6')), ('Resources', ('vcpus', 'memory', 'disk')), ('Config Context', ('local_context_data',)), @@ -666,7 +667,6 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm): parent = DynamicModelChoiceField( queryset=VMInterface.objects.all(), required=False, - display_field='display_name', query_params={ 'virtualmachine_id': 'virtual_machine', } @@ -702,6 +702,10 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm): queryset=Tag.objects.all(), required=False ) + field_order = ( + 'virtual_machine', 'name_pattern', 'enabled', 'parent', 'mtu', 'mac_address', 'description', 'mode', + 'untagged_vlan', 'tagged_vlans', 'tags' + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -751,8 +755,7 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): ) parent = DynamicModelChoiceField( queryset=VMInterface.objects.all(), - required=False, - display_field='display_name' + required=False ) enabled = forms.NullBooleanField( required=False, diff --git a/netbox/virtualization/migrations/0023_virtualmachine_natural_ordering.py b/netbox/virtualization/migrations/0023_virtualmachine_natural_ordering.py new file mode 100644 index 0000000000..2fc2a38e08 --- /dev/null +++ b/netbox/virtualization/migrations/0023_virtualmachine_natural_ordering.py @@ -0,0 +1,32 @@ +from django.db import migrations +import utilities.fields +import utilities.ordering + + +def naturalize_virtualmachines(apps, schema_editor): + VirtualMachine = apps.get_model('virtualization', 'VirtualMachine') + for name in VirtualMachine.objects.values_list('name', flat=True).order_by('name').distinct(): + VirtualMachine.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100)) + + +class Migration(migrations.Migration): + + dependencies = [ + ('virtualization', '0022_vminterface_parent'), + ] + + operations = [ + migrations.AlterModelOptions( + name='virtualmachine', + options={'ordering': ('_name', 'pk')}, + ), + migrations.AddField( + model_name='virtualmachine', + name='_name', + field=utilities.fields.NaturalOrderingField('name', max_length=100, blank=True, naturalize_function=utilities.ordering.naturalize), + ), + migrations.RunPython( + code=naturalize_virtualmachines, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index dd79204841..76f7fe8450 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -226,6 +226,11 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): name = models.CharField( max_length=64 ) + _name = NaturalOrderingField( + target_field='name', + max_length=100, + blank=True + ) status = models.CharField( max_length=50, choices=VirtualMachineStatusChoices, @@ -296,7 +301,7 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): ] class Meta: - ordering = ('name', 'pk') # Name may be non-unique + ordering = ('_name', 'pk') # Name may be non-unique unique_together = [ ['cluster', 'tenant', 'name'] ] @@ -463,6 +468,10 @@ class VMInterface(PrimaryModel, BaseInterface): f"({self.parent.virtual_machine})." }) + # An interface cannot be its own parent + if self.pk and self.parent_id == self.pk: + raise ValidationError({'parent': "An interface cannot be its own parent."}) + # Validate untagged VLAN if self.untagged_vlan and self.untagged_vlan.site not in [self.virtual_machine.site, None]: raise ValidationError({ diff --git a/requirements.txt b/requirements.txt index cb277e9165..a9e000bf36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ Django==3.2 django-cacheops==5.1 django-cors-headers==3.7.0 -django-debug-toolbar==3.2 +django-debug-toolbar==3.2.1 django-filter==2.4.0 django-mptt==0.12.0 django-pglocks==1.0.4 django-prometheus==2.1.0 django-rq==2.4.1 django-tables2==2.3.4 -django-taggit==1.3.0 +django-taggit==1.4.0 django-timezone-field==4.1.2 djangorestframework==3.12.4 drf-yasg[validation]==1.20.0