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

Fixes #20551: Support quick-add form prefix in automatic slug generation (#20624)

* Fixes #20551: Support quick-add form prefix in automatic slug generation

The slug generation logic in `reslug.ts` looks for form fields using hard-coded ID selectors like `#id_slug` and `#id_name`. In quick-add modals, Django applies a `quickadd` prefix to form fields (introduced in #20542), resulting in IDs like `#id_quickadd-slug` and `#id_quickadd-name`. The logic couldn't find these prefixed fields, so automatic slug generation failed silently in quick-add modals. This fix updates the field selectors to try both unprefixed and prefixed patterns using the nullish coalescing operator (`??`), checking for the standard field ID first and falling back to the quickadd-prefixed ID if the standard one isn't found.

* Address PR feedback

The slug generation logic required updates to support form prefixes like `quickadd`. Python-side changes
ensure `SlugField.get_bound_field()` updates the `slug-source` attribute to include the form prefix when
present, so JavaScript receives the correct prefixed field ID. `SlugWidget.__init__()` now adds a
`slug-field` class to enable selector-based field discovery. On the frontend, `reslug.ts` now uses class
selectors (`button.reslug` and `input.slug-field`) instead of ID-based lookups, eliminating the need for
fallback logic. The template was updated to use `class="reslug"` instead of `id="reslug"` on the button to
avoid ID duplication issues.
Jason Novinger 3 месяцев назад
Родитель
Сommit
d01d7b4156

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js.map


+ 4 - 2
netbox/project-static/src/buttons/reslug.ts

@@ -20,11 +20,13 @@ function slugify(slug: string, chars: number): string {
  * For any slug fields, add event listeners to handle automatically generating slug values.
  */
 export function initReslug(): void {
-  for (const slugButton of getElements<HTMLButtonElement>('button#reslug')) {
+  for (const slugButton of getElements<HTMLButtonElement>('button.reslug')) {
     const form = slugButton.form;
     if (form == null) continue;
-    const slugField = form.querySelector('#id_slug') as HTMLInputElement;
+
+    const slugField = form.querySelector('input.slug-field') as HTMLInputElement;
     if (slugField == null) continue;
+
     const sourceId = slugField.getAttribute('slug-source');
     const sourceField = form.querySelector(`#id_${sourceId}`) as HTMLInputElement;
 

+ 8 - 0
netbox/utilities/forms/fields/fields.py

@@ -53,6 +53,14 @@ class SlugField(forms.SlugField):
 
         self.widget.attrs['slug-source'] = slug_source
 
+    def get_bound_field(self, form, field_name):
+        if prefix := form.prefix:
+            slug_source = self.widget.attrs.get('slug-source')
+            if slug_source and not slug_source.startswith(f'{prefix}-'):
+                self.widget.attrs['slug-source'] = f"{prefix}-{slug_source}"
+
+        return super().get_bound_field(form, field_name)
+
 
 class ColorField(forms.CharField):
     """

+ 8 - 0
netbox/utilities/forms/widgets/misc.py

@@ -56,6 +56,14 @@ class SlugWidget(forms.TextInput):
     """
     template_name = 'widgets/sluginput.html'
 
+    def __init__(self, attrs=None):
+        local_attrs = {} if attrs is None else attrs.copy()
+        if 'class' in local_attrs:
+            local_attrs['class'] = f"{local_attrs['class']} slug-field"
+        else:
+            local_attrs['class'] = 'slug-field'
+        super().__init__(local_attrs)
+
 
 class ArrayWidget(forms.Textarea):
     """

+ 1 - 1
netbox/utilities/templates/form_helpers/render_field.html

@@ -19,7 +19,7 @@
     {% if field|widget_type == 'slugwidget' %}
       <div class="input-group">
         {{ field }}
-        <button id="reslug" type="button" title="{% trans "Regenerate Slug" %}" class="btn">
+        <button type="button" title="{% trans "Regenerate Slug" %}" class="btn reslug">
           <i class="mdi mdi-reload"></i>
         </button>
       </div>

Некоторые файлы не были показаны из-за большого количества измененных файлов