From ebf555e1fb1267348ca620c15ce456767d91042a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 16 Nov 2022 17:22:09 -0500 Subject: [PATCH] Use strings to specify prerequisite models --- netbox/circuits/models/circuits.py | 8 +++---- netbox/dcim/models/devices.py | 23 +++++++++---------- netbox/dcim/models/power.py | 16 ++++++------- netbox/dcim/models/racks.py | 16 ++++++------- netbox/dcim/models/sites.py | 7 +++--- netbox/ipam/models/ip.py | 18 +++++++-------- netbox/ipam/models/l2vpn.py | 8 +++---- netbox/netbox/models/__init__.py | 10 +------- netbox/netbox/views/generic/utils.py | 21 +++++++++-------- netbox/virtualization/models/clusters.py | 7 +++--- .../virtualization/models/virtualmachines.py | 8 +++---- netbox/wireless/models.py | 5 ---- 12 files changed, 61 insertions(+), 86 deletions(-) diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index ebba74738a2..8ef5761fd87 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -104,6 +104,10 @@ class Circuit(PrimaryModel): clone_fields = ( 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description', ) + prerequisite_models = ( + 'circuits.CircuitType', + 'circuits.Provider', + ) class Meta: ordering = ['provider', 'cid'] @@ -117,10 +121,6 @@ class Meta: def __str__(self): return self.cid - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('circuits.Provider'), CircuitType] - def get_absolute_url(self): return reverse('circuits:circuit', args=[self.pk]) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 3a76c826f9d..8b03015be6a 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -124,6 +124,9 @@ class DeviceType(PrimaryModel, WeightMixin): clone_fields = ( 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit' ) + prerequisite_models = ( + 'dcim.Manufacturer', + ) class Meta: ordering = ['manufacturer', 'model'] @@ -151,10 +154,6 @@ def __init__(self, *args, **kwargs): self._original_front_image = self.front_image self._original_rear_image = self.rear_image - @classmethod - def get_prerequisite_models(cls): - return [Manufacturer, ] - def get_absolute_url(self): return reverse('dcim:devicetype', args=[self.pk]) @@ -325,6 +324,9 @@ class ModuleType(PrimaryModel, WeightMixin): ) clone_fields = ('manufacturer', 'weight', 'weight_unit',) + prerequisite_models = ( + 'dcim.Manufacturer', + ) class Meta: ordering = ('manufacturer', 'model') @@ -338,10 +340,6 @@ class Meta: def __str__(self): return self.model - @classmethod - def get_prerequisite_models(cls): - return [Manufacturer, ] - def get_absolute_url(self): return reverse('dcim:moduletype', args=[self.pk]) @@ -599,6 +597,11 @@ class Device(PrimaryModel, ConfigContextModel): 'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'face', 'status', 'airflow', 'cluster', 'virtual_chassis', ) + prerequisite_models = ( + 'dcim.Site', + 'dcim.DeviceRole', + 'dcim.DeviceType', + ) class Meta: ordering = ('_name', 'pk') # Name may be null @@ -638,10 +641,6 @@ def __str__(self): return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' return super().__str__() - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('dcim.Site'), DeviceRole, DeviceType, ] - def get_absolute_url(self): return reverse('dcim:device', args=[self.pk]) diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index a910b1437d7..3377a9edb6d 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -1,4 +1,3 @@ -from django.apps import apps from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator @@ -48,6 +47,10 @@ class PowerPanel(PrimaryModel): to='extras.ImageAttachment' ) + prerequisite_models = ( + 'dcim.Site', + ) + class Meta: ordering = ['site', 'name'] constraints = ( @@ -60,10 +63,6 @@ class Meta: def __str__(self): return self.name - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('dcim.Site'), ] - def get_absolute_url(self): return reverse('dcim:powerpanel', args=[self.pk]) @@ -137,6 +136,9 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel): 'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', ) + prerequisite_models = ( + 'dcim.PowerPanel', + ) class Meta: ordering = ['power_panel', 'name'] @@ -150,10 +152,6 @@ class Meta: def __str__(self): return self.name - @classmethod - def get_prerequisite_models(cls): - return [PowerPanel, ] - def get_absolute_url(self): return reverse('dcim:powerfeed', args=[self.pk]) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index ff37aff5aa6..8cdc9c6918b 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -1,7 +1,6 @@ import decimal from functools import cached_property -from django.apps import apps from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.fields import ArrayField @@ -177,6 +176,9 @@ class Rack(PrimaryModel, WeightMixin): 'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit', ) + prerequisite_models = ( + 'dcim.Site', + ) class Meta: ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique @@ -197,10 +199,6 @@ def __str__(self): return f'{self.name} ({self.facility_id})' return self.name - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('dcim.Site'), ] - def get_absolute_url(self): return reverse('dcim:rack', args=[self.pk]) @@ -488,16 +486,16 @@ class RackReservation(PrimaryModel): max_length=200 ) + prerequisite_models = ( + 'dcim.Rack', + ) + class Meta: ordering = ['created', 'pk'] def __str__(self): return "Reservation for rack {}".format(self.rack) - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('dcim.Site'), Rack, ] - def get_absolute_url(self): return reverse('dcim:rackreservation', args=[self.pk]) diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 33f695e7001..c035fc1dbd5 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -286,6 +286,9 @@ class Location(NestedGroupModel): ) clone_fields = ('site', 'parent', 'status', 'tenant', 'description') + prerequisite_models = ( + 'dcim.Site', + ) class Meta: ordering = ['site', 'name'] @@ -312,10 +315,6 @@ class Meta: ), ) - @classmethod - def get_prerequisite_models(cls): - return [Site, ] - def get_absolute_url(self): return reverse('dcim:location', args=[self.pk]) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index dd92f97cc96..20530d66c03 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -10,7 +10,6 @@ from dcim.fields import ASNField from dcim.models import Device -from netbox.models import OrganizationalModel, PrimaryModel from ipam.choices import * from ipam.constants import * from ipam.fields import IPNetworkField, IPAddressField @@ -18,9 +17,9 @@ from ipam.querysets import PrefixQuerySet from ipam.validators import DNSValidator from netbox.config import get_config +from netbox.models import OrganizationalModel, PrimaryModel from virtualization.models import VirtualMachine - __all__ = ( 'Aggregate', 'ASN', @@ -101,6 +100,10 @@ class ASN(PrimaryModel): null=True ) + prerequisite_models = ( + 'ipam.RIR', + ) + class Meta: ordering = ['asn'] verbose_name = 'ASN' @@ -109,10 +112,6 @@ class Meta: def __str__(self): return f'AS{self.asn_with_asdot}' - @classmethod - def get_prerequisite_models(cls): - return [RIR, ] - def get_absolute_url(self): return reverse('ipam:asn', args=[self.pk]) @@ -163,6 +162,9 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel): clone_fields = ( 'rir', 'tenant', 'date_added', 'description', ) + prerequisite_models = ( + 'ipam.RIR', + ) class Meta: ordering = ('prefix', 'pk') # prefix may be non-unique @@ -170,10 +172,6 @@ class Meta: def __str__(self): return str(self.prefix) - @classmethod - def get_prerequisite_models(cls): - return [RIR, ] - def get_absolute_url(self): return reverse('ipam:aggregate', args=[self.pk]) diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index f3f7a1d556c..c858d1a0c87 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -1,4 +1,3 @@ -from django.apps import apps from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError @@ -95,6 +94,9 @@ class L2VPNTermination(NetBoxModel): ) clone_fields = ('l2vpn',) + prerequisite_models = ( + 'ipam.L2VPN', + ) class Meta: ordering = ('l2vpn',) @@ -111,10 +113,6 @@ def __str__(self): return f'{self.assigned_object} <> {self.l2vpn}' return super().__str__() - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('ipam.L2VPN'), ] - def get_absolute_url(self): return reverse('ipam:l2vpntermination', args=[self.pk]) diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index 661470ee0b2..d3f3e78bc2b 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -3,9 +3,9 @@ from django.db import models from mptt.models import MPTTModel, TreeForeignKey +from netbox.models.features import * from utilities.mptt import TreeManager from utilities.querysets import RestrictedQuerySet -from netbox.models.features import * __all__ = ( 'ChangeLoggedModel', @@ -33,14 +33,6 @@ class Meta: def docs_url(self): return f'{settings.STATIC_URL}docs/models/{self._meta.app_label}/{self._meta.model_name}/' - @classmethod - def get_prerequisite_models(cls): - """ - Return a list of model types that are required to create this model or empty list if none. This is used for - showing prerequisite warnings in the UI on the list and detail views. - """ - return [] - # # Base model classes diff --git a/netbox/netbox/views/generic/utils.py b/netbox/netbox/views/generic/utils.py index 61c6dc24291..61d73e81116 100644 --- a/netbox/netbox/views/generic/utils.py +++ b/netbox/netbox/views/generic/utils.py @@ -1,12 +1,13 @@ -def get_prerequisite_model(queryset): - model = queryset.model +from django.apps import apps - if not queryset.exists(): - if hasattr(model, 'get_prerequisite_models'): - prerequisites = model.get_prerequisite_models() - if prerequisites: - for prereq in prerequisites: - if not prereq.objects.exists(): - return prereq - return None +def get_prerequisite_model(queryset): + """ + Return any prerequisite model that must be created prior to creating + an instance of the current model. + """ + if not queryset.exists(): + for prereq in getattr(queryset.model, 'prerequisite_models', []): + model = apps.get_model(prereq) + if not model.objects.exists(): + return model diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py index e7c1294c2e3..517b92ef2ca 100644 --- a/netbox/virtualization/models/clusters.py +++ b/netbox/virtualization/models/clusters.py @@ -94,6 +94,9 @@ class Cluster(PrimaryModel): clone_fields = ( 'type', 'group', 'status', 'tenant', 'site', ) + prerequisite_models = ( + 'virtualization.ClusterType', + ) class Meta: ordering = ['name'] @@ -111,10 +114,6 @@ class Meta: def __str__(self): return self.name - @classmethod - def get_prerequisite_models(cls): - return [ClusterType, ] - def get_absolute_url(self): return reverse('virtualization:cluster', args=[self.pk]) diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index d64289eb2e7..cc39044f982 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -15,7 +15,6 @@ from utilities.ordering import naturalize_interface from utilities.query_functions import CollateAsChar from virtualization.choices import * -from .clusters import Cluster __all__ = ( 'VirtualMachine', @@ -131,6 +130,9 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): clone_fields = ( 'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk', ) + prerequisite_models = ( + 'virtualization.Cluster', + ) class Meta: ordering = ('_name', 'pk') # Name may be non-unique @@ -150,10 +152,6 @@ class Meta: def __str__(self): return self.name - @classmethod - def get_prerequisite_models(cls): - return [Cluster, ] - def get_absolute_url(self): return reverse('virtualization:virtualmachine', args=[self.pk]) diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 5858e641cda..261e5c67d69 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -1,4 +1,3 @@ -from django.apps import apps from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse @@ -193,10 +192,6 @@ class Meta: def __str__(self): return f'#{self.pk}' - @classmethod - def get_prerequisite_models(cls): - return [apps.get_model('dcim.Interface'), ] - def get_absolute_url(self): return reverse('wireless:wirelesslink', args=[self.pk])