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

feat(ipam): Add HTMX support to prefix bulk add form

Enable dynamic form updates in the prefix bulk add view by introducing
HTMX partial rendering. Inherit from PrefixForm to support scope and
VLAN fields, and add htmx_template_name for efficient field updates.
Martin Hauser 1 день назад
Родитель
Сommit
775d6aa936

+ 11 - 20
netbox/ipam/forms/model_forms.py

@@ -250,31 +250,22 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
                 self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
                 self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
 
 
 
 
-class PrefixBulkAddForm(TenancyForm, NetBoxModelForm):
-    vrf = DynamicModelChoiceField(
-        queryset=VRF.objects.all(),
-        required=False,
-        label=_('VRF')
-    )
-    role = DynamicModelChoiceField(
-        label=_('Role'),
-        queryset=Role.objects.all(),
-        required=False,
-        quick_add=True
-    )
+class PrefixBulkAddForm(PrefixForm):
+    """
+    Subclass of PrefixForm for bulk creation. The prefix field is inherited
+    but excluded from fieldsets — it is populated programmatically by BulkCreateView
+    from the expanded pattern.
+    """
 
 
     fieldsets = (
     fieldsets = (
-        FieldSet('status', 'role', 'vrf', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')),
+        FieldSet(
+            'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
+        ),
+        FieldSet('scope_type', 'scope', name=_('Scope')),
+        FieldSet('vlan', name=_('VLAN Assignment')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
     )
     )
 
 
-    class Meta:
-        model = Prefix
-        fields = [
-            'prefix', 'vrf', 'status', 'role', 'is_pool', 'mark_utilized', 'description', 'tenant_group', 'tenant',
-            'tags',
-        ]
-
 
 
 class IPRangeForm(TenancyForm, PrimaryModelForm):
 class IPRangeForm(TenancyForm, PrimaryModelForm):
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(

+ 13 - 0
netbox/netbox/views/generic/bulk_views.py

@@ -225,6 +225,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
     form = None
     form = None
     model_form = None
     model_form = None
     pattern_target = ''
     pattern_target = ''
+    htmx_template_name = 'htmx/bulk_add_form.html'
 
 
     def get_required_permission(self):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'add')
         return get_permission_for_model(self.queryset.model, 'add')
@@ -280,6 +281,12 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
         form = self.form()
         form = self.form()
         model_form = self.model_form(initial=initial)
         model_form = self.model_form(initial=initial)
 
 
+        # HTMX partial: only re-render the model form fields
+        if htmx_partial(request):
+            return render(request, self.htmx_template_name, {
+                'model_form': model_form,
+            })
+
         return render(request, self.template_name, self._get_context(request, form, model_form))
         return render(request, self.template_name, self._get_context(request, form, model_form))
 
 
     def post(self, request):
     def post(self, request):
@@ -288,6 +295,12 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
         form = self.form(request.POST)
         form = self.form(request.POST)
         model_form = self.model_form(request.POST)
         model_form = self.model_form(request.POST)
 
 
+        # HTMX partial: only re-render the model form fields
+        if htmx_partial(request):
+            return render(request, self.htmx_template_name, {
+                'model_form': model_form,
+            })
+
         if form.is_valid():
         if form.is_valid():
             logger.debug("Form validation was successful")
             logger.debug("Form validation was successful")
 
 

+ 4 - 2
netbox/templates/generic/bulk_add.html

@@ -20,14 +20,16 @@
   </ul>
   </ul>
 {% endblock %}
 {% endblock %}
 
 
-{% block form %}
+{% block pre_form_fields %}
     <div class="field-group my-5">
     <div class="field-group my-5">
         <div class="row">
         <div class="row">
           <h2 class="col-9 offset-3">{% trans "Pattern" %}</h2>
           <h2 class="col-9 offset-3">{% trans "Pattern" %}</h2>
         </div>
         </div>
         {% render_field form.pattern %}
         {% render_field form.pattern %}
     </div>
     </div>
+{% endblock pre_form_fields %}
 
 
+{% block form %}
     {% if model_form.fieldsets %}
     {% if model_form.fieldsets %}
       {% for fieldset in model_form.fieldsets %}
       {% for fieldset in model_form.fieldsets %}
         {% render_fieldset model_form fieldset %}
         {% render_fieldset model_form fieldset %}
@@ -46,4 +48,4 @@
             {% render_custom_fields model_form %}
             {% render_custom_fields model_form %}
         </div>
         </div>
     {% endif %}
     {% endif %}
-{% endblock %}
+{% endblock form %}

+ 1 - 0
netbox/templates/generic/object_edit.html

@@ -61,6 +61,7 @@ Context:
     <form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
     <form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
       {% csrf_token %}
       {% csrf_token %}
 
 
+      {% block pre_form_fields %}{% endblock pre_form_fields %}
       <div id="form_fields" hx-disinherit="hx-select hx-swap">
       <div id="form_fields" hx-disinherit="hx-select hx-swap">
         {% block form %}
         {% block form %}
           {% include 'htmx/form.html' %}
           {% include 'htmx/form.html' %}

+ 22 - 0
netbox/templates/htmx/bulk_add_form.html

@@ -0,0 +1,22 @@
+{% load helpers %}
+{% load form_helpers %}
+{% load i18n %}
+
+{% if model_form.fieldsets %}
+  {% for fieldset in model_form.fieldsets %}
+    {% render_fieldset model_form fieldset %}
+  {% endfor %}
+{% else %}
+  <div class="field-group my-5">
+    {% render_form model_form %}
+  </div>
+{% endif %}
+
+{% if model_form.custom_fields %}
+    <div class="field-group my-5">
+        <div class="row">
+          <h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
+        </div>
+        {% render_custom_fields model_form %}
+    </div>
+{% endif %}

+ 2 - 2
netbox/templates/ipam/inc/prefix_edit_header.html

@@ -3,8 +3,8 @@
 
 
 <ul class="nav nav-tabs">
 <ul class="nav nav-tabs">
   <li class="nav-item">
   <li class="nav-item">
-    <a href="{% url 'ipam:prefix_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
-      {% if object.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
+    <a href="{% if object.pk %}{% url 'ipam:prefix_edit' pk=object.pk %}{% else %}{% url 'ipam:prefix_add' %}{% querystring request %}{% endif %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
+      {% if object.pk %}{% trans "Prefix" %}{% else %}{% trans "Create" %}{% endif %}
     </a>
     </a>
   </li>
   </li>
   {% if not object.pk %}
   {% if not object.pk %}