Jeremy Stretch пре 2 година
родитељ
комит
2afce6c94b

+ 2 - 8
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',

+ 3 - 13
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:

+ 3 - 14
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 = (

+ 2 - 8
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',
     )

+ 2 - 5
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',

+ 5 - 19
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 = (

+ 3 - 5
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',)
 

+ 13 - 0
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.

+ 2 - 7
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',
     )

+ 3 - 9
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',

+ 2 - 6
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 = (