diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a37c5dfb1..6919ff16f 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 b480e629a..19d9696ad 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 6282eedde..7d7bde225 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 778dca235..5e675583e 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 9182457a0..99ba1f9ce 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 3ed910791..a27bcab83 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 f7f6f36e9..fb47400f2 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 d356fc235..9c46d3400 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 77bb1d04b..838022006 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 000000000..548868a3d --- /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 7ae016dc4..2dc4faefb 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 3d24d2ff8..9f4916d4b 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 cc1d78fa3..4f7c05c71 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 5da50e0db..a9aee80f1 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 29ecc3ef3..156b88065 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 54afa700e..0bbaddb52 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 24a6455a5..7d4fb91da 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 8728d7c7c..05bd2170f 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 b89245a39..cd90e2771 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 9bc0e3df7..88569c694 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 9d2a14abd..5fb56e280 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 000000000..2fc2a38e0 --- /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 dd7920484..76f7fe845 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 cb277e916..a9e000bf3 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