Просмотр исходного кода

Merge pull request #11219 from netbox-community/develop

Release v3.4.1
Jeremy Stretch 3 лет назад
Родитель
Сommit
27c71b8ec0

+ 1 - 1
.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

+ 1 - 1
.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

+ 11 - 0
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/`

+ 19 - 0
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"

+ 10 - 3
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
+            ]

+ 1 - 2
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?")

+ 3 - 0
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',

+ 1 - 1
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.

+ 3 - 4
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
 

+ 3 - 2
netbox/netbox/tables/columns.py

@@ -537,14 +537,15 @@ class MPTTColumn(tables.TemplateColumn):
     """
     template_code = """
         {% load helpers %}
-        {% for i in record.level|as_range %}<i class="mdi mdi-circle-small"></i>{% endfor %}
+        {% if not table.order_by %}
+          {% for i in record.level|as_range %}<i class="mdi mdi-circle-small"></i>{% endfor %}
+        {% endif %}
         <a href="{{ record.get_absolute_url }}">{{ record.name }}</a>
     """
 
     def __init__(self, *args, **kwargs):
         super().__init__(
             template_code=self.template_code,
-            orderable=False,
             attrs={'td': {'class': 'text-nowrap'}},
             *args,
             **kwargs

+ 1 - 0
netbox/templates/extras/htmx/script_result.html

@@ -1,3 +1,4 @@
+{% load humanize %}
 {% load helpers %}
 {% load log_levels %}
 

+ 3 - 3
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" %}
+
       <form method="post" class="form form-horizontal">
         {% csrf_token %}
         {# "Select all" form #}
@@ -96,9 +99,6 @@ Context:
           </div>
         {% endif %}
 
-        {# Object table controls #}
-        {% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %}
-
         <div class="form form-horizontal">
           {% csrf_token %}
           <input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />

+ 2 - 1
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):