Quellcode durchsuchen

Merge branch 'develop' into feature

jeremystretch vor 3 Jahren
Ursprung
Commit
10352ff5ad

+ 2 - 0
.github/workflows/ci.yml

@@ -1,5 +1,7 @@
 name: CI
 on: [push, pull_request]
+permissions:
+  contents: read
 jobs:
   build:
     runs-on: ubuntu-latest

+ 5 - 1
.github/workflows/lock.yml

@@ -4,6 +4,11 @@ name: 'Lock threads'
 on:
   schedule:
     - cron: '0 3 * * *'
+  workflow_dispatch:
+
+permissions:
+  issues: write
+  pull-requests: write
 
 jobs:
   lock:
@@ -11,7 +16,6 @@ jobs:
     steps:
       - uses: dessant/lock-threads@v3
         with:
-          github-token: ${{ github.token }}
           issue-inactive-days: 90
           pr-inactive-days: 30
           issue-lock-reason: 'resolved'

+ 8 - 1
.github/workflows/stale.yml

@@ -1,14 +1,21 @@
 # close-stale-issues (https://github.com/marketplace/actions/close-stale-issues)
 name: 'Close stale issues/PRs'
+
 on:
   schedule:
     - cron: '0 4 * * *'
+  workflow_dispatch:
+
+permissions:
+  issues: write
+  pull-requests: write
 
 jobs:
   stale:
+
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v5
+      - uses: actions/stale@v6
         with:
           close-issue-message: >
             This issue has been automatically closed due to lack of activity. In an

+ 2 - 2
docs/_theme/main.html

@@ -2,8 +2,8 @@
 
 {% block site_meta %}
   {{ super() }}
-  {# Disable search indexing unless we're building for ReadTheDocs #}
-  {% if not config.extra.readthedocs %}
+  {# Disable search indexing unless we're building for ReadTheDocs (see #10496) #}
+  {% if page.canonical_url != 'https://docs.netbox.dev/' %}
     <meta name="robots" content="noindex">
   {% endif %}
 {% endblock %}

+ 10 - 0
docs/release-notes/version-3.3.md

@@ -2,17 +2,27 @@
 
 ## v3.3.5 (FUTURE)
 
+### Enhancements
+
+* [#10465](https://github.com/netbox-community/netbox/issues/10465) - Improve formatting of device heights and rack positions
+
 ### Bug Fixes
 
 * [#9497](https://github.com/netbox-community/netbox/issues/9497) - Adjust non-racked device filter on site and location detailed view
+* [#10408](https://github.com/netbox-community/netbox/issues/10408) - Fix validation when attempting to add redundant contact assignments
 * [#10435](https://github.com/netbox-community/netbox/issues/10435) - Fix exception when filtering VLANs by virtual machine with no cluster assigned
 * [#10439](https://github.com/netbox-community/netbox/issues/10439) - Fix form widget styling for DeviceType airflow field
+* [#10445](https://github.com/netbox-community/netbox/issues/10445) - Avoid rounding virtual machine memory values
+* [#10461](https://github.com/netbox-community/netbox/issues/10461) - Enable filtering by read-only custom fields in the UI
+* [#10470](https://github.com/netbox-community/netbox/issues/10470) - Omit read-only custom fields from CSV import forms
+* [#10480](https://github.com/netbox-community/netbox/issues/10480) - Cable trace SVG links should not force a new window
 
 ---
 
 ## v3.3.4 (2022-09-16)
 
 ### Bug Fixes
+
 * [#10383](https://github.com/netbox-community/netbox/issues/10383) - Fix assignment of component templates to module types via web UI
 * [#10387](https://github.com/netbox-community/netbox/issues/10387) - Fix `MultiValueDictKeyError` exception when editing a device interface
 

+ 0 - 1
mkdocs.yml

@@ -38,7 +38,6 @@ plugins:
             show_root_toc_entry: false
             show_source: false
 extra:
-  readthedocs: !ENV READTHEDOCS
   social:
     - icon: fontawesome/brands/github
       link: https://github.com/netbox-community/netbox

+ 1 - 1
netbox/dcim/svg/cables.py

@@ -35,7 +35,7 @@ class Node(Hyperlink):
     """
 
     def __init__(self, position, width, url, color, labels, radius=10, **extra):
-        super(Node, self).__init__(href=url, target='_blank', **extra)
+        super(Node, self).__init__(href=url, target='_parent', **extra)
 
         x, y = position
 

+ 2 - 1
netbox/dcim/svg/racks.py

@@ -9,6 +9,7 @@ from svgwrite.text import Text
 from django.conf import settings
 from django.core.exceptions import FieldError
 from django.db.models import Q
+from django.template.defaultfilters import floatformat
 from django.urls import reverse
 from django.utils.http import urlencode
 
@@ -41,7 +42,7 @@ def get_device_description(device):
         device.device_role,
         device.device_type.manufacturer.name,
         device.device_type.model,
-        device.device_type.u_height,
+        floatformat(device.device_type.u_height),
         device.asset_tag or '',
         device.serial or ''
     )

+ 3 - 0
netbox/dcim/tables/devicetypes.py

@@ -85,6 +85,9 @@ class DeviceTypeTable(NetBoxTable):
     tags = columns.TagColumn(
         url_name='dcim:devicetype_list'
     )
+    u_height = columns.TemplateColumn(
+        template_code='{{ value|floatformat }}'
+    )
     weight = columns.TemplateColumn(
         template_code=DEVICE_WEIGHT,
         order_by=('_abs_weight', 'weight_unit')

+ 3 - 8
netbox/extras/forms/customfields.py

@@ -34,7 +34,9 @@ class CustomFieldsMixin:
         return ContentType.objects.get_for_model(self.model)
 
     def _get_custom_fields(self, content_type):
-        return CustomField.objects.filter(content_types=content_type)
+        return CustomField.objects.filter(content_types=content_type).exclude(
+            ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN
+        )
 
     def _get_form_field(self, customfield):
         return customfield.to_form_field()
@@ -50,13 +52,6 @@ class CustomFieldsMixin:
             field_name = f'cf_{customfield.name}'
             self.fields[field_name] = self._get_form_field(customfield)
 
-            if customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
-                self.fields[field_name].disabled = True
-                if self.fields[field_name].help_text:
-                    self.fields[field_name].help_text += '<br />'
-                self.fields[field_name].help_text += '<i class="mdi mdi-alert-circle-outline"></i> ' \
-                                                     'Field is set to read-only.'
-
             # Annotate the field in the list of CustomField form fields
             self.custom_fields[field_name] = customfield
             if customfield.group_name not in self.custom_field_groups:

+ 8 - 1
netbox/extras/models/customfields.py

@@ -295,12 +295,13 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
             return model.objects.filter(pk__in=value)
         return value
 
-    def to_form_field(self, set_initial=True, enforce_required=True, for_csv_import=False):
+    def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibility=True, for_csv_import=False):
         """
         Return a form field suitable for setting a CustomField's value for an object.
 
         set_initial: Set initial data for the field. This should be False when generating a field for bulk editing.
         enforce_required: Honor the value of CustomField.required. Set to False for filtering/bulk editing.
+        enforce_visibility: Honor the value of CustomField.ui_visibility. Set to False for filtering.
         for_csv_import: Return a form field suitable for bulk import of objects in CSV format.
         """
         initial = self.default if set_initial else None
@@ -407,6 +408,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge
         if self.description:
             field.help_text = escape(self.description)
 
+        # Annotate read-only fields
+        if enforce_visibility and self.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_ONLY:
+            field.disabled = True
+            prepend = '<br />' if field.help_text else ''
+            field.help_text += f'{prepend}<i class="mdi mdi-alert-circle-outline"></i> Field is set to read-only.'
+
         return field
 
     def to_filter(self, lookup_expr=None):

+ 8 - 3
netbox/netbox/forms/base.py

@@ -2,7 +2,7 @@ from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 
-from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
+from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
 from extras.forms.customfields import CustomFieldsMixin
 from extras.models import CustomField, Tag
 from utilities.forms import BootstrapMixin, CSVModelForm
@@ -63,6 +63,11 @@ class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm):
     """
     tags = None  # Temporary fix in lieu of tag import support (see #9158)
 
+    def _get_custom_fields(self, content_type):
+        return CustomField.objects.filter(content_types=content_type).filter(
+            ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE
+        )
+
     def _get_form_field(self, customfield):
         return customfield.to_form_field(for_csv_import=True)
 
@@ -125,10 +130,10 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
     )
 
     def _get_custom_fields(self, content_type):
-        return CustomField.objects.filter(content_types=content_type).exclude(
+        return super()._get_custom_fields(content_type).exclude(
             Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
             Q(type=CustomFieldTypeChoices.TYPE_JSON)
         )
 
     def _get_form_field(self, customfield):
-        return customfield.to_form_field(set_initial=False, enforce_required=False)
+        return customfield.to_form_field(set_initial=False, enforce_required=False, enforce_visibility=False)

+ 2 - 2
netbox/templates/dcim/device.html

@@ -66,7 +66,7 @@
                                     {% with object.parent_bay.device as parent %}
                                         {{ parent|linkify }} / {{ object.parent_bay }}
                                         {% if parent.position %}
-                                            (U{{ parent.position }} / {{ parent.get_face_display }})
+                                            (U{{ parent.position|floatformat }} / {{ parent.get_face_display }})
                                         {% endif %}
                                     {% endwith %}
                                 {% elif object.rack and object.position %}
@@ -90,7 +90,7 @@
                         <tr>
                             <th scope="row">Device Type</th>
                             <td>
-                                {{ object.device_type|linkify:"get_full_name" }} ({{ object.device_type.u_height }}U)
+                                {{ object.device_type|linkify:"get_full_name" }} ({{ object.device_type.u_height|floatformat }}U)
                             </td>
                         </tr>
                         <tr>

+ 1 - 1
netbox/templates/dcim/devicetype.html

@@ -29,7 +29,7 @@
                         </tr>
                         <tr>
                             <td>Height (U)</td>
-                            <td>{{ object.u_height }}</td>
+                            <td>{{ object.u_height|floatformat }}</td>
                         </tr>
                         <tr>
                             <td>Full Depth</td>

+ 3 - 0
netbox/templates/tenancy/contactassignment_edit.html

@@ -3,6 +3,9 @@
 {% load form_helpers %}
 
 {% block form %}
+  {% for field in form.hidden_fields %}
+    {{ field }}
+  {% endfor %}
   <div class="field-group my-5">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Contact Assignment</h5>

+ 3 - 1
netbox/tenancy/forms/models.py

@@ -119,8 +119,10 @@ class ContactAssignmentForm(BootstrapMixin, forms.ModelForm):
     class Meta:
         model = ContactAssignment
         fields = (
-            'group', 'contact', 'role', 'priority',
+            'content_type', 'object_id', 'group', 'contact', 'role', 'priority',
         )
         widgets = {
+            'content_type': forms.HiddenInput(),
+            'object_id': forms.HiddenInput(),
             'priority': StaticSelect(),
         }

+ 2 - 2
netbox/utilities/templatetags/helpers.py

@@ -73,9 +73,9 @@ def humanize_megabytes(mb):
     """
     if not mb:
         return ''
-    if mb >= 1048576:
+    if not mb % 1048576:  # 1024^2
         return f'{int(mb / 1048576)} TB'
-    if mb >= 1024:
+    if not mb % 1024:
         return f'{int(mb / 1024)} GB'
     return f'{mb} MB'