Skip to content

Commit be74436

Browse files
Closes #20304: Object owners (#20634)
1 parent 52d4498 commit be74436

File tree

196 files changed

+16203
-3087
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

196 files changed

+16203
-3087
lines changed

contrib/openapi.json

Lines changed: 11697 additions & 774 deletions
Large diffs are not rendered by default.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Resource Ownership
2+
3+
!!! info "This feature was introduced in NetBox v4.5."
4+
5+
Most objects in NetBox can be assigned an owner. An owner is a set of users and/or groups who are responsible for the administration of associated objects. For example, you might designate the operations team at a site as the owner for all prefixes and VLANs deployed at that site. The users and groups assigned to an owner are referred to as its members.
6+
7+
!!! note
8+
Ownership of an object should not be confused with the concept of [tenancy](./tenancy.md), which indicates the dedication of an object to a specific tenant. For instance, a tenant might represent a customer served by the object, whereas an owner typically represents a set of internal users responsible for the management of the object.
9+
10+
Owners can be organized into groups for easier management.

docs/features/tenancy.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tenancy
22

3-
Most core objects within NetBox's data model support _tenancy_. This is the association of an object with a particular tenant to convey ownership or dependency. For example, an enterprise might represent its internal business units as tenants, whereas a managed services provider might create a tenant in NetBox to represent each of its customers.
3+
Most core objects within NetBox's data model support _tenancy_. This is the association of an object with a particular tenant to convey assignment or dependency. For example, an enterprise might represent its internal business units as tenants, whereas a managed services provider might create a tenant in NetBox to represent each of its customers.
44

