diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 81d699b11..e57934353 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -1,5 +1,6 @@ from collections import OrderedDict +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 @@ -202,6 +203,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]) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 966d90876..6e77d4396 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -560,6 +560,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView): # class RackListView(generic.ObjectListView): + required_prerequisites = [Site] queryset = Rack.objects.prefetch_related('devices__device_type').annotate( device_count=count_related(Device, 'rack') ) diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index ea2feb8de..2524c7c9b 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -52,6 +52,10 @@ class NetBoxModel(NetBoxFeatureSet, models.Model): class Meta: abstract = True + @classmethod + def get_prerequisite_models(cls): + return [] + class NestedGroupModel(NetBoxFeatureSet, MPTTModel): """ diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 5aea9c469..29007985c 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -25,6 +25,7 @@ from utilities.htmx import is_htmx from utilities.permissions import get_permission_for_model from utilities.views import GetReturnURLMixin from .base import BaseMultiObjectView +from .utils import get_prerequisite_model __all__ = ( 'BulkComponentCreateView', @@ -143,6 +144,7 @@ class ObjectListView(BaseMultiObjectView): """ model = self.queryset.model content_type = ContentType.objects.get_for_model(model) + requirement = get_prerequisite_model(self.queryset) if self.filterset: self.queryset = self.filterset(request.GET, self.queryset).qs @@ -198,6 +200,8 @@ class ObjectListView(BaseMultiObjectView): 'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None, **self.get_extra_context(request), } + if requirement: + context['required_model'] = requirement return render(request, self.template_name, context) @@ -256,6 +260,17 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView): form = self.form() model_form = self.model_form(initial=initial) + context = { + 'obj_type': self.model_form._meta.model._meta.verbose_name, + 'form': form, + 'model_form': model_form, + 'return_url': self.get_return_url(request), + **self.get_extra_context(request), + } + + if requirement: + context['required_model'] = requirement + return render(request, self.template_name, { 'obj_type': self.model_form._meta.model._meta.verbose_name, 'form': form, diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 88e078ae3..878f293a0 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -20,6 +20,7 @@ from utilities.permissions import get_permission_for_model from utilities.utils import get_viewname, normalize_querydict, prepare_cloned_fields from utilities.views import GetReturnURLMixin from .base import BaseObjectView +from .utils import get_prerequisite_model __all__ = ( 'ComponentCreateView', @@ -342,12 +343,19 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): form = self.form(instance=obj, initial=initial_data) restrict_form_fields(form, request.user) - return render(request, self.template_name, { + context = { 'object': obj, 'form': form, 'return_url': self.get_return_url(request, obj), **self.get_extra_context(request, obj), - }) + } + + requirement = get_prerequisite_model(self.queryset) + if requirement: + context['required_model'] = requirement + context['model'] = self.queryset.model + + return render(request, self.template_name, context) def post(self, request, *args, **kwargs): """ diff --git a/netbox/netbox/views/generic/utils.py b/netbox/netbox/views/generic/utils.py new file mode 100644 index 000000000..57c7b5eba --- /dev/null +++ b/netbox/netbox/views/generic/utils.py @@ -0,0 +1,12 @@ +def get_prerequisite_model(queryset): + requirement = None + model = queryset.model + + if not queryset.count(): + prerequisites = model.get_prerequisite_models() + if prerequisites: + for prereq in prerequisites: + if not prereq.objects.count(): + requirement = prereq + + return requirement diff --git a/netbox/templates/generic/object_edit.html b/netbox/templates/generic/object_edit.html index 892c7d2b1..73e9727bb 100644 --- a/netbox/templates/generic/object_edit.html +++ b/netbox/templates/generic/object_edit.html @@ -40,6 +40,10 @@ Context: {% endif %} + {% if required_model %} + {% include 'inc/missing_prerequisites.html' %} + {% endif %} +
{% csrf_token %} diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index 1e2ae796f..6910aa116 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -100,6 +100,12 @@ Context: {# Object table #} + + {% if required_model %} + {% include 'inc/missing_prerequisites.html' %} + {% endif %} + +
{% include 'htmx/table.html' %} diff --git a/netbox/templates/inc/missing_prerequisites.html b/netbox/templates/inc/missing_prerequisites.html new file mode 100644 index 000000000..c12b157d0 --- /dev/null +++ b/netbox/templates/inc/missing_prerequisites.html @@ -0,0 +1,5 @@ +{% load buttons %} + +