Skip to content

Commit

Permalink
Merge feature
Browse files Browse the repository at this point in the history
  • Loading branch information
bctiemann committed Feb 25, 2025
1 parent 7f7f08e commit b5e9ae0
Show file tree
Hide file tree
Showing 103 changed files with 1,176 additions and 1,192 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/01-feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.2.3
placeholder: v4.2.4
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/02-bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.2.3
placeholder: v4.2.4
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .tx/config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[main]
host = https://app.transifex.com

[o:netbox-community:p:netbox:r:9cbf4fcf95b3d92e4ebbf1a5e5d1caee]
[o:netbox-community:p:netbox:r:034999968a7366ba27a8bdf1ab63bf42]
file_filter = netbox/translations/<lang>/LC_MESSAGES/django.po
source_file = netbox/translations/en/LC_MESSAGES/django.po
type = PO
Expand Down
27 changes: 24 additions & 3 deletions docs/development/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This documentation describes the process of packaging and publishing a new NetBo

While major releases generally introduce some very substantial change to the application, they are typically treated the same as minor version increments for the purpose of release packaging.

For patch releases (e.g. upgrading from v4.2.2 to v4.2.3), begin at the [patch releases](#patch-releases) heading below. For minor or major releases, complete the entire checklist.

## Minor Version Releases

### Address Constrained Dependencies
Expand Down Expand Up @@ -85,7 +87,20 @@ In cases where upgrading a dependency to its most recent release is breaking, it

### Update UI Dependencies

Check whether any UI dependencies (JavaScript packages, fonts, etc.) need to be updated by running `yarn outdated` from within the `project-static/` directory. [Upgrade these dependencies](./web-ui.md#updating-dependencies) as necessary, then run `yarn bundle` to generate the necessary files for distribution.
Check whether any UI dependencies (JavaScript packages, fonts, etc.) need to be updated by running `yarn outdated` from within the `project-static/` directory. [Upgrade these dependencies](./web-ui.md#updating-dependencies) as necessary, then run `yarn bundle` to generate the necessary files for distribution:

```
$ yarn bundle
yarn run v1.22.19
$ node bundle.js
✅ Bundled source file 'styles/external.scss' to 'netbox-external.css'
✅ Bundled source file 'styles/netbox.scss' to 'netbox.css'
✅ Bundled source file 'styles/svg/rack_elevation.scss' to 'rack_elevation.css'
✅ Bundled source file 'styles/svg/cable_trace.scss' to 'cable_trace.css'
✅ Bundled source file 'index.ts' to 'netbox.js'
✅ Copied graphiql files
Done in 1.00s.
```

### Rebuild the Device Type Definition Schema

Expand Down Expand Up @@ -116,16 +131,22 @@ Then, compile these portable (`.po`) files for use in the application:

### Update Version and Changelog

* Update the version and published date in `release.yaml` with the current version & date. Add a designation (e.g.g `beta1`) if applicable.
* Update the version number and date in `netbox/release.yaml`. Add or remove the designation (e.g. `beta1`) if applicable.
* Update the example version numbers in the feature request and bug report templates under `.github/ISSUE_TEMPLATES/`.
* Replace the "FUTURE" placeholder in the release notes with the current date.
* Add a section for this release at the top of the changelog page for the minor version (e.g. `docs/release-notes/version-4.2.md`) listing all relevant changes made in this release.

!!! tip
Put yourself in the shoes of the user when recording change notes. Focus on the effect that each change has for the end user, rather than the specific bits of code that were modified in a PR. Ensure that each message conveys meaning absent context of the initial feature request or bug report. Remember to include key words or phrases (such as exception names) that can be easily searched.

### Submit a Pull Request

Commit the above changes and submit a pull request titled **"Release vX.Y.Z"** to merge the current release branch (e.g. `release-vX.Y.Z`) into `main`. Copy the documented release notes into the pull request's body.

Once CI has completed and a colleague has reviewed the PR, merge it. This effects a new release in the `main` branch.

!!! warning
To ensure a streamlined review process, the pull request for a release **must** be limited to the changes outlined in this document. A release PR must never include functional changes to the application: Any unrelated "cleanup" needs to be captured in a separate PR prior to the release being shipped.

### Create a New Release

Create a [new release](https://github.com/netbox-community/netbox/releases/new) on GitHub with the following parameters.
Expand Down
2 changes: 1 addition & 1 deletion docs/development/style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ NetBox generally follows the [Django style guide](https://docs.djangoproject.com

### Linting

The [ruff](https://docs.astral.sh/ruff/) linter is used to enforce code style. A [pre-commit hook](./getting-started.md#3-enable-pre-commit-hooks) which runs this automatically is included with NetBox. To invoke `ruff` manually, run:
The [ruff](https://docs.astral.sh/ruff/) linter is used to enforce code style, and is run automatically by [pre-commit](./getting-started.md#5-install-pre-commit). To invoke `ruff` manually, run:

```
ruff check netbox/
Expand Down
5 changes: 4 additions & 1 deletion docs/development/translations.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ To download translated strings automatically, you'll need to:
1. Install the [Transifex CLI client](https://github.com/transifex/cli)
2. Generate a [Transifex API token](https://app.transifex.com/user/settings/api/)

Once you have the client set up, run the following command:
Once you have the client set up, run the following command from the project root (e.g. `/opt/netbox/`):

```no-highlight
TX_TOKEN=$TOKEN tx pull
Expand All @@ -46,6 +46,9 @@ Once retrieved, the updated strings need to be compiled into new `.mo` files so

Once any new `.mo` files have been generated, they need to be committed and pushed back up to GitHub. (Again, this is typically done as part of publishing a new NetBox release.)

!!! tip
Run `git status` to check that both `*.mo` & `*.po` files have been updated as expected.

## Proposing New Languages

If you'd like to add support for a new language to NetBox, the first step is to [submit a GitHub issue](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+translation&projects=&template=translation.yaml) to capture the proposal. While we'd like to add as many languages as possible, we do need to limit the rate at which new languages are added. New languages will be selected according to community interest and the number of volunteers who sign up as translators.
Expand Down
2 changes: 1 addition & 1 deletion docs/integrations/rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## What is a REST API?

REST stands for [representational state transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It's a particular type of API which employs HTTP requests and [JavaScript Object Notation (JSON)](https://www.json.org/) to facilitate create, retrieve, update, and delete (CRUD) operations on objects within an application. Each type of operation is associated with a particular HTTP verb:
REST stands for [representational state transfer](https://en.wikipedia.org/wiki/REST). It's a particular type of API which employs HTTP requests and [JavaScript Object Notation (JSON)](https://www.json.org/) to facilitate create, retrieve, update, and delete (CRUD) operations on objects within an application. Each type of operation is associated with a particular HTTP verb:

* `GET`: Retrieve an object or list of objects
* `POST`: Create an object
Expand Down
27 changes: 27 additions & 0 deletions docs/release-notes/version-4.2.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# NetBox v4.2

## v4.2.4 (2025-02-21)

### Enhancements

* [#17309](https://github.com/netbox-community/netbox/issues/17309) - Omit empty counts in related object tables
* [#18277](https://github.com/netbox-community/netbox/issues/18277) - Improve multi-table inheritance in serialization of change-logged models
* [#18286](https://github.com/netbox-community/netbox/issues/18286) - Add more job duration choices
* [#18357](https://github.com/netbox-community/netbox/issues/18357) - Display author name in plugin list for locally installed plugins
* [#18408](https://github.com/netbox-community/netbox/issues/18408) - Add Paused status for virtual machines
* [#18584](https://github.com/netbox-community/netbox/issues/18584) - Add rack type column to manufacturer list

### Bug Fixes

* [#17436](https://github.com/netbox-community/netbox/issues/17436) - Fix {module} replacement in module bays
* [#18013](https://github.com/netbox-community/netbox/issues/18013) - Limit object type to selected object in change log filter
* [#18241](https://github.com/netbox-community/netbox/issues/18241) - Default logging level of custom scripts changed to INFO
* [#18247](https://github.com/netbox-community/netbox/issues/18247) - Fix visibility of disabled cable paths in dark mode
* [#18480](https://github.com/netbox-community/netbox/issues/18480) - Clean data passed to script in runscript command
* [#18555](https://github.com/netbox-community/netbox/issues/18555) - Add default get_absolute_url method to plugin models
* [#18585](https://github.com/netbox-community/netbox/issues/18585) - Fix filtering circuits by location
* [#18593](https://github.com/netbox-community/netbox/issues/18593) - Fix "Create & Add Another" IP Address workflow
* [#18594](https://github.com/netbox-community/netbox/issues/18594) - Enable sorting by ASN count on site and provider lists
* [#18619](https://github.com/netbox-community/netbox/issues/18619) - Ensure shift-click selection selects only visible list items
* [#18674](https://github.com/netbox-community/netbox/issues/18674) - Preserve form values when selecting speed on circuit termination

---

## v4.2.3 (2025-02-04)

### Enhancements
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ markdown_extensions:
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
not_in_nav: |
/index.md
nav:
- Introduction: 'introduction.md'
- Features:
Expand Down
5 changes: 5 additions & 0 deletions netbox/circuits/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
to_field_name='slug',
label=_('Site (slug)'),
)
location_id = django_filters.ModelMultipleChoiceFilter(
field_name='terminations___location',
label=_('Location (ID)'),
queryset=Location.objects.all(),
)
termination_a_id = django_filters.ModelMultipleChoiceFilter(
queryset=CircuitTermination.objects.all(),
label=_('Termination A (ID)'),
Expand Down
7 changes: 6 additions & 1 deletion netbox/circuits/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
'type_id', 'status', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit',
name=_('Attributes')
),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
)
Expand Down Expand Up @@ -181,6 +181,11 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
},
label=_('Site')
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
label=_('Location')
)
install_date = forms.DateField(
label=_('Install date'),
required=False,
Expand Down
3 changes: 1 addition & 2 deletions netbox/circuits/models/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,8 @@ def get_absolute_url(self):
def clean(self):
super().clean()

# Must define either site *or* provider network
if self.termination is None:
raise ValidationError(_("A circuit termination must attach to termination."))
raise ValidationError(_("A circuit termination must attach to a terminating object."))

def save(self, *args, **kwargs):
# Cache objects associated with the terminating object (for filtering)
Expand Down
1 change: 0 additions & 1 deletion netbox/circuits/tables/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
verbose_name=_('ASNs')
)
asn_count = columns.LinkedCountColumn(
accessor=tables.A('asns__count'),
viewname='ipam:asn_list',
url_params={'provider_id': 'pk'},
verbose_name=_('ASN Count')
Expand Down
24 changes: 22 additions & 2 deletions netbox/circuits/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from circuits.choices import *
from circuits.filtersets import *
from circuits.models import *
from dcim.choices import InterfaceTypeChoices
from dcim.models import Cable, Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site, SiteGroup
from dcim.choices import InterfaceTypeChoices, LocationStatusChoices
from dcim.models import (
Cable, Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Region, Site, SiteGroup
)
from ipam.models import ASN, RIR
from netbox.choices import DistanceUnitChoices
from tenancy.models import Tenant, TenantGroup
Expand Down Expand Up @@ -225,6 +227,17 @@ def setUpTestData(cls):
)
ProviderNetwork.objects.bulk_create(provider_networks)

locations = (
Location.objects.create(
site=sites[0], name='Test Location 1', slug='test-location-1',
status=LocationStatusChoices.STATUS_ACTIVE,
),
Location.objects.create(
site=sites[1], name='Test Location 2', slug='test-location-2',
status=LocationStatusChoices.STATUS_ACTIVE,
),
)

circuits = (
Circuit(
provider=providers[0],
Expand Down Expand Up @@ -305,7 +318,9 @@ def setUpTestData(cls):

circuit_terminations = ((
CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A'),
CircuitTermination(circuit=circuits[0], termination=locations[0], term_side='Z'),
CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A'),
CircuitTermination(circuit=circuits[1], termination=locations[1], term_side='Z'),
CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A'),
CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
Expand Down Expand Up @@ -395,6 +410,11 @@ def test_site(self):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_location(self):
location_ids = Location.objects.values_list('id', flat=True)[:2]
params = {'location_id': location_ids}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_tenant(self):
tenants = Tenant.objects.all()[:2]
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
Expand Down
4 changes: 3 additions & 1 deletion netbox/circuits/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.utils.translation import gettext_lazy as _

from dcim.views import PathTraceView
from ipam.models import ASN
from netbox.views import generic
from tenancy.views import ObjectContactsView
from utilities.forms import ConfirmationForm
Expand All @@ -20,7 +21,8 @@
@register_model_view(Provider, 'list', path='', detail=False)
class ProviderListView(generic.ObjectListView):
queryset = Provider.objects.annotate(
count_circuits=count_related(Circuit, 'provider')
count_circuits=count_related(Circuit, 'provider'),
asn_count=count_related(ASN, 'providers'),
)
filterset = filtersets.ProviderFilterSet
filterset_form = forms.ProviderFilterForm
Expand Down
2 changes: 2 additions & 0 deletions netbox/core/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ class JobIntervalChoices(ChoiceSet):
CHOICES = (
(INTERVAL_MINUTELY, _('Minutely')),
(INTERVAL_HOURLY, _('Hourly')),
(INTERVAL_HOURLY * 12, _('12 hours')),
(INTERVAL_DAILY, _('Daily')),
(INTERVAL_WEEKLY, _('Weekly')),
(INTERVAL_DAILY * 30, _('30 days')),
)


Expand Down
2 changes: 2 additions & 0 deletions netbox/core/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):


class JobFilterForm(SavedFiltersMixin, FilterForm):
model = Job
fieldsets = (
FieldSet('q', 'filter_id'),
FieldSet('object_type', 'status', name=_('Attributes')),
Expand Down Expand Up @@ -162,6 +163,7 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):


class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm):
model = ConfigRevision
fieldsets = (
FieldSet('q', 'filter_id'),
)
10 changes: 10 additions & 0 deletions netbox/core/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class Plugin:
release_recent_history: list[PluginVersion] = field(default_factory=list)
is_local: bool = False # extra field for locally installed plugins
is_installed: bool = False
failed_to_load: bool = False
installed_version: str = ''
netbox_min_version: str = ''
netbox_max_version: str = ''
Expand All @@ -86,6 +87,13 @@ def get_local_plugins(plugins=None):
if plugin_config.release_track:
installed_version = f'{installed_version}-{plugin_config.release_track}'

if plugin_config.author:
author = PluginAuthor(
name=plugin_config.author,
)
else:
author = None

local_plugins[plugin_config.name] = Plugin(
config_name=plugin_config.name,
title_short=plugin_config.verbose_name,
Expand All @@ -94,6 +102,8 @@ def get_local_plugins(plugins=None):
description_short=plugin_config.description,
is_local=True,
is_installed=plugin_name in registry['plugins']['installed'],
failed_to_load=plugin_name not in registry['plugins']['installed'],
author=author,
installed_version=installed_version,
netbox_min_version=plugin_config.min_version,
netbox_max_version=plugin_config.max_version,
Expand Down
6 changes: 3 additions & 3 deletions netbox/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class DataFileBulkDeleteView(generic.BulkDeleteView):

@register_model_view(Job, 'list', path='', detail=False)
class JobListView(generic.ObjectListView):
queryset = Job.objects.all()
queryset = Job.objects.defer('data')
filterset = filtersets.JobFilterSet
filterset_form = forms.JobFilterForm
table = tables.JobTable
Expand All @@ -183,12 +183,12 @@ class JobView(generic.ObjectView):

@register_model_view(Job, 'delete')
class JobDeleteView(generic.ObjectDeleteView):
queryset = Job.objects.all()
queryset = Job.objects.defer('data')


@register_model_view(Job, 'bulk_delete', path='delete', detail=False)
class JobBulkDeleteView(generic.BulkDeleteView):
queryset = Job.objects.all()
queryset = Job.objects.defer('data')
filterset = filtersets.JobFilterSet
table = tables.JobTable

Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,7 @@ def search(self, queryset, name, value):
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(virtual_chassis__name__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(inventoryitems__serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) |
Expand Down
Loading

0 comments on commit b5e9ae0

Please sign in to comment.