Skip to content

Commit

Permalink
Closes #17789: Use a single scope field for VLANGroup bulk edit
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Oct 17, 2024
1 parent 9f7743e commit 6a316df
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 130 deletions.
103 changes: 29 additions & 74 deletions netbox/ipam/forms/bulk_edit.py
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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'),
Expand All @@ -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):
Expand Down
9 changes: 6 additions & 3 deletions netbox/netbox/views/generic/bulk_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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
Expand Down Expand Up @@ -576,7 +576,10 @@ def _update_objects(self, form, request):
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])
Expand Down Expand Up @@ -688,7 +691,7 @@ def post(self, request, **kwargs):
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
Expand Down
106 changes: 53 additions & 53 deletions netbox/templates/generic/bulk_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,71 +42,71 @@
{# 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>

Expand Down

0 comments on commit 6a316df

Please sign in to comment.