Bladeren bron

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

Jeremy Stretch 1 jaar geleden
bovenliggende
commit
6a316df787
3 gewijzigde bestanden met toevoegingen van 88 en 130 verwijderingen
  1. 29 74
      netbox/ipam/forms/bulk_edit.py
  2. 6 3
      netbox/netbox/views/generic/bulk_views.py
  3. 53 53
      netbox/templates/generic/bulk_edit.html

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

@@ -1,22 +1,23 @@
 from django import forms
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
 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.constants import *
 from ipam.models import *
 from ipam.models import ASN
 from netbox.forms import NetBoxModelBulkEditForm
 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 (
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     NumericRangeArrayField,
 )
 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__ = (
     'AggregateBulkEditForm',
@@ -429,62 +430,17 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     scope_type = ContentTypeChoiceField(
-        label=_('Scope type'),
         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,
-        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,
-        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(
         label=_('VLAN ID ranges'),
@@ -494,24 +450,23 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
     model = VLANGroup
     fieldsets = (
         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):

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

@@ -3,7 +3,7 @@ import re
 from copy import deepcopy
 
 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.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
 from django.db import transaction, IntegrityError
@@ -576,7 +576,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
             for name, model_field in model_fields.items():
                 # Handle nullification
                 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
                 elif name in form.changed_data:
                     setattr(obj, name, form.cleaned_data[name])
@@ -688,7 +691,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
                 logger.debug("Form validation failed")
 
         else:
-            form = self.form(initial=initial_data)
+            form = self.form(request.POST, initial=initial_data)
             restrict_form_fields(form, request.user)
 
         # Retrieve objects being edited

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

@@ -42,71 +42,71 @@ Context:
   {# Edit form #}
   <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">
-
-      {% 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 %}
 
-        {# 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>
-            {% 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>
-            {% 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>
-            {% 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>
-
     </form>
   </div>