Quellcode durchsuchen

Closes #17789: Use a single scope field for VLANGroup bulk edit

Jeremy Stretch vor 1 Jahr
Ursprung
Commit
6a316df787

+ 29 - 74
netbox/ipam/forms/bulk_edit.py

@@ -1,22 +1,23 @@
 from django import forms
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from dcim.models import Location, Rack, Region, Site, SiteGroup
+from dcim.models import Region, Site, SiteGroup
 from ipam.choices import *
 from ipam.choices import *
 from ipam.constants import *
 from ipam.constants import *
 from ipam.models import *
 from ipam.models import *
 from ipam.models import ASN
 from ipam.models import ASN
 from netbox.forms import NetBoxModelBulkEditForm
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
-from utilities.forms import add_blank_choice
+from utilities.forms import add_blank_choice, get_field_value
 from utilities.forms.fields import (
 from utilities.forms.fields import (
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     NumericRangeArrayField,
     NumericRangeArrayField,
 )
 )
 from utilities.forms.rendering import FieldSet
 from utilities.forms.rendering import FieldSet
-from utilities.forms.widgets import BulkEditNullBooleanSelect
-from virtualization.models import Cluster, ClusterGroup
+from utilities.forms.widgets import BulkEditNullBooleanSelect, HTMXSelect
+from utilities.templatetags.builtins.filters import bettertitle
 
 
 __all__ = (
 __all__ = (
     'AggregateBulkEditForm',
     'AggregateBulkEditForm',
@@ -429,62 +430,17 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
         required=False
         required=False
     )
     )
     scope_type = ContentTypeChoiceField(
     scope_type = ContentTypeChoiceField(
-        label=_('Scope type'),
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
         queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
-        required=False
-    )
-    scope_id = forms.IntegerField(
-        required=False,
-        widget=forms.HiddenInput()
-    )
-    region = DynamicModelChoiceField(
-        label=_('Region'),
-        queryset=Region.objects.all(),
-        required=False
-    )
-    sitegroup = DynamicModelChoiceField(
-        queryset=SiteGroup.objects.all(),
-        required=False,
-        label=_('Site group')
-    )
-    site = DynamicModelChoiceField(
-        label=_('Site'),
-        queryset=Site.objects.all(),
-        required=False,
-        query_params={
-            'region_id': '$region',
-            'group_id': '$sitegroup',
-        }
-    )
-    location = DynamicModelChoiceField(
-        label=_('Location'),
-        queryset=Location.objects.all(),
+        widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
         required=False,
         required=False,
-        query_params={
-            'site_id': '$site',
-        }
-    )
-    rack = DynamicModelChoiceField(
-        label=_('Rack'),
-        queryset=Rack.objects.all(),
-        required=False,
-        query_params={
-            'site_id': '$site',
-            'location_id': '$location',
-        }
+        label=_('Scope type')
     )
     )