55
```mermaid
66
flowchart TD
@@ -19,20 +19,36 @@ Tenants can be grouped by any logic that your use case demands, and groups can b
1919

2020
Typically, the tenant model is used to represent a customer or internal organization, however it can be used for whatever purpose meets your needs.
2121

22-
Most core objects within NetBox can be assigned to particular tenant, so this model provides a very convenient way to correlate ownership across object types. For example, each of your customers might have its own racks, devices, IP addresses, circuits and so on: These can all be easily tracked via tenant assignment.
22+
Most core objects within NetBox can be assigned to a particular tenant, so this model provides a very convenient way to correlate resource allocation across object types. For example, each of your customers might have its own racks, devices, IP addresses, circuits and so on: These can all be easily tracked via tenant assignment.
2323

2424
The following objects can be assigned to tenants:
2525

26-
* Sites
26+
* Circuits
27+
* Circuit groups
28+
* Virtual circuits
29+
* Cables
30+
* Devices
31+
* Virtual device contexts
32+
* Power feeds
2733
* Racks
2834
* Rack reservations
29-
* Devices
30-
* VRFs
35+
* Sites
36+
* Locations
37+
* ASNs
38+
* ASN ranges
39+
* Aggregates
3140
* Prefixes
41+
* IP ranges
3242
* IP addresses
3343
* VLANs
34-
* Circuits
44+
* VLAN groups
45+
* VRFs
46+
* Route targets
3547
* Clusters
3648
* Virtual machines
49+
* L2VPNs
50+
* Tunnels
51+
* Wireless LANs
52+
* Wireless links
3753

38-
Tenant assignment is used to signify the ownership of an object in NetBox. As such, each object may only be owned by a single tenant. For example, if you have a firewall dedicated to a particular customer, you would assign it to the tenant which represents that customer. However, if the firewall serves multiple customers, it doesn't *belong* to any particular customer, so tenant assignment would not be appropriate.
54+
Tenancy represents the dedication of an object to a specific tenant. As such, each object may only be assigned to a single tenant. For example, if you have a firewall dedicated to a particular customer, you would assign it to the tenant which represents that customer. However, if the firewall serves multiple customers, it doesn't *belong* to any particular customer, so the assignment of a tenant would not be appropriate.

docs/models/users/owner.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Owner
2+
3+
An owner is a set of users and/or groups who are responsible for the administration of certain resources within NetBox. The users and groups assigned to an owner are referred to as its members. Owner assignments are useful for indicating which parties are responsible for the administration of a particular object.
4+
5+
Most objects within NetBox can be assigned an owner, although this is not required.
6+
7+
## Fields
8+
9+
### Name
10+
11+
The owner's name.
12+
13+
### Group
14+
15+
The [group](./ownergroup.md) to which the owner is assigned. The assignment of an owner to a group is optional.
16+
17+
### User Groups
18+
19+
Groups of users that are members of the owner.
20+
21+
### Users
22+
23+
Individual users that are members of the owner.

docs/models/users/ownergroup.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Owner Groups
2+
3+
Groups are used to correlate and organize [owners](./owner.md). The assignment of an owner to a group has no bearing on the relationship of owned objects to their owners.
4+
5+
## Fields
6+
7+
### Name
8+
9+
The name of the group.

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ nav:
7777
- Wireless: 'features/wireless.md'
7878
- Virtualization: 'features/virtualization.md'
7979
- VPN Tunnels: 'features/vpn-tunnels.md'
80+
- Resource Ownership: 'features/resource-ownership.md'
8081
- Tenancy: 'features/tenancy.md'
8182
- Contacts: 'features/contacts.md'
8283
- Search: 'features/search.md'
@@ -273,6 +274,9 @@ nav:
273274
- ContactRole: 'models/tenancy/contactrole.md'
274275
- Tenant: 'models/tenancy/tenant.md'
275276
- TenantGroup: 'models/tenancy/tenantgroup.md'
277+
- Users:
278+
- Owner: 'models/users/owner.md'
279+
- OwnerGroup: 'models/users/ownergroup.md'
276280
- Virtualization:
277281
- Cluster: 'models/virtualization/cluster.md'
278282
- ClusterGroup: 'models/virtualization/clustergroup.md'

netbox/circuits/api/serializers_/circuits.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
from dcim.api.serializers_.device_components import InterfaceSerializer
1212
from dcim.api.serializers_.cables import CabledObjectSerializer
1313
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
14-
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
14+
from netbox.api.serializers import (
15+
NetBoxModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer,
16+
)
1517
from netbox.choices import DistanceUnitChoices
1618
from tenancy.api.serializers_.tenants import TenantSerializer
1719
from utilities.api import get_serializer_for_model
@@ -29,16 +31,16 @@
2931
)
3032

3133

32-
class CircuitTypeSerializer(NetBoxModelSerializer):
34+
class CircuitTypeSerializer(OrganizationalModelSerializer):
3335

3436
# Related object counts
3537
circuit_count = RelatedObjectCountField('circuits')
3638

3739
class Meta:
3840
model = CircuitType
3941
fields = [
40-
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
41-
'created', 'last_updated', 'circuit_count',
42+
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
43+
'custom_fields', 'created', 'last_updated', 'circuit_count',
4244
]
4345
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'circuit_count')
4446

@@ -71,15 +73,15 @@ def get_termination(self, obj):
7173
return serializer(obj.termination, nested=True, context=context).data
7274

7375

74-
class CircuitGroupSerializer(NetBoxModelSerializer):
76+
class CircuitGroupSerializer(OrganizationalModelSerializer):
7577
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
7678
circuit_count = RelatedObjectCountField('assignments')
7779

7880
class Meta:
7981
model = CircuitGroup
8082
fields = [
81-
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tenant',
82-
'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count'
83+
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tenant', 'owner', 'tags',
84+
'custom_fields', 'created', 'last_updated', 'circuit_count'
8385
]
8486
brief_fields = ('id', 'url', 'display', 'name')
8587

@@ -99,7 +101,7 @@ class Meta:
99101
brief_fields = ('id', 'url', 'display', 'group', 'priority')
100102

101103

102-
class CircuitSerializer(NetBoxModelSerializer):
104+
class CircuitSerializer(PrimaryModelSerializer):
103105
provider = ProviderSerializer(nested=True)
104106
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
105107
status = ChoiceField(choices=CircuitStatusChoices, required=False)
@@ -115,7 +117,7 @@ class Meta:
115117
fields = [
116118
'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant',
117119
'install_date', 'termination_date', 'commit_rate', 'description', 'distance', 'distance_unit',
118-
'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
120+
'termination_a', 'termination_z', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
119121
'assignments',
120122
]
121123
brief_fields = ('id', 'url', 'display', 'provider', 'cid', 'description')
@@ -176,21 +178,21 @@ def get_member(self, obj):
176178
return serializer(obj.member, nested=True, context=context).data
177179

178180

179-
class VirtualCircuitTypeSerializer(NetBoxModelSerializer):
181+
class VirtualCircuitTypeSerializer(OrganizationalModelSerializer):
180182

181183
# Related object counts
182184
virtual_circuit_count = RelatedObjectCountField('virtual_circuits')
183185

184186
class Meta:
185187
model = VirtualCircuitType
186188
fields = [
187-
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
188-
'created', 'last_updated', 'virtual_circuit_count',
189+
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
190+
'custom_fields', 'created', 'last_updated', 'virtual_circuit_count',
189191
]
190192
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'virtual_circuit_count')
191193

192194

193-
class VirtualCircuitSerializer(NetBoxModelSerializer):
195+
class VirtualCircuitSerializer(PrimaryModelSerializer):
194196
provider_network = ProviderNetworkSerializer(nested=True)
195197
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
196198
type = VirtualCircuitTypeSerializer(nested=True)
@@ -201,7 +203,7 @@ class Meta:
201203
model = VirtualCircuit
202204
fields = [
203205
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'type', 'status',
204-
'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
206+
'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
205207
]
206208
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
207209

netbox/circuits/api/serializers_/providers.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ipam.api.serializers_.asns import ASNSerializer
55
from ipam.models import ASN
66
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
7-
from netbox.api.serializers import NetBoxModelSerializer
7+
from netbox.api.serializers import PrimaryModelSerializer
88
from .nested import NestedProviderAccountSerializer
99

1010
__all__ = (
@@ -14,7 +14,7 @@
1414
)
1515

1616

17-
class ProviderSerializer(NetBoxModelSerializer):
17+
class ProviderSerializer(PrimaryModelSerializer):
1818
accounts = SerializedPKRelatedField(
1919
queryset=ProviderAccount.objects.all(),
2020
serializer=NestedProviderAccountSerializer,
@@ -35,32 +35,32 @@ class ProviderSerializer(NetBoxModelSerializer):
3535
class Meta:
3636
model = Provider
3737
fields = [
38-
'id', 'url', 'display_url', 'display', 'name', 'slug', 'accounts', 'description', 'comments',
38+
'id', 'url', 'display_url', 'display', 'name', 'slug', 'accounts', 'description', 'owner', 'comments',
3939
'asns', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
4040
]
4141
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'circuit_count')
4242

4343

44-
class ProviderAccountSerializer(NetBoxModelSerializer):
44+
class ProviderAccountSerializer(PrimaryModelSerializer):
4545
provider = ProviderSerializer(nested=True)
4646
name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='')
4747

4848
class Meta:
4949
model = ProviderAccount
5050
fields = [
51-
'id', 'url', 'display_url', 'display', 'provider', 'name', 'account', 'description', 'comments', 'tags',
52-
'custom_fields', 'created', 'last_updated',
51+
'id', 'url', 'display_url', 'display', 'provider', 'name', 'account', 'description', 'owner', 'comments',
52+
'tags', 'custom_fields', 'created', 'last_updated',
5353
]
5454
brief_fields = ('id', 'url', 'display', 'name', 'account', 'description')
5555

5656

57-
class ProviderNetworkSerializer(NetBoxModelSerializer):
57+
class ProviderNetworkSerializer(PrimaryModelSerializer):
5858
provider = ProviderSerializer(nested=True)
5959

6060
class Meta:
6161
model = ProviderNetwork
6262
fields = [
63-
'id', 'url', 'display_url', 'display', 'provider', 'name', 'service_id', 'description', 'comments', 'tags',
64-
'custom_fields', 'created', 'last_updated',
63+
'id', 'url', 'display_url', 'display', 'provider', 'name', 'service_id', 'description', 'owner', 'comments',
64+
'tags', 'custom_fields', 'created', 'last_updated',
6565
]
6666
brief_fields = ('id', 'url', 'display', 'name', 'description')

netbox/circuits/filtersets.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from dcim.filtersets import CabledObjectFilterSet
77
from dcim.models import Interface, Location, Region, Site, SiteGroup
88
from ipam.models import ASN
9-
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
9+
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
1010
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
1111
from utilities.filters import (
1212
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
@@ -29,7 +29,7 @@
2929
)
3030

3131

32-
class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
32+
class ProviderFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
3333
region_id = TreeNodeMultipleChoiceFilter(
3434
queryset=Region.objects.all(),
3535
field_name='circuits__terminations___region',
@@ -95,7 +95,7 @@ def search(self, queryset, name, value):
9595
)
9696

9797

98-
class ProviderAccountFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
98+
class ProviderAccountFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
9999
provider_id = django_filters.ModelMultipleChoiceFilter(
100100
queryset=Provider.objects.all(),
101101
label=_('Provider (ID)'),
@@ -122,7 +122,7 @@ def search(self, queryset, name, value):
122122
).distinct()
123123

124124

125-
class ProviderNetworkFilterSet(NetBoxModelFilterSet):
125+
class ProviderNetworkFilterSet(PrimaryModelFilterSet):
126126
provider_id = django_filters.ModelMultipleChoiceFilter(
127127
queryset=Provider.objects.all(),
128128
label=_('Provider (ID)'),
@@ -156,7 +156,7 @@ class Meta:
156156
fields = ('id', 'name', 'slug', 'color', 'description')
157157

158158

159-
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
159+
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
160160
provider_id = django_filters.ModelMultipleChoiceFilter(
161161
queryset=Provider.objects.all(),
162162
label=_('Provider (ID)'),
@@ -475,7 +475,7 @@ class Meta:
475475
fields = ('id', 'name', 'slug', 'color', 'description')
476476

477477

478-
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
478+
class VirtualCircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
479479
provider_id = django_filters.ModelMultipleChoiceFilter(
480480
field_name='provider_network__provider',
481481
queryset=Provider.objects.all(),

0 commit comments

Comments
 (0)