Jelajahi Sumber

Merge branch 'develop' into develop-2.9

Jeremy Stretch 5 tahun lalu
induk
melakukan
28a14cf5ae

+ 3 - 4
.github/ISSUE_TEMPLATE/bug_report.md

@@ -30,10 +30,9 @@ about: Report a reproducible bug in the current release of NetBox
     library such as pynetbox.
 -->
 ### Steps to Reproduce
-1. Disable any installed plugins by commenting out the `PLUGINS` setting in
-   `configuration.py`.
-2.
-3.
+1. 
+2. 
+3. 
 
 <!-- What did you expect to happen? -->
 ### Expected Behavior

+ 17 - 2
docs/configuration/optional-settings.md

@@ -86,7 +86,12 @@ CORS_ORIGIN_WHITELIST = [
 
 Default: False
 
-This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
+This setting enables debugging. This should be done only during development or troubleshooting. Note that only clients
+which access NetBox from a recognized [internal IP address](#internal_ips) will see debugging tools in the user
+interface.
+
+!!! warning
+    Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
 
 ---
 
@@ -184,6 +189,16 @@ HTTP_PROXIES = {
 
 ---
 
+## INTERNAL_IPS
+
+Default: `('127.0.0.1', '::1',)`
+
+A list of IP addresses recognized as internal to the system, used to control the display of debugging output. For
+example, the debugging toolbar will be viewable only when a client is accessing NetBox from one of the listed IP
+addresses (and [`DEBUG`](#debug) is true).
+
+---
+
 ## LOGGING
 
 By default, all messages of INFO severity or higher will be logged to the console. Additionally, if `DEBUG` is False and email access has been configured, ERROR and CRITICAL messages will be emailed to the users defined in `ADMINS`.
@@ -385,7 +400,7 @@ When remote user authentication is in use, this is the name of the HTTP header w
 
 ## REMOTE_AUTH_AUTO_CREATE_USER
 
-Default: `True`
+Default: `False`
 
 If true, NetBox will automatically create local accounts for users authenticated via a remote service. (Requires `REMOTE_AUTH_ENABLED`.)
 

+ 13 - 2
docs/release-notes/version-2.8.md

@@ -1,21 +1,32 @@
 # NetBox v2.8
 
-v2.8.5 (FUTURE)
+## v2.8.5 (2020-05-26)
 
 **Note:** The minimum required version of PostgreSQL is now 9.6.
 
+### Enhancements
+
+* [#4650](https://github.com/netbox-community/netbox/issues/4650) - Expose `INTERNAL_IPS` configuration parameter
+* [#4651](https://github.com/netbox-community/netbox/issues/4651) - Add `csrf_token` context for plugin templates
+* [#4652](https://github.com/netbox-community/netbox/issues/4652) - Add permissions context for plugin templates
+* [#4665](https://github.com/netbox-community/netbox/issues/4665) - Add NEMA L14 and L21 power port/outlet types
+* [#4672](https://github.com/netbox-community/netbox/issues/4672) - Set default color for rack and devices roles
+
 ### Bug Fixes
 
 * [#3304](https://github.com/netbox-community/netbox/issues/3304) - Fix caching invalidation issue related to device/virtual machine primary IP addresses
+* [#4525](https://github.com/netbox-community/netbox/issues/4525) - Allow passing initial data to custom script MultiObjectVar
 * [#4644](https://github.com/netbox-community/netbox/issues/4644) - Fix ordering of services table by parent
 * [#4646](https://github.com/netbox-community/netbox/issues/4646) - Correct UI link for reports with custom name
 * [#4647](https://github.com/netbox-community/netbox/issues/4647) - Fix caching invalidation issue related to assigning new IP addresses to interfaces
 * [#4648](https://github.com/netbox-community/netbox/issues/4648) - Fix bulk CSV import of child devices
 * [#4649](https://github.com/netbox-community/netbox/issues/4649) - Fix interface assignment for bulk-imported IP addresses
+* [#4676](https://github.com/netbox-community/netbox/issues/4676) - Set default value of `REMOTE_AUTH_AUTO_CREATE_USER` as `False` in docs
+* [#4684](https://github.com/netbox-community/netbox/issues/4684) - Respect `comments` field when importing device type in YAML/JSON format
 
 ---
 
-v2.8.4 (2020-05-13)
+## v2.8.4 (2020-05-13)
 
 ### Enhancements
 

+ 16 - 0
netbox/dcim/choices.py

@@ -276,6 +276,10 @@ class PowerPortTypeChoices(ChoiceSet):
     TYPE_NEMA_L620P = 'nema-l6-20p'
     TYPE_NEMA_L630P = 'nema-l6-30p'
     TYPE_NEMA_L650P = 'nema-l6-50p'
+    TYPE_NEMA_L1420P = 'nema-l14-20p'
+    TYPE_NEMA_L1430P = 'nema-l14-30p'
+    TYPE_NEMA_L2120P = 'nema-l21-20p'
+    TYPE_NEMA_L2130P = 'nema-l21-30p'
     # California style
     TYPE_CS6361C = 'cs6361c'
     TYPE_CS6365C = 'cs6365c'
@@ -337,6 +341,10 @@ class PowerPortTypeChoices(ChoiceSet):
             (TYPE_NEMA_L620P, 'NEMA L6-20P'),
             (TYPE_NEMA_L630P, 'NEMA L6-30P'),
             (TYPE_NEMA_L650P, 'NEMA L6-50P'),
+            (TYPE_NEMA_L1420P, 'NEMA L14-20P'),
+            (TYPE_NEMA_L1430P, 'NEMA L14-30P'),
+            (TYPE_NEMA_L2120P, 'NEMA L21-20P'),
+            (TYPE_NEMA_L2130P, 'NEMA L21-30P'),
         )),
         ('California Style', (
             (TYPE_CS6361C, 'CS6361C'),
@@ -405,6 +413,10 @@ class PowerOutletTypeChoices(ChoiceSet):
     TYPE_NEMA_L620R = 'nema-l6-20r'
     TYPE_NEMA_L630R = 'nema-l6-30r'
     TYPE_NEMA_L650R = 'nema-l6-50r'
+    TYPE_NEMA_L1420R = 'nema-l14-20r'
+    TYPE_NEMA_L1430R = 'nema-l14-30r'
+    TYPE_NEMA_L2120R = 'nema-l21-20r'
+    TYPE_NEMA_L2130R = 'nema-l21-30r'
     # California style
     TYPE_CS6360C = 'CS6360C'
     TYPE_CS6364C = 'CS6364C'
@@ -467,6 +479,10 @@ class PowerOutletTypeChoices(ChoiceSet):
             (TYPE_NEMA_L620R, 'NEMA L6-20R'),
             (TYPE_NEMA_L630R, 'NEMA L6-30R'),
             (TYPE_NEMA_L650R, 'NEMA L6-50R'),
+            (TYPE_NEMA_L1420R, 'NEMA L14-20R'),
+            (TYPE_NEMA_L1430R, 'NEMA L14-30R'),
+            (TYPE_NEMA_L2120R, 'NEMA L21-20R'),
+            (TYPE_NEMA_L2130R, 'NEMA L21-30R'),
         )),
         ('California Style', (
             (TYPE_CS6360C, 'CS6360C'),

+ 2 - 2
netbox/dcim/filters.py

@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
 from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
 from tenancy.filters import TenancyFilterSet
 from tenancy.models import Tenant
-from utilities.constants import COLOR_CHOICES
+from utilities.choices import ColorChoices
 from utilities.filters import (
     BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
     NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter,
@@ -1084,7 +1084,7 @@ class CableFilterSet(BaseFilterSet):
         choices=CableStatusChoices
     )
     color = django_filters.MultipleChoiceFilter(
-        choices=COLOR_CHOICES
+        choices=ColorChoices
     )
     device_id = MultiValueNumberFilter(
         method='filter_device'

+ 1 - 0
netbox/dcim/forms.py

@@ -932,6 +932,7 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm):
         model = DeviceType
         fields = [
             'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
+            'comments',
         ]
 
 

+ 24 - 0
netbox/dcim/migrations/0106_role_default_color.py

@@ -0,0 +1,24 @@
+# Generated by Django 3.0.6 on 2020-05-26 13:33
+
+from django.db import migrations
+import utilities.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0105_interface_name_collation'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='devicerole',
+            name='color',
+            field=utilities.fields.ColorField(default='9e9e9e', max_length=6),
+        ),
+        migrations.AlterField(
+            model_name='rackrole',
+            name='color',
+            field=utilities.fields.ColorField(default='9e9e9e', max_length=6),
+        ),
+    ]

+ 7 - 2
netbox/dcim/models/__init__.py

@@ -23,6 +23,7 @@ from dcim.fields import ASNField
 from dcim.elevations import RackElevationSVG
 from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
 from extras.utils import extras_features
+from utilities.choices import ColorChoices
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.models import ChangeLoggedModel
 from utilities.utils import serialize_object, to_meters
@@ -379,7 +380,9 @@ class RackRole(ChangeLoggedModel):
     slug = models.SlugField(
         unique=True
     )
-    color = ColorField()
+    color = ColorField(
+        default=ColorChoices.COLOR_GREY
+    )
     description = models.CharField(
         max_length=200,
         blank=True,
@@ -1190,7 +1193,9 @@ class DeviceRole(ChangeLoggedModel):
     slug = models.SlugField(
         unique=True
     )
-    color = ColorField()
+    color = ColorField(
+        default=ColorChoices.COLOR_GREY
+    )
     vm_role = models.BooleanField(
         default=True,
         verbose_name='VM Role',

+ 2 - 0
netbox/dcim/tests/test_views.py

@@ -366,6 +366,7 @@ manufacturer: Generic
 model: TEST-1000
 slug: test-1000
 u_height: 2
+comments: test comment
 console-ports:
   - name: Console Port 1
     type: de-9
@@ -456,6 +457,7 @@ device-bays:
         self.assertHttpStatus(response, 200)
 
         dt = DeviceType.objects.get(model='TEST-1000')
+        self.assertEqual(dt.comments, 'test comment')
 
         # Verify all of the components were created
         self.assertEqual(dt.consoleport_templates.count(), 3)

+ 3 - 3
netbox/extras/forms.py

@@ -432,11 +432,11 @@ class ScriptForm(BootstrapMixin, forms.Form):
 
     def __init__(self, vars, *args, commit_default=True, **kwargs):
 
-        super().__init__(*args, **kwargs)
-
         # Dynamically populate fields for variables
         for name, var in vars.items():
-            self.fields[name] = var.as_field()
+            self.base_fields[name] = var.as_field()
+
+        super().__init__(*args, **kwargs)
 
         # Toggle default commit behavior based on Meta option
         if not commit_default:

+ 2 - 1
netbox/extras/models/tags.py

@@ -3,6 +3,7 @@ from django.urls import reverse
 from django.utils.text import slugify
 from taggit.models import TagBase, GenericTaggedItemBase
 
+from utilities.choices import ColorChoices
 from utilities.fields import ColorField
 from utilities.models import ChangeLoggedModel
 
@@ -13,7 +14,7 @@ from utilities.models import ChangeLoggedModel
 
 class Tag(TagBase, ChangeLoggedModel):
     color = ColorField(
-        default='9e9e9e'
+        default=ColorChoices.COLOR_GREY
     )
     description = models.CharField(
         max_length=200,

+ 2 - 0
netbox/extras/templatetags/plugins.py

@@ -18,6 +18,8 @@ def _get_registered_content(obj, method, template_context):
         'object': obj,
         'request': template_context['request'],
         'settings': template_context['settings'],
+        'csrf_token': template_context['csrf_token'],
+        'perms': template_context['perms'],
     }
 
     model_name = obj._meta.label_lower

+ 4 - 0
netbox/netbox/configuration.example.py

@@ -132,6 +132,10 @@ EXEMPT_VIEW_PERMISSIONS = [
 #     'https': 'http://10.10.1.10:1080',
 # }
 
+# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
+# NetBox from an internal IP.
+INTERNAL_IPS = ('127.0.0.1', '::1')
+
 # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
 #   https://docs.djangoproject.com/en/stable/topics/logging/
 LOGGING = {}

+ 1 - 9
netbox/netbox/settings.py

@@ -78,6 +78,7 @@ EMAIL = getattr(configuration, 'EMAIL', {})
 ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
 EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
 HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
+INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
 LOGGING = getattr(configuration, 'LOGGING', {})
 LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
 LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
@@ -615,15 +616,6 @@ RQ_QUEUES = {
     'check_releases': RQ_PARAMS,
 }
 
-#
-# Django debug toolbar
-#
-
-INTERNAL_IPS = (
-    '127.0.0.1',
-    '::1',
-)
-
 
 #
 # NetBox internal settings

+ 64 - 0
netbox/utilities/choices.py

@@ -80,6 +80,70 @@ def unpack_grouped_choices(choices):
     return unpacked_choices
 
 
+#
+# Generic color choices
+#
+
+class ColorChoices(ChoiceSet):
+    COLOR_DARK_RED = 'aa1409'
+    COLOR_RED = 'f44336'
+    COLOR_PINK = 'e91e63'
+    COLOR_ROSE = 'ffe4e1'
+    COLOR_FUCHSIA = 'ff66ff'
+    COLOR_PURPLE = '9c27b0'
+    COLOR_DARK_PURPLE = '673ab7'
+    COLOR_INDIGO = '3f51b5'
+    COLOR_BLUE = '2196f3'
+    COLOR_LIGHT_BLUE = '03a9f4'
+    COLOR_CYAN = '00bcd4'
+    COLOR_TEAL = '009688'
+    COLOR_AQUA = '00ffff'
+    COLOR_DARK_GREEN = '2f6a31'
+    COLOR_GREEN = '4caf50'
+    COLOR_LIGHT_GREEN = '8bc34a'
+    COLOR_LIME = 'cddc39'
+    COLOR_YELLOW = 'ffeb3b'
+    COLOR_AMBER = 'ffc107'
+    COLOR_ORANGE = 'ff9800'
+    COLOR_DARK_ORANGE = 'ff5722'
+    COLOR_BROWN = '795548'
+    COLOR_LIGHT_GREY = 'c0c0c0'
+    COLOR_GREY = '9e9e9e'
+    COLOR_DARK_GREY = '607d8b'
+    COLOR_BLACK = '111111'
+    COLOR_WHITE = 'ffffff'
+
+    CHOICES = (
+        (COLOR_DARK_RED, 'Dark red'),
+        (COLOR_RED, 'Red'),
+        (COLOR_PINK, 'Pink'),
+        (COLOR_ROSE, 'Rose'),
+        (COLOR_FUCHSIA, 'Fuchsia'),
+        (COLOR_PURPLE, 'Purple'),
+        (COLOR_DARK_PURPLE, 'Dark purple'),
+        (COLOR_INDIGO, 'Indigo'),
+        (COLOR_BLUE, 'Blue'),
+        (COLOR_LIGHT_BLUE, 'Light blue'),
+        (COLOR_CYAN, 'Cyan'),
+        (COLOR_TEAL, 'Teal'),
+        (COLOR_AQUA, 'Aqua'),
+        (COLOR_DARK_GREEN, 'Dark green'),
+        (COLOR_GREEN, 'Green'),
+        (COLOR_LIGHT_GREEN, 'Light green'),
+        (COLOR_LIME, 'Lime'),
+        (COLOR_YELLOW, 'Yellow'),
+        (COLOR_AMBER, 'Amber'),
+        (COLOR_ORANGE, 'Orange'),
+        (COLOR_DARK_ORANGE, 'Dark orange'),
+        (COLOR_BROWN, 'Brown'),
+        (COLOR_LIGHT_GREY, 'Light grey'),
+        (COLOR_GREY, 'Grey'),
+        (COLOR_DARK_GREY, 'Dark grey'),
+        (COLOR_BLACK, 'Black'),
+        (COLOR_WHITE, 'White'),
+    )
+
+
 #
 # Button color choices
 #

+ 0 - 31
netbox/utilities/constants.py

@@ -1,34 +1,3 @@
-COLOR_CHOICES = (
-    ('aa1409', 'Dark red'),
-    ('f44336', 'Red'),
-    ('e91e63', 'Pink'),
-    ('ffe4e1', 'Rose'),
-    ('ff66ff', 'Fuschia'),
-    ('9c27b0', 'Purple'),
-    ('673ab7', 'Dark purple'),
-    ('3f51b5', 'Indigo'),
-    ('2196f3', 'Blue'),
-    ('03a9f4', 'Light blue'),
-    ('00bcd4', 'Cyan'),
-    ('009688', 'Teal'),
-    ('00ffff', 'Aqua'),
-    ('2f6a31', 'Dark green'),
-    ('4caf50', 'Green'),
-    ('8bc34a', 'Light green'),
-    ('cddc39', 'Lime'),
-    ('ffeb3b', 'Yellow'),
-    ('ffc107', 'Amber'),
-    ('ff9800', 'Orange'),
-    ('ff5722', 'Dark orange'),
-    ('795548', 'Brown'),
-    ('c0c0c0', 'Light grey'),
-    ('9e9e9e', 'Grey'),
-    ('607d8b', 'Dark grey'),
-    ('111111', 'Black'),
-    ('ffffff', 'White'),
-)
-
-
 #
 # Filter lookup expressions
 #

+ 14 - 6
netbox/utilities/forms.py

@@ -14,8 +14,7 @@ from django.forms import BoundField
 from django.forms.models import fields_for_model
 from django.urls import reverse
 
-from .choices import unpack_grouped_choices
-from .constants import *
+from .choices import ColorChoices, unpack_grouped_choices
 from .validators import EnhancedURLValidator
 
 NUMERIC_EXPANSION_PATTERN = r'\[((?:\d+[?:,-])+\d+)\]'
@@ -163,7 +162,7 @@ class ColorSelect(forms.Select):
     option_template_name = 'widgets/colorselect_option.html'
 
     def __init__(self, *args, **kwargs):
-        kwargs['choices'] = add_blank_choice(COLOR_CHOICES)
+        kwargs['choices'] = add_blank_choice(ColorChoices)
         super().__init__(*args, **kwargs)
         self.attrs['class'] = 'netbox-select2-color-picker'
 
@@ -607,15 +606,18 @@ class DynamicModelChoiceMixin:
     filter = django_filters.ModelChoiceFilter
     widget = APISelect
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+    def _get_initial_value(self, initial_data, field_name):
+        return initial_data.get(field_name)
 
     def get_bound_field(self, form, field_name):
         bound_field = BoundField(form, self, field_name)
 
+        # Override initial() to allow passing multiple values
+        bound_field.initial = self._get_initial_value(form.initial, field_name)
+
         # Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options
         # will be populated on-demand via the APISelect widget.
-        data = self.prepare_value(bound_field.data or bound_field.initial)
+        data = bound_field.value()
         if data:
             filter = self.filter(field_name=self.to_field_name or 'pk', queryset=self.queryset)
             self.queryset = filter.filter(self.queryset, data)
@@ -648,6 +650,12 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip
     filter = django_filters.ModelMultipleChoiceFilter
     widget = APISelectMultiple
 
+    def _get_initial_value(self, initial_data, field_name):
+        # If a QueryDict has been passed as initial form data, get *all* listed values
+        if hasattr(initial_data, 'getlist'):
+            return initial_data.getlist(field_name)
+        return initial_data.get(field_name)
+
 
 class LaxURLField(forms.URLField):
     """