Skip to content

Commit

Permalink
Merge pull request #7437 from netbox-community/develop
Browse files Browse the repository at this point in the history
Release v3.0.5
  • Loading branch information
jeremystretch authored Oct 4, 2021
2 parents 84d83fb + 339bcb8 commit d9c6609
Show file tree
Hide file tree
Showing 44 changed files with 337 additions and 287 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ body:
What version of NetBox are you currently running? (If you don't have access to the most
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
before opening a bug report to see if your issue has already been addressed.)
placeholder: v3.0.4
placeholder: v3.0.5
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v3.0.4
placeholder: v3.0.5
validations:
required: true
- type: dropdown
Expand Down
14 changes: 14 additions & 0 deletions docs/customization/custom-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ Defining script variables is optional: You may create a script with only a `run(

Any output generated by the script during its execution will be displayed under the "output" tab in the UI.

By default, scripts within a module are ordered alphabetically in the scripts list page. To return scripts in a specific order, you can define the `script_order` variable at the end of your module. The `script_order` variable is a tuple which contains each Script class in the desired order. Any scripts that are omitted from this list will be listed last.

```python
from extras.scripts import Script

class MyCustomScript(Script):
...

class AnotherCustomScript(Script):
...

script_order = (MyCustomScript, AnotherCustomScript)
```

## Module Attributes

### `name`
Expand Down
2 changes: 1 addition & 1 deletion docs/installation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The following sections detail how to set up a new instance of NetBox:
5. [HTTP server](5-http-server.md)
6. [LDAP authentication](6-ldap.md) (optional)

The video below demonstrates the installation of NetBox v2.10.3 on Ubuntu 20.04 for your reference.
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>

Expand Down
28 changes: 28 additions & 0 deletions docs/release-notes/version-3.0.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# NetBox v3.0

## v3.0.5 (2021-10-04)

### Enhancements

* [#5925](https://github.com/netbox-community/netbox/issues/5925) - Always show IP addresses tab under prefix view
* [#6423](https://github.com/netbox-community/netbox/issues/6423) - Cache rendered REST API specifications
* [#6708](https://github.com/netbox-community/netbox/issues/6708) - Add image attachment support for circuits, power panels
* [#7387](https://github.com/netbox-community/netbox/issues/7387) - Enable arbitrary ordering of custom scripts
* [#7427](https://github.com/netbox-community/netbox/issues/7427) - Don't select hidden rows when selecting all in a table

### Bug Fixes

* [#6433](https://github.com/netbox-community/netbox/issues/6433) - Fix bulk editing of child prefixes under aggregate view
* [#6817](https://github.com/netbox-community/netbox/issues/6817) - Custom field columns should be removed from tables upon their deletion
* [#6895](https://github.com/netbox-community/netbox/issues/6895) - Remove errant markup for null values in CSV export
* [#7215](https://github.com/netbox-community/netbox/issues/7215) - Prevent rack elevations from overlapping when higher width is specified
* [#7373](https://github.com/netbox-community/netbox/issues/7373) - Fix flashing when server, client, and browser color-mode preferences are mismatched
* [#7397](https://github.com/netbox-community/netbox/issues/7397) - Fix AttributeError exception when rendering export template for devices via REST API
* [#7401](https://github.com/netbox-community/netbox/issues/7401) - Pin `jsonschema` package to v3.2.0 to fix REST API docs rendering
* [#7411](https://github.com/netbox-community/netbox/issues/7411) - Fix exception in UI when adding member devices to virtual chassis
* [#7412](https://github.com/netbox-community/netbox/issues/7412) - Fix exception in UI when adding child device to device bay
* [#7417](https://github.com/netbox-community/netbox/issues/7417) - Prevent exception when filtering objects list by invalid tag
* [#7425](https://github.com/netbox-community/netbox/issues/7425) - Housekeeping command should honor zero verbosity

---

## v3.0.4 (2021-09-29)

### Enhancements
Expand Down Expand Up @@ -30,6 +56,8 @@
* [#7374](https://github.com/netbox-community/netbox/issues/7374) - Add missing `face` parameter to API elevations request when editing device
* [#7392](https://github.com/netbox-community/netbox/issues/7392) - Fix "help" links for custom fields, other models

---

## v3.0.3 (2021-09-20)

### Enhancements
Expand Down
4 changes: 4 additions & 0 deletions netbox/circuits/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
Expand Down Expand Up @@ -202,6 +203,9 @@ class Circuit(PrimaryModel):
comments = models.TextField(
blank=True
)
images = GenericRelation(
to='extras.ImageAttachment'
)

# Cache associated CircuitTerminations
termination_a = models.ForeignKey(
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
'LocationForm',
'ManufacturerForm',
'PlatformForm',
'PopulateDeviceBayForm',
'PowerFeedForm',
'PowerOutletForm',
'PowerOutletTemplateForm',
Expand All @@ -52,6 +53,7 @@
'RegionForm',
'SiteForm',
'SiteGroupForm',
'VCMemberSelectForm',
'VirtualChassisForm',
)

Expand Down
4 changes: 4 additions & 0 deletions netbox/dcim/models/power.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
Expand Down Expand Up @@ -39,6 +40,9 @@ class PowerPanel(PrimaryModel):
name = models.CharField(
max_length=100
)
images = GenericRelation(
to='extras.ImageAttachment'
)

objects = RestrictedQuerySet.as_manager()

Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/tables/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django_tables2.utils import Accessor

from dcim.models import Cable
from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, ToggleColumn
from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, TemplateColumn, ToggleColumn
from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT

__all__ = (
Expand Down Expand Up @@ -45,7 +45,7 @@ class CableTable(BaseTable):
verbose_name='Termination B'
)
status = ChoiceFieldColumn()
length = tables.TemplateColumn(
length = TemplateColumn(
template_code=CABLE_LENGTH,
order_by='_abs_length'
)
Expand Down
8 changes: 4 additions & 4 deletions netbox/dcim/tables/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from tenancy.tables import TenantColumn
from utilities.tables import (
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
MarkdownColumn, TagColumn, ToggleColumn,
MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn,
)
from .template_code import (
CABLETERMINATION, CONSOLEPORT_BUTTONS, CONSOLESERVERPORT_BUTTONS, DEVICE_LINK, DEVICEBAY_BUTTONS, DEVICEBAY_STATUS,
Expand Down Expand Up @@ -258,7 +258,7 @@ class CableTerminationTable(BaseTable):
orderable=False,
verbose_name='Cable Color'
)
cable_peer = tables.TemplateColumn(
cable_peer = TemplateColumn(
accessor='_cable_peer',
template_code=CABLETERMINATION,
orderable=False,
Expand All @@ -268,7 +268,7 @@ class CableTerminationTable(BaseTable):


class PathEndpointTable(CableTerminationTable):
connection = tables.TemplateColumn(
connection = TemplateColumn(
accessor='_path.last_node',
template_code=CABLETERMINATION,
verbose_name='Connection',
Expand Down Expand Up @@ -470,7 +470,7 @@ class BaseInterfaceTable(BaseTable):
verbose_name='IP Addresses'
)
untagged_vlan = tables.Column(linkify=True)
tagged_vlans = tables.TemplateColumn(
tagged_vlans = TemplateColumn(
template_code=INTERFACE_TAGGED_VLANS,
orderable=False,
verbose_name='Tagged VLANs'
Expand Down
6 changes: 1 addition & 5 deletions netbox/dcim/tables/template_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
<i class="mdi mdi-chevron-right"></i>
{% endif %}
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
{% else %}
&mdash;
{% endif %}
"""

CABLE_LENGTH = """
{% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}&mdash;{% endif %}
{% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% endif %}
"""

CABLE_TERMINATION_PARENT = """
Expand Down Expand Up @@ -63,8 +61,6 @@
{% endfor %}
{% elif record.mode == 'tagged-all' %}
All
{% else %}
&mdash;
{% endif %}
"""

Expand Down
6 changes: 1 addition & 5 deletions netbox/dcim/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import path

from extras.views import ImageAttachmentEditView, ObjectChangeLogView, ObjectJournalView
from extras.views import ObjectChangeLogView, ObjectJournalView
from ipam.views import ServiceEditView
from utilities.views import SlugRedirectView
from . import views
Expand Down Expand Up @@ -43,7 +43,6 @@
path('sites/<int:pk>/delete/', views.SiteDeleteView.as_view(), name='site_delete'),
path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
path('sites/<int:pk>/journal/', ObjectJournalView.as_view(), name='site_journal', kwargs={'model': Site}),
path('sites/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}),

# Locations
path('locations/', views.LocationListView.as_view(), name='location_list'),
Expand All @@ -55,7 +54,6 @@
path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
path('locations/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='location_add_image', kwargs={'model': Location}),

# Rack roles
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),
Expand Down Expand Up @@ -92,7 +90,6 @@
path('racks/<int:pk>/delete/', views.RackDeleteView.as_view(), name='rack_delete'),
path('racks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rack_changelog', kwargs={'model': Rack}),
path('racks/<int:pk>/journal/', ObjectJournalView.as_view(), name='rack_journal', kwargs={'model': Rack}),
path('racks/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='rack_add_image', kwargs={'model': Rack}),

# Manufacturers
path('manufacturers/', views.ManufacturerListView.as_view(), name='manufacturer_list'),
Expand Down Expand Up @@ -229,7 +226,6 @@
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
path('devices/<int:device>/services/assign/', ServiceEditView.as_view(), name='device_service_assign'),
path('devices/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='device_add_image', kwargs={'model': Device}),

# Console ports
path('console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'),
Expand Down
62 changes: 39 additions & 23 deletions netbox/extras/management/commands/housekeeping.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,60 @@ class Command(BaseCommand):
def handle(self, *args, **options):

# Clear expired authentication sessions (essentially replicating the `clearsessions` command)
self.stdout.write("[*] Clearing expired authentication sessions")
if options['verbosity'] >= 2:
self.stdout.write(f"\tConfigured session engine: {settings.SESSION_ENGINE}")
if options['verbosity']:
self.stdout.write("[*] Clearing expired authentication sessions")
if options['verbosity'] >= 2:
self.stdout.write(f"\tConfigured session engine: {settings.SESSION_ENGINE}")
engine = import_module(settings.SESSION_ENGINE)
try:
engine.SessionStore.clear_expired()
self.stdout.write("\tSessions cleared.", self.style.SUCCESS)
if options['verbosity']:
self.stdout.write("\tSessions cleared.", self.style.SUCCESS)
except NotImplementedError:
self.stdout.write(
f"\tThe configured session engine ({settings.SESSION_ENGINE}) does not support "
f"clearing sessions; skipping."
)
if options['verbosity']:
self.stdout.write(
f"\tThe configured session engine ({settings.SESSION_ENGINE}) does not support "
f"clearing sessions; skipping."
)

# Delete expired ObjectRecords
self.stdout.write("[*] Checking for expired changelog records")
if options['verbosity']:
self.stdout.write("[*] Checking for expired changelog records")
if settings.CHANGELOG_RETENTION:
cutoff = timezone.now() - timedelta(days=settings.CHANGELOG_RETENTION)
if options['verbosity'] >= 2:
self.stdout.write(f"Retention period: {settings.CHANGELOG_RETENTION} days")
self.stdout.write(f"\tRetention period: {settings.CHANGELOG_RETENTION} days")
self.stdout.write(f"\tCut-off time: {cutoff}")
expired_records = ObjectChange.objects.filter(time__lt=cutoff).count()
if expired_records:
self.stdout.write(f"\tDeleting {expired_records} expired records... ", self.style.WARNING, ending="")
self.stdout.flush()
if options['verbosity']:
self.stdout.write(
f"\tDeleting {expired_records} expired records... ",
self.style.WARNING,
ending=""
)
self.stdout.flush()
ObjectChange.objects.filter(time__lt=cutoff)._raw_delete(using=DEFAULT_DB_ALIAS)
self.stdout.write("Done.", self.style.WARNING)
else:
self.stdout.write("\tNo expired records found.")
else:
if options['verbosity']:
self.stdout.write("Done.", self.style.SUCCESS)
elif options['verbosity']:
self.stdout.write("\tNo expired records found.", self.style.SUCCESS)
elif options['verbosity']:
self.stdout.write(
f"\tSkipping: No retention period specified (CHANGELOG_RETENTION = {settings.CHANGELOG_RETENTION})"
)

# Check for new releases (if enabled)
self.stdout.write("[*] Checking for latest release")
if options['verbosity']:
self.stdout.write("[*] Checking for latest release")
if settings.RELEASE_CHECK_URL:
headers = {
'Accept': 'application/vnd.github.v3+json',
}

try:
self.stdout.write(f"\tFetching {settings.RELEASE_CHECK_URL}")
if options['verbosity'] >= 2:
self.stdout.write(f"\tFetching {settings.RELEASE_CHECK_URL}")
response = requests.get(
url=settings.RELEASE_CHECK_URL,
headers=headers,
Expand All @@ -73,15 +85,19 @@ def handle(self, *args, **options):
continue
releases.append((version.parse(release['tag_name']), release.get('html_url')))
latest_release = max(releases)
self.stdout.write(f"\tFound {len(response.json())} releases; {len(releases)} usable")
self.stdout.write(f"\tLatest release: {latest_release[0]}")
if options['verbosity'] >= 2:
self.stdout.write(f"\tFound {len(response.json())} releases; {len(releases)} usable")
if options['verbosity']:
self.stdout.write(f"\tLatest release: {latest_release[0]}", self.style.SUCCESS)

# Cache the most recent release
cache.set('latest_release', latest_release, None)

except requests.exceptions.RequestException as exc:
self.stdout.write(f"\tRequest error: {exc}")
self.stdout.write(f"\tRequest error: {exc}", self.style.ERROR)
else:
self.stdout.write(f"\tSkipping: RELEASE_CHECK_URL not set")
if options['verbosity']:
self.stdout.write(f"\tSkipping: RELEASE_CHECK_URL not set")

self.stdout.write("Finished.", self.style.SUCCESS)
if options['verbosity']:
self.stdout.write("Finished.", self.style.SUCCESS)
8 changes: 5 additions & 3 deletions netbox/extras/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,16 +470,18 @@ def get_scripts(use_names=False):
defined name in place of the actual module name.
"""
scripts = OrderedDict()

# Iterate through all modules within the reports path. These are the user-created files in which reports are
# defined.
for importer, module_name, _ in pkgutil.iter_modules([settings.SCRIPTS_ROOT]):
module = importer.find_module(module_name).load_module(module_name)
if use_names and hasattr(module, 'name'):
module_name = module.name
module_scripts = OrderedDict()
for name, cls in inspect.getmembers(module, is_script):
module_scripts[name] = cls
script_order = getattr(module, "script_order", ())
ordered_scripts = [cls for cls in script_order if is_script(cls)]
unordered_scripts = [cls for _, cls in inspect.getmembers(module, is_script) if cls not in script_order]
for cls in [*ordered_scripts, *unordered_scripts]:
module_scripts[cls.__name__] = cls
if module_scripts:
scripts[module_name] = module_scripts

Expand Down
1 change: 1 addition & 0 deletions netbox/extras/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
kwargs={'model': models.ConfigContext}),

# Image attachments
path('image-attachments/add/', views.ImageAttachmentEditView.as_view(), name='imageattachment_add'),
path('image-attachments/<int:pk>/edit/', views.ImageAttachmentEditView.as_view(), name='imageattachment_edit'),
path('image-attachments/<int:pk>/delete/', views.ImageAttachmentDeleteView.as_view(), name='imageattachment_delete'),

Expand Down
Loading

0 comments on commit d9c6609

Please sign in to comment.