From 2afce6c94b149f2f4eb051e011df7b447de0e304 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 09:43:44 -0400 Subject: [PATCH] Introduce ContactsMixin --- netbox/circuits/models/circuits.py | 10 ++------ netbox/circuits/models/providers.py | 16 +++---------- netbox/dcim/models/devices.py | 17 +++---------- netbox/dcim/models/power.py | 10 ++------ netbox/dcim/models/racks.py | 7 ++---- netbox/dcim/models/sites.py | 24 ++++--------------- netbox/ipam/models/l2vpn.py | 8 +++---- netbox/netbox/models/features.py | 13 ++++++++++ netbox/tenancy/models/tenants.py | 9 ++----- netbox/virtualization/models/clusters.py | 12 +++------- .../virtualization/models/virtualmachines.py | 8 ++----- 11 files changed, 40 insertions(+), 94 deletions(-) diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index e1f07b5ae..0322b67c6 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -1,4 +1,3 @@ -from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse @@ -7,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ from circuits.choices import * from dcim.models import CabledObjectModel from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel -from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, ImageAttachmentsMixin, TagsMixin +from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ImageAttachmentsMixin, TagsMixin __all__ = ( 'Circuit', @@ -30,7 +29,7 @@ class CircuitType(OrganizationalModel): verbose_name_plural = _('circuit types') -class Circuit(ImageAttachmentsMixin, PrimaryModel): +class Circuit(ContactsMixin, ImageAttachmentsMixin, PrimaryModel): """ A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple circuits. Each circuit is also assigned a CircuitType and a Site, and may optionally be assigned to a particular @@ -88,11 +87,6 @@ class Circuit(ImageAttachmentsMixin, PrimaryModel): help_text=_("Committed rate") ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - # Cache associated CircuitTerminations termination_a = models.ForeignKey( to='circuits.CircuitTermination', diff --git a/netbox/circuits/models/providers.py b/netbox/circuits/models/providers.py index 5a4154709..31c8bccb2 100644 --- a/netbox/circuits/models/providers.py +++ b/netbox/circuits/models/providers.py @@ -1,10 +1,10 @@ -from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.db.models import Q from django.urls import reverse from django.utils.translation import gettext_lazy as _ from netbox.models import PrimaryModel +from netbox.models.features import ContactsMixin __all__ = ( 'ProviderNetwork', @@ -13,7 +13,7 @@ __all__ = ( ) -class Provider(PrimaryModel): +class Provider(ContactsMixin, PrimaryModel): """ Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model stores information pertinent to the user's relationship with the Provider. @@ -35,11 +35,6 @@ class Provider(PrimaryModel): blank=True ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - clone_fields = () class Meta: @@ -54,7 +49,7 @@ class Provider(PrimaryModel): return reverse('circuits:provider', args=[self.pk]) -class ProviderAccount(PrimaryModel): +class ProviderAccount(ContactsMixin, PrimaryModel): """ This is a discrete account within a provider. Each Circuit belongs to a Provider Account. """ @@ -73,11 +68,6 @@ class ProviderAccount(PrimaryModel): blank=True ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - clone_fields = ('provider', ) class Meta: diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index e74c01f81..6b8e92743 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -3,7 +3,6 @@ import yaml from functools import cached_property -from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -20,7 +19,7 @@ from extras.models import ConfigContextModel from extras.querysets import ConfigContextModelQuerySet from netbox.config import ConfigItem from netbox.models import OrganizationalModel, PrimaryModel -from netbox.models.features import ImageAttachmentsMixin +from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.choices import ColorChoices from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField from utilities.tracking import TrackingModelMixin @@ -45,15 +44,10 @@ __all__ = ( # Device Types # -class Manufacturer(OrganizationalModel): +class Manufacturer(ContactsMixin, OrganizationalModel): """ A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell. """ - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - class Meta: ordering = ('name',) verbose_name = _('manufacturer') @@ -531,7 +525,7 @@ def update_interface_bridges(device, interface_templates, module=None): interface.save() -class Device(ImageAttachmentsMixin, PrimaryModel, ConfigContextModel, TrackingModelMixin): +class Device(ContactsMixin, ImageAttachmentsMixin, PrimaryModel, ConfigContextModel, TrackingModelMixin): """ A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique. @@ -758,11 +752,6 @@ class Device(ImageAttachmentsMixin, PrimaryModel, ConfigContextModel, TrackingMo to_field='device' ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - objects = ConfigContextModelQuerySet.as_manager() clone_fields = ( diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index 1e57b6f5d..83e5eb23a 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -1,4 +1,3 @@ -from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -8,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.choices import * from netbox.config import ConfigItem from netbox.models import PrimaryModel -from netbox.models.features import ImageAttachmentsMixin +from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.validators import ExclusionValidator from .device_components import CabledObjectModel, PathEndpoint @@ -22,7 +21,7 @@ __all__ = ( # Power # -class PowerPanel(ImageAttachmentsMixin, PrimaryModel): +class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel): """ A distribution point for electrical power; e.g. a data center RPP. """ @@ -41,11 +40,6 @@ class PowerPanel(ImageAttachmentsMixin, PrimaryModel): max_length=100 ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - prerequisite_models = ( 'dcim.Site', ) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index a71d798b3..ef0dde4da 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -15,7 +15,7 @@ from dcim.choices import * from dcim.constants import * from dcim.svg import RackElevationSVG from netbox.models import OrganizationalModel, PrimaryModel -from netbox.models.features import ImageAttachmentsMixin +from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.choices import ColorChoices from utilities.fields import ColorField, NaturalOrderingField from utilities.utils import array_to_string, drange, to_grams @@ -53,7 +53,7 @@ class RackRole(OrganizationalModel): return reverse('dcim:rackrole', args=[self.pk]) -class Rack(ImageAttachmentsMixin, PrimaryModel, WeightMixin): +class Rack(ContactsMixin, ImageAttachmentsMixin, PrimaryModel, WeightMixin): """ Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Each Rack is assigned to a Site and (optionally) a Location. @@ -194,9 +194,6 @@ class Rack(ImageAttachmentsMixin, PrimaryModel, WeightMixin): object_id_field='scope_id', related_query_name='rack' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) clone_fields = ( 'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width', diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 87566a194..d2797bf95 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -8,7 +8,7 @@ from timezone_field import TimeZoneField from dcim.choices import * from dcim.constants import * from netbox.models import NestedGroupModel, PrimaryModel -from netbox.models.features import ImageAttachmentsMixin +from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.fields import NaturalOrderingField __all__ = ( @@ -23,22 +23,18 @@ __all__ = ( # Regions # -class Region(NestedGroupModel): +class Region(ContactsMixin, NestedGroupModel): """ A region represents a geographic collection of sites. For example, you might create regions representing countries, states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are also considered to be members of its parent and ancestor region(s). """ - # Generic relations vlan_groups = GenericRelation( to='ipam.VLANGroup', content_type_field='scope_type', object_id_field='scope_id', related_query_name='region' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) class Meta: constraints = ( @@ -80,22 +76,18 @@ class Region(NestedGroupModel): # Site groups # -class SiteGroup(NestedGroupModel): +class SiteGroup(ContactsMixin, NestedGroupModel): """ A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be nested recursively to form a hierarchy. """ - # Generic relations vlan_groups = GenericRelation( to='ipam.VLANGroup', content_type_field='scope_type', object_id_field='scope_id', related_query_name='site_group' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) class Meta: constraints = ( @@ -137,7 +129,7 @@ class SiteGroup(NestedGroupModel): # Sites # -class Site(ImageAttachmentsMixin, PrimaryModel): +class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel): """ A Site represents a geographic location within a network; typically a building or campus. The optional facility field can be used to include an external designation, such as a data center name (e.g. Equinix SV6). @@ -235,9 +227,6 @@ class Site(ImageAttachmentsMixin, PrimaryModel): object_id_field='scope_id', related_query_name='site' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) clone_fields = ( 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'physical_address', 'shipping_address', @@ -263,7 +252,7 @@ class Site(ImageAttachmentsMixin, PrimaryModel): # Locations # -class Location(ImageAttachmentsMixin, NestedGroupModel): +class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel): """ A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a site, or a room within a building, for example. @@ -294,9 +283,6 @@ class Location(ImageAttachmentsMixin, NestedGroupModel): object_id_field='scope_id', related_query_name='location' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) clone_fields = ('site', 'parent', 'status', 'tenant', 'description') prerequisite_models = ( diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index 7b3463f42..3072fc6c3 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -1,4 +1,4 @@ -from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ from ipam.choices import L2VPNTypeChoices from ipam.constants import L2VPN_ASSIGNMENT_MODELS from netbox.models import NetBoxModel, PrimaryModel +from netbox.models.features import ContactsMixin __all__ = ( 'L2VPN', @@ -16,7 +17,7 @@ __all__ = ( ) -class L2VPN(PrimaryModel): +class L2VPN(ContactsMixin, PrimaryModel): name = models.CharField( verbose_name=_('name'), max_length=100, @@ -54,9 +55,6 @@ class L2VPN(PrimaryModel): blank=True, null=True ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) clone_fields = ('type',) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 7576ec357..08693850f 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -25,6 +25,7 @@ __all__ = ( 'BookmarksMixin', 'ChangeLoggingMixin', 'CloningMixin', + 'ContactsMixin', 'CustomFieldsMixin', 'CustomLinksMixin', 'CustomValidationMixin', @@ -320,6 +321,18 @@ class ImageAttachmentsMixin(models.Model): abstract = True +class ContactsMixin(models.Model): + """ + Enables the assignments of Contacts (via ContactAssignment). + """ + contacts = GenericRelation( + to='tenancy.ContactAssignment' + ) + + class Meta: + abstract = True + + class BookmarksMixin(models.Model): """ Enables support for user bookmarks. diff --git a/netbox/tenancy/models/tenants.py b/netbox/tenancy/models/tenants.py index ab08bd812..dfc144dd2 100644 --- a/netbox/tenancy/models/tenants.py +++ b/netbox/tenancy/models/tenants.py @@ -1,10 +1,10 @@ -from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.db.models import Q from django.urls import reverse from django.utils.translation import gettext_lazy as _ from netbox.models import NestedGroupModel, PrimaryModel +from netbox.models.features import ContactsMixin __all__ = ( 'Tenant', @@ -36,7 +36,7 @@ class TenantGroup(NestedGroupModel): return reverse('tenancy:tenantgroup', args=[self.pk]) -class Tenant(PrimaryModel): +class Tenant(ContactsMixin, PrimaryModel): """ A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal department. @@ -57,11 +57,6 @@ class Tenant(PrimaryModel): null=True ) - # Generic relations - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - clone_fields = ( 'group', 'description', ) diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py index aed064406..6c8fd0c4b 100644 --- a/netbox/virtualization/models/clusters.py +++ b/netbox/virtualization/models/clusters.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.models import Device from netbox.models import OrganizationalModel, PrimaryModel +from netbox.models.features import ContactsMixin from virtualization.choices import * __all__ = ( @@ -28,20 +29,16 @@ class ClusterType(OrganizationalModel): return reverse('virtualization:clustertype', args=[self.pk]) -class ClusterGroup(OrganizationalModel): +class ClusterGroup(ContactsMixin, OrganizationalModel): """ An organizational group of Clusters. """ - # Generic relations vlan_groups = GenericRelation( to='ipam.VLANGroup', content_type_field='scope_type', object_id_field='scope_id', related_query_name='cluster_group' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) class Meta: ordering = ('name',) @@ -52,7 +49,7 @@ class ClusterGroup(OrganizationalModel): return reverse('virtualization:clustergroup', args=[self.pk]) -class Cluster(PrimaryModel): +class Cluster(ContactsMixin, PrimaryModel): """ A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices. """ @@ -101,9 +98,6 @@ class Cluster(PrimaryModel): object_id_field='scope_id', related_query_name='cluster' ) - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) clone_fields = ( 'type', 'group', 'status', 'tenant', 'site', diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index fd2ea86eb..1cacd8adc 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -12,6 +12,7 @@ from extras.models import ConfigContextModel from extras.querysets import ConfigContextModelQuerySet from netbox.config import get_config from netbox.models import NetBoxModel, PrimaryModel +from netbox.models.features import ContactsMixin from utilities.fields import CounterCacheField, NaturalOrderingField from utilities.ordering import naturalize_interface from utilities.query_functions import CollateAsChar @@ -24,7 +25,7 @@ __all__ = ( ) -class VirtualMachine(PrimaryModel, ConfigContextModel): +class VirtualMachine(ContactsMixin, PrimaryModel, ConfigContextModel): """ A virtual machine which runs inside a Cluster. """ @@ -129,11 +130,6 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): to_field='virtual_machine' ) - # Generic relation - contacts = GenericRelation( - to='tenancy.ContactAssignment' - ) - objects = ConfigContextModelQuerySet.as_manager() clone_fields = (