1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge pull request #10096 from arthanson/art-6454

Fixes #6454 - Adds warning for prerequisite models
This commit is contained in:
Jeremy Stretch
2022-08-22 16:09:59 -04:00
committed by GitHub
16 changed files with 112 additions and 2 deletions

View File

@ -1,3 +1,4 @@
from django.apps import apps
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db import models
@ -136,6 +137,10 @@ class Circuit(NetBoxModel):
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])

View File

@ -1,6 +1,8 @@
import decimal
import yaml
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
@ -159,6 +161,10 @@ class DeviceType(NetBoxModel):
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])
@ -338,6 +344,10 @@ class ModuleType(NetBoxModel):
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])
@ -658,6 +668,10 @@ class Device(NetBoxModel, ConfigContextModel):
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])

View File

@ -1,3 +1,4 @@
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
@ -54,6 +55,10 @@ class PowerPanel(NetBoxModel):
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])
@ -138,6 +143,10 @@ class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
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])

View File

@ -1,5 +1,6 @@
import decimal
from django.apps import apps
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
@ -201,6 +202,10 @@ class Rack(NetBoxModel):
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])
@ -477,6 +482,10 @@ class RackReservation(NetBoxModel):
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])

View File

@ -411,6 +411,10 @@ class Location(NestedGroupModel):
super().validate_unique(exclude=exclude)
@classmethod
def get_prerequisite_models(cls):
return [Site, ]
def get_absolute_url(self):
return reverse('dcim:location', args=[self.pk])

View File

@ -124,6 +124,10 @@ class ASN(NetBoxModel):
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])
@ -185,6 +189,10 @@ class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
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])

View File

@ -1,3 +1,4 @@
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
@ -103,6 +104,10 @@ class L2VPNTermination(NetBoxModel):
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])

View File

@ -28,6 +28,14 @@ class NetBoxFeatureSet(
class Meta:
abstract = True
@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 prequisite warnings in the UI on the list and detail views.
"""
return []
#
# Base model classes

View File

@ -26,6 +26,7 @@ from utilities.permissions import get_permission_for_model
from utilities.views import GetReturnURLMixin
from .base import BaseMultiObjectView
from .mixins import ActionsMixin, TableMixin
from .utils import get_prerequisite_model
__all__ = (
'BulkComponentCreateView',
@ -165,13 +166,16 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
'table': table,
})
return render(request, self.template_name, {
context = {
'model': model,
'table': table,
'actions': actions,
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
'prerequisite_model': get_prerequisite_model(self.queryset),
**self.get_extra_context(request),
})
}
return render(request, self.template_name, context)
class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):

View File

@ -21,6 +21,7 @@ from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fi
from utilities.views import GetReturnURLMixin
from .base import BaseObjectView
from .mixins import ActionsMixin, TableMixin
from .utils import get_prerequisite_model
__all__ = (
'ComponentCreateView',
@ -340,15 +341,18 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
"""
obj = self.get_object(**kwargs)
obj = self.alter_object(obj, request, args, kwargs)
model = self.queryset.model
initial_data = normalize_querydict(request.GET)
form = self.form(instance=obj, initial=initial_data)
restrict_form_fields(form, request.user)
return render(request, self.template_name, {
'model': model,
'object': obj,
'form': form,
'return_url': self.get_return_url(request, obj),
'prerequisite_model': get_prerequisite_model(self.queryset),
**self.get_extra_context(request, obj),
})

View File

@ -0,0 +1,12 @@
def get_prerequisite_model(queryset):
model = queryset.model
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

View File

@ -40,6 +40,10 @@ Context:
</div>
{% endif %}
{% if prerequisite_model %}
{% include 'inc/missing_prerequisites.html' %}
{% endif %}
<form action="" method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
{% csrf_token %}

View File

@ -100,6 +100,11 @@ Context:
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
{# Object table #}
{% if prerequisite_model %}
{% include 'inc/missing_prerequisites.html' %}
{% endif %}
<div class="card">
<div class="card-body" id="object_list">
{% include 'htmx/table.html' %}

View File

@ -0,0 +1,6 @@
{% load buttons %}
<div class="alert alert-warning" role="alert">
<i class="mdi mdi-alert"></i> Before you can add a {{ model|meta:"verbose_name" }} you must first create a
<strong>{{ prerequisite_model|meta:"verbose_name"|title }}</strong> which can be added here: {% add_button prerequisite_model %}
</div>

View File

@ -167,6 +167,10 @@ class Cluster(NetBoxModel):
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])
@ -312,6 +316,10 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
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])

View File

@ -1,3 +1,4 @@
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
@ -190,6 +191,10 @@ class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
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])