mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Fixes #8358: Fix inconsistent styling of custom fields on filter & bulk edit forms
This commit is contained in:
@ -24,6 +24,7 @@
|
|||||||
* [#8319](https://github.com/netbox-community/netbox/issues/8319) - Custom URL fields should honor `ALLOWED_URL_SCHEMES` config parameter
|
* [#8319](https://github.com/netbox-community/netbox/issues/8319) - Custom URL fields should honor `ALLOWED_URL_SCHEMES` config parameter
|
||||||
* [#8342](https://github.com/netbox-community/netbox/issues/8342) - Restore `created` & `last_updated` fields missing from several REST API serializers
|
* [#8342](https://github.com/netbox-community/netbox/issues/8342) - Restore `created` & `last_updated` fields missing from several REST API serializers
|
||||||
* [#8357](https://github.com/netbox-community/netbox/issues/8357) - Add missing tags field to location filter form
|
* [#8357](https://github.com/netbox-community/netbox/issues/8357) - Add missing tags field to location filter form
|
||||||
|
* [#8358](https://github.com/netbox-community/netbox/issues/8358) - Fix inconsistent styling of custom fields on filter & bulk edit forms
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from utilities.forms import BootstrapMixin, BulkEditForm, CSVModelForm, FilterForm
|
from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CustomFieldModelCSVForm',
|
'CustomFieldModelCSVForm',
|
||||||
@ -34,6 +34,9 @@ class CustomFieldsMixin:
|
|||||||
raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
|
raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
|
||||||
return ContentType.objects.get_for_model(self.model)
|
return ContentType.objects.get_for_model(self.model)
|
||||||
|
|
||||||
|
def _get_custom_fields(self, content_type):
|
||||||
|
return CustomField.objects.filter(content_types=content_type)
|
||||||
|
|
||||||
def _get_form_field(self, customfield):
|
def _get_form_field(self, customfield):
|
||||||
return customfield.to_form_field()
|
return customfield.to_form_field()
|
||||||
|
|
||||||
@ -41,10 +44,7 @@ class CustomFieldsMixin:
|
|||||||
"""
|
"""
|
||||||
Append form fields for all CustomFields assigned to this object type.
|
Append form fields for all CustomFields assigned to this object type.
|
||||||
"""
|
"""
|
||||||
content_type = self._get_content_type()
|
for customfield in self._get_custom_fields(self._get_content_type()):
|
||||||
|
|
||||||
# Append form fields; assign initial values if modifying and existing object
|
|
||||||
for customfield in CustomField.objects.filter(content_types=content_type):
|
|
||||||
field_name = f'cf_{customfield.name}'
|
field_name = f'cf_{customfield.name}'
|
||||||
self.fields[field_name] = self._get_form_field(customfield)
|
self.fields[field_name] = self._get_form_field(customfield)
|
||||||
|
|
||||||
@ -86,40 +86,37 @@ class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
|
|||||||
return customfield.to_form_field(for_csv_import=True)
|
return customfield.to_form_field(for_csv_import=True)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelBulkEditForm(BulkEditForm):
|
class CustomFieldModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def _get_form_field(self, customfield):
|
||||||
super().__init__(*args, **kwargs)
|
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
||||||
|
|
||||||
self.custom_fields = []
|
def _append_customfield_fields(self):
|
||||||
self.obj_type = ContentType.objects.get_for_model(self.model)
|
"""
|
||||||
|
Append form fields for all CustomFields assigned to this object type.
|
||||||
# Add all applicable CustomFields to the form
|
"""
|
||||||
custom_fields = CustomField.objects.filter(content_types=self.obj_type)
|
for customfield in self._get_custom_fields(self._get_content_type()):
|
||||||
for cf in custom_fields:
|
|
||||||
# Annotate non-required custom fields as nullable
|
# Annotate non-required custom fields as nullable
|
||||||
if not cf.required:
|
if not customfield.required:
|
||||||
self.nullable_fields.append(cf.name)
|
self.nullable_fields.append(customfield.name)
|
||||||
self.fields[cf.name] = cf.to_form_field(set_initial=False, enforce_required=False)
|
|
||||||
# Annotate this as a custom field
|
self.fields[customfield.name] = self._get_form_field(customfield)
|
||||||
self.custom_fields.append(cf.name)
|
|
||||||
|
# Annotate the field in the list of CustomField form fields
|
||||||
|
self.custom_fields.append(customfield.name)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelFilterForm(FilterForm):
|
class CustomFieldModelFilterForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='Search'
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def _get_custom_fields(self, content_type):
|
||||||
|
return CustomField.objects.filter(content_types=content_type).exclude(
|
||||||
self.obj_type = ContentType.objects.get_for_model(self.model)
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Add all applicable CustomFields to the form
|
|
||||||
self.custom_field_filters = []
|
|
||||||
custom_fields = CustomField.objects.filter(content_types=self.obj_type).exclude(
|
|
||||||
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
|
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
|
||||||
Q(type=CustomFieldTypeChoices.TYPE_JSON)
|
Q(type=CustomFieldTypeChoices.TYPE_JSON)
|
||||||
)
|
)
|
||||||
for cf in custom_fields:
|
|
||||||
field_name = f'cf_{cf.name}'
|
def _get_form_field(self, customfield):
|
||||||
self.fields[field_name] = cf.to_form_field(set_initial=False, enforce_required=False)
|
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
||||||
self.custom_field_filters.append(field_name)
|
|
||||||
|
@ -24,17 +24,17 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{# List all non-customfield filters as declared in the form class #}
|
{# List all non-customfield filters as declared in the form class #}
|
||||||
{% for field in filter_form.visible_fields %}
|
{% for field in filter_form.visible_fields %}
|
||||||
{% if not filter_form.custom_field_filters or field.name not in filter_form.custom_field_filters %}
|
{% if not filter_form.custom_fields or field.name not in filter_form.custom_fields %}
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
{% render_field field %}
|
{% render_field field %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if filter_form.custom_field_filters %}
|
{% if filter_form.custom_fields %}
|
||||||
{# List all custom field filters #}
|
{# List all custom field filters #}
|
||||||
<hr class="card-divider mt-0" />
|
<hr class="card-divider mt-0" />
|
||||||
{% for name in filter_form.custom_field_filters %}
|
{% for name in filter_form.custom_fields %}
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
{% with field=filter_form|get_item:name %}
|
{% with field=filter_form|get_item:name %}
|
||||||
{% render_field field %}
|
{% render_field field %}
|
||||||
|
@ -3,7 +3,6 @@ import re
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSelect
|
from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSelect
|
||||||
|
|
||||||
@ -11,6 +10,7 @@ from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSel
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'BootstrapMixin',
|
'BootstrapMixin',
|
||||||
'BulkEditForm',
|
'BulkEditForm',
|
||||||
|
'BulkEditBaseForm',
|
||||||
'BulkRenameForm',
|
'BulkRenameForm',
|
||||||
'ConfirmationForm',
|
'ConfirmationForm',
|
||||||
'CSVModelForm',
|
'CSVModelForm',
|
||||||
@ -75,11 +75,10 @@ class ConfirmationForm(BootstrapMixin, ReturnURLForm):
|
|||||||
confirm = forms.BooleanField(required=True, widget=forms.HiddenInput(), initial=True)
|
confirm = forms.BooleanField(required=True, widget=forms.HiddenInput(), initial=True)
|
||||||
|
|
||||||
|
|
||||||
class BulkEditForm(BootstrapMixin, forms.Form):
|
class BulkEditBaseForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Base form for editing multiple objects in bulk
|
Base form for editing multiple objects in bulk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model, *args, **kwargs):
|
def __init__(self, model, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.model = model
|
self.model = model
|
||||||
@ -90,6 +89,10 @@ class BulkEditForm(BootstrapMixin, forms.Form):
|
|||||||
self.nullable_fields = self.Meta.nullable_fields
|
self.nullable_fields = self.Meta.nullable_fields
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditForm(BootstrapMixin, BulkEditBaseForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BulkRenameForm(BootstrapMixin, forms.Form):
|
class BulkRenameForm(BootstrapMixin, forms.Form):
|
||||||
"""
|
"""
|
||||||
An extendable form to be used for renaming objects in bulk.
|
An extendable form to be used for renaming objects in bulk.
|
||||||
@ -185,10 +188,7 @@ class FilterForm(BootstrapMixin, forms.Form):
|
|||||||
"""
|
"""
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(
|
label='Search'
|
||||||
attrs={'placeholder': _('All fields')}
|
|
||||||
),
|
|
||||||
label=_('Search')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user