1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge v3.1.8

This commit is contained in:
jeremystretch
2022-02-15 10:05:07 -05:00
14 changed files with 113 additions and 26 deletions

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.1.7 placeholder: v3.1.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.1.7 placeholder: v3.1.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -11,10 +11,6 @@ The following sections detail how to set up a new instance of NetBox:
5. [HTTP server](5-http-server.md) 5. [HTTP server](5-http-server.md)
6. [LDAP authentication](6-ldap.md) (optional) 6. [LDAP authentication](6-ldap.md) (optional)
The video below demonstrates the installation of NetBox v3.0 on Ubuntu 20.04 for your reference.
<iframe width="560" height="315" src="https://www.youtube.com/embed/7Fpd2-q9_28" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Requirements ## Requirements
| Dependency | Minimum Version | | Dependency | Minimum Version |

View File

@ -1,20 +1,30 @@
# NetBox v3.1 # NetBox v3.1
## v3.1.8 (FUTURE) ## v3.1.9 (FUTURE)
---
## v3.1.8 (2022-02-15)
### Enhancements ### Enhancements
* [#7150](https://github.com/netbox-community/netbox/issues/7150) - Linkify devices on the far side of a rack elevation * [#7150](https://github.com/netbox-community/netbox/issues/7150) - Linkify devices on the far side of a rack elevation
* [#8398](https://github.com/netbox-community/netbox/issues/8398) - Embiggen configuration form fields for banner message content * [#8398](https://github.com/netbox-community/netbox/issues/8398) - Embiggen configuration form fields for banner message content
* [#8556](https://github.com/netbox-community/netbox/issues/8556) - Add full username column to changelog table
* [#8620](https://github.com/netbox-community/netbox/issues/8620) - Enable tab completion for `nbshell`
### Bug Fixes ### Bug Fixes
* [#8331](https://github.com/netbox-community/netbox/issues/8331) - Implement `replaceAll` string utility function to improve browser compatibility * [#8331](https://github.com/netbox-community/netbox/issues/8331) - Implement `replaceAll` string utility function to improve browser compatibility
* [#8391](https://github.com/netbox-community/netbox/issues/8391) - Null date columns should return empty strings during CSV export
* [#8548](https://github.com/netbox-community/netbox/issues/8548) - Fix display of VC members when position is zero * [#8548](https://github.com/netbox-community/netbox/issues/8548) - Fix display of VC members when position is zero
* [#8561](https://github.com/netbox-community/netbox/issues/8561) - Include option to connect a rear port to a console port * [#8561](https://github.com/netbox-community/netbox/issues/8561) - Include option to connect a rear port to a console port
* [#8564](https://github.com/netbox-community/netbox/issues/8564) - Fix errant table configuration key `available_columns` * [#8564](https://github.com/netbox-community/netbox/issues/8564) - Fix errant table configuration key `available_columns`
* [#8577](https://github.com/netbox-community/netbox/issues/8577) - Show contact assignment counts in global search results
* [#8578](https://github.com/netbox-community/netbox/issues/8578) - Object change log tables should honor user's configured preferences * [#8578](https://github.com/netbox-community/netbox/issues/8578) - Object change log tables should honor user's configured preferences
* [#8604](https://github.com/netbox-community/netbox/issues/8604) - Fix tag filter on config context list filter form * [#8604](https://github.com/netbox-community/netbox/issues/8604) - Fix tag filter on config context list filter form
* [#8609](https://github.com/netbox-community/netbox/issues/8609) - Display validation error when attempting to assign VLANs to interface with no mode during bulk edit
* [#8611](https://github.com/netbox-community/netbox/issues/8611) - Fix bulk editing for certain custom link, webhook, and journal entry fields
--- ---

View File

@ -1114,8 +1114,14 @@ class InterfaceBulkEditForm(
def clean(self): def clean(self):
super().clean() super().clean()
if not self.cleaned_data['mode']:
if self.cleaned_data['untagged_vlan']:
raise forms.ValidationError({'untagged_vlan': "Interface mode must be specified to assign VLANs"})
elif self.cleaned_data['tagged_vlans']:
raise forms.ValidationError({'tagged_vlans': "Interface mode must be specified to assign VLANs"})
# Untagged interfaces cannot be assigned tagged VLANs # Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']: elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
raise forms.ValidationError({ raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned." 'mode': "An access interface cannot have tagged VLANs assigned."
}) })

View File

@ -4,7 +4,9 @@ from django.contrib.contenttypes.models import ContentType
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from extras.utils import FeatureQuery from extras.utils import FeatureQuery
from utilities.forms import BulkEditForm, BulkEditNullBooleanSelect, ColorField, ContentTypeChoiceField, StaticSelect from utilities.forms import (
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, ContentTypeChoiceField, StaticSelect,
)
__all__ = ( __all__ = (
'ConfigContextBulkEditForm', 'ConfigContextBulkEditForm',
@ -58,7 +60,7 @@ class CustomLinkBulkEditForm(BulkEditForm):
required=False required=False
) )
button_class = forms.ChoiceField( button_class = forms.ChoiceField(
choices=CustomLinkButtonClassChoices, choices=add_blank_choice(CustomLinkButtonClassChoices),
required=False, required=False,
widget=StaticSelect() widget=StaticSelect()
) )
@ -116,21 +118,25 @@ class WebhookBulkEditForm(BulkEditForm):
widget=BulkEditNullBooleanSelect() widget=BulkEditNullBooleanSelect()
) )
http_method = forms.ChoiceField( http_method = forms.ChoiceField(
choices=WebhookHttpMethodChoices, choices=add_blank_choice(WebhookHttpMethodChoices),
required=False required=False,
label='HTTP method'
) )
payload_url = forms.CharField( payload_url = forms.CharField(
required=False required=False,
label='Payload URL'
) )
ssl_verification = forms.NullBooleanField( ssl_verification = forms.NullBooleanField(
required=False, required=False,
widget=BulkEditNullBooleanSelect() widget=BulkEditNullBooleanSelect(),
label='SSL verification'
) )
secret = forms.CharField( secret = forms.CharField(
required=False required=False
) )
ca_file_path = forms.CharField( ca_file_path = forms.CharField(
required=False required=False,
label='CA file path'
) )
nullable_fields = ('secret', 'conditions', 'ca_file_path') nullable_fields = ('secret', 'conditions', 'ca_file_path')
@ -179,7 +185,7 @@ class JournalEntryBulkEditForm(BulkEditForm):
widget=forms.MultipleHiddenInput widget=forms.MultipleHiddenInput
) )
kind = forms.ChoiceField( kind = forms.ChoiceField(
choices=JournalEntryKindChoices, choices=add_blank_choice(JournalEntryKindChoices),
required=False required=False
) )
comments = forms.CharField( comments = forms.CharField(

View File

@ -70,10 +70,23 @@ class Command(BaseCommand):
return namespace return namespace
def handle(self, **options): def handle(self, **options):
namespace = self.get_namespace()
# If Python code has been passed, execute it and exit. # If Python code has been passed, execute it and exit.
if options['command']: if options['command']:
exec(options['command'], self.get_namespace()) exec(options['command'], namespace)
return return
shell = code.interact(banner=BANNER_TEXT, local=self.get_namespace()) # Try to enable tab-complete
try:
import readline
import rlcompleter
except ModuleNotFoundError:
pass
else:
readline.set_completer(rlcompleter.Completer(namespace).complete)
readline.parse_and_bind('tab: complete')
# Run interactive shell
shell = code.interact(banner=BANNER_TEXT, local=namespace)
return shell return shell

View File

@ -26,6 +26,11 @@ CONFIGCONTEXT_ACTIONS = """
{% endif %} {% endif %}
""" """
OBJECTCHANGE_FULL_NAME = """
{% load helpers %}
{{ record.user.get_full_name|placeholder }}
"""
OBJECTCHANGE_OBJECT = """ OBJECTCHANGE_OBJECT = """
{% if record.changed_object and record.changed_object.get_absolute_url %} {% if record.changed_object and record.changed_object.get_absolute_url %}
<a href="{{ record.changed_object.get_absolute_url }}">{{ record.object_repr }}</a> <a href="{{ record.changed_object.get_absolute_url }}">{{ record.object_repr }}</a>
@ -196,6 +201,14 @@ class ObjectChangeTable(NetBoxTable):
linkify=True, linkify=True,
format=settings.SHORT_DATETIME_FORMAT format=settings.SHORT_DATETIME_FORMAT
) )
user_name = tables.Column(
verbose_name='Username'
)
full_name = tables.TemplateColumn(
template_code=OBJECTCHANGE_FULL_NAME,
verbose_name='Full Name',
orderable=False
)
action = columns.ChoiceFieldColumn() action = columns.ChoiceFieldColumn()
changed_object_type = columns.ContentTypeColumn( changed_object_type = columns.ContentTypeColumn(
verbose_name='Type' verbose_name='Type'
@ -212,7 +225,7 @@ class ObjectChangeTable(NetBoxTable):
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = ObjectChange model = ObjectChange
fields = ('id', 'time', 'user_name', 'action', 'changed_object_type', 'object_repr', 'request_id') fields = ('id', 'time', 'user_name', 'full_name', 'action', 'changed_object_type', 'object_repr', 'request_id')
class ObjectJournalTable(NetBoxTable): class ObjectJournalTable(NetBoxTable):

View File

@ -18,7 +18,7 @@ from ipam.filtersets import (
from ipam.models import Aggregate, ASN, IPAddress, Prefix, VLAN, VRF from ipam.models import Aggregate, ASN, IPAddress, Prefix, VLAN, VRF
from ipam.tables import AggregateTable, ASNTable, IPAddressTable, PrefixTable, VLANTable, VRFTable from ipam.tables import AggregateTable, ASNTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
from tenancy.filtersets import ContactFilterSet, TenantFilterSet from tenancy.filtersets import ContactFilterSet, TenantFilterSet
from tenancy.models import Contact, Tenant from tenancy.models import Contact, Tenant, ContactAssignment
from tenancy.tables import ContactTable, TenantTable from tenancy.tables import ContactTable, TenantTable
from utilities.utils import count_related from utilities.utils import count_related
from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet
@ -186,7 +186,7 @@ SEARCH_TYPES = OrderedDict((
'url': 'tenancy:tenant_list', 'url': 'tenancy:tenant_list',
}), }),
('contact', { ('contact', {
'queryset': Contact.objects.prefetch_related('group', 'assignments'), 'queryset': Contact.objects.prefetch_related('group', 'assignments').annotate(assignment_count=count_related(ContactAssignment, 'contact')),
'filterset': ContactFilterSet, 'filterset': ContactFilterSet,
'table': ContactTable, 'table': ContactTable,
'url': 'tenancy:contact_list', 'url': 'tenancy:contact_list',

View File

@ -4,9 +4,12 @@ from typing import Optional
import django_tables2 as tables import django_tables2 as tables
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.db.models import DateField, DateTimeField
from django.template import Context, Template from django.template import Context, Template
from django.urls import reverse from django.urls import reverse
from django.utils.formats import date_format
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django_tables2.columns import library
from django_tables2.utils import Accessor from django_tables2.utils import Accessor
from extras.choices import CustomFieldTypeChoices from extras.choices import CustomFieldTypeChoices
@ -32,6 +35,42 @@ __all__ = (
) )
@library.register
class DateColumn(tables.DateColumn):
"""
Overrides the default implementation of DateColumn to better handle null values, returning a default value for
tables and null when exporting data. It is registered in the tables library to use this class instead of the
default, making this behavior consistent in all fields of type DateField.
"""
def value(self, value):
return value
@classmethod
def from_field(cls, field, **kwargs):
if isinstance(field, DateField):
return cls(**kwargs)
@library.register
class DateTimeColumn(tables.DateTimeColumn):
"""
Overrides the default implementation of DateTimeColumn to better handle null values, returning a default value for
tables and null when exporting data. It is registered in the tables library to use this class instead of the
default, making this behavior consistent in all fields of type DateTimeField.
"""
def value(self, value):
if value:
return date_format(value, format="SHORT_DATETIME_FORMAT")
return None
@classmethod
def from_field(cls, field, **kwargs):
if isinstance(field, DateTimeField):
return cls(**kwargs)
class ToggleColumn(tables.CheckBoxColumn): class ToggleColumn(tables.CheckBoxColumn):
""" """
Extend CheckBoxColumn to add a "toggle all" checkbox in the column header. Extend CheckBoxColumn to add a "toggle all" checkbox in the column header.

View File

@ -38,7 +38,11 @@
<tr> <tr>
<th scope="row">User</th> <th scope="row">User</th>
<td> <td>
{{ object.user|default:object.user_name }} {% if object.user.get_full_name %}
{{ object.user.get_full_name }} ({{ object.user_name }})
{% else %}
{{ object.user_name }}
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -204,7 +204,7 @@ def deepmerge(original, new):
""" """
merged = OrderedDict(original) merged = OrderedDict(original)
for key, val in new.items(): for key, val in new.items():
if key in original and isinstance(original[key], dict) and isinstance(val, dict): if key in original and isinstance(original[key], dict) and val and isinstance(val, dict):
merged[key] = deepmerge(original[key], val) merged[key] = deepmerge(original[key], val)
else: else:
merged[key] = val merged[key] = val

View File

@ -65,7 +65,7 @@ class ClusterCSVForm(NetBoxModelCSVForm):
class VirtualMachineCSVForm(NetBoxModelCSVForm): class VirtualMachineCSVForm(NetBoxModelCSVForm):
status = CSVChoiceField( status = CSVChoiceField(
choices=VirtualMachineStatusChoices, choices=VirtualMachineStatusChoices,
help_text='Operational status of device' help_text='Operational status'
) )
cluster = CSVModelChoiceField( cluster = CSVModelChoiceField(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),

View File

@ -20,10 +20,10 @@ gunicorn==20.1.0
Jinja2==3.0.3 Jinja2==3.0.3
Markdown==3.3.6 Markdown==3.3.6
markdown-include==0.6.0 markdown-include==0.6.0
mkdocs-material==8.1.9 mkdocs-material==8.1.11
mkdocstrings==0.17.0 mkdocstrings==0.17.0
netaddr==0.8.0 netaddr==0.8.0
Pillow==8.4.0 Pillow==9.0.1
psycopg2-binary==2.9.3 psycopg2-binary==2.9.3
PyYAML==6.0 PyYAML==6.0
social-auth-app-django==5.0.0 social-auth-app-django==5.0.0