mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into develop-2.10
This commit is contained in:
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,11 +7,9 @@ about: Report a reproducible bug in the current release of NetBox
|
|||||||
<!--
|
<!--
|
||||||
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
||||||
|
|
||||||
This form is only for reproducible bugs. If you need assistance with
|
This form is only for reporting reproducible bugs. If you need assistance
|
||||||
NetBox installation, or if you have a general question, DO NOT open an
|
with NetBox installation, or if you have a general question, please start a
|
||||||
issue. Instead, post to our mailing list:
|
discussion instead: https://github.com/netbox-community/netbox/discussions
|
||||||
|
|
||||||
https://groups.google.com/g/netbox-discuss
|
|
||||||
|
|
||||||
Please describe the environment in which you are running NetBox. Be sure
|
Please describe the environment in which you are running NetBox. Be sure
|
||||||
that you are running an unmodified instance of the latest stable release
|
that you are running an unmodified instance of the latest stable release
|
||||||
|
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -8,10 +8,8 @@ about: Propose a new NetBox feature or enhancement
|
|||||||
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.
|
||||||
|
|
||||||
This form is only for proposing specific new features or enhancements.
|
This form is only for proposing specific new features or enhancements.
|
||||||
If you have a general idea or question, please post to our mailing list
|
If you have a general idea or question, please start a discussion instead:
|
||||||
instead of opening an issue:
|
https://github.com/netbox-community/netbox/discussions
|
||||||
|
|
||||||
https://groups.google.com/g/netbox-discuss
|
|
||||||
|
|
||||||
NOTE: Due to an excessive backlog of feature requests, we are not currently
|
NOTE: Due to an excessive backlog of feature requests, we are not currently
|
||||||
accepting any proposals which significantly extend NetBox's feature scope.
|
accepting any proposals which significantly extend NetBox's feature scope.
|
||||||
|
15
README.md
15
README.md
@ -37,18 +37,21 @@ or join us in the **#netbox** Slack channel on [NetworkToCode](https://networkto
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Please see [the documentation](http://netbox.readthedocs.io/en/stable/) for
|
Please see [the documentation](http://netbox.readthedocs.io/en/stable/) for
|
||||||
instructions on installing NetBox. To upgrade NetBox, please download the [latest release](https://github.com/netbox-community/netbox/releases)
|
instructions on installing NetBox. To upgrade NetBox, please download the
|
||||||
and run `upgrade.sh`.
|
[latest release](https://github.com/netbox-community/netbox/releases) and
|
||||||
|
run `upgrade.sh`.
|
||||||
|
|
||||||
## Providing Feedback
|
## Providing Feedback
|
||||||
|
|
||||||
Feature requests and bug reports must be submitted as GiHub issues. (Please be
|
The best platform for general feedback, assistance, and other discussion is our
|
||||||
sure to use the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose).)
|
[GitHub discussions](https://github.com/netbox-community/netbox/discussions).
|
||||||
For general discussion, please consider joining our [mailing list](https://groups.google.com/g/netbox-discuss).
|
To report a bug or request a specific feature, please open a GitHub issue using
|
||||||
|
the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose).
|
||||||
|
|
||||||
If you are interested in contributing to the development of NetBox, please read
|
If you are interested in contributing to the development of NetBox, please read
|
||||||
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
||||||
|
|
||||||
## Related projects
|
## Related projects
|
||||||
|
|
||||||
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for a list of relevant community projects.
|
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions)
|
||||||
|
for a list of relevant community projects.
|
||||||
|
@ -7,7 +7,8 @@ NetBox is maintained as a [GitHub project](https://github.com/netbox-community/n
|
|||||||
Communication among developers should always occur via public channels:
|
Communication among developers should always occur via public channels:
|
||||||
|
|
||||||
* [GitHub issues](https://github.com/netbox-community/netbox/issues) - All feature requests, bug reports, and other substantial changes to the code base **must** be documented in an issue.
|
* [GitHub issues](https://github.com/netbox-community/netbox/issues) - All feature requests, bug reports, and other substantial changes to the code base **must** be documented in an issue.
|
||||||
* [The mailing list](https://groups.google.com/g/netbox-discuss) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
|
* [GitHub discussions](https://github.com/netbox-community/netbox/discussions) - The preferred forum for general discussion and support issues. Ideal for shaping a feature request prior to submitting an issue.
|
||||||
|
* [The mailing list](https://groups.google.com/g/netbox-discuss) - An alternative forum for general discussion (GitHub is preferred).
|
||||||
* [#netbox on NetworkToCode](http://slack.networktocode.com/) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained long.
|
* [#netbox on NetworkToCode](http://slack.networktocode.com/) - Good for quick chats. Avoid any discussion that might need to be referenced later on, as the chat history is not retained long.
|
||||||
|
|
||||||
## Governance
|
## Governance
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
# NetBox v2.9
|
# NetBox v2.9
|
||||||
|
|
||||||
## v2.9.11 (FUTURE)
|
## v2.9.11 (2020-12-11)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#5424](https://github.com/netbox-community/netbox/issues/5424) - Allow passing Python code to `nbshell` using `--command`
|
* [#5424](https://github.com/netbox-community/netbox/issues/5424) - Allow passing Python code to `nbshell` using `--command`
|
||||||
|
* [#5439](https://github.com/netbox-community/netbox/issues/5439) - Add CS and SN fiber port types
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* [#5383](https://github.com/netbox-community/netbox/issues/5383) - Fix setting user password via REST API
|
* [#5383](https://github.com/netbox-community/netbox/issues/5383) - Fix setting user password via REST API
|
||||||
* [#5396](https://github.com/netbox-community/netbox/issues/5396) - Fix uniqueness constraint for virtual machine names
|
* [#5396](https://github.com/netbox-community/netbox/issues/5396) - Fix uniqueness constraint for virtual machine names
|
||||||
|
* [#5387](https://github.com/netbox-community/netbox/issues/5387) - Fix error when rendering config contexts when objects have multiple tags assigned
|
||||||
* [#5407](https://github.com/netbox-community/netbox/issues/5407) - Add direct link to secret on secrets list
|
* [#5407](https://github.com/netbox-community/netbox/issues/5407) - Add direct link to secret on secrets list
|
||||||
* [#5408](https://github.com/netbox-community/netbox/issues/5408) - Fix updating secrets without setting new plaintext
|
* [#5408](https://github.com/netbox-community/netbox/issues/5408) - Fix updating secrets without setting new plaintext
|
||||||
* [#5410](https://github.com/netbox-community/netbox/issues/5410) - Restore tags field on cable connection forms
|
* [#5410](https://github.com/netbox-community/netbox/issues/5410) - Restore tags field on cable connection forms
|
||||||
|
* [#5433](https://github.com/netbox-community/netbox/issues/5433) - Exclude SVG files from front/rear image upload for device types (currently unsupported)
|
||||||
* [#5436](https://github.com/netbox-community/netbox/issues/5436) - Show assigned IP addresses in interfaces list
|
* [#5436](https://github.com/netbox-community/netbox/issues/5436) - Show assigned IP addresses in interfaces list
|
||||||
|
* [#5446](https://github.com/netbox-community/netbox/issues/5446) - Fix validation for plugin version and required settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -887,6 +887,8 @@ class PortTypeChoices(ChoiceSet):
|
|||||||
TYPE_LSH = 'lsh'
|
TYPE_LSH = 'lsh'
|
||||||
TYPE_LSH_APC = 'lsh-apc'
|
TYPE_LSH_APC = 'lsh-apc'
|
||||||
TYPE_SPLICE = 'splice'
|
TYPE_SPLICE = 'splice'
|
||||||
|
TYPE_CS = 'cs'
|
||||||
|
TYPE_SN = 'sn'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
@ -914,6 +916,8 @@ class PortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_SC, 'SC'),
|
(TYPE_SC, 'SC'),
|
||||||
(TYPE_SC_APC, 'SC/APC'),
|
(TYPE_SC_APC, 'SC/APC'),
|
||||||
(TYPE_ST, 'ST'),
|
(TYPE_ST, 'ST'),
|
||||||
|
(TYPE_CS, 'CS'),
|
||||||
|
(TYPE_SN, 'SN'),
|
||||||
(TYPE_SPLICE, 'Splice'),
|
(TYPE_SPLICE, 'Splice'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -922,7 +922,14 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
'front_image', 'rear_image', 'comments', 'tags',
|
'front_image', 'rear_image', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'subdevice_role': StaticSelect2()
|
'subdevice_role': StaticSelect2(),
|
||||||
|
# Exclude SVG images (unsupported by PIL)
|
||||||
|
'front_image': forms.FileInput(attrs={
|
||||||
|
'accept': 'image/bmp,image/gif,image/jpeg,image/png,image/tiff'
|
||||||
|
}),
|
||||||
|
'rear_image': forms.FileInput(attrs={
|
||||||
|
'accept': 'image/bmp,image/gif,image/jpeg,image/png,image/tiff'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import inspect
|
|||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
|
|
||||||
@ -71,10 +70,10 @@ class PluginConfig(AppConfig):
|
|||||||
register_menu_items(self.verbose_name, menu_items)
|
register_menu_items(self.verbose_name, menu_items)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, user_config):
|
def validate(cls, user_config, netbox_version):
|
||||||
|
|
||||||
# Enforce version constraints
|
# Enforce version constraints
|
||||||
current_version = version.parse(settings.VERSION)
|
current_version = version.parse(netbox_version)
|
||||||
if cls.min_version is not None:
|
if cls.min_version is not None:
|
||||||
min_version = version.parse(cls.min_version)
|
min_version = version.parse(cls.min_version)
|
||||||
if current_version < min_version:
|
if current_version < min_version:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.db.models import OuterRef, Subquery, Q
|
from django.db.models import OuterRef, Subquery, Q
|
||||||
|
|
||||||
|
from extras.models.tags import TaggedItem
|
||||||
from utilities.query_functions import EmptyGroupByJSONBAgg, OrderableJSONBAgg
|
from utilities.query_functions import EmptyGroupByJSONBAgg, OrderableJSONBAgg
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
@ -81,11 +82,25 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
|
|||||||
|
|
||||||
def _get_config_context_filters(self):
|
def _get_config_context_filters(self):
|
||||||
# Construct the set of Q objects for the specific object types
|
# Construct the set of Q objects for the specific object types
|
||||||
|
tag_query_filters = {
|
||||||
|
"object_id": OuterRef(OuterRef('pk')),
|
||||||
|
"content_type__app_label": self.model._meta.app_label,
|
||||||
|
"content_type__model": self.model._meta.model_name
|
||||||
|
}
|
||||||
base_query = Q(
|
base_query = Q(
|
||||||
Q(platforms=OuterRef('platform')) | Q(platforms=None),
|
Q(platforms=OuterRef('platform')) | Q(platforms=None),
|
||||||
Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),
|
Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),
|
||||||
Q(tenants=OuterRef('tenant')) | Q(tenants=None),
|
Q(tenants=OuterRef('tenant')) | Q(tenants=None),
|
||||||
Q(tags=OuterRef('tags')) | Q(tags=None),
|
Q(
|
||||||
|
tags__pk__in=Subquery(
|
||||||
|
TaggedItem.objects.filter(
|
||||||
|
**tag_query_filters
|
||||||
|
).values_list(
|
||||||
|
'tag_id',
|
||||||
|
flat=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) | Q(tags=None),
|
||||||
is_active=True,
|
is_active=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -321,3 +321,46 @@ class ConfigContextTest(TestCase):
|
|||||||
annotated_queryset = Device.objects.filter(name=device.name).annotate_config_context_data()
|
annotated_queryset = Device.objects.filter(name=device.name).annotate_config_context_data()
|
||||||
self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 1)
|
self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 1)
|
||||||
self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context())
|
self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context())
|
||||||
|
|
||||||
|
def test_multiple_tags_return_distinct_objects_with_seperate_config_contexts(self):
|
||||||
|
"""
|
||||||
|
Tagged items use a generic relationship, which results in duplicate rows being returned when queried.
|
||||||
|
This is combatted by by appending distinct() to the config context querysets. This test creates a config
|
||||||
|
context assigned to two tags and ensures objects related by those same two tags result in only a single
|
||||||
|
config context record being returned.
|
||||||
|
|
||||||
|
This test case is seperate from the above in that it deals with multiple config context objects in play.
|
||||||
|
|
||||||
|
See https://github.com/netbox-community/netbox/issues/5387
|
||||||
|
"""
|
||||||
|
tag_context_1 = ConfigContext.objects.create(
|
||||||
|
name="tag-1",
|
||||||
|
weight=100,
|
||||||
|
data={
|
||||||
|
"tag": 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tag_context_1.tags.add(self.tag)
|
||||||
|
tag_context_2 = ConfigContext.objects.create(
|
||||||
|
name="tag-2",
|
||||||
|
weight=100,
|
||||||
|
data={
|
||||||
|
"tag": 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tag_context_2.tags.add(self.tag2)
|
||||||
|
|
||||||
|
device = Device.objects.create(
|
||||||
|
name="Device 3",
|
||||||
|
site=self.site,
|
||||||
|
tenant=self.tenant,
|
||||||
|
platform=self.platform,
|
||||||
|
device_role=self.devicerole,
|
||||||
|
device_type=self.devicetype
|
||||||
|
)
|
||||||
|
device.tags.add(self.tag)
|
||||||
|
device.tags.add(self.tag2)
|
||||||
|
|
||||||
|
annotated_queryset = Device.objects.filter(name=device.name).annotate_config_context_data()
|
||||||
|
self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 2)
|
||||||
|
self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context())
|
||||||
|
@ -86,21 +86,19 @@ class PluginTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
self.assertIn('extras.tests.dummy_plugin.*', settings.CACHEOPS)
|
self.assertIn('extras.tests.dummy_plugin.*', settings.CACHEOPS)
|
||||||
|
|
||||||
@override_settings(VERSION='0.9')
|
|
||||||
def test_min_version(self):
|
def test_min_version(self):
|
||||||
"""
|
"""
|
||||||
Check enforcement of minimum NetBox version.
|
Check enforcement of minimum NetBox version.
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(ImproperlyConfigured):
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
dummy_config.validate({})
|
dummy_config.validate({}, '0.9')
|
||||||
|
|
||||||
@override_settings(VERSION='10.0')
|
|
||||||
def test_max_version(self):
|
def test_max_version(self):
|
||||||
"""
|
"""
|
||||||
Check enforcement of maximum NetBox version.
|
Check enforcement of maximum NetBox version.
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(ImproperlyConfigured):
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
dummy_config.validate({})
|
dummy_config.validate({}, '10.0')
|
||||||
|
|
||||||
def test_required_settings(self):
|
def test_required_settings(self):
|
||||||
"""
|
"""
|
||||||
@ -110,11 +108,11 @@ class PluginTest(TestCase):
|
|||||||
required_settings = ['foo']
|
required_settings = ['foo']
|
||||||
|
|
||||||
# Validation should pass when all required settings are present
|
# Validation should pass when all required settings are present
|
||||||
DummyConfigWithRequiredSettings.validate({'foo': True})
|
DummyConfigWithRequiredSettings.validate({'foo': True}, settings.VERSION)
|
||||||
|
|
||||||
# Validation should fail when a required setting is missing
|
# Validation should fail when a required setting is missing
|
||||||
with self.assertRaises(ImproperlyConfigured):
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
DummyConfigWithRequiredSettings.validate({})
|
DummyConfigWithRequiredSettings.validate({}, settings.VERSION)
|
||||||
|
|
||||||
def test_default_settings(self):
|
def test_default_settings(self):
|
||||||
"""
|
"""
|
||||||
@ -127,10 +125,10 @@ class PluginTest(TestCase):
|
|||||||
|
|
||||||
# Populate the default value if setting has not been specified
|
# Populate the default value if setting has not been specified
|
||||||
user_config = {}
|
user_config = {}
|
||||||
DummyConfigWithDefaultSettings.validate(user_config)
|
DummyConfigWithDefaultSettings.validate(user_config, settings.VERSION)
|
||||||
self.assertEqual(user_config['bar'], 123)
|
self.assertEqual(user_config['bar'], 123)
|
||||||
|
|
||||||
# Don't overwrite specified values
|
# Don't overwrite specified values
|
||||||
user_config = {'bar': 456}
|
user_config = {'bar': 456}
|
||||||
DummyConfigWithDefaultSettings.validate(user_config)
|
DummyConfigWithDefaultSettings.validate(user_config, settings.VERSION)
|
||||||
self.assertEqual(user_config['bar'], 456)
|
self.assertEqual(user_config['bar'], 456)
|
||||||
|
@ -609,7 +609,7 @@ for plugin_name in PLUGINS:
|
|||||||
# Validate user-provided configuration settings and assign defaults
|
# Validate user-provided configuration settings and assign defaults
|
||||||
if plugin_name not in PLUGINS_CONFIG:
|
if plugin_name not in PLUGINS_CONFIG:
|
||||||
PLUGINS_CONFIG[plugin_name] = {}
|
PLUGINS_CONFIG[plugin_name] = {}
|
||||||
plugin_config.validate(PLUGINS_CONFIG[plugin_name])
|
plugin_config.validate(PLUGINS_CONFIG[plugin_name], VERSION)
|
||||||
|
|
||||||
# Add middleware
|
# Add middleware
|
||||||
plugin_middleware = plugin_config.middleware
|
plugin_middleware = plugin_config.middleware
|
||||||
|
Reference in New Issue
Block a user