From 6c845bd5decd7cbb95f0fdfb8d7ba12a9bcb1286 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Aug 2024 07:53:43 -0400 Subject: [PATCH 01/15] Update CONTRIBUTING.md Add warning about intentionally submitting duplicate issues --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37797785e..a760b8371 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ NetBox users are welcome to participate in either role, on stage or in the crowd * First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases) of NetBox. If you're running an older version, it's likely that the bug has already been fixed. -* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the bottom left corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated. +* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the bottom left corner of the issue and add a thumbs up ( :thumbsup: ). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated. * If you can't find any existing issues (open or closed) that seem to match yours, you're welcome to [submit a new bug report](https://github.com/netbox-community/netbox/issues/new?label=type%3A+bug&template=bug_report.yaml). Be sure to complete the entire report template, including detailed steps that someone triaging your issue can follow to confirm the reported behavior. (If we're not able to replicate the bug based on the information provided, we'll ask for additional detail.) @@ -56,7 +56,9 @@ intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Poli ## :bulb: Feature Requests -* First, check the GitHub [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the feature you have in mind has already been proposed. If you happen to find an open feature request that matches your idea, click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This ensures that the issue has a better chance of receiving attention. Also feel free to add a comment with any additional justification for the feature. +* First, check the GitHub [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the feature you have in mind has already been proposed. If you happen to find an open feature request that matches your idea, click "add a reaction" in the top right corner of the issue and add a thumbs up ( :thumbsup: ). This ensures that the issue has a better chance of receiving attention. Also feel free to add a comment with any additional justification for the feature. + +* Please don't submit duplicate issues! Sometimes we reject feature requests, for various reasons. Even if you disagree with those reasons, please **do not** submit a duplicate feature request. It is very disrepectful of the maintainers' time, and you may be barred from opening future issues. * If you have a rough idea that's not quite ready for formal submission yet, start a [GitHub discussion](https://github.com/netbox-community/netbox/discussions) instead. This is a great way to test the viability and narrow down the scope of a new feature prior to submitting a formal proposal, and can serve to generate interest in your idea from other community members. From 6ae3af2f2655d7c497a2d921c16f7ee7b79fe162 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Sat, 10 Aug 2024 21:24:02 +0700 Subject: [PATCH 02/15] 13459 Fix OpenAPI type for TreeNodeMultipleChoiceFilter (#17095) * 13459 Correct OpenAPI type for TreeNodeMultipleChoiceFilter * 13459 Correct OpenAPI type for TreeNodeMultipleChoiceFilter --- netbox/utilities/filters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 7bbc4a6a2..05454543e 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -3,8 +3,8 @@ from django import forms from django.conf import settings from django.core.exceptions import ValidationError from django_filters.constants import EMPTY_VALUES -from drf_spectacular.utils import extend_schema_field from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field __all__ = ( 'ContentTypeFilter', @@ -116,6 +116,7 @@ class MultiValueWWNFilter(django_filters.MultipleChoiceFilter): field_class = multivalue_field_factory(forms.CharField) +@extend_schema_field(OpenApiTypes.STR) class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter): """ Filters for a set of Models, including all descendant models within a Tree. Example: [,] From b7b0ab16f589b5d2fce1508ac4ad00bfbad9d680 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 7 Aug 2024 16:53:16 +0700 Subject: [PATCH 03/15] 17064 fix markdown padding for first line --- netbox/project-static/dist/netbox.css | Bin 551938 -> 551961 bytes .../styles/overrides/_tabler.scss | 4 ++++ 2 files changed, 4 insertions(+) diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 36ed4defca8e8ed69115468fac126f7bbb72ce1d..fda6bcf0c9bda970693183b52857e74750f2fcf1 100644 GIT binary patch delta 59 zcmZp=p*Zt~VnYjK3sVbo3rh=Y3tJ2O77p1cv4Wyhh2;E{)aru7l$6Z8bgR<5;?$Dq PAHz80wp&GUoDc*6fl(EF delta 36 scmbPvL$T?GVnYjK3sVbo3rh=Y3tJ2O77p2{>7T+lWVZ)KahwnY0QoiyzyJUM diff --git a/netbox/project-static/styles/overrides/_tabler.scss b/netbox/project-static/styles/overrides/_tabler.scss index 97f1298df..9ff87e4ef 100644 --- a/netbox/project-static/styles/overrides/_tabler.scss +++ b/netbox/project-static/styles/overrides/_tabler.scss @@ -44,3 +44,7 @@ table a { [data-bs-theme=dark] ::selection { background-color: rgba(var(--tblr-primary-rgb),.48) } + +pre code { + padding: unset; +} From f6c164211650e6c2d6ceb0662aa205c2cce3c7b4 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 7 Aug 2024 14:15:25 +0700 Subject: [PATCH 04/15] 16176 Add UI for multi-termination cables --- netbox/dcim/forms/connections.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/forms/connections.py b/netbox/dcim/forms/connections.py index 44bea047a..f107c3476 100644 --- a/netbox/dcim/forms/connections.py +++ b/netbox/dcim/forms/connections.py @@ -19,7 +19,7 @@ def get_cable_form(a_type, b_type): # Device component if hasattr(term_cls, 'device'): - attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField( + attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), label=_('Device'), required=False, @@ -33,6 +33,7 @@ def get_cable_form(a_type, b_type): label=term_cls._meta.verbose_name.title(), context={ 'disabled': '_occupied', + 'parent': 'device', }, query_params={ 'device_id': f'$termination_{cable_end}_device', @@ -43,7 +44,7 @@ def get_cable_form(a_type, b_type): # PowerFeed elif term_cls == PowerFeed: - attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField( + attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelMultipleChoiceField( queryset=PowerPanel.objects.all(), label=_('Power Panel'), required=False, @@ -57,6 +58,7 @@ def get_cable_form(a_type, b_type): label=_('Power Feed'), context={ 'disabled': '_occupied', + 'parent': 'powerpanel', }, query_params={ 'power_panel_id': f'$termination_{cable_end}_powerpanel', @@ -66,7 +68,7 @@ def get_cable_form(a_type, b_type): # CircuitTermination elif term_cls == CircuitTermination: - attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField( + attrs[f'termination_{cable_end}_circuit'] = DynamicModelMultipleChoiceField( queryset=Circuit.objects.all(), label=_('Circuit'), selector=True, @@ -79,6 +81,7 @@ def get_cable_form(a_type, b_type): label=_('Side'), context={ 'disabled': '_occupied', + 'parent': 'circuit', }, query_params={ 'circuit_id': f'$termination_{cable_end}_circuit', From 34d20fccd51fa6a53c9083d100e58043afc3ef00 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Sat, 10 Aug 2024 22:47:06 +0700 Subject: [PATCH 05/15] 17036 international messages (#17041) * 17036 international messages * 17036 fix typo * 17036 fix _ * Misc cleanup & fixes * More cleanup --------- Co-authored-by: Jeremy Stretch --- netbox/account/views.py | 8 ++--- netbox/circuits/views.py | 7 ++-- netbox/core/views.py | 27 +++++++------- netbox/dcim/views.py | 30 ++++++++++++---- netbox/netbox/views/generic/bulk_views.py | 37 ++++++++++++++++---- netbox/netbox/views/generic/feature_views.py | 13 ++++--- netbox/virtualization/views.py | 12 ++++--- 7 files changed, 94 insertions(+), 40 deletions(-) diff --git a/netbox/account/views.py b/netbox/account/views.py index d39b00d7a..18c2eb412 100644 --- a/netbox/account/views.py +++ b/netbox/account/views.py @@ -109,7 +109,7 @@ class LoginView(View): # Authenticate user auth_login(request, form.get_user()) logger.info(f"User {request.user} successfully authenticated") - messages.success(request, f"Logged in as {request.user}.") + messages.success(request, _("Logged in as {user}.").format(user=request.user)) # Ensure the user has a UserConfig defined. (This should normally be handled by # create_userconfig() on user creation.) @@ -159,7 +159,7 @@ class LogoutView(View): username = request.user auth_logout(request) logger.info(f"User {username} has logged out") - messages.info(request, "You have logged out.") + messages.info(request, _("You have logged out.")) # Delete session key & language cookies (if set) upon logout response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL)) @@ -234,7 +234,7 @@ class ChangePasswordView(LoginRequiredMixin, View): def get(self, request): # LDAP users cannot change their password here if getattr(request.user, 'ldap_username', None): - messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.") + messages.warning(request, _("LDAP-authenticated user credentials cannot be changed within NetBox.")) return redirect('account:profile') form = PasswordChangeForm(user=request.user) @@ -249,7 +249,7 @@ class ChangePasswordView(LoginRequiredMixin, View): if form.is_valid(): form.save() update_session_auth_hash(request, form.user) - messages.success(request, "Your password has been changed successfully.") + messages.success(request, _("Your password has been changed successfully.")) return redirect('account:profile') return render(request, self.template_name, { diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index b10b83b23..0182e1a93 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -1,6 +1,7 @@ from django.contrib import messages from django.db import transaction from django.shortcuts import get_object_or_404, redirect, render +from django.utils.translation import gettext_lazy as _ from dcim.views import PathTraceView from netbox.views import generic @@ -326,7 +327,9 @@ class CircuitSwapTerminations(generic.ObjectEditView): # Circuit must have at least one termination to swap if not circuit.termination_a and not circuit.termination_z: - messages.error(request, "No terminations have been defined for circuit {}.".format(circuit)) + messages.error(request, _( + "No terminations have been defined for circuit {circuit}." + ).format(circuit=circuit)) return redirect('circuits:circuit', pk=circuit.pk) return render(request, 'circuits/circuit_terminations_swap.html', { @@ -374,7 +377,7 @@ class CircuitSwapTerminations(generic.ObjectEditView): circuit.termination_z = None circuit.save() - messages.success(request, f"Swapped terminations for circuit {circuit}.") + messages.success(request, _("Swapped terminations for circuit {circuit}.").format(circuit=circuit)) return redirect('circuits:circuit', pk=circuit.pk) return render(request, 'circuits/circuit_terminations_swap.html', { diff --git a/netbox/core/views.py b/netbox/core/views.py index 466c95afa..d453cf004 100644 --- a/netbox/core/views.py +++ b/netbox/core/views.py @@ -76,7 +76,10 @@ class DataSourceSyncView(BaseObjectView): datasource = get_object_or_404(self.queryset, pk=pk) job = datasource.enqueue_sync_job(request) - messages.success(request, f"Queued job #{job.pk} to sync {datasource}") + messages.success( + request, + _("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource) + ) return redirect(datasource.get_absolute_url()) @@ -235,7 +238,7 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View): candidate_config = get_object_or_404(ConfigRevision, pk=pk) candidate_config.activate() - messages.success(request, f"Restored configuration revision #{pk}") + messages.success(request, _("Restored configuration revision #{id}").format(id=pk)) return redirect(candidate_config.get_absolute_url()) @@ -379,9 +382,9 @@ class BackgroundTaskDeleteView(BaseRQView): # Remove job id from queue and delete the actual job queue.connection.lrem(queue.key, 0, job.id) job.delete() - messages.success(request, f'Deleted job {job_id}') + messages.success(request, _('Job {id} has been deleted.').format(id=job_id)) else: - messages.error(request, f'Error deleting job: {form.errors[0]}') + messages.error(request, _('Error deleting job {id}: {error}').format(id=job_id, error=form.errors[0])) return redirect(reverse('core:background_queue_list')) @@ -394,13 +397,13 @@ class BackgroundTaskRequeueView(BaseRQView): try: job = RQ_Job.fetch(job_id, connection=get_redis_connection(config['connection_config']),) except NoSuchJobError: - raise Http404(_("Job {job_id} not found").format(job_id=job_id)) + raise Http404(_("Job {id} not found.").format(id=job_id)) queue_index = QUEUES_MAP[job.origin] queue = get_queue_by_index(queue_index) requeue_job(job_id, connection=queue.connection, serializer=queue.serializer) - messages.success(request, f'You have successfully requeued: {job_id}') + messages.success(request, _('Job {id} has been re-enqueued.').format(id=job_id)) return redirect(reverse('core:background_task', args=[job_id])) @@ -412,7 +415,7 @@ class BackgroundTaskEnqueueView(BaseRQView): try: job = RQ_Job.fetch(job_id, connection=get_redis_connection(config['connection_config']),) except NoSuchJobError: - raise Http404(_("Job {job_id} not found").format(job_id=job_id)) + raise Http404(_("Job {id} not found.").format(id=job_id)) queue_index = QUEUES_MAP[job.origin] queue = get_queue_by_index(queue_index) @@ -435,7 +438,7 @@ class BackgroundTaskEnqueueView(BaseRQView): registry = ScheduledJobRegistry(queue.name, queue.connection) registry.remove(job) - messages.success(request, f'You have successfully enqueued: {job_id}') + messages.success(request, _('Job {id} has been enqueued.').format(id=job_id)) return redirect(reverse('core:background_task', args=[job_id])) @@ -452,11 +455,11 @@ class BackgroundTaskStopView(BaseRQView): queue_index = QUEUES_MAP[job.origin] queue = get_queue_by_index(queue_index) - stopped, _ = stop_jobs(queue, job_id) - if len(stopped) == 1: - messages.success(request, f'You have successfully stopped {job_id}') + stopped_jobs = stop_jobs(queue, job_id)[0] + if len(stopped_jobs) == 1: + messages.success(request, _('Job {id} has been stopped.').format(id=job_id)) else: - messages.error(request, f'Failed to stop {job_id}') + messages.error(request, _('Failed to stop job {id}').format(id=job_id)) return redirect(reverse('core:background_task', args=[job_id])) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index b3d8d298e..b18ecdd5b 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2059,7 +2059,7 @@ class DeviceRenderConfigView(generic.ObjectView): try: rendered_config = config_template.render(context=context_data) except TemplateError as e: - messages.error(request, f"An error occurred while rendering the template: {e}") + messages.error(request, _("An error occurred while rendering the template: {error}").format(error=e)) rendered_config = traceback.format_exc() return { @@ -2823,7 +2823,13 @@ class DeviceBayPopulateView(generic.ObjectEditView): device_bay.snapshot() device_bay.installed_device = form.cleaned_data['installed_device'] device_bay.save() - messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay)) + messages.success( + request, + _("Installed device {device} in bay {device_bay}.").format( + device=device_bay.installed_device, + device_bay=device_bay + ) + ) return_url = self.get_return_url(request) return redirect(return_url) @@ -2858,7 +2864,13 @@ class DeviceBayDepopulateView(generic.ObjectEditView): removed_device = device_bay.installed_device device_bay.installed_device = None device_bay.save() - messages.success(request, f"{removed_device} has been removed from {device_bay}.") + messages.success( + request, + _("Removed device {device} from bay {device_bay}.").format( + device=removed_device, + device_bay=device_bay + ) + ) return_url = self.get_return_url(request, device_bay.device) return redirect(return_url) @@ -3426,7 +3438,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix membership_form.save() messages.success(request, mark_safe( - f'Added member {escape(device)}' + _('Added member {escape(device)}').format(url=device.get_absolute_url()) )) if '_addanother' in request.POST: @@ -3471,7 +3483,10 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL # Protect master device from being removed virtual_chassis = VirtualChassis.objects.filter(master=device).first() if virtual_chassis is not None: - messages.error(request, f'Unable to remove master device {device} from the virtual chassis.') + messages.error( + request, + _('Unable to remove master device {device} from the virtual chassis.').format(device=device) + ) return redirect(device.get_absolute_url()) if form.is_valid(): @@ -3483,7 +3498,10 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL device.vc_priority = None device.save() - msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis) + msg = _('Removed {device} from virtual chassis {chassis}').format( + device=device, + chassis=device.virtual_chassis + ) messages.success(request, msg) return redirect(self.get_return_url(request, device)) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 71ce411ba..bc6ace885 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -106,7 +106,13 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin): try: return template.render_to_response(self.queryset) except Exception as e: - messages.error(request, f"There was an error rendering the selected export template ({template.name}): {e}") + messages.error( + request, + _("There was an error rendering the selected export template ({template}): {error}").format( + template=template.name, + error=e + ) + ) # Strip the `export` param and redirect user to the filtered objects list query_params = request.GET.copy() query_params.pop('export') @@ -668,7 +674,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): # Retrieve objects being edited table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False) if not table.rows: - messages.warning(request, "No {} were selected.".format(model._meta.verbose_name_plural)) + messages.warning( + request, + _("No {object_type} were selected.").format(object_type=model._meta.verbose_name_plural) + ) return redirect(self.get_return_url(request)) return render(request, self.template_name, { @@ -745,8 +754,13 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView): if self.queryset.filter(pk__in=renamed_pks).count() != len(selected_objects): raise PermissionsViolation - model_name = self.queryset.model._meta.verbose_name_plural - messages.success(request, f"Renamed {len(selected_objects)} {model_name}") + messages.success( + request, + _("Renamed {count} {object_type}").format( + count=len(selected_objects), + object_type=self.queryset.model._meta.verbose_name_plural + ) + ) return redirect(self.get_return_url(request)) except (AbortRequest, PermissionsViolation) as e: @@ -838,7 +852,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): messages.error(request, mark_safe(e.message)) return redirect(self.get_return_url(request)) - msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}" + msg = _("Deleted {count} {object_type}").format( + count=deleted_count, + object_type=model._meta.verbose_name_plural + ) logger.info(msg) messages.success(request, msg) return redirect(self.get_return_url(request)) @@ -855,7 +872,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): # Retrieve objects being deleted table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False) if not table.rows: - messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural)) + messages.warning( + request, + _("No {object_type} were selected.").format(object_type=model._meta.verbose_name_plural) + ) return redirect(self.get_return_url(request)) return render(request, self.template_name, { @@ -900,7 +920,10 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView): selected_objects = self.parent_model.objects.filter(pk__in=pk_list) if not selected_objects: - messages.warning(request, "No {} were selected.".format(self.parent_model._meta.verbose_name_plural)) + messages.warning( + request, + _("No {object_type} were selected.").format(object_type=self.parent_model._meta.verbose_name_plural) + ) return redirect(self.get_return_url(request)) table = self.table(selected_objects, orderable=False) diff --git a/netbox/netbox/views/generic/feature_views.py b/netbox/netbox/views/generic/feature_views.py index 95b7b5712..240e8ca28 100644 --- a/netbox/netbox/views/generic/feature_views.py +++ b/netbox/netbox/views/generic/feature_views.py @@ -202,11 +202,14 @@ class ObjectSyncDataView(View): obj = get_object_or_404(qs, **kwargs) if not obj.data_file: - messages.error(request, f"Unable to synchronize data: No data file set.") + messages.error(request, _("Unable to synchronize data: No data file set.")) return redirect(obj.get_absolute_url()) obj.sync(save=True) - messages.success(request, f"Synchronized data for {model._meta.verbose_name} {obj}.") + messages.success(request, _("Synchronized data for {object_type} {object}.").format( + object_type=model._meta.verbose_name, + object=obj + )) return redirect(obj.get_absolute_url()) @@ -228,7 +231,9 @@ class BulkSyncDataView(GetReturnURLMixin, BaseMultiObjectView): for obj in selected_objects: obj.sync(save=True) - model_name = self.queryset.model._meta.verbose_name_plural - messages.success(request, f"Synced {len(selected_objects)} {model_name}") + messages.success(request, _("Synced {count} {object_type}").format( + count=len(selected_objects), + object_type=self.queryset.model._meta.verbose_name_plural + )) return redirect(self.get_return_url(request)) diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 1030fed04..7346f6b0b 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -271,8 +271,9 @@ class ClusterAddDevicesView(generic.ObjectEditView): device.cluster = cluster device.save() - messages.success(request, "Added {} devices to cluster {}".format( - len(device_pks), cluster + messages.success(request, _("Added {count} devices to cluster {cluster}").format( + count=len(device_pks), + cluster=cluster )) return redirect(cluster.get_absolute_url()) @@ -305,8 +306,9 @@ class ClusterRemoveDevicesView(generic.ObjectEditView): device.cluster = None device.save() - messages.success(request, "Removed {} devices from cluster {}".format( - len(device_pks), cluster + messages.success(request, _("Removed {count} devices from cluster {cluster}").format( + count=len(device_pks), + cluster=cluster )) return redirect(cluster.get_absolute_url()) @@ -444,7 +446,7 @@ class VirtualMachineRenderConfigView(generic.ObjectView): try: rendered_config = config_template.render(context=context_data) except TemplateError as e: - messages.error(request, f"An error occurred while rendering the template: {e}") + messages.error(request, _("An error occurred while rendering the template: {error}").format(error=e)) rendered_config = traceback.format_exc() return { From 20967bf88dcd4b5ea50a3a044519efa624a37d32 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Sat, 10 Aug 2024 11:49:34 -0400 Subject: [PATCH 06/15] Changelog for #13459, #16176, #17038, #17064 --- docs/release-notes/version-4.0.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release-notes/version-4.0.md b/docs/release-notes/version-4.0.md index f59fc8793..4afa3eadb 100644 --- a/docs/release-notes/version-4.0.md +++ b/docs/release-notes/version-4.0.md @@ -2,6 +2,13 @@ ## v4.0.9 (FUTURE) +### Bug Fixes + +* [#13459](https://github.com/netbox-community/netbox/issues/13459) - Correct OpenAPI schema type for `TreeNodeMultipleChoiceFilter` +* [#16176](https://github.com/netbox-community/netbox/issues/16176) - Restore ability to select multiple terminating devices when connecting a cable +* [#17038](https://github.com/netbox-community/netbox/issues/17038) - Fix AttributeError exception when attempting to export system status data +* [#17064](https://github.com/netbox-community/netbox/issues/17064) - Fix misaligned text within rendered Markdown code blocks + --- ## v4.0.8 (2024-07-26) From 9c7002f691a67dad64d12f289a6652a2d66bb180 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Sat, 10 Aug 2024 11:58:14 -0400 Subject: [PATCH 07/15] Fixes #17124: BaseTable should follow reverse one-to-one relationships when prefetching related objects --- netbox/netbox/tables/tables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index b191896fa..f95263f6c 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import AnonymousUser from django.contrib.contenttypes.fields import GenericForeignKey from django.core.exceptions import FieldDoesNotExist from django.db.models.fields.related import RelatedField +from django.db.models.fields.reverse_related import ManyToOneRel from django.urls import reverse from django.urls.exceptions import NoReverseMatch from django.utils.safestring import mark_safe @@ -102,7 +103,7 @@ class BaseTable(tables.Table): field = model._meta.get_field(field_name) except FieldDoesNotExist: break - if isinstance(field, RelatedField): + if isinstance(field, (RelatedField, ManyToOneRel)): # Follow ForeignKeys to the related model prefetch_path.append(field_name) model = field.remote_field.model From 81fe12a7d92f4a18522bb58557b2975d78ed2fc9 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 11 Aug 2024 05:02:11 +0000 Subject: [PATCH 08/15] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 188 +++++++++++++++++-- 1 file changed, 168 insertions(+), 20 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index e972d5ce2..5a346dc93 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-02 05:02+0000\n" +"POT-Creation-Date: 2024-08-11 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -58,10 +58,27 @@ msgstr "" msgid "Allowed IPs" msgstr "" +#: netbox/account/views.py:112 +#, python-brace-format +msgid "Logged in as {user}." +msgstr "" + +#: netbox/account/views.py:162 +msgid "You have logged out." +msgstr "" + #: netbox/account/views.py:214 msgid "Your preferences have been updated." msgstr "" +#: netbox/account/views.py:237 +msgid "LDAP-authenticated user credentials cannot be changed within NetBox." +msgstr "" + +#: netbox/account/views.py:252 +msgid "Your password has been changed successfully." +msgstr "" + #: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20 #: netbox/dcim/choices.py:102 netbox/dcim/choices.py:174 #: netbox/dcim/choices.py:220 netbox/dcim/choices.py:1459 @@ -309,7 +326,7 @@ msgstr "" #: netbox/circuits/forms/filtersets.py:212 #: netbox/circuits/forms/model_forms.py:109 #: netbox/circuits/forms/model_forms.py:131 -#: netbox/circuits/tables/circuits.py:98 netbox/dcim/forms/connections.py:71 +#: netbox/circuits/tables/circuits.py:98 netbox/dcim/forms/connections.py:73 #: netbox/templates/circuits/circuit.html:15 #: netbox/templates/circuits/circuittermination.html:19 #: netbox/templates/dcim/inc/cable_termination.html:55 @@ -532,7 +549,7 @@ msgstr "" #: netbox/dcim/tables/devices.py:802 netbox/dcim/tables/power.py:77 #: netbox/extras/forms/bulk_import.py:39 netbox/extras/tables/tables.py:290 #: netbox/extras/tables/tables.py:362 netbox/extras/tables/tables.py:480 -#: netbox/netbox/tables/tables.py:239 netbox/templates/circuits/circuit.html:30 +#: netbox/netbox/tables/tables.py:240 netbox/templates/circuits/circuit.html:30 #: netbox/templates/core/datasource.html:38 netbox/templates/dcim/cable.html:15 #: netbox/templates/dcim/consoleport.html:36 #: netbox/templates/dcim/consoleserverport.html:36 @@ -964,7 +981,7 @@ msgstr "" #: netbox/ipam/forms/filtersets.py:266 netbox/ipam/forms/filtersets.py:307 #: netbox/ipam/forms/filtersets.py:382 netbox/ipam/forms/filtersets.py:475 #: netbox/ipam/forms/filtersets.py:534 netbox/ipam/forms/filtersets.py:552 -#: netbox/netbox/tables/tables.py:255 +#: netbox/netbox/tables/tables.py:256 #: netbox/virtualization/forms/filtersets.py:45 #: netbox/virtualization/forms/filtersets.py:103 #: netbox/virtualization/forms/filtersets.py:194 @@ -1377,6 +1394,16 @@ msgstr "" msgid "ASN Count" msgstr "" +#: netbox/circuits/views.py:331 +#, python-brace-format +msgid "No terminations have been defined for circuit {circuit}." +msgstr "" + +#: netbox/circuits/views.py:380 +#, python-brace-format +msgid "Swapped terminations for circuit {circuit}." +msgstr "" + #: netbox/core/api/views.py:36 msgid "This user does not have permission to synchronize this data source." msgstr "" @@ -1959,7 +1986,7 @@ msgstr "" #: netbox/core/tables/jobs.py:10 netbox/core/tables/tasks.py:76 #: netbox/dcim/tables/devicetypes.py:165 netbox/extras/tables/tables.py:185 -#: netbox/extras/tables/tables.py:357 netbox/netbox/tables/tables.py:188 +#: netbox/extras/tables/tables.py:357 netbox/netbox/tables/tables.py:189 #: netbox/templates/dcim/virtualchassis_edit.html:52 #: netbox/utilities/forms/forms.py:73 netbox/wireless/tables/wirelesslink.py:16 msgid "ID" @@ -1969,7 +1996,7 @@ msgstr "" #: netbox/extras/tables/tables.py:248 netbox/extras/tables/tables.py:294 #: netbox/extras/tables/tables.py:367 netbox/extras/tables/tables.py:485 #: netbox/extras/tables/tables.py:516 netbox/extras/tables/tables.py:556 -#: netbox/extras/tables/tables.py:593 netbox/netbox/tables/tables.py:243 +#: netbox/extras/tables/tables.py:593 netbox/netbox/tables/tables.py:244 #: netbox/templates/extras/eventrule.html:84 #: netbox/templates/extras/journalentry.html:18 #: netbox/templates/extras/objectchange.html:58 @@ -2063,12 +2090,56 @@ msgstr "" msgid "No workers found" msgstr "" -#: netbox/core/views.py:331 netbox/core/views.py:374 netbox/core/views.py:397 -#: netbox/core/views.py:415 netbox/core/views.py:450 +#: netbox/core/views.py:81 +#, python-brace-format +msgid "Queued job #{id} to sync {datasource}" +msgstr "" + +#: netbox/core/views.py:241 +#, python-brace-format +msgid "Restored configuration revision #{id}" +msgstr "" + +#: netbox/core/views.py:334 netbox/core/views.py:377 netbox/core/views.py:453 #, python-brace-format msgid "Job {job_id} not found" msgstr "" +#: netbox/core/views.py:385 +#, python-brace-format +msgid "Job {id} has been deleted." +msgstr "" + +#: netbox/core/views.py:387 +#, python-brace-format +msgid "Error deleting job {id}: {error}" +msgstr "" + +#: netbox/core/views.py:400 netbox/core/views.py:418 +#, python-brace-format +msgid "Job {id} not found." +msgstr "" + +#: netbox/core/views.py:406 +#, python-brace-format +msgid "Job {id} has been re-enqueued." +msgstr "" + +#: netbox/core/views.py:441 +#, python-brace-format +msgid "Job {id} has been enqueued." +msgstr "" + +#: netbox/core/views.py:460 +#, python-brace-format +msgid "Job {id} has been stopped." +msgstr "" + +#: netbox/core/views.py:462 +#, python-brace-format +msgid "Failed to stop job {id}" +msgstr "" + #: netbox/dcim/api/serializers_/devices.py:50 #: netbox/dcim/api/serializers_/devicetypes.py:26 msgid "Position (U)" @@ -4091,7 +4162,7 @@ msgstr "" msgid "A {model} named {name} already exists" msgstr "" -#: netbox/dcim/forms/connections.py:48 netbox/dcim/forms/model_forms.py:686 +#: netbox/dcim/forms/connections.py:49 netbox/dcim/forms/model_forms.py:686 #: netbox/dcim/tables/power.py:66 #: netbox/templates/dcim/inc/cable_termination.html:37 #: netbox/templates/dcim/powerfeed.html:24 @@ -4100,13 +4171,13 @@ msgstr "" msgid "Power Panel" msgstr "" -#: netbox/dcim/forms/connections.py:57 netbox/dcim/forms/model_forms.py:713 +#: netbox/dcim/forms/connections.py:58 netbox/dcim/forms/model_forms.py:713 #: netbox/templates/dcim/powerfeed.html:21 #: netbox/templates/dcim/powerport.html:80 msgid "Power Feed" msgstr "" -#: netbox/dcim/forms/connections.py:79 +#: netbox/dcim/forms/connections.py:81 msgid "Side" msgstr "" @@ -6081,7 +6152,7 @@ msgstr "" #: netbox/templates/virtualization/virtualmachine/base.html:27 #: netbox/templates/virtualization/virtualmachine_list.html:14 #: netbox/virtualization/tables/virtualmachines.py:100 -#: netbox/virtualization/views.py:363 netbox/wireless/tables/wirelesslan.py:55 +#: netbox/virtualization/views.py:365 netbox/wireless/tables/wirelesslan.py:55 msgid "Interfaces" msgstr "" @@ -6381,24 +6452,53 @@ msgstr "" #: netbox/dcim/views.py:2019 netbox/extras/forms/model_forms.py:453 #: netbox/templates/extras/configcontext.html:10 #: netbox/virtualization/forms/model_forms.py:225 -#: netbox/virtualization/views.py:404 +#: netbox/virtualization/views.py:406 msgid "Config Context" msgstr "" -#: netbox/dcim/views.py:2029 netbox/virtualization/views.py:414 +#: netbox/dcim/views.py:2029 netbox/virtualization/views.py:416 msgid "Render Config" msgstr "" +#: netbox/dcim/views.py:2062 netbox/virtualization/views.py:449 +#, python-brace-format +msgid "An error occurred while rendering the template: {error}" +msgstr "" + #: netbox/dcim/views.py:2080 netbox/extras/tables/tables.py:447 #: netbox/netbox/navigation/menu.py:234 netbox/netbox/navigation/menu.py:236 #: netbox/virtualization/views.py:179 msgid "Virtual Machines" msgstr "" -#: netbox/dcim/views.py:2963 netbox/ipam/tables/ip.py:234 +#: netbox/dcim/views.py:2828 +#, python-brace-format +msgid "Installed device {device} in bay {device_bay}." +msgstr "" + +#: netbox/dcim/views.py:2869 +#, python-brace-format +msgid "Removed device {device} from bay {device_bay}." +msgstr "" + +#: netbox/dcim/views.py:2975 netbox/ipam/tables/ip.py:234 msgid "Children" msgstr "" +#: netbox/dcim/views.py:3441 +msgid "Added member {escape(device)}" +msgstr "" + +#: netbox/dcim/views.py:3488 +#, python-brace-format +msgid "Unable to remove master device {device} from the virtual chassis." +msgstr "" + +#: netbox/dcim/views.py:3501 +#, python-brace-format +msgid "Removed {device} from virtual chassis {chassis}" +msgstr "" + #: netbox/extras/api/customfields.py:88 #, python-brace-format msgid "Unknown related object(s): {name}" @@ -10167,7 +10267,7 @@ msgstr "" #: netbox/templates/virtualization/virtualmachine/base.html:32 #: netbox/templates/virtualization/virtualmachine_list.html:21 #: netbox/virtualization/tables/virtualmachines.py:103 -#: netbox/virtualization/views.py:385 +#: netbox/virtualization/views.py:387 msgid "Virtual Disks" msgstr "" @@ -10538,17 +10638,17 @@ msgstr "" msgid "Error" msgstr "" -#: netbox/netbox/tables/tables.py:57 +#: netbox/netbox/tables/tables.py:58 #, python-brace-format msgid "No {model_name} found" msgstr "" -#: netbox/netbox/tables/tables.py:248 +#: netbox/netbox/tables/tables.py:249 #: netbox/templates/generic/bulk_import.html:117 msgid "Field" msgstr "" -#: netbox/netbox/tables/tables.py:251 +#: netbox/netbox/tables/tables.py:252 msgid "Value" msgstr "" @@ -10556,11 +10656,35 @@ msgstr "" msgid "Dummy Plugin" msgstr "" -#: netbox/netbox/views/generic/bulk_views.py:405 +#: netbox/netbox/views/generic/bulk_views.py:111 +#, python-brace-format +msgid "" +"There was an error rendering the selected export template ({template}): " +"{error}" +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:411 #, python-brace-format msgid "Row {i}: Object with ID {id} does not exist" msgstr "" +#: netbox/netbox/views/generic/bulk_views.py:679 +#: netbox/netbox/views/generic/bulk_views.py:877 +#: netbox/netbox/views/generic/bulk_views.py:925 +#, python-brace-format +msgid "No {object_type} were selected." +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:759 +#, python-brace-format +msgid "Renamed {count} {object_type}" +msgstr "" + +#: netbox/netbox/views/generic/bulk_views.py:855 +#, python-brace-format +msgid "Deleted {count} {object_type}" +msgstr "" + #: netbox/netbox/views/generic/feature_views.py:38 msgid "Changelog" msgstr "" @@ -10569,6 +10693,20 @@ msgstr "" msgid "Journal" msgstr "" +#: netbox/netbox/views/generic/feature_views.py:205 +msgid "Unable to synchronize data: No data file set." +msgstr "" + +#: netbox/netbox/views/generic/feature_views.py:209 +#, python-brace-format +msgid "Synchronized data for {object_type} {object}." +msgstr "" + +#: netbox/netbox/views/generic/feature_views.py:234 +#, python-brace-format +msgid "Synced {count} {object_type}" +msgstr "" + #: netbox/netbox/views/generic/object_views.py:108 #, python-brace-format msgid "{class_name} must implement get_children()" @@ -14335,6 +14473,16 @@ msgstr "" msgid "virtual disks" msgstr "" +#: netbox/virtualization/views.py:274 +#, python-brace-format +msgid "Added {count} devices to cluster {cluster}" +msgstr "" + +#: netbox/virtualization/views.py:309 +#, python-brace-format +msgid "Removed {count} devices from cluster {cluster}" +msgstr "" + #: netbox/vpn/choices.py:31 msgid "IPsec - Transport" msgstr "" From 7c9a77b77f8f43391185a1da9a53e5b5ec1e19ee Mon Sep 17 00:00:00 2001 From: Matthew Mehrtens <12023414+mcmehrtens@users.noreply.github.com> Date: Mon, 12 Aug 2024 07:03:46 -0500 Subject: [PATCH 09/15] 17006 Add Wi-Fi 7 IEEE 802.11be (#17125) * Add .devcontainer to .gitignore * Closes #17006: Add Wi-Fi 7 IEEE 802.11be * Revert out-of-scope change --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/choices.py | 2 ++ netbox/dcim/constants.py | 1 + 2 files changed, 3 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index fe8d8a158..980c258c0 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -886,6 +886,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_80211AD = 'ieee802.11ad' TYPE_80211AX = 'ieee802.11ax' TYPE_80211AY = 'ieee802.11ay' + TYPE_80211BE = 'ieee802.11be' TYPE_802151 = 'ieee802.15.1' TYPE_OTHER_WIRELESS = 'other-wireless' @@ -1057,6 +1058,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_80211AD, 'IEEE 802.11ad'), (TYPE_80211AX, 'IEEE 802.11ax'), (TYPE_80211AY, 'IEEE 802.11ay'), + (TYPE_80211BE, 'IEEE 802.11be'), (TYPE_802151, 'IEEE 802.15.1 (Bluetooth)'), (TYPE_OTHER_WIRELESS, 'Other (Wireless)'), ) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 303fc2344..049be6117 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -49,6 +49,7 @@ WIRELESS_IFACE_TYPES = [ InterfaceTypeChoices.TYPE_80211AD, InterfaceTypeChoices.TYPE_80211AX, InterfaceTypeChoices.TYPE_80211AY, + InterfaceTypeChoices.TYPE_80211BE, InterfaceTypeChoices.TYPE_802151, InterfaceTypeChoices.TYPE_OTHER_WIRELESS, ] From 6a663e2a3ec6beedb7c084a42c2ea549d70292d5 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 05:02:12 +0000 Subject: [PATCH 10/15] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 106 +++++++++---------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 5a346dc93..b79bc5c54 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-11 05:02+0000\n" +"POT-Creation-Date: 2024-08-13 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -81,8 +81,8 @@ msgstr "" #: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20 #: netbox/dcim/choices.py:102 netbox/dcim/choices.py:174 -#: netbox/dcim/choices.py:220 netbox/dcim/choices.py:1459 -#: netbox/dcim/choices.py:1535 netbox/dcim/choices.py:1585 +#: netbox/dcim/choices.py:220 netbox/dcim/choices.py:1461 +#: netbox/dcim/choices.py:1537 netbox/dcim/choices.py:1587 #: netbox/virtualization/choices.py:20 netbox/virtualization/choices.py:45 #: netbox/vpn/choices.py:18 msgid "Planned" @@ -95,7 +95,7 @@ msgstr "" #: netbox/circuits/choices.py:23 netbox/core/tables/tasks.py:22 #: netbox/dcim/choices.py:22 netbox/dcim/choices.py:103 #: netbox/dcim/choices.py:173 netbox/dcim/choices.py:219 -#: netbox/dcim/choices.py:1534 netbox/dcim/choices.py:1584 +#: netbox/dcim/choices.py:1536 netbox/dcim/choices.py:1586 #: netbox/extras/tables/tables.py:392 netbox/ipam/choices.py:31 #: netbox/ipam/choices.py:49 netbox/ipam/choices.py:69 #: netbox/ipam/choices.py:154 netbox/templates/extras/configcontext.html:25 @@ -106,8 +106,8 @@ msgid "Active" msgstr "" #: netbox/circuits/choices.py:24 netbox/dcim/choices.py:172 -#: netbox/dcim/choices.py:218 netbox/dcim/choices.py:1533 -#: netbox/dcim/choices.py:1586 netbox/virtualization/choices.py:24 +#: netbox/dcim/choices.py:218 netbox/dcim/choices.py:1535 +#: netbox/dcim/choices.py:1588 netbox/virtualization/choices.py:24 #: netbox/virtualization/choices.py:43 msgid "Offline" msgstr "" @@ -1430,7 +1430,7 @@ msgstr "" #: netbox/core/choices.py:22 netbox/core/choices.py:59 #: netbox/core/constants.py:20 netbox/core/tables/tasks.py:34 #: netbox/dcim/choices.py:176 netbox/dcim/choices.py:222 -#: netbox/dcim/choices.py:1536 netbox/extras/choices.py:230 +#: netbox/dcim/choices.py:1538 netbox/extras/choices.py:230 #: netbox/virtualization/choices.py:47 msgid "Failed" msgstr "" @@ -1682,7 +1682,7 @@ msgstr "" msgid "Rack Elevations" msgstr "" -#: netbox/core/forms/model_forms.py:157 netbox/dcim/choices.py:1447 +#: netbox/core/forms/model_forms.py:157 netbox/dcim/choices.py:1449 #: netbox/dcim/forms/bulk_edit.py:867 netbox/dcim/forms/bulk_edit.py:1250 #: netbox/dcim/forms/bulk_edit.py:1268 netbox/dcim/tables/racks.py:89 #: netbox/netbox/navigation/menu.py:276 netbox/netbox/navigation/menu.py:280 @@ -2154,7 +2154,7 @@ msgid "Staging" msgstr "" #: netbox/dcim/choices.py:23 netbox/dcim/choices.py:178 -#: netbox/dcim/choices.py:223 netbox/dcim/choices.py:1460 +#: netbox/dcim/choices.py:223 netbox/dcim/choices.py:1462 #: netbox/virtualization/choices.py:23 netbox/virtualization/choices.py:48 msgid "Decommissioning" msgstr "" @@ -2217,7 +2217,7 @@ msgstr "" msgid "Millimeters" msgstr "" -#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1482 +#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1484 msgid "Inches" msgstr "" @@ -2303,7 +2303,7 @@ msgstr "" msgid "Side to rear" msgstr "" -#: netbox/dcim/choices.py:198 netbox/dcim/choices.py:1255 +#: netbox/dcim/choices.py:198 netbox/dcim/choices.py:1257 msgid "Passive" msgstr "" @@ -2332,8 +2332,8 @@ msgid "Proprietary" msgstr "" #: netbox/dcim/choices.py:543 netbox/dcim/choices.py:782 -#: netbox/dcim/choices.py:1171 netbox/dcim/choices.py:1173 -#: netbox/dcim/choices.py:1378 netbox/dcim/choices.py:1380 +#: netbox/dcim/choices.py:1173 netbox/dcim/choices.py:1175 +#: netbox/dcim/choices.py:1380 netbox/dcim/choices.py:1382 #: netbox/netbox/navigation/menu.py:187 msgid "Other" msgstr "" @@ -2346,11 +2346,11 @@ msgstr "" msgid "Physical" msgstr "" -#: netbox/dcim/choices.py:813 netbox/dcim/choices.py:978 +#: netbox/dcim/choices.py:813 netbox/dcim/choices.py:979 msgid "Virtual" msgstr "" -#: netbox/dcim/choices.py:814 netbox/dcim/choices.py:1051 +#: netbox/dcim/choices.py:814 netbox/dcim/choices.py:1052 #: netbox/dcim/forms/bulk_edit.py:1408 netbox/dcim/forms/filtersets.py:1251 #: netbox/dcim/forms/model_forms.py:936 netbox/dcim/forms/model_forms.py:1344 #: netbox/netbox/navigation/menu.py:127 netbox/netbox/navigation/menu.py:131 @@ -2358,11 +2358,11 @@ msgstr "" msgid "Wireless" msgstr "" -#: netbox/dcim/choices.py:976 +#: netbox/dcim/choices.py:977 msgid "Virtual interfaces" msgstr "" -#: netbox/dcim/choices.py:979 netbox/dcim/forms/bulk_edit.py:1303 +#: netbox/dcim/choices.py:980 netbox/dcim/forms/bulk_edit.py:1303 #: netbox/dcim/forms/bulk_import.py:779 netbox/dcim/forms/model_forms.py:922 #: netbox/dcim/tables/devices.py:649 netbox/templates/dcim/interface.html:106 #: netbox/templates/virtualization/vminterface.html:43 @@ -2372,27 +2372,27 @@ msgstr "" msgid "Bridge" msgstr "" -#: netbox/dcim/choices.py:980 +#: netbox/dcim/choices.py:981 msgid "Link Aggregation Group (LAG)" msgstr "" -#: netbox/dcim/choices.py:984 +#: netbox/dcim/choices.py:985 msgid "Ethernet (fixed)" msgstr "" -#: netbox/dcim/choices.py:999 +#: netbox/dcim/choices.py:1000 msgid "Ethernet (modular)" msgstr "" -#: netbox/dcim/choices.py:1035 +#: netbox/dcim/choices.py:1036 msgid "Ethernet (backplane)" msgstr "" -#: netbox/dcim/choices.py:1065 +#: netbox/dcim/choices.py:1067 msgid "Cellular" msgstr "" -#: netbox/dcim/choices.py:1117 netbox/dcim/forms/filtersets.py:304 +#: netbox/dcim/choices.py:1119 netbox/dcim/forms/filtersets.py:304 #: netbox/dcim/forms/filtersets.py:740 netbox/dcim/forms/filtersets.py:894 #: netbox/dcim/forms/filtersets.py:1446 #: netbox/templates/dcim/inventoryitem.html:52 @@ -2400,127 +2400,127 @@ msgstr "" msgid "Serial" msgstr "" -#: netbox/dcim/choices.py:1132 +#: netbox/dcim/choices.py:1134 msgid "Coaxial" msgstr "" -#: netbox/dcim/choices.py:1152 +#: netbox/dcim/choices.py:1154 msgid "Stacking" msgstr "" -#: netbox/dcim/choices.py:1202 +#: netbox/dcim/choices.py:1204 msgid "Half" msgstr "" -#: netbox/dcim/choices.py:1203 +#: netbox/dcim/choices.py:1205 msgid "Full" msgstr "" -#: netbox/dcim/choices.py:1204 netbox/netbox/preferences.py:31 +#: netbox/dcim/choices.py:1206 netbox/netbox/preferences.py:31 #: netbox/wireless/choices.py:480 msgid "Auto" msgstr "" -#: netbox/dcim/choices.py:1215 +#: netbox/dcim/choices.py:1217 msgid "Access" msgstr "" -#: netbox/dcim/choices.py:1216 netbox/ipam/tables/vlans.py:168 +#: netbox/dcim/choices.py:1218 netbox/ipam/tables/vlans.py:168 #: netbox/ipam/tables/vlans.py:213 #: netbox/templates/dcim/inc/interface_vlans_table.html:7 msgid "Tagged" msgstr "" -#: netbox/dcim/choices.py:1217 +#: netbox/dcim/choices.py:1219 msgid "Tagged (All)" msgstr "" -#: netbox/dcim/choices.py:1246 +#: netbox/dcim/choices.py:1248 msgid "IEEE Standard" msgstr "" -#: netbox/dcim/choices.py:1257 +#: netbox/dcim/choices.py:1259 msgid "Passive 24V (2-pair)" msgstr "" -#: netbox/dcim/choices.py:1258 +#: netbox/dcim/choices.py:1260 msgid "Passive 24V (4-pair)" msgstr "" -#: netbox/dcim/choices.py:1259 +#: netbox/dcim/choices.py:1261 msgid "Passive 48V (2-pair)" msgstr "" -#: netbox/dcim/choices.py:1260 +#: netbox/dcim/choices.py:1262 msgid "Passive 48V (4-pair)" msgstr "" -#: netbox/dcim/choices.py:1322 netbox/dcim/choices.py:1418 +#: netbox/dcim/choices.py:1324 netbox/dcim/choices.py:1420 msgid "Copper" msgstr "" -#: netbox/dcim/choices.py:1345 +#: netbox/dcim/choices.py:1347 msgid "Fiber Optic" msgstr "" -#: netbox/dcim/choices.py:1434 +#: netbox/dcim/choices.py:1436 msgid "Fiber" msgstr "" -#: netbox/dcim/choices.py:1458 netbox/dcim/forms/filtersets.py:1158 +#: netbox/dcim/choices.py:1460 netbox/dcim/forms/filtersets.py:1158 msgid "Connected" msgstr "" -#: netbox/dcim/choices.py:1477 +#: netbox/dcim/choices.py:1479 msgid "Kilometers" msgstr "" -#: netbox/dcim/choices.py:1478 netbox/templates/dcim/cable_trace.html:65 +#: netbox/dcim/choices.py:1480 netbox/templates/dcim/cable_trace.html:65 msgid "Meters" msgstr "" -#: netbox/dcim/choices.py:1479 +#: netbox/dcim/choices.py:1481 msgid "Centimeters" msgstr "" -#: netbox/dcim/choices.py:1480 +#: netbox/dcim/choices.py:1482 msgid "Miles" msgstr "" -#: netbox/dcim/choices.py:1481 netbox/templates/dcim/cable_trace.html:66 +#: netbox/dcim/choices.py:1483 netbox/templates/dcim/cable_trace.html:66 msgid "Feet" msgstr "" -#: netbox/dcim/choices.py:1497 netbox/templates/dcim/device.html:327 +#: netbox/dcim/choices.py:1499 netbox/templates/dcim/device.html:327 #: netbox/templates/dcim/rack.html:152 msgid "Kilograms" msgstr "" -#: netbox/dcim/choices.py:1498 +#: netbox/dcim/choices.py:1500 msgid "Grams" msgstr "" -#: netbox/dcim/choices.py:1499 netbox/templates/dcim/rack.html:153 +#: netbox/dcim/choices.py:1501 netbox/templates/dcim/rack.html:153 msgid "Pounds" msgstr "" -#: netbox/dcim/choices.py:1500 +#: netbox/dcim/choices.py:1502 msgid "Ounces" msgstr "" -#: netbox/dcim/choices.py:1546 netbox/tenancy/choices.py:17 +#: netbox/dcim/choices.py:1548 netbox/tenancy/choices.py:17 msgid "Primary" msgstr "" -#: netbox/dcim/choices.py:1547 +#: netbox/dcim/choices.py:1549 msgid "Redundant" msgstr "" -#: netbox/dcim/choices.py:1568 +#: netbox/dcim/choices.py:1570 msgid "Single phase" msgstr "" -#: netbox/dcim/choices.py:1569 +#: netbox/dcim/choices.py:1571 msgid "Three-phase" msgstr "" From 9e54cfe3407b18f58d35d8e0ad0ef538cb6c3136 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 12 Aug 2024 08:35:16 -0400 Subject: [PATCH 11/15] Fixes #17131: Fix exception when creating object-type custom field without selecting related object type --- netbox/extras/models/customfields.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index d8f02ec6c..67ff5a5e6 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -352,13 +352,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT): if not self.related_object_type: raise ValidationError({ - 'object_type': _("Object fields must define an object type.") + 'related_object_type': _("Object fields must define an object type.") }) elif self.related_object_type: raise ValidationError({ - 'object_type': _( - "{type} fields may not define an object type.") - .format(type=self.get_type_display()) + 'type': _("{type} fields may not define an object type.") .format(type=self.get_type_display()) }) def serialize(self, value): From 6feb8bf0e3be6535082a558b3105f8b278d44da4 Mon Sep 17 00:00:00 2001 From: PieterL75 <74899468+PieterL75@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:54:07 +0200 Subject: [PATCH 12/15] add 'vlan' to prefix bulk edit (#17142) * add 'vlan' to prefix bulk edit * Move VLAN fields to a separate field set in bulk edit form --------- Co-authored-by: Pieter Lambrecht Co-authored-by: Jeremy Stretch --- netbox/ipam/forms/bulk_edit.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/forms/bulk_edit.py b/netbox/ipam/forms/bulk_edit.py index c7f64ab1d..5f3353ea1 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -221,6 +221,19 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm): 'group_id': '$site_group', } ) + vlan_group = DynamicModelChoiceField( + queryset=VLANGroup.objects.all(), + required=False, + label=_('VLAN Group') + ) + vlan = DynamicModelChoiceField( + queryset=VLAN.objects.all(), + required=False, + label=_('VLAN'), + query_params={ + 'group_id': '$vlan_group', + } + ) vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -269,9 +282,10 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm): FieldSet('tenant', 'status', 'role', 'description'), FieldSet('region', 'site_group', 'site', name=_('Site')), FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')), + FieldSet('vlan_group', 'vlan', name=_('VLAN Assignment')), ) nullable_fields = ( - 'site', 'vrf', 'tenant', 'role', 'description', 'comments', + 'site', 'vlan', 'vrf', 'tenant', 'role', 'description', 'comments', ) From d5c1a5acda0ad914cea6f40c0797f30b271b594f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Aug 2024 14:14:51 -0400 Subject: [PATCH 13/15] Fixes #17144: Avoid displaying duplicate pop-up messages --- netbox/project-static/dist/netbox.js | Bin 391492 -> 391520 bytes netbox/project-static/dist/netbox.js.map | Bin 356229 -> 356260 bytes netbox/project-static/src/messages.ts | 4 +++- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index f7e00ea839c414e90abb626726c389f949619b66..10c0b5e7d6cc9a84abd2b5ab7a4dd029a55fe0b9 100644 GIT binary patch delta 86 zcmX@|N&LYl@rD-07N#xCq6;iC(=V+vN^^2Ft8-FI6jC#7^HR$dJc@x5n%1eA ldYQ$+8TsXT8k#jVKwdGBt)W@lZn%IMh*`E9E?^ao1OSKk9~}Sy delta 58 zcmaFxN&Lts@rD-07N#xCq6=h7G8JuY^Gb7a)YS4)%N0C|HA*ry^@=m{%QZCHeHSnT MG0S$}1+2o60Ff9J-~a#s diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 6786a1086feba55b4417ad9e64e48c30542f8944..1eb6368c913bfe225c474e847d55904ce39aa1ca 100644 GIT binary patch delta 82 zcmZp@FS_KuXhREQ3)2>6*SzVwZ!vLAKf=i@tQDx^?&#>Lx5VLI0&102d1ORQ;8G`@- delta 70 zcmZ2-U$phUXhREQ3)2>6*E|hJM@LT`Z%4;S9bZS+6dgxLmt-AxkeH{Vqc4zf&T-Q5 UcaDl`@5y5ZVwUYad8`tQ03tvYxBvhE diff --git a/netbox/project-static/src/messages.ts b/netbox/project-static/src/messages.ts index d17541e5f..a12d63cd0 100644 --- a/netbox/project-static/src/messages.ts +++ b/netbox/project-static/src/messages.ts @@ -10,7 +10,9 @@ export function initMessages(): void { for (const element of elements) { if (element !== null) { const toast = new Toast(element); - toast.show(); + if (!toast.isShown()) { + toast.show(); + } } } } From 8789aaaa39b3e62a7dc741733d22103a05c7c96b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Aug 2024 16:11:58 -0400 Subject: [PATCH 14/15] Changelog for #16692, #17006, #17124, #17131, #17144 --- docs/release-notes/version-4.0.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-4.0.md b/docs/release-notes/version-4.0.md index 4afa3eadb..15f98e4f5 100644 --- a/docs/release-notes/version-4.0.md +++ b/docs/release-notes/version-4.0.md @@ -2,12 +2,20 @@ ## v4.0.9 (FUTURE) +### Enhancements + +* [#16692](https://github.com/netbox-community/netbox/issues/16692) - Enable modifying VLAN assignment while bulk editing prefixes +* [#17006](https://github.com/netbox-community/netbox/issues/17006) - Add IEEE 802.11be interface type + ### Bug Fixes * [#13459](https://github.com/netbox-community/netbox/issues/13459) - Correct OpenAPI schema type for `TreeNodeMultipleChoiceFilter` * [#16176](https://github.com/netbox-community/netbox/issues/16176) - Restore ability to select multiple terminating devices when connecting a cable * [#17038](https://github.com/netbox-community/netbox/issues/17038) - Fix AttributeError exception when attempting to export system status data * [#17064](https://github.com/netbox-community/netbox/issues/17064) - Fix misaligned text within rendered Markdown code blocks +* [#17124](https://github.com/netbox-community/netbox/issues/17124) - `BaseTable` should follow reverse one-to-one relationships when prefetching related objects +* [#17131](https://github.com/netbox-community/netbox/issues/17131) - Fix exception when creating object-type custom field without selecting related object type +* [#17144](https://github.com/netbox-community/netbox/issues/17144) - Avoid showing duplicated pop-up messages --- From 09d36469dd0dcc68797992aae0d8cb189d09690a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 05:02:17 +0000 Subject: [PATCH 15/15] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 186 ++++++++++--------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index b79bc5c54..b45baa085 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-13 05:02+0000\n" +"POT-Creation-Date: 2024-08-14 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -193,8 +193,8 @@ msgstr "" #: netbox/dcim/tables/power.py:26 netbox/dcim/tables/power.py:93 #: netbox/dcim/tables/racks.py:62 netbox/dcim/tables/racks.py:138 #: netbox/dcim/tables/sites.py:129 netbox/extras/filtersets.py:477 -#: netbox/ipam/forms/bulk_edit.py:216 netbox/ipam/forms/bulk_edit.py:270 -#: netbox/ipam/forms/bulk_edit.py:448 netbox/ipam/forms/bulk_edit.py:522 +#: netbox/ipam/forms/bulk_edit.py:216 netbox/ipam/forms/bulk_edit.py:283 +#: netbox/ipam/forms/bulk_edit.py:462 netbox/ipam/forms/bulk_edit.py:536 #: netbox/ipam/forms/bulk_import.py:170 netbox/ipam/forms/bulk_import.py:437 #: netbox/ipam/forms/filtersets.py:153 netbox/ipam/forms/filtersets.py:231 #: netbox/ipam/forms/filtersets.py:432 netbox/ipam/forms/filtersets.py:496 @@ -375,10 +375,10 @@ msgstr "" #: netbox/ipam/forms/bulk_edit.py:51 netbox/ipam/forms/bulk_edit.py:71 #: netbox/ipam/forms/bulk_edit.py:91 netbox/ipam/forms/bulk_edit.py:115 #: netbox/ipam/forms/bulk_edit.py:144 netbox/ipam/forms/bulk_edit.py:173 -#: netbox/ipam/forms/bulk_edit.py:192 netbox/ipam/forms/bulk_edit.py:261 -#: netbox/ipam/forms/bulk_edit.py:305 netbox/ipam/forms/bulk_edit.py:353 -#: netbox/ipam/forms/bulk_edit.py:396 netbox/ipam/forms/bulk_edit.py:424 -#: netbox/ipam/forms/bulk_edit.py:554 netbox/ipam/forms/bulk_edit.py:585 +#: netbox/ipam/forms/bulk_edit.py:192 netbox/ipam/forms/bulk_edit.py:274 +#: netbox/ipam/forms/bulk_edit.py:319 netbox/ipam/forms/bulk_edit.py:367 +#: netbox/ipam/forms/bulk_edit.py:410 netbox/ipam/forms/bulk_edit.py:438 +#: netbox/ipam/forms/bulk_edit.py:568 netbox/ipam/forms/bulk_edit.py:599 #: netbox/templates/account/token.html:35 #: netbox/templates/circuits/circuit.html:59 #: netbox/templates/circuits/circuittype.html:26 @@ -605,8 +605,8 @@ msgstr "" #: netbox/dcim/tables/devices.py:1034 netbox/dcim/tables/modules.py:69 #: netbox/dcim/tables/power.py:74 netbox/dcim/tables/racks.py:66 #: netbox/dcim/tables/sites.py:82 netbox/dcim/tables/sites.py:133 -#: netbox/ipam/forms/bulk_edit.py:241 netbox/ipam/forms/bulk_edit.py:290 -#: netbox/ipam/forms/bulk_edit.py:338 netbox/ipam/forms/bulk_edit.py:544 +#: netbox/ipam/forms/bulk_edit.py:254 netbox/ipam/forms/bulk_edit.py:304 +#: netbox/ipam/forms/bulk_edit.py:352 netbox/ipam/forms/bulk_edit.py:558 #: netbox/ipam/forms/bulk_import.py:191 netbox/ipam/forms/bulk_import.py:256 #: netbox/ipam/forms/bulk_import.py:292 netbox/ipam/forms/bulk_import.py:458 #: netbox/ipam/forms/filtersets.py:210 netbox/ipam/forms/filtersets.py:281 @@ -671,8 +671,8 @@ msgstr "" #: netbox/extras/forms/filtersets.py:405 netbox/ipam/forms/bulk_edit.py:41 #: netbox/ipam/forms/bulk_edit.py:66 netbox/ipam/forms/bulk_edit.py:110 #: netbox/ipam/forms/bulk_edit.py:139 netbox/ipam/forms/bulk_edit.py:164 -#: netbox/ipam/forms/bulk_edit.py:236 netbox/ipam/forms/bulk_edit.py:285 -#: netbox/ipam/forms/bulk_edit.py:333 netbox/ipam/forms/bulk_edit.py:539 +#: netbox/ipam/forms/bulk_edit.py:249 netbox/ipam/forms/bulk_edit.py:299 +#: netbox/ipam/forms/bulk_edit.py:347 netbox/ipam/forms/bulk_edit.py:553 #: netbox/ipam/forms/bulk_import.py:37 netbox/ipam/forms/bulk_import.py:66 #: netbox/ipam/forms/bulk_import.py:94 netbox/ipam/forms/bulk_import.py:114 #: netbox/ipam/forms/bulk_import.py:134 netbox/ipam/forms/bulk_import.py:163 @@ -883,7 +883,7 @@ msgstr "" #: netbox/dcim/tables/devices.py:157 netbox/dcim/tables/power.py:30 #: netbox/dcim/tables/racks.py:58 netbox/dcim/tables/racks.py:143 #: netbox/extras/filtersets.py:488 netbox/extras/forms/filtersets.py:329 -#: netbox/ipam/forms/bulk_edit.py:457 netbox/ipam/forms/filtersets.py:173 +#: netbox/ipam/forms/bulk_edit.py:471 netbox/ipam/forms/filtersets.py:173 #: netbox/ipam/forms/filtersets.py:414 netbox/ipam/forms/filtersets.py:437 #: netbox/ipam/forms/filtersets.py:474 netbox/ipam/forms/model_forms.py:599 #: netbox/templates/dcim/device.html:26 @@ -927,7 +927,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:111 netbox/dcim/forms/object_create.py:375 #: netbox/dcim/tables/devices.py:143 netbox/dcim/tables/sites.py:85 #: netbox/extras/filtersets.py:455 netbox/ipam/forms/bulk_edit.py:206 -#: netbox/ipam/forms/bulk_edit.py:438 netbox/ipam/forms/bulk_edit.py:512 +#: netbox/ipam/forms/bulk_edit.py:452 netbox/ipam/forms/bulk_edit.py:526 #: netbox/ipam/forms/filtersets.py:217 netbox/ipam/forms/filtersets.py:422 #: netbox/ipam/forms/filtersets.py:482 netbox/ipam/forms/model_forms.py:571 #: netbox/templates/dcim/device.html:18 netbox/templates/dcim/rack.html:16 @@ -950,8 +950,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:675 netbox/dcim/forms/filtersets.py:919 #: netbox/dcim/forms/filtersets.py:1033 netbox/dcim/forms/filtersets.py:1072 #: netbox/dcim/forms/object_create.py:383 netbox/extras/filtersets.py:472 -#: netbox/ipam/forms/bulk_edit.py:211 netbox/ipam/forms/bulk_edit.py:445 -#: netbox/ipam/forms/bulk_edit.py:517 netbox/ipam/forms/filtersets.py:222 +#: netbox/ipam/forms/bulk_edit.py:211 netbox/ipam/forms/bulk_edit.py:459 +#: netbox/ipam/forms/bulk_edit.py:531 netbox/ipam/forms/filtersets.py:222 #: netbox/ipam/forms/filtersets.py:427 netbox/ipam/forms/filtersets.py:487 #: netbox/ipam/forms/model_forms.py:584 #: netbox/virtualization/forms/bulk_edit.py:86 @@ -1237,7 +1237,7 @@ msgstr "" #: netbox/extras/tables/tables.py:215 netbox/extras/tables/tables.py:263 #: netbox/extras/tables/tables.py:286 netbox/extras/tables/tables.py:336 #: netbox/extras/tables/tables.py:388 netbox/extras/tables/tables.py:411 -#: netbox/ipam/forms/bulk_edit.py:391 netbox/ipam/forms/filtersets.py:386 +#: netbox/ipam/forms/bulk_edit.py:405 netbox/ipam/forms/filtersets.py:386 #: netbox/ipam/tables/asn.py:16 netbox/ipam/tables/ip.py:85 #: netbox/ipam/tables/ip.py:160 netbox/ipam/tables/services.py:15 #: netbox/ipam/tables/services.py:40 netbox/ipam/tables/vlans.py:64 @@ -2890,8 +2890,8 @@ msgstr "" #: netbox/dcim/tables/devices.py:615 netbox/ipam/filtersets.py:316 #: netbox/ipam/filtersets.py:327 netbox/ipam/filtersets.py:483 #: netbox/ipam/filtersets.py:584 netbox/ipam/filtersets.py:595 -#: netbox/ipam/forms/bulk_edit.py:227 netbox/ipam/forms/bulk_edit.py:282 -#: netbox/ipam/forms/bulk_edit.py:324 netbox/ipam/forms/bulk_import.py:156 +#: netbox/ipam/forms/bulk_edit.py:240 netbox/ipam/forms/bulk_edit.py:296 +#: netbox/ipam/forms/bulk_edit.py:338 netbox/ipam/forms/bulk_import.py:156 #: netbox/ipam/forms/bulk_import.py:242 netbox/ipam/forms/bulk_import.py:278 #: netbox/ipam/forms/filtersets.py:67 netbox/ipam/forms/filtersets.py:172 #: netbox/ipam/forms/filtersets.py:309 netbox/ipam/forms/model_forms.py:60 @@ -3050,7 +3050,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:116 netbox/dcim/forms/bulk_import.py:99 #: netbox/dcim/forms/model_forms.py:116 netbox/dcim/tables/sites.py:89 -#: netbox/ipam/filtersets.py:985 netbox/ipam/forms/bulk_edit.py:531 +#: netbox/ipam/filtersets.py:985 netbox/ipam/forms/bulk_edit.py:545 #: netbox/ipam/forms/bulk_import.py:444 netbox/ipam/forms/model_forms.py:526 #: netbox/ipam/tables/fhrp.py:67 netbox/ipam/tables/vlans.py:118 #: netbox/ipam/tables/vlans.py:222 netbox/templates/dcim/interface.html:284 @@ -3114,8 +3114,8 @@ msgstr "" #: netbox/dcim/tables/devices.py:169 netbox/dcim/tables/devices.py:797 #: netbox/dcim/tables/devices.py:908 netbox/dcim/tables/devicetypes.py:305 #: netbox/dcim/tables/racks.py:69 netbox/extras/filtersets.py:504 -#: netbox/ipam/forms/bulk_edit.py:246 netbox/ipam/forms/bulk_edit.py:295 -#: netbox/ipam/forms/bulk_edit.py:343 netbox/ipam/forms/bulk_edit.py:549 +#: netbox/ipam/forms/bulk_edit.py:259 netbox/ipam/forms/bulk_edit.py:309 +#: netbox/ipam/forms/bulk_edit.py:357 netbox/ipam/forms/bulk_edit.py:563 #: netbox/ipam/forms/bulk_import.py:196 netbox/ipam/forms/bulk_import.py:261 #: netbox/ipam/forms/bulk_import.py:297 netbox/ipam/forms/bulk_import.py:463 #: netbox/ipam/forms/filtersets.py:237 netbox/ipam/forms/filtersets.py:289 @@ -3237,7 +3237,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:422 netbox/dcim/forms/model_forms.py:703 #: netbox/dcim/forms/object_create.py:400 netbox/dcim/tables/devices.py:161 #: netbox/dcim/tables/power.py:70 netbox/dcim/tables/racks.py:148 -#: netbox/ipam/forms/bulk_edit.py:465 netbox/ipam/forms/filtersets.py:442 +#: netbox/ipam/forms/bulk_edit.py:479 netbox/ipam/forms/filtersets.py:442 #: netbox/ipam/forms/model_forms.py:610 netbox/templates/dcim/device.html:30 #: netbox/templates/dcim/inc/cable_termination.html:16 #: netbox/templates/dcim/powerfeed.html:28 netbox/templates/dcim/rack.html:13 @@ -3644,8 +3644,8 @@ msgid "Wireless LANs" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1401 netbox/dcim/forms/filtersets.py:1249 -#: netbox/dcim/forms/model_forms.py:1337 netbox/ipam/forms/bulk_edit.py:271 -#: netbox/ipam/forms/bulk_edit.py:362 netbox/ipam/forms/filtersets.py:169 +#: netbox/dcim/forms/model_forms.py:1337 netbox/ipam/forms/bulk_edit.py:284 +#: netbox/ipam/forms/bulk_edit.py:376 netbox/ipam/forms/filtersets.py:169 #: netbox/templates/dcim/interface.html:122 #: netbox/templates/ipam/prefix.html:95 #: netbox/virtualization/forms/model_forms.py:349 @@ -3819,7 +3819,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:456 netbox/dcim/forms/filtersets.py:659 #: netbox/dcim/forms/filtersets.py:829 netbox/dcim/forms/model_forms.py:465 #: netbox/dcim/tables/devices.py:202 netbox/extras/filtersets.py:548 -#: netbox/extras/forms/filtersets.py:331 netbox/ipam/forms/bulk_edit.py:479 +#: netbox/extras/forms/filtersets.py:331 netbox/ipam/forms/bulk_edit.py:493 #: netbox/ipam/forms/filtersets.py:415 netbox/ipam/forms/filtersets.py:459 #: netbox/ipam/forms/model_forms.py:627 netbox/templates/dcim/device.html:239 #: netbox/templates/virtualization/cluster.html:10 @@ -4228,7 +4228,7 @@ msgid "Has virtual device contexts" msgstr "" #: netbox/dcim/forms/filtersets.py:834 netbox/extras/filtersets.py:537 -#: netbox/ipam/forms/bulk_edit.py:476 netbox/ipam/forms/filtersets.py:464 +#: netbox/ipam/forms/bulk_edit.py:490 netbox/ipam/forms/filtersets.py:464 #: netbox/ipam/forms/model_forms.py:624 #: netbox/virtualization/forms/filtersets.py:112 msgid "Cluster group" @@ -7735,111 +7735,111 @@ msgstr "" msgid "Object fields must define an object type." msgstr "" -#: netbox/extras/models/customfields.py:360 +#: netbox/extras/models/customfields.py:359 #, python-brace-format msgid "{type} fields may not define an object type." msgstr "" -#: netbox/extras/models/customfields.py:440 +#: netbox/extras/models/customfields.py:438 msgid "True" msgstr "" -#: netbox/extras/models/customfields.py:441 +#: netbox/extras/models/customfields.py:439 msgid "False" msgstr "" -#: netbox/extras/models/customfields.py:523 +#: netbox/extras/models/customfields.py:521 #, python-brace-format msgid "Values must match this regex: {regex}" msgstr "" -#: netbox/extras/models/customfields.py:617 +#: netbox/extras/models/customfields.py:615 msgid "Value must be a string." msgstr "" -#: netbox/extras/models/customfields.py:619 +#: netbox/extras/models/customfields.py:617 #, python-brace-format msgid "Value must match regex '{regex}'" msgstr "" -#: netbox/extras/models/customfields.py:624 +#: netbox/extras/models/customfields.py:622 msgid "Value must be an integer." msgstr "" -#: netbox/extras/models/customfields.py:627 -#: netbox/extras/models/customfields.py:642 +#: netbox/extras/models/customfields.py:625 +#: netbox/extras/models/customfields.py:640 #, python-brace-format msgid "Value must be at least {minimum}" msgstr "" -#: netbox/extras/models/customfields.py:631 -#: netbox/extras/models/customfields.py:646 +#: netbox/extras/models/customfields.py:629 +#: netbox/extras/models/customfields.py:644 #, python-brace-format msgid "Value must not exceed {maximum}" msgstr "" -#: netbox/extras/models/customfields.py:639 +#: netbox/extras/models/customfields.py:637 msgid "Value must be a decimal." msgstr "" -#: netbox/extras/models/customfields.py:651 +#: netbox/extras/models/customfields.py:649 msgid "Value must be true or false." msgstr "" -#: netbox/extras/models/customfields.py:659 +#: netbox/extras/models/customfields.py:657 msgid "Date values must be in ISO 8601 format (YYYY-MM-DD)." msgstr "" -#: netbox/extras/models/customfields.py:672 +#: netbox/extras/models/customfields.py:670 msgid "Date and time values must be in ISO 8601 format (YYYY-MM-DD HH:MM:SS)." msgstr "" -#: netbox/extras/models/customfields.py:679 +#: netbox/extras/models/customfields.py:677 #, python-brace-format msgid "Invalid choice ({value}) for choice set {choiceset}." msgstr "" -#: netbox/extras/models/customfields.py:689 +#: netbox/extras/models/customfields.py:687 #, python-brace-format msgid "Invalid choice(s) ({value}) for choice set {choiceset}." msgstr "" -#: netbox/extras/models/customfields.py:698 +#: netbox/extras/models/customfields.py:696 #, python-brace-format msgid "Value must be an object ID, not {type}" msgstr "" -#: netbox/extras/models/customfields.py:704 +#: netbox/extras/models/customfields.py:702 #, python-brace-format msgid "Value must be a list of object IDs, not {type}" msgstr "" -#: netbox/extras/models/customfields.py:708 +#: netbox/extras/models/customfields.py:706 #, python-brace-format msgid "Found invalid object ID: {id}" msgstr "" -#: netbox/extras/models/customfields.py:711 +#: netbox/extras/models/customfields.py:709 msgid "Required field cannot be empty." msgstr "" -#: netbox/extras/models/customfields.py:730 +#: netbox/extras/models/customfields.py:728 msgid "Base set of predefined choices (optional)" msgstr "" -#: netbox/extras/models/customfields.py:742 +#: netbox/extras/models/customfields.py:740 msgid "Choices are automatically ordered alphabetically" msgstr "" -#: netbox/extras/models/customfields.py:749 +#: netbox/extras/models/customfields.py:747 msgid "custom field choice set" msgstr "" -#: netbox/extras/models/customfields.py:750 +#: netbox/extras/models/customfields.py:748 msgid "custom field choice sets" msgstr "" -#: netbox/extras/models/customfields.py:786 +#: netbox/extras/models/customfields.py:784 msgid "Must define base or extra choices." msgstr "" @@ -8596,7 +8596,7 @@ msgid "Prefixes which contain this prefix or IP" msgstr "" #: netbox/ipam/filtersets.py:304 netbox/ipam/filtersets.py:572 -#: netbox/ipam/forms/bulk_edit.py:327 netbox/ipam/forms/filtersets.py:196 +#: netbox/ipam/forms/bulk_edit.py:341 netbox/ipam/forms/filtersets.py:196 #: netbox/ipam/forms/filtersets.py:331 msgid "Mask length" msgstr "" @@ -8741,26 +8741,52 @@ msgstr "" msgid "Date added" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:230 +#: netbox/ipam/forms/bulk_edit.py:227 netbox/ipam/forms/model_forms.py:637 +#: netbox/ipam/forms/model_forms.py:679 netbox/ipam/tables/ip.py:251 +#: netbox/templates/ipam/vlan_edit.html:37 +#: netbox/templates/ipam/vlangroup.html:27 +msgid "VLAN Group" +msgstr "" + +#: netbox/ipam/forms/bulk_edit.py:232 netbox/ipam/forms/bulk_import.py:184 +#: netbox/ipam/forms/filtersets.py:256 netbox/ipam/forms/model_forms.py:216 +#: netbox/ipam/models/vlans.py:214 netbox/ipam/tables/ip.py:255 +#: netbox/templates/ipam/prefix.html:60 netbox/templates/ipam/vlan.html:12 +#: netbox/templates/ipam/vlan/base.html:6 +#: netbox/templates/ipam/vlan_edit.html:10 +#: netbox/templates/wireless/wirelesslan.html:30 +#: netbox/vpn/forms/bulk_import.py:304 netbox/vpn/forms/filtersets.py:284 +#: netbox/vpn/forms/model_forms.py:433 netbox/vpn/forms/model_forms.py:452 +#: netbox/wireless/forms/bulk_edit.py:55 +#: netbox/wireless/forms/bulk_import.py:48 +#: netbox/wireless/forms/model_forms.py:48 netbox/wireless/models.py:101 +msgid "VLAN" +msgstr "" + +#: netbox/ipam/forms/bulk_edit.py:243 msgid "Prefix length" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:253 netbox/ipam/forms/filtersets.py:241 +#: netbox/ipam/forms/bulk_edit.py:266 netbox/ipam/forms/filtersets.py:241 #: netbox/templates/ipam/prefix.html:85 msgid "Is a pool" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:258 netbox/ipam/forms/bulk_edit.py:302 +#: netbox/ipam/forms/bulk_edit.py:271 netbox/ipam/forms/bulk_edit.py:316 #: netbox/ipam/forms/filtersets.py:248 netbox/ipam/forms/filtersets.py:293 #: netbox/ipam/models/ip.py:272 netbox/ipam/models/ip.py:539 msgid "Treat as fully utilized" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:350 netbox/ipam/models/ip.py:772 +#: netbox/ipam/forms/bulk_edit.py:285 netbox/ipam/forms/filtersets.py:171 +msgid "VLAN Assignment" +msgstr "" + +#: netbox/ipam/forms/bulk_edit.py:364 netbox/ipam/models/ip.py:772 msgid "DNS name" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:371 netbox/ipam/forms/bulk_edit.py:572 +#: netbox/ipam/forms/bulk_edit.py:385 netbox/ipam/forms/bulk_edit.py:586 #: netbox/ipam/forms/bulk_import.py:393 netbox/ipam/forms/bulk_import.py:477 #: netbox/ipam/forms/bulk_import.py:503 netbox/ipam/forms/filtersets.py:390 #: netbox/ipam/forms/filtersets.py:537 netbox/templates/ipam/fhrpgroup.html:22 @@ -8770,12 +8796,12 @@ msgstr "" msgid "Protocol" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:378 netbox/ipam/forms/filtersets.py:397 +#: netbox/ipam/forms/bulk_edit.py:392 netbox/ipam/forms/filtersets.py:397 #: netbox/ipam/tables/fhrp.py:22 netbox/templates/ipam/fhrpgroup.html:26 msgid "Group ID" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:383 netbox/ipam/forms/filtersets.py:402 +#: netbox/ipam/forms/bulk_edit.py:397 netbox/ipam/forms/filtersets.py:402 #: netbox/wireless/forms/bulk_edit.py:68 netbox/wireless/forms/bulk_edit.py:115 #: netbox/wireless/forms/bulk_import.py:62 #: netbox/wireless/forms/bulk_import.py:65 @@ -8786,11 +8812,11 @@ msgstr "" msgid "Authentication type" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:388 netbox/ipam/forms/filtersets.py:406 +#: netbox/ipam/forms/bulk_edit.py:402 netbox/ipam/forms/filtersets.py:406 msgid "Authentication key" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:405 netbox/ipam/forms/filtersets.py:383 +#: netbox/ipam/forms/bulk_edit.py:419 netbox/ipam/forms/filtersets.py:383 #: netbox/ipam/forms/model_forms.py:472 netbox/netbox/navigation/menu.py:370 #: netbox/templates/ipam/fhrpgroup.html:49 #: netbox/templates/wireless/inc/authentication_attrs.html:5 @@ -8802,28 +8828,28 @@ msgstr "" msgid "Authentication" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:415 +#: netbox/ipam/forms/bulk_edit.py:429 msgid "Minimum child VLAN VID" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:421 +#: netbox/ipam/forms/bulk_edit.py:435 msgid "Maximum child VLAN VID" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:429 netbox/ipam/forms/model_forms.py:566 +#: netbox/ipam/forms/bulk_edit.py:443 netbox/ipam/forms/model_forms.py:566 msgid "Scope type" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:491 netbox/ipam/forms/model_forms.py:641 +#: netbox/ipam/forms/bulk_edit.py:505 netbox/ipam/forms/model_forms.py:641 #: netbox/ipam/tables/vlans.py:71 netbox/templates/ipam/vlangroup.html:38 msgid "Scope" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:563 +#: netbox/ipam/forms/bulk_edit.py:577 msgid "Site & Group" msgstr "" -#: netbox/ipam/forms/bulk_edit.py:577 netbox/ipam/forms/model_forms.py:705 +#: netbox/ipam/forms/bulk_edit.py:591 netbox/ipam/forms/model_forms.py:705 #: netbox/ipam/forms/model_forms.py:737 netbox/ipam/tables/services.py:19 #: netbox/ipam/tables/services.py:49 netbox/templates/ipam/service.html:36 #: netbox/templates/ipam/servicetemplate.html:23 @@ -8847,20 +8873,6 @@ msgstr "" msgid "VLAN's group (if any)" msgstr "" -#: netbox/ipam/forms/bulk_import.py:184 netbox/ipam/forms/filtersets.py:256 -#: netbox/ipam/forms/model_forms.py:216 netbox/ipam/models/vlans.py:214 -#: netbox/ipam/tables/ip.py:255 netbox/templates/ipam/prefix.html:60 -#: netbox/templates/ipam/vlan.html:12 netbox/templates/ipam/vlan/base.html:6 -#: netbox/templates/ipam/vlan_edit.html:10 -#: netbox/templates/wireless/wirelesslan.html:30 -#: netbox/vpn/forms/bulk_import.py:304 netbox/vpn/forms/filtersets.py:284 -#: netbox/vpn/forms/model_forms.py:433 netbox/vpn/forms/model_forms.py:452 -#: netbox/wireless/forms/bulk_edit.py:55 -#: netbox/wireless/forms/bulk_import.py:48 -#: netbox/wireless/forms/model_forms.py:48 netbox/wireless/models.py:101 -msgid "VLAN" -msgstr "" - #: netbox/ipam/forms/bulk_import.py:307 msgid "Parent device of assigned interface (if any)" msgstr "" @@ -8987,10 +8999,6 @@ msgstr "" msgid "End" msgstr "" -#: netbox/ipam/forms/filtersets.py:171 -msgid "VLAN Assignment" -msgstr "" - #: netbox/ipam/forms/filtersets.py:186 msgid "Search within" msgstr "" @@ -9112,12 +9120,6 @@ msgstr "" msgid "Assignment already exists" msgstr "" -#: netbox/ipam/forms/model_forms.py:637 netbox/ipam/forms/model_forms.py:679 -#: netbox/ipam/tables/ip.py:251 netbox/templates/ipam/vlan_edit.html:37 -#: netbox/templates/ipam/vlangroup.html:27 -msgid "VLAN Group" -msgstr "" - #: netbox/ipam/forms/model_forms.py:638 msgid "Child VLANs" msgstr ""