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

This commit is contained in:
Jeremy Stretch 2024-10-16 16:05:01 -04:00
parent 9f7743e5da
commit 6a316df787
3 changed files with 90 additions and 132 deletions

View File

@ -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(
widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
required=False,
widget=forms.HiddenInput()
label=_('Scope type')
)
region = DynamicModelChoiceField(
label=_('Region'),
queryset=Region.objects.all(),
required=False
)
sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
scope = DynamicModelChoiceField(
label=_('Scope'),
queryset=Site.objects.none(), # Initial queryset
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(),
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',
}
)
clustergroup = DynamicModelChoiceField(
queryset=ClusterGroup.objects.all(),
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')
),
FieldSet('scope_type', 'scope', name=_('Scope')),
)
nullable_fields = ('description',)
nullable_fields = ('description', 'scope')
def clean(self):
super().clean()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 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')
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):

View File

@ -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

View File

@ -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 %}
{# 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>
{% endif %}
{# 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>
{% endif %}
{% 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 %}
{% 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>
</div>
{% render_custom_fields form %}
</div>
{% 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>
{# 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>
{% endif %}
{% 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 %}
{% 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>
</div>
</form>
</div>