diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 09f0d5d93..89cb0b88e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.0 + placeholder: v3.4.1 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index d94f9e9de..ed84ff821 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.0 + placeholder: v3.4.1 validations: required: true - type: dropdown diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 3756b6a83..5a7c8bebd 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -12,6 +12,17 @@ BASE_PATH = 'netbox/' --- +## DEFAULT_LANGUAGE + +Default: `en-us` (US English) + +Defines the default preferred language/locale for requests that do not specify one. This is used to alter e.g. the display of dates and numbers to fit the user's locale. See [this list](http://www.i18nguy.com/unicode/language-identifiers.html) of standard language codes. (This parameter maps to Django's [`LANGUAGE_CODE`](https://docs.djangoproject.com/en/stable/ref/settings/#language-code) internal setting.) + +!!! note + Altering this parameter will *not* change the language used in NetBox. We hope to provide translation support in a future NetBox release. + +--- + ## DOCS_ROOT Default: `$INSTALL_ROOT/docs/` diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 0527fee3f..82d0b151c 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,5 +1,24 @@ # NetBox v3.4 +## v3.4.1 (2022-12-16) + +### Enhancements + +* [#9971](https://github.com/netbox-community/netbox/issues/9971) - Enable ordering of nested group models by name +* [#11214](https://github.com/netbox-community/netbox/issues/11214) - Introduce the `DEFAULT_LANGUAGE` configuration parameter + +### Bug Fixes + +* [#11175](https://github.com/netbox-community/netbox/issues/11175) - Fix cloning of fields containing special characters +* [#11178](https://github.com/netbox-community/netbox/issues/11178) - Pressing enter in quick search box should not trigger bulk operations +* [#11184](https://github.com/netbox-community/netbox/issues/11184) - Correct visualization of cable path which splits across multiple circuit terminations +* [#11185](https://github.com/netbox-community/netbox/issues/11185) - Fix TemplateSyntaxError when viewing custom script results +* [#11189](https://github.com/netbox-community/netbox/issues/11189) - Fix localization of dates & numbers +* [#11205](https://github.com/netbox-community/netbox/issues/11205) - Correct cloning behavior for recursively-nested models +* [#11206](https://github.com/netbox-community/netbox/issues/11206) - Avoid clearing assigned groups if `REMOTE_AUTH_DEFAULT_GROUPS` is invalid + +--- + ## v3.4.0 (2022-12-14) !!! warning "PostgreSQL 11 Required" diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index fc9d2d7a1..48c1f92db 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -567,11 +567,12 @@ class CablePath(models.Model): elif isinstance(remote_terminations[0], CircuitTermination): # Follow a CircuitTermination to its corresponding CircuitTermination (A to Z or vice versa) - term_side = remote_terminations[0].term_side - assert all(ct.term_side == term_side for ct in remote_terminations[1:]) + if len(remote_terminations) > 1: + is_split = True + break circuit_termination = CircuitTermination.objects.filter( circuit=remote_terminations[0].circuit, - term_side='Z' if term_side == 'A' else 'A' + term_side='Z' if remote_terminations[0].term_side == 'A' else 'A' ).first() if circuit_termination is None: break @@ -685,6 +686,7 @@ class CablePath(models.Model): """ Return all available next segments in a split cable path. """ + from circuits.models import CircuitTermination nodes = self.path_objects[-1] # RearPort splitting to multiple FrontPorts with no stack position @@ -694,3 +696,8 @@ class CablePath(models.Model): # RearPorts connected to different cables elif type(nodes[0]) is FrontPort: return RearPort.objects.filter(pk__in=[fp.rear_port_id for fp in nodes]) + # Cable terminating to multiple CircuitTerminations + elif type(nodes[0]) is CircuitTermination: + return [ + ct.get_peer_termination() for ct in nodes + ] diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 89d71b815..798cb80e2 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -382,5 +382,4 @@ def user_default_groups_handler(backend, user, response, *args, **kwargs): if group_list: user.groups.add(*group_list) else: - user.groups.clear() - logger.debug(f"Stripping user {user} from Groups") + logger.info(f"No valid group assignments for {user} - REMOTE_AUTH_DEFAULT_GROUPS may be incorrectly set?") diff --git a/netbox/netbox/configuration_example.py b/netbox/netbox/configuration_example.py index 5e057d54a..f298b35fe 100644 --- a/netbox/netbox/configuration_example.py +++ b/netbox/netbox/configuration_example.py @@ -106,6 +106,9 @@ CORS_ORIGIN_REGEX_WHITELIST = [ # on a production system. DEBUG = False +# Set the default preferred language/locale +DEFAULT_LANGUAGE = 'en-us' + # Email settings EMAIL = { 'SERVER': 'localhost', diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index d3f3e78bc..a4c8e0ec2 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -75,7 +75,7 @@ class PrimaryModel(NetBoxModel): abstract = True -class NestedGroupModel(NetBoxFeatureSet, MPTTModel): +class NestedGroupModel(CloningMixin, NetBoxFeatureSet, MPTTModel): """ Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest recursively using MPTT. Within each parent, each child instance must have a unique name. diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index eb516a8f9..bc1b713b1 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -24,7 +24,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.4.0' +VERSION = '3.4.1' # Hostname HOSTNAME = platform.node() @@ -94,6 +94,7 @@ FIELD_CHOICES = getattr(configuration, 'FIELD_CHOICES', {}) HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None) INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1')) JINJA2_FILTERS = getattr(configuration, 'JINJA2_FILTERS', {}) +LANGUAGE_CODE = getattr(configuration, 'DEFAULT_LANGUAGE', 'en-us') LOGGING = getattr(configuration, 'LOGGING', {}) LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False) LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False) @@ -339,6 +340,7 @@ MIDDLEWARE = [ 'django_prometheus.middleware.PrometheusBeforeMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -385,9 +387,6 @@ AUTHENTICATION_BACKENDS = [ 'netbox.authentication.ObjectPermissionBackend', ] -# Internationalization -LANGUAGE_CODE = 'en-us' - # Time zones USE_TZ = True diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 2f5c228e4..519f6021e 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -537,14 +537,15 @@ class MPTTColumn(tables.TemplateColumn): """ template_code = """ {% load helpers %} - {% for i in record.level|as_range %}{% endfor %} + {% if not table.order_by %} + {% for i in record.level|as_range %}{% endfor %} + {% endif %} {{ record.name }} """ def __init__(self, *args, **kwargs): super().__init__( template_code=self.template_code, - orderable=False, attrs={'td': {'class': 'text-nowrap'}}, *args, **kwargs diff --git a/netbox/templates/extras/htmx/script_result.html b/netbox/templates/extras/htmx/script_result.html index ca2d278d3..fe06b8309 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -1,3 +1,4 @@ +{% load humanize %} {% load helpers %} {% load log_levels %} diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index 906b4ed0a..8b3e317c0 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -70,6 +70,9 @@ Context: {% applied_filters model filter_form request.GET %} {% endif %} + {# Object table controls #} + {% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %} +
{% csrf_token %} {# "Select all" form #} @@ -96,9 +99,6 @@ Context: {% endif %} - {# Object table controls #} - {% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %} -
{% csrf_token %} diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index a3609b478..f635fc728 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -19,6 +19,7 @@ from dcim.choices import CableLengthUnitChoices, WeightUnitChoices from extras.plugins import PluginConfig from extras.utils import is_taggable from netbox.config import get_config +from urllib.parse import urlencode from utilities.constants import HTTP_REQUEST_META_SAFE_COPY @@ -353,7 +354,7 @@ def prepare_cloned_fields(instance): params.append((key, '')) # Return a QueryDict with the parameters - return QueryDict('&'.join([f'{k}={v}' for k, v in params]), mutable=True) + return QueryDict(urlencode(params), mutable=True) def shallow_compare_dict(source_dict, destination_dict, exclude=None):