diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 073e8dc5c..3c52c973c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -17,7 +17,7 @@ body: 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.2 + placeholder: v2.11.3 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 6ea8b6597..9181f7ce4 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v2.10.4 + placeholder: v2.11.3 validations: required: true - type: dropdown diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 45f233a55..0f617e8aa 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,8 +17,8 @@ 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 diff --git a/README.md b/README.md index f1821f78a..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 @@ -12,43 +14,34 @@ complete list of requirements, see `requirements.txt`. The code is available [on 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 * [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - Discussion forum hosted by GitHub; ideal for Q&A and other structured discussions * [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/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 4b5ababbc..0827d5434 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -1,21 +1,29 @@ # NetBox v2.11 -## v2.11.3 (FUTURE) +## 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 --- 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/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/views.py b/netbox/circuits/views.py index 612602316..b4bb0155e 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -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() @@ -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/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 391d6bb4c..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. @@ -333,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 @@ -384,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". @@ -718,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({ @@ -847,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): """ @@ -857,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. @@ -865,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 0c70a9a83..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. 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/views.py b/netbox/dcim/views.py index 734f9bd1a..4ed80d6c8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1407,7 +1407,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', @@ -1529,7 +1529,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 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 616d11aba..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. 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/secrets/models.py b/netbox/secrets/models.py index dc3a65747..04a7ed58c 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -233,7 +233,7 @@ class SessionKey(BigIDModel): return session_key -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class SecretRole(OrganizationalModel): """ A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 752d12424..29af26fc0 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -82,8 +82,8 @@ {% plugin_left_page object %}
- {% include 'circuits/inc/circuit_termination.html' with termination=termination_a side='A' %} - {% include 'circuits/inc/circuit_termination.html' with termination=termination_z side='Z' %} + {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %} + {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %} {% plugin_right_page object %}
diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index 0adc2dab5..9e5391bb2 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -78,6 +78,10 @@

{{ stats.vrf_count }}

VRFs

+
+

{{ stats.aggregate_count }}

+

Aggregates

+

{{ stats.prefix_count }}

Prefixes

diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html index 04f97fd6a..f9a37aaea 100644 --- a/netbox/templates/virtualization/clustergroup.html +++ b/netbox/templates/virtualization/clustergroup.html @@ -51,7 +51,7 @@
{% if perms.virtualization.add_cluster %} diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index cad1b3c20..c9f55ec84 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -14,7 +14,7 @@ __all__ = ( ) -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class TenantGroup(NestedGroupModel): """ An arbitrary collection of Tenants. diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 45dffb3c0..b4a29a2e6 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,6 +1,6 @@ from circuits.models import Circuit from dcim.models import Site, Rack, Device, RackReservation -from ipam.models import IPAddress, Prefix, VLAN, VRF +from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF from netbox.views import generic from utilities.tables import paginate_table from virtualization.models import VirtualMachine, Cluster @@ -101,6 +101,7 @@ class TenantView(generic.ObjectView): 'device_count': Device.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'vrf_count': VRF.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(), + 'aggregate_count': Aggregate.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'ipaddress_count': IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(tenant=instance).count(), diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 76f7fe845..5aa43a869 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -30,7 +30,7 @@ __all__ = ( # Cluster types # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class ClusterType(OrganizationalModel): """ A type of Cluster. @@ -73,7 +73,7 @@ class ClusterType(OrganizationalModel): # Cluster groups # -@extras_features('custom_fields', 'export_templates', 'webhooks') +@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class ClusterGroup(OrganizationalModel): """ An organizational group of Clusters. diff --git a/requirements.txt b/requirements.txt index a9e000bf3..f7ae8178d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -Django==3.2 -django-cacheops==5.1 +Django==3.2.2 +django-cacheops==6.0 django-cors-headers==3.7.0 django-debug-toolbar==3.2.1 django-filter==2.4.0