Просмотр исходного кода

Use strings to specify prerequisite models

jeremystretch 3 лет назад
Родитель
Сommit
ebf555e1fb

+ 4 - 4
netbox/circuits/models/circuits.py

@@ -104,6 +104,10 @@ class Circuit(PrimaryModel):
     clone_fields = (
     clone_fields = (
         'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
         'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
     )
     )
+    prerequisite_models = (
+        'circuits.CircuitType',
+        'circuits.Provider',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ['provider', 'cid']
         ordering = ['provider', 'cid']
@@ -117,10 +121,6 @@ class Circuit(PrimaryModel):
     def __str__(self):
     def __str__(self):
         return self.cid
         return self.cid
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('circuits.Provider'), CircuitType]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('circuits:circuit', args=[self.pk])
         return reverse('circuits:circuit', args=[self.pk])
 
 

+ 11 - 12
netbox/dcim/models/devices.py

@@ -124,6 +124,9 @@ class DeviceType(PrimaryModel, WeightMixin):
     clone_fields = (
     clone_fields = (
         'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
         'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
     )
     )
+    prerequisite_models = (
+        'dcim.Manufacturer',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ['manufacturer', 'model']
         ordering = ['manufacturer', 'model']
@@ -151,10 +154,6 @@ class DeviceType(PrimaryModel, WeightMixin):
         self._original_front_image = self.front_image
         self._original_front_image = self.front_image
         self._original_rear_image = self.rear_image
         self._original_rear_image = self.rear_image
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [Manufacturer, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:devicetype', args=[self.pk])
         return reverse('dcim:devicetype', args=[self.pk])
 
 
@@ -325,6 +324,9 @@ class ModuleType(PrimaryModel, WeightMixin):
     )
     )
 
 
     clone_fields = ('manufacturer', 'weight', 'weight_unit',)
     clone_fields = ('manufacturer', 'weight', 'weight_unit',)
+    prerequisite_models = (
+        'dcim.Manufacturer',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('manufacturer', 'model')
         ordering = ('manufacturer', 'model')
@@ -338,10 +340,6 @@ class ModuleType(PrimaryModel, WeightMixin):
     def __str__(self):
     def __str__(self):
         return self.model
         return self.model
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [Manufacturer, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:moduletype', args=[self.pk])
         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',
         'device_type', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'face', 'status', 'airflow',
         'cluster', 'virtual_chassis',
         'cluster', 'virtual_chassis',
     )
     )
+    prerequisite_models = (
+        'dcim.Site',
+        'dcim.DeviceRole',
+        'dcim.DeviceType',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('_name', 'pk')  # Name may be null
         ordering = ('_name', 'pk')  # Name may be null
@@ -638,10 +641,6 @@ class Device(PrimaryModel, ConfigContextModel):
             return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
             return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
         return super().__str__()
         return super().__str__()
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('dcim.Site'), DeviceRole, DeviceType, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:device', args=[self.pk])
         return reverse('dcim:device', args=[self.pk])
 
 

+ 7 - 9
netbox/dcim/models/power.py

@@ -1,4 +1,3 @@
-from django.apps import apps
 from django.contrib.contenttypes.fields import GenericRelation
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
@@ -48,6 +47,10 @@ class PowerPanel(PrimaryModel):
         to='extras.ImageAttachment'
         to='extras.ImageAttachment'
     )
     )
 
 
+    prerequisite_models = (
+        'dcim.Site',
+    )
+
     class Meta:
     class Meta:
         ordering = ['site', 'name']
         ordering = ['site', 'name']
         constraints = (
         constraints = (
@@ -60,10 +63,6 @@ class PowerPanel(PrimaryModel):
     def __str__(self):
     def __str__(self):
         return self.name
         return self.name
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('dcim.Site'), ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:powerpanel', args=[self.pk])
         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',
         'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
         'max_utilization',
         'max_utilization',
     )
     )
+    prerequisite_models = (
+        'dcim.PowerPanel',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ['power_panel', 'name']
         ordering = ['power_panel', 'name']
@@ -150,10 +152,6 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
     def __str__(self):
     def __str__(self):
         return self.name
         return self.name
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [PowerPanel, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:powerfeed', args=[self.pk])
         return reverse('dcim:powerfeed', args=[self.pk])
 
 

+ 7 - 9
netbox/dcim/models/racks.py

@@ -1,7 +1,6 @@
 import decimal
 import decimal
 from functools import cached_property
 from functools import cached_property
 
 
-from django.apps import apps
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.fields import GenericRelation
 from django.contrib.contenttypes.fields import GenericRelation
 from django.contrib.postgres.fields import ArrayField
 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',
         'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
         'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit',
         'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit',
     )
     )
+    prerequisite_models = (
+        'dcim.Site',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('site', 'location', '_name', 'pk')  # (site, location, name) may be non-unique
         ordering = ('site', 'location', '_name', 'pk')  # (site, location, name) may be non-unique
@@ -197,10 +199,6 @@ class Rack(PrimaryModel, WeightMixin):
             return f'{self.name} ({self.facility_id})'
             return f'{self.name} ({self.facility_id})'
         return self.name
         return self.name
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('dcim.Site'), ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:rack', args=[self.pk])
         return reverse('dcim:rack', args=[self.pk])
 
 
@@ -488,16 +486,16 @@ class RackReservation(PrimaryModel):
         max_length=200
         max_length=200
     )
     )
 
 
+    prerequisite_models = (
+        'dcim.Rack',
+    )
+
     class Meta:
     class Meta:
         ordering = ['created', 'pk']
         ordering = ['created', 'pk']
 
 
     def __str__(self):
     def __str__(self):
         return "Reservation for rack {}".format(self.rack)
         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):
     def get_absolute_url(self):
         return reverse('dcim:rackreservation', args=[self.pk])
         return reverse('dcim:rackreservation', args=[self.pk])
 
 

+ 3 - 4
netbox/dcim/models/sites.py

@@ -286,6 +286,9 @@ class Location(NestedGroupModel):
     )
     )
 
 
     clone_fields = ('site', 'parent', 'status', 'tenant', 'description')
     clone_fields = ('site', 'parent', 'status', 'tenant', 'description')
+    prerequisite_models = (
+        'dcim.Site',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ['site', 'name']
         ordering = ['site', 'name']
@@ -312,10 +315,6 @@ class Location(NestedGroupModel):
             ),
             ),
         )
         )
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [Site, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:location', args=[self.pk])
         return reverse('dcim:location', args=[self.pk])
 
 

+ 8 - 10
netbox/ipam/models/ip.py

@@ -10,7 +10,6 @@ from django.utils.translation import gettext as _
 
 
 from dcim.fields import ASNField
 from dcim.fields import ASNField
 from dcim.models import Device
 from dcim.models import Device
-from netbox.models import OrganizationalModel, PrimaryModel
 from ipam.choices import *
 from ipam.choices import *
 from ipam.constants import *
 from ipam.constants import *
 from ipam.fields import IPNetworkField, IPAddressField
 from ipam.fields import IPNetworkField, IPAddressField
@@ -18,9 +17,9 @@ from ipam.managers import IPAddressManager
 from ipam.querysets import PrefixQuerySet
 from ipam.querysets import PrefixQuerySet
 from ipam.validators import DNSValidator
 from ipam.validators import DNSValidator
 from netbox.config import get_config
 from netbox.config import get_config
+from netbox.models import OrganizationalModel, PrimaryModel
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
 
 
-
 __all__ = (
 __all__ = (
     'Aggregate',
     'Aggregate',
     'ASN',
     'ASN',
@@ -101,6 +100,10 @@ class ASN(PrimaryModel):
         null=True
         null=True
     )
     )
 
 
+    prerequisite_models = (
+        'ipam.RIR',
+    )
+
     class Meta:
     class Meta:
         ordering = ['asn']
         ordering = ['asn']
         verbose_name = 'ASN'
         verbose_name = 'ASN'
@@ -109,10 +112,6 @@ class ASN(PrimaryModel):
     def __str__(self):
     def __str__(self):
         return f'AS{self.asn_with_asdot}'
         return f'AS{self.asn_with_asdot}'
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [RIR, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('ipam:asn', args=[self.pk])
         return reverse('ipam:asn', args=[self.pk])
 
 
@@ -163,6 +162,9 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
     clone_fields = (
     clone_fields = (
         'rir', 'tenant', 'date_added', 'description',
         'rir', 'tenant', 'date_added', 'description',
     )
     )
+    prerequisite_models = (
+        'ipam.RIR',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('prefix', 'pk')  # prefix may be non-unique
         ordering = ('prefix', 'pk')  # prefix may be non-unique
@@ -170,10 +172,6 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
     def __str__(self):
     def __str__(self):
         return str(self.prefix)
         return str(self.prefix)
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [RIR, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('ipam:aggregate', args=[self.pk])
         return reverse('ipam:aggregate', args=[self.pk])
 
 

+ 3 - 5
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.fields import GenericRelation, GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
@@ -95,6 +94,9 @@ class L2VPNTermination(NetBoxModel):
     )
     )
 
 
     clone_fields = ('l2vpn',)
     clone_fields = ('l2vpn',)
+    prerequisite_models = (
+        'ipam.L2VPN',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('l2vpn',)
         ordering = ('l2vpn',)
@@ -111,10 +113,6 @@ class L2VPNTermination(NetBoxModel):
             return f'{self.assigned_object} <> {self.l2vpn}'
             return f'{self.assigned_object} <> {self.l2vpn}'
         return super().__str__()
         return super().__str__()
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('ipam.L2VPN'), ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('ipam:l2vpntermination', args=[self.pk])
         return reverse('ipam:l2vpntermination', args=[self.pk])
 
 

+ 1 - 9
netbox/netbox/models/__init__.py

@@ -3,9 +3,9 @@ from django.core.validators import ValidationError
 from django.db import models
 from django.db import models
 from mptt.models import MPTTModel, TreeForeignKey
 from mptt.models import MPTTModel, TreeForeignKey
 
 
+from netbox.models.features import *
 from utilities.mptt import TreeManager
 from utilities.mptt import TreeManager
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
-from netbox.models.features import *
 
 
 __all__ = (
 __all__ = (
     'ChangeLoggedModel',
     'ChangeLoggedModel',
@@ -33,14 +33,6 @@ class NetBoxFeatureSet(
     def docs_url(self):
     def docs_url(self):
         return f'{settings.STATIC_URL}docs/models/{self._meta.app_label}/{self._meta.model_name}/'
         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
 # Base model classes

+ 11 - 10
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

+ 3 - 4
netbox/virtualization/models/clusters.py

@@ -94,6 +94,9 @@ class Cluster(PrimaryModel):
     clone_fields = (
     clone_fields = (
         'type', 'group', 'status', 'tenant', 'site',
         'type', 'group', 'status', 'tenant', 'site',
     )
     )
+    prerequisite_models = (
+        'virtualization.ClusterType',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ['name']
         ordering = ['name']
@@ -111,10 +114,6 @@ class Cluster(PrimaryModel):
     def __str__(self):
     def __str__(self):
         return self.name
         return self.name
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [ClusterType, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('virtualization:cluster', args=[self.pk])
         return reverse('virtualization:cluster', args=[self.pk])
 
 

+ 3 - 5
netbox/virtualization/models/virtualmachines.py

@@ -15,7 +15,6 @@ from utilities.fields import NaturalOrderingField
 from utilities.ordering import naturalize_interface
 from utilities.ordering import naturalize_interface
 from utilities.query_functions import CollateAsChar
 from utilities.query_functions import CollateAsChar
 from virtualization.choices import *
 from virtualization.choices import *
-from .clusters import Cluster
 
 
 __all__ = (
 __all__ = (
     'VirtualMachine',
     'VirtualMachine',
@@ -131,6 +130,9 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
     clone_fields = (
     clone_fields = (
         'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk',
         'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk',
     )
     )
+    prerequisite_models = (
+        'virtualization.Cluster',
+    )
 
 
     class Meta:
     class Meta:
         ordering = ('_name', 'pk')  # Name may be non-unique
         ordering = ('_name', 'pk')  # Name may be non-unique
@@ -150,10 +152,6 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
     def __str__(self):
     def __str__(self):
         return self.name
         return self.name
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [Cluster, ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('virtualization:virtualmachine', args=[self.pk])
         return reverse('virtualization:virtualmachine', args=[self.pk])
 
 

+ 0 - 5
netbox/wireless/models.py

@@ -1,4 +1,3 @@
-from django.apps import apps
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.db import models
 from django.urls import reverse
 from django.urls import reverse
@@ -193,10 +192,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
     def __str__(self):
     def __str__(self):
         return f'#{self.pk}'
         return f'#{self.pk}'
 
 
-    @classmethod
-    def get_prerequisite_models(cls):
-        return [apps.get_model('dcim.Interface'), ]
-
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('wireless:wirelesslink', args=[self.pk])
         return reverse('wireless:wirelesslink', args=[self.pk])