-    clustergroup = DynamicModelChoiceField(
-        queryset=ClusterGroup.objects.all(),
+    scope = DynamicModelChoiceField(
+        label=_('Scope'),
+        queryset=Site.objects.none(),  # Initial queryset
         required=False,
         required=False,
-        label=_('Cluster group')
-    )
-    cluster = DynamicModelChoiceField(
-        label=_('Cluster'),
-        queryset=Cluster.objects.all(),
-        required=False,
-        query_params={
-            'group_id': '$clustergroup',
-        }
+        disabled=True,
+        selector=True
     )
     )
     vid_ranges = NumericRangeArrayField(
     vid_ranges = NumericRangeArrayField(
         label=_('VLAN ID ranges'),
         label=_('VLAN ID ranges'),
@@ -494,24 +450,23 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     model = VLANGroup
     model = VLANGroup
     fieldsets = (
     fieldsets = (
         FieldSet('site', 'vid_ranges', 'description'),
         FieldSet('site', 'vid_ranges', 'description'),
-        FieldSet(
-            'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
-        ),
-    )
-    nullable_fields = ('description',)
-
-    def clean(self):
-        super().clean()
-
-        # Assign scope based on scope_type
-        if self.cleaned_data.get('scope_type'):
-            scope_field = self.cleaned_data['scope_type'].model
-            if scope_obj := self.cleaned_data.get(scope_field):
-                self.cleaned_data['scope_id'] = scope_obj.pk
-                self.changed_data.append('scope_id')
-            else:
-                self.cleaned_data.pop('scope_type')
-                self.changed_data.remove('scope_type')
+        FieldSet('scope_type', 'scope', name=_('Scope')),
+    )
+    nullable_fields = ('description', 'scope')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        if scope_type_id := get_field_value(self, 'scope_type'):
+            try:
+                scope_type = ContentType.objects.get(pk=scope_type_id)
+                model = scope_type.model_class()
+                self.fields['scope'].queryset = model.objects.all()
+                self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
+                self.fields['scope'].disabled = False
+                self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
+            except ObjectDoesNotExist:
+                pass
 
 
 
 
 class VLANBulkEditForm(NetBoxModelBulkEditForm):
 class VLANBulkEditForm(NetBoxModelBulkEditForm):

+ 6 - 3
netbox/netbox/views/generic/bulk_views.py

@@ -3,7 +3,7 @@ import re
 from copy import deepcopy
 from copy import deepcopy
 
 
 from django.contrib import messages
 from django.contrib import messages
-from django.contrib.contenttypes.fields import GenericRel
+from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
 from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
 from django.db import transaction, IntegrityError
 from django.db import transaction, IntegrityError
@@ -576,7 +576,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
             for name, model_field in model_fields.items():
             for name, model_field in model_fields.items():
                 # Handle nullification
                 # Handle nullification
                 if name in form.nullable_fields and name in nullified_fields:
                 if name in form.nullable_fields and name in nullified_fields:
-                    setattr(obj, name, None if model_field.null else '')
+                    if type(model_field) is GenericForeignKey:
+                        setattr(obj, name, None)
+                    else:
+                        setattr(obj, name, None if model_field.null else '')
                 # Normal fields
                 # Normal fields
                 elif name in form.changed_data:
                 elif name in form.changed_data:
                     setattr(obj, name, form.cleaned_data[name])
                     setattr(obj, name, form.cleaned_data[name])
@@ -688,7 +691,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
                 logger.debug("Form validation failed")
                 logger.debug("Form validation failed")
 
 
         else:
         else:
-            form = self.form(initial=initial_data)
+            form = self.form(request.POST, initial=initial_data)
             restrict_form_fields(form, request.user)
             restrict_form_fields(form, request.user)
 
 
         # Retrieve objects being edited
         # Retrieve objects being edited

+ 53 - 53
netbox/templates/generic/bulk_edit.html

@@ -42,71 +42,71 @@ Context:
   {# Edit form #}
   {# Edit form #}
   <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="edit-form-tab">
   <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="edit-form-tab">
     <form action="" method="post" class="form form-horizontal mt-5">
     <form action="" method="post" class="form form-horizontal mt-5">
-
-      {% csrf_token %}
-      {% if request.POST.return_url %}
-        <input type="hidden" name="return_url" value="{{ request.POST.return_url }}" />
-      {% endif %}
-      {% for field in form.hidden_fields %}
-        {{ field }}
-      {% endfor %}
-
-      {% if form.fieldsets %}
-
-        {# Render grouped fields according to declared fieldsets #}
-        {% for fieldset in form.fieldsets %}
-          {% render_fieldset form fieldset %}
+      <div id="form_fields" hx-disinherit="hx-select hx-swap">
+        {% csrf_token %}
+        {% if request.POST.return_url %}
+          <input type="hidden" name="return_url" value="{{ request.POST.return_url }}" />
+        {% endif %}
+        {% for field in form.hidden_fields %}
+          {{ field }}
         {% endfor %}
         {% endfor %}
 
 
-        {# Render tag add/remove fields #}
-        {% if form.add_tags and form.remove_tags %}
-          <div class="field-group mb-5">
-            <div class="row">
-              <h2 class="col-9 offset-3">{% trans "Tags" %}</h2>
+        {% if form.fieldsets %}
+
+          {# Render grouped fields according to declared fieldsets #}
+          {% for fieldset in form.fieldsets %}
+            {% render_fieldset form fieldset %}
+          {% endfor %}
+
+          {# Render tag add/remove fields #}
+          {% if form.add_tags and form.remove_tags %}
+            <div class="field-group mb-5">
+              <div class="row">
+                <h2 class="col-9 offset-3">{% trans "Tags" %}</h2>
+              </div>
+              {% render_field form.add_tags %}
+              {% render_field form.remove_tags %}
             </div>
             </div>
-            {% render_field form.add_tags %}
-            {% render_field form.remove_tags %}
-          </div>
-        {% endif %}
+          {% endif %}
 
 
-        {# Render custom fields #}
-        {% if form.custom_fields %}
-          <div class="field-group mb-5">
-            <div class="row">
-              <h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
+          {# Render custom fields #}
+          {% if form.custom_fields %}
+            <div class="field-group mb-5">
+              <div class="row">
+                <h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
+              </div>
+              {% render_custom_fields form %}
             </div>
             </div>
-            {% render_custom_fields form %}
-          </div>
-        {% endif %}
+          {% endif %}
 
 
-        {# Render comments #}
-        {% if form.comments %}
-          <div class="field-group mb-5">
-            <div class="row">
-              <h2 class="col-9 offset-3">{% trans "Comments" %}</h2>
+          {# Render comments #}
+          {% if form.comments %}
+            <div class="field-group mb-5">
+              <div class="row">
+                <h2 class="col-9 offset-3">{% trans "Comments" %}</h2>
+              </div>
+              {% render_field form.comments bulk_nullable=True %}
             </div>
             </div>
-            {% render_field form.comments bulk_nullable=True %}
-          </div>
-        {% endif %}
+          {% endif %}
 
 
-      {% else %}
+        {% else %}
 
 
-        {# Render all fields #}
-        {% for field in form.visible_fields %}
-          {% if field.name in form.nullable_fields %}
-            {% render_field field bulk_nullable=True %}
-          {% else %}
-            {% render_field field %}
-          {% endif %}
-        {% endfor %}
+          {# Render all fields #}
+          {% for field in form.visible_fields %}
+            {% if field.name in form.nullable_fields %}
+              {% render_field field bulk_nullable=True %}
+            {% else %}
+              {% render_field field %}
+            {% endif %}
+          {% endfor %}
 
 
-      {% endif %}
+        {% endif %}
 
 
-      <div class="btn-float-group-right">
-        <a href="{{ return_url }}" class="btn btn-outline-secondary btn-float">{% trans "Cancel" %}</a>
-        <button type="submit" name="_apply" class="btn btn-primary">{% trans "Apply" %}</button>
+        <div class="btn-float-group-right">
+          <a href="{{ return_url }}" class="btn btn-outline-secondary btn-float">{% trans "Cancel" %}</a>
+          <button type="submit" name="_apply" class="btn btn-primary">{% trans "Apply" %}</button>
+        </div>
       </div>
       </div>
-
     </form>
     </form>
   </div>
   </div>