Skip to content

Commit

Permalink
Merge pull request #3034 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v2.5.9
  • Loading branch information
jeremystretch committed Apr 2, 2019
2 parents d112b60 + 738a20a commit fdf1689
Show file tree
Hide file tree
Showing 24 changed files with 122 additions and 44 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,28 @@
v2.5.9 (2019-04-01)

## Enhancements

* [#2933](https://github.com/digitalocean/netbox/issues/2933) - Add username to outbound webhook requests
* [#3011](https://github.com/digitalocean/netbox/issues/3011) - Add SSL support for django-rq (requires django-rq v1.3.1+)
* [#3025](https://github.com/digitalocean/netbox/issues/3025) - Add request ID to outbound webhook requests (for correlating all changes part of a single request)

## Bug Fixes

* [#2207](https://github.com/digitalocean/netbox/issues/2207) - Fixes deterministic ordering of interfaces
* [#2577](https://github.com/digitalocean/netbox/issues/2577) - Clarification of wording in API regarding filtering
* [#2924](https://github.com/digitalocean/netbox/issues/2924) - Add interface type for QSFP28 50GE
* [#2936](https://github.com/digitalocean/netbox/issues/2936) - Fix device role selection showing duplicate first entry
* [#2998](https://github.com/digitalocean/netbox/issues/2998) - Limit device query to non-racked devices if no rack selected when creating a cable
* [#3001](https://github.com/digitalocean/netbox/issues/3001) - Fix API representation of ObjectChange `action` and add `changed_object_type`
* [#3014](https://github.com/digitalocean/netbox/issues/3014) - Fixes VM Role filtering
* [#3019](https://github.com/digitalocean/netbox/issues/3019) - Fix tag population when running NetBox within a path
* [#3022](https://github.com/digitalocean/netbox/issues/3022) - Add missing cable termination types to DCIM `_choices` endpoint
* [#3026](https://github.com/digitalocean/netbox/issues/3026) - Tweak prefix/IP filter forms to filter using VRF ID rather than route distinguisher
* [#3027](https://github.com/digitalocean/netbox/issues/3027) - Ignore empty local context data when rendering config contexts
* [#3032](https://github.com/digitalocean/netbox/issues/3032) - Save assigned tags when creating a new secret

---

v2.5.8 (2019-03-11)

## Enhancements
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -45,13 +45,13 @@ and run `upgrade.sh`.

## Supported SDK

- [pynetbox](https://github.com/digitalocean/pynetbox) Python API client library for Netbox.
- [pynetbox](https://github.com/digitalocean/pynetbox) - A Python API client library for Netbox

## Community SDK

- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) A ruby client library for Netbox v2.
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) - A Ruby client library for Netbox
- [powerbox](https://github.com/BatmanAMA/powerbox) - A PowerShell library for Netbox

## Ansible Inventory

- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) Ansible dynamic inventory script for Netbox.

- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) - Ansible dynamic inventory script for Netbox
2 changes: 1 addition & 1 deletion docs/api/overview.md
Expand Up @@ -261,7 +261,7 @@ A list of objects retrieved via the API can be filtered by passing one or more q
GET /api/ipam/prefixes/?status=1
```

The same filter can be incldued multiple times. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:
Certain filters can be included multiple times within a single request. These will effect a logical OR and return objects matching any of the given values. For example, the following will return all active and reserved prefixes:

```
GET /api/ipam/prefixes/?status=1&status=2
Expand Down
7 changes: 7 additions & 0 deletions docs/configuration/optional-settings.md
Expand Up @@ -283,6 +283,7 @@ REDIS = {
'PASSWORD': '',
'DATABASE': 0,
'DEFAULT_TIMEOUT': 300,
'SSL': False,
}
```

Expand Down Expand Up @@ -315,3 +316,9 @@ The TCP port to use when connecting to the Redis server.
Default: None

The password to use when authenticating to the Redis server (optional).

### SSL

Default: False

Use secure sockets layer to encrypt the connections to the Redis server.
9 changes: 7 additions & 2 deletions netbox/dcim/api/serializers.py
@@ -1,3 +1,4 @@
from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
Expand Down Expand Up @@ -502,8 +503,12 @@ class Meta:
#

class CableSerializer(ValidatedModelSerializer):
termination_a_type = ContentTypeField()
termination_b_type = ContentTypeField()
termination_a_type = ContentTypeField(
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
)
termination_b_type = ContentTypeField(
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
)
termination_a = serializers.SerializerMethodField(read_only=True)
termination_b = serializers.SerializerMethodField(read_only=True)
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/api/views.py
@@ -1,7 +1,7 @@
from collections import OrderedDict

from django.conf import settings
from django.db.models import F, Q
from django.db.models import F
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
Expand Down Expand Up @@ -35,7 +35,7 @@

class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(Cable, ['length_unit', 'status', 'type']),
(Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
(ConsolePort, ['connection_status']),
(Device, ['face', 'status']),
(DeviceType, ['subdevice_role']),
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/constants.py
Expand Up @@ -83,6 +83,7 @@
IFACE_FF_10GE_X2 = 1320
IFACE_FF_25GE_SFP28 = 1350
IFACE_FF_40GE_QSFP_PLUS = 1400
IFACE_FF_50GE_QSFP28 = 1420
IFACE_FF_100GE_CFP = 1500
IFACE_FF_100GE_CFP2 = 1510
IFACE_FF_100GE_CFP4 = 1520
Expand Down Expand Up @@ -164,6 +165,7 @@
[IFACE_FF_10GE_X2, 'X2 (10GE)'],
[IFACE_FF_25GE_SFP28, 'SFP28 (25GE)'],
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
[IFACE_FF_50GE_QSFP28, 'QSFP28 (50GE)'],
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
[IFACE_FF_200GE_CFP2, 'CFP2 (200GE)'],
Expand Down
1 change: 0 additions & 1 deletion netbox/dcim/forms.py
Expand Up @@ -1700,7 +1700,6 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
widget=APISelectMultiple(
api_url="/api/dcim/device-roles/",
value_field="slug",
null_option=True,
)
)
tenant = FilterChoiceField(
Expand Down
6 changes: 5 additions & 1 deletion netbox/dcim/managers.py
Expand Up @@ -64,11 +64,15 @@ def get_queryset(self):
The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
match any of the prescribed fields.
The `id` field is included to enforce deterministic ordering of interfaces in similar vein of other device
components.
"""

sql_col = '{}.name'.format(self.model._meta.db_table)
ordering = [
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name',
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name', 'pk'

]

fields = {
Expand Down
24 changes: 19 additions & 5 deletions netbox/extras/api/serializers.py
@@ -1,3 +1,4 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from taggit.models import Tag
Expand Down Expand Up @@ -88,7 +89,9 @@ class Meta:
#

class ImageAttachmentSerializer(ValidatedModelSerializer):
content_type = ContentTypeField()
content_type = ContentTypeField(
queryset=ContentType.objects.all()
)
parent = serializers.SerializerMethodField(read_only=True)

class Meta:
Expand Down Expand Up @@ -205,14 +208,25 @@ class ReportDetailSerializer(ReportSerializer):
#

class ObjectChangeSerializer(serializers.ModelSerializer):
user = NestedUserSerializer(read_only=True)
content_type = ContentTypeField(read_only=True)
changed_object = serializers.SerializerMethodField(read_only=True)
user = NestedUserSerializer(
read_only=True
)
action = ChoiceField(
choices=OBJECTCHANGE_ACTION_CHOICES,
read_only=True
)
changed_object_type = ContentTypeField(
read_only=True
)
changed_object = serializers.SerializerMethodField(
read_only=True
)

class Meta:
model = ObjectChange
fields = [
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'content_type', 'changed_object', 'object_data',
'id', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object',
'object_data',
]

def get_changed_object(self, obj):
Expand Down
4 changes: 2 additions & 2 deletions netbox/extras/api/views.py
Expand Up @@ -10,7 +10,7 @@

from extras import filters
from extras.models import (
ConfigContext, CustomField, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, TopologyMap,
)
from extras.reports import get_report, get_reports
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
Expand All @@ -23,8 +23,8 @@

class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(CustomField, ['type']),
(Graph, ['type']),
(ObjectChange, ['action']),
)


Expand Down
1 change: 1 addition & 0 deletions netbox/extras/apps.py
Expand Up @@ -22,6 +22,7 @@ def ready(self):
port=settings.REDIS_PORT,
db=settings.REDIS_DATABASE,
password=settings.REDIS_PASSWORD or None,
ssl=settings.REDIS_SSL,
)
rs.ping()
except redis.exceptions.ConnectionError:
Expand Down
4 changes: 2 additions & 2 deletions netbox/extras/middleware.py
Expand Up @@ -37,7 +37,7 @@ def _record_object_deleted(request, instance, **kwargs):
if hasattr(instance, 'log_change'):
instance.log_change(request.user, request.id, OBJECTCHANGE_ACTION_DELETE)

enqueue_webhooks(instance, OBJECTCHANGE_ACTION_DELETE)
enqueue_webhooks(instance, request.user, request.id, OBJECTCHANGE_ACTION_DELETE)


class ObjectChangeMiddleware(object):
Expand Down Expand Up @@ -83,7 +83,7 @@ def __call__(self, request):
obj.log_change(request.user, request.id, action)

# Enqueue webhooks
enqueue_webhooks(obj, action)
enqueue_webhooks(obj, request.user, request.id, action)

# Housekeeping: 1% chance of clearing out expired ObjectChanges
if _thread_locals.changed_objects and settings.CHANGELOG_RETENTION and random.randint(1, 100) == 1:
Expand Down
2 changes: 1 addition & 1 deletion netbox/extras/models.py
Expand Up @@ -720,7 +720,7 @@ def get_config_context(self):
data = deepmerge(data, context.data)

# If the object has local config context data defined, merge it last
if self.local_context_data is not None:
if self.local_context_data:
data = deepmerge(data, self.local_context_data)

return data
Expand Down
6 changes: 4 additions & 2 deletions netbox/extras/webhooks.py
Expand Up @@ -9,7 +9,7 @@
from .constants import WEBHOOK_MODELS


def enqueue_webhooks(instance, action):
def enqueue_webhooks(instance, user, request_id, action):
"""
Find Webhook(s) assigned to this instance + action and enqueue them
to be processed
Expand Down Expand Up @@ -47,5 +47,7 @@ def enqueue_webhooks(instance, action):
serializer.data,
instance._meta.model_name,
action,
str(datetime.datetime.now())
str(datetime.datetime.now()),
user.username,
request_id
)
4 changes: 3 additions & 1 deletion netbox/extras/webhooks_worker.py
Expand Up @@ -10,14 +10,16 @@


@job('default')
def process_webhook(webhook, data, model_name, event, timestamp):
def process_webhook(webhook, data, model_name, event, timestamp, username, request_id):
"""
Make a POST request to the defined Webhook
"""
payload = {
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
'timestamp': timestamp,
'model': model_name,
'username': username,
'request_id': request_id,
'data': data
}
headers = {
Expand Down
2 changes: 2 additions & 0 deletions netbox/ipam/api/serializers.py
Expand Up @@ -128,6 +128,7 @@ def validate(self, data):
#

class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
family = ChoiceField(choices=AF_CHOICES, read_only=True)
site = NestedSiteSerializer(required=False, allow_null=True)
vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True)
Expand Down Expand Up @@ -189,6 +190,7 @@ def get_url(self, obj):


class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
family = ChoiceField(choices=AF_CHOICES, read_only=True)
vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
Expand Down
8 changes: 2 additions & 6 deletions netbox/ipam/forms.py
Expand Up @@ -524,14 +524,12 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Mask length',
widget=StaticSelect2()
)
vrf = FilterChoiceField(
vrf_id = FilterChoiceField(
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF',
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/ipam/vrfs/",
value_field="rd",
null_option=True,
)
)
Expand Down Expand Up @@ -973,14 +971,12 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Mask length',
widget=StaticSelect2()
)
vrf = FilterChoiceField(
vrf_id = FilterChoiceField(
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF',
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/ipam/vrfs/",
value_field="rd",
null_option=True,
)
)
Expand Down
1 change: 1 addition & 0 deletions netbox/netbox/configuration.example.py
Expand Up @@ -132,6 +132,7 @@
'PASSWORD': '',
'DATABASE': 0,
'DEFAULT_TIMEOUT': 300,
'SSL': False,
}

# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
Expand Down
4 changes: 3 additions & 1 deletion netbox/netbox/settings.py
Expand Up @@ -22,7 +22,7 @@
)


VERSION = '2.5.8'
VERSION = '2.5.9'

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand Down Expand Up @@ -131,6 +131,7 @@
REDIS_PASSWORD = REDIS.get('PASSWORD', '')
REDIS_DATABASE = REDIS.get('DATABASE', 0)
REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300)
REDIS_SSL = REDIS.get('SSL', False)

# Email
EMAIL_HOST = EMAIL.get('SERVER')
Expand Down Expand Up @@ -291,6 +292,7 @@
'DB': REDIS_DATABASE,
'PASSWORD': REDIS_PASSWORD,
'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT,
'SSL': REDIS_SSL,
}
}

Expand Down
5 changes: 4 additions & 1 deletion netbox/project-static/js/forms.js
Expand Up @@ -155,10 +155,13 @@ $(document).ready(function() {

filter_for_elements.each(function(index, filter_for_element) {
var param_name = $(filter_for_element).attr(attr_name);
var is_nullable = $(filter_for_element).attr("nullable");
var value = $(filter_for_element).val();

if (param_name && value) {
parameters[param_name] = value;
} else if (param_name && is_nullable) {
parameters[param_name] = "null";
}
});

Expand Down Expand Up @@ -247,7 +250,7 @@ $(document).ready(function() {

ajax: {
delay: 250,
url: "/api/extras/tags/",
url: netbox_api_path + "extras/tags/",

data: function(params) {
// Paging. Note that `params.page` indexes at 1
Expand Down

0 comments on commit fdf1689

Please sign in to comment.