Compare commits

..

33 Commits

Author SHA1 Message Date
Martin Hauser
90717f7c24 chore(ruff): Enable I (isort) and stabilize import ordering
- Adopt Ruff `I` (isort) rules for consistent import sorting
- Update intra-app imports to use relative imports within each Django
  app to avoid circular import issues
- Add two `# isort: split` boundaries to keep required imports pinned
  in `__init__.py` modules
2026-02-12 17:13:30 +01:00
Jason Novinger
6c383f293c Fixes #20435: Fix navigation margin issue when scrollbar appears (#21403)
Override Tabler's problematic margin-left: calc(100vw - 100%) rule that
causes a gap between the sidebar and main content when vertical scrollbar
is present on Windows/Linux browsers.

Uses scrollbar-gutter: stable to match the upstream fix in Tabler PR #2548.
2026-02-12 11:30:33 +01:00
github-actions
5bf516c63d Update source translation strings 2026-02-12 05:28:54 +00:00
Aditya Sharma
7df062d590 Fixes #21358: Prevent exception when sorting by Token column (#21391)
Mark the `token` TemplateColumn as non-orderable since it maps to a
Python property rather than a database field, causing a FieldError
when django-tables2 attempts to sort by it.

Add a regression test for TokenTable following the existing pattern
in circuits and vpn test suites.
2026-02-12 00:21:49 +01:00
Aditya Sharma
4b22be03a0 Fixes #21354: Fix Swagger-UI generating wrong URLs when BASE_PATH is set (#21392) 2026-02-11 11:35:13 -08:00
Dylan Lucci
24769ce127 Closes #21266: Add installed device table columns to DeviceBay table (#21348)
Expose additional properties of the device installed in each bay as
configurable table columns.

- Rename `role` → `installed_role`
- Rename `device_type` → `installed_device_type`
- Add `installed_description`, `installed_serial`, and
  `installed_asset_tag` columns to `DeviceBayTable`

---------

Co-authored-by: Martin Hauser <mhauser@netboxlabs.com>
2026-02-11 13:55:37 +01:00
github-actions
164e9db98d Update source translation strings 2026-02-11 05:29:43 +00:00
Martin Hauser
23f1c86e9c Closes #20211: Use thumbnails for ImageAttachment hover previews to improve page load performance (#21386) 2026-02-10 11:01:33 -06:00
Martin Hauser
02ffdd9d5d Closes #21268: Add Device Type details panel to Device view (#21368) 2026-02-10 10:37:35 -06:00
Martin Hauser
5013297326 feat(virtualization): Refactor VirtualMachine view to UI layout
Migrate the VirtualMachine detail view to SimpleLayout with standardized
panels for attributes, clusters, and resources. Modularize templates
to improve maintainability and reuse.

Fixes #21337
2026-02-10 10:22:18 -05:00
github-actions
584e0a9b8c Update source translation strings 2026-02-10 05:29:34 +00:00
Martin Hauser
3ac9d0b8bf Closes #20981: Enhance JSON rendering for Custom Validators and Protection Rules in Config Revision View (#21376)
* feat(config): Add extra context to ConfigRevisionView

Introduces `get_extra_context` method for `ConfigRevisionView` to
format JSON-based attributes like `CUSTOM_VALIDATORS`,
`DEFAULT_USER_PREFERENCES`, and `PROTECTION_RULES`.
This ensures clearer rendering of configuration data in the UI.

Fixes #20981

* Reduce padding on JSON blocks

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2026-02-09 09:48:39 -05:00
github-actions
b387ea5f58 Update source translation strings 2026-02-06 05:22:42 +00:00
bctiemann
ba9f6bf359 Fixes: #19129 - Richer display of MAC addresses in InterfaceTable when multiple MACs are present (#21270)
* Richer display of MAC addresses in InterfaceTable when multiple MACs are present

* Fix docstring

* Fix docstring

* Use mac_address_display in interface detail page

* Ensure "-" null placeholder still shows up on detail page

* Also include vminterface.html

* Simplify Multiple MAC addresses with additional selectable column for tables in list view and detail view

* Use ManyToManyColumn
2026-02-05 11:16:31 -05:00
Martin Hauser
ee6cbdcefe Fixes #21320: Prevent Rack validation errors when site or optional fields are missing during import (#21321) 2026-02-03 09:32:07 -06:00
bctiemann
de1c5120dd Merge pull request #21346 from netbox-community/release-v4.5.2
Release v4.5.2
2026-02-03 08:42:21 -05:00
Jeremy Stretch
87d2e02c85 Release v4.5.2 2026-02-03 08:09:14 -05:00
github-actions
cbbc4f74b8 Update source translation strings 2026-02-03 05:22:13 +00:00
Martin Hauser
be5bd74d4e feat(ipam): Add parent object fields for Services
Include `parent_object_type` and `parent_object_id` in `clone_fields`
for services. This improves cloning behavior for models using parent
object references, ensuring more accurate data duplication.

Fixes #21168
2026-02-02 16:05:09 -05:00
Jason Novinger
cf12bb5bf5 Fixes #20902: Avoid conflict when Git URL contains embedded username (#21252) 2026-02-02 11:16:32 -08:00
Jeremy Stretch
c060eef1d8 Closes #21300: Cache model-specific custom field lookups for the duration of a request (#21334) 2026-02-02 10:58:12 -08:00
bctiemann
96f0debe6e Merge pull request #21328 from netbox-community/21327-ContentTypeField-caching
Closes #21327: Leverage `get_by_natural_key()` to resolve ContentTypes
2026-02-02 13:46:04 -05:00
Martin Hauser
b26c7f34cd feat(models): Handle GFK attributes in CloningMixin
Extend the CloningMixin to inject GenericForeignKey (GFK) attributes
when both content type and ID fields are present. Improves support for
models using GFK fields during cloning operations.

Fixes #21201
2026-02-02 13:02:32 -05:00
bctiemann
d6428c6aa4 Merge pull request #21314 from marsteel/21233-UI-Add-horizontal-padding-to-Release-info-section
Fixes #21233: UI Add horizontal padding to Release info section in Navigation menu
2026-02-02 11:17:30 -05:00
github-actions
e3eca98897 Update source translation strings 2026-01-31 05:14:50 +00:00
Jeremy Stretch
cdc735fe41 Closes #21302: Avoid redundant uniqueness checks in REST API serializers 2026-01-30 19:36:42 -05:00
Jeremy Stretch
aa4a9da955 Closes #21303: Cache serialized post-change data on object (#21325)
* Closes #21303: Cache serialized post-change data on object

* Set to_objectchange.alters_data

* Restructure logic for determining post-change snapshot
2026-01-30 14:49:12 -05:00
Jeremy Stretch
5c6fc2fb6f Closes #21110: Support for cursor-based pagination in GraphQL API (#21322) 2026-01-30 11:45:35 -08:00
Jeremy Stretch
ad29cb2d66 Closes #21263: Prefetch related objects after creating/updating objects via REST API (#21329)
* Closes #21263: Prefetch related objects after creating/updating objects via REST API

* Add comment re: ordering by PK
2026-01-30 14:13:05 -05:00
Aditya Sharma
bec5ecf6a9 Closes #21209: Accept case-insensitive model names in configuration (#21275)
NetBox now accepts case-insensitive model identifiers in configuration, allowing
both lowercase (e.g. "dcim.site") and PascalCase (e.g. "dcim.Site") for
DEFAULT_DASHBOARD, CUSTOM_VALIDATORS, and PROTECTION_RULES.
This makes model name handling consistent with FIELD_CHOICES.

- Add a shared case-insensitive config lookup helper (get_config_value_ci())
- Use the helper in extras/signals.py and core/signals.py
- Update FIELD_CHOICES ChoiceSetMeta to support case-insensitive replace/extend
  (only compute extend choices if no replacement is defined)
- Add unit tests for get_config_value_ci()
- Add integration tests for case-insensitive FIELD_CHOICES replacement/extension
- Update documentation examples to use PascalCase consistently
2026-01-30 13:48:38 +01:00
github-actions
c98f55dbd2 Update source translation strings 2026-01-30 05:18:59 +00:00
Jeremy Stretch
dfe20532a1 Closes #21327: Leverage get_by_natural_key() to resolve ContentTypes 2026-01-29 19:46:22 -05:00
MA Gang
43ae52089f Add padding to release info div
Add padding to release info div in layout.html
2026-01-28 14:29:38 +01:00
562 changed files with 30847 additions and 23855 deletions

View File

@@ -15,7 +15,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.5.1
placeholder: v4.5.2
validations:
required: true
- type: dropdown

View File

@@ -27,7 +27,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.5.1
placeholder: v4.5.2
validations:
required: true
- type: dropdown

View File

@@ -8,7 +8,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.5.1
placeholder: v4.5.2
validations:
required: true
- type: dropdown

View File

@@ -85,7 +85,7 @@ drf-spectacular-sidecar
feedparser
# WSGI HTTP server
# https://docs.gunicorn.org/en/latest/news.html
# https://gunicorn.org/news/
gunicorn
# Platform-agnostic template rendering engine

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ This is a mapping of models to [custom validators](../customization/custom-valid
```python
CUSTOM_VALIDATORS = {
"dcim.site": [
"dcim.Site": [
{
"name": {
"min_length": 5,
@@ -17,12 +17,15 @@ CUSTOM_VALIDATORS = {
},
"my_plugin.validators.Validator1"
],
"dcim.device": [
"dcim.Device": [
"my_plugin.validators.Validator1"
]
}
```
!!! info "Case-Insensitive Model Names"
Model identifiers are case-insensitive. Both `dcim.site` and `dcim.Site` are valid and equivalent.
---
## FIELD_CHOICES
@@ -53,6 +56,9 @@ FIELD_CHOICES = {
}
```
!!! info "Case-Insensitive Field Identifiers"
Field identifiers are case-insensitive. Both `dcim.Site.status` and `dcim.site.status` are valid and equivalent.
The following model fields support configurable choices:
* `circuits.Circuit.status`
@@ -98,7 +104,7 @@ This is a mapping of models to [custom validators](../customization/custom-valid
```python
PROTECTION_RULES = {
"dcim.site": [
"dcim.Site": [
{
"status": {
"eq": "decommissioning"
@@ -108,3 +114,6 @@ PROTECTION_RULES = {
]
}
```
!!! info "Case-Insensitive Model Names"
Model identifiers are case-insensitive. Both `dcim.site` and `dcim.Site` are valid and equivalent.

View File

@@ -18,17 +18,7 @@ They can also be used as a mechanism for validating the integrity of data within
Custom scripts are Python code which exists outside the NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
!!! danger "Only install trusted scripts"
Custom scripts have unrestricted access to change anything in the database and are inherently unsafe and should only be installed and run from trusted sources. You should also review and set permissions for who can run scripts if the script can modify any data.
!!! tip "Permissions for Custom Scripts"
A user can be granted permissions on all Custom Scripts via the "Managed File" object-level permission. To further restrict a user to only be able to access certain scripts, create an additional permission on the "Script" object type, with appropriate queryset-style constraints matching fields available on Script. For example:
```json
{
"name__in": [
"MyScript"
]
}
```
Custom scripts have unrestricted access to change anything in the databse and are inherently unsafe and should only be installed and run from trusted sources. You should also review and set permissions for who can run scripts if the script can modify any data.
## Writing Custom Scripts

View File

@@ -144,7 +144,7 @@ Then, compile these portable (`.po`) files for use in the application:
* Update the version number and published date in `netbox/release.yaml`. Add or remove the designation (e.g. `beta1`) if applicable.
* Copy the version number from `release.yaml` to `pyproject.toml` in the project root.
* Update the example version numbers in the feature request and bug report templates under `.github/ISSUE_TEMPLATES/`.
* Update the example version numbers in the feature request, bug report, and performance templates under `.github/ISSUE_TEMPLATES/`.
* 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

View File

@@ -133,23 +133,67 @@ The field "class_type" is an easy way to distinguish what type of object it is w
## Pagination
Queries can be paginated by specifying pagination in the query and supplying an offset and optionaly a limit in the query. If no limit is given, a default of 100 is used. Queries are not paginated unless requested in the query. An example paginated query is shown below:
The GraphQL API supports two types of pagination. Offset-based pagination operates using an offset relative to the first record in a set, specified by the `offset` parameter. For example, the response to a request specifying an offset of 100 will contain the 101st and later matching records. Offset-based pagination feels very natural, but its performance can suffer when dealing with large data sets due to the overhead involved in calculating the relative offset.
The alternative approach is cursor-based pagination, which operates using absolute (rather than relative) primary key values. (These are the numeric IDs assigned to each object in the database.) When using cursor-based pagination, the response will contain records with a primary key greater than or equal to the specified start value, up to the maximum number of results. This strategy requires keeping track of the last seen primary key from each response when paginating through data, but is extremely performant. The cursor is specified by passing the starting object ID via the `start` parameter.
To ensure consistent ordering, objects will always be ordered by their primary keys when cursor-based pagination is used.
!!! note "Cursor-based pagination was introduced in NetBox v4.5.2."
Both pagination strategies support passing an optional `limit` parameter. In both approaches, this specifies the maximum number of objects to include in the response. If no limit is specified, a default value of 100 is used.
### Offset Pagination
The first page will have an `offset` of zero, or the `offset` parameter will be omitted:
```
query {
device_list(pagination: { offset: 0, limit: 20 }) {
device_list(pagination: {offset: 0, limit: 20}) {
id
}
}
```
The second page will have an offset equal to the size of the first page. If the number of records is less than the specified limit, there are no more records to process. For example, if a request specifies a `limit` of 20 but returns only 13 records, we can conclude that this is the final page of records.
```
query {
device_list(pagination: {offset: 20, limit: 20}) {
id
}
}
```
### Cursor Pagination
Set the `start` value to zero to fetch the first page. Note that if the `start` parameter is omitted, offset-based pagination will be used by default.
```
query {
device_list(pagination: {start: 0, limit: 20}) {
id
}
}
```
To determine the `start` value for the next page, add 1 to the primary key (`id`) of the last record in the previous page.
For example, if the ID of the last record in the previous response was 123, we would specify a `start` value of 124:
```
query {
device_list(pagination: {start: 124, limit: 20}) {
id
}
}
```
This will return up to 20 records with an ID greater than or equal to 124.
## Authentication
NetBox's GraphQL API uses the same API authentication tokens as its REST API. Authentication tokens are included with requests by attaching an `Authorization` HTTP header in the following form:
```
Authorization: Token $TOKEN
```
NetBox's GraphQL API uses the same API authentication tokens as its REST API. See the [REST API authentication](./rest-api.md#authentication) documentation for further detail.
## Disabling the GraphQL API

View File

@@ -1,5 +1,53 @@
# NetBox v4.5
## v4.5.2 (2026-02-03)
### Enhancements
* [#15801](https://github.com/netbox-community/netbox/issues/15801) - Add link peer and connection columns to the VLAN device interfaces table
* [#19221](https://github.com/netbox-community/netbox/issues/19221) - Truncate long image attachment filenames in the UI
* [#19869](https://github.com/netbox-community/netbox/issues/19869) - Display peer connections for LAG member interfaces
* [#20052](https://github.com/netbox-community/netbox/issues/20052) - Increase logging level of error message when a custom script fails to load
* [#20172](https://github.com/netbox-community/netbox/issues/20172) - Add `cabled` filter for interfaces in GraphQL API
* [#21081](https://github.com/netbox-community/netbox/issues/21081) - Add owner group table columns & filters across all supported object list views
* [#21088](https://github.com/netbox-community/netbox/issues/21088) - Add max depth and max length dropdowns for child prefix views
* [#21110](https://github.com/netbox-community/netbox/issues/21110) - Support cursor-based pagination in GraphQL API
* [#21201](https://github.com/netbox-community/netbox/issues/21201) - Pre-populate GenericForeignKey form fields when cloning
* [#21209](https://github.com/netbox-community/netbox/issues/21209) - Ignore case sensitivity for configuration parameters which specify an app label and model name
* [#21228](https://github.com/netbox-community/netbox/issues/21228) - Support image attachments for rack types
* [#21244](https://github.com/netbox-community/netbox/issues/21244) - Enable omitting specific fields from REST API responses with `?omit=` parameter
### Performance Improvements
* [#21249](https://github.com/netbox-community/netbox/issues/21249) - Avoid extraneous user query when no event rules are present
* [#21259](https://github.com/netbox-community/netbox/issues/21259) - Cache ObjectType lookups for the duration of a request
* [#21260](https://github.com/netbox-community/netbox/issues/21260) - Defer object serialization for events pipeline processing
* [#21263](https://github.com/netbox-community/netbox/issues/21263) - Prefetch related objects after creating/updating objects via REST API
* [#21300](https://github.com/netbox-community/netbox/issues/21300) - Cache custom field lookups for the duration of a request
* [#21302](https://github.com/netbox-community/netbox/issues/21302) - Avoid redundant uniqueness checks in ValidatedModelSerializer
* [#21303](https://github.com/netbox-community/netbox/issues/21303) - Cache post-change snapshot on each instance after serialization
* [#21327](https://github.com/netbox-community/netbox/issues/21327) - Always leverage `get_by_natural_key()` to resolve ContentTypes
### Bug Fixes
* [#20212](https://github.com/netbox-community/netbox/issues/20212) - Fix support for image attachment thumbnails when using S3 storage
* [#20383](https://github.com/netbox-community/netbox/issues/20383) - When editing a device, clearing the assigned unit should also clear the rack face selection
* [#20902](https://github.com/netbox-community/netbox/issues/20902) - Avoid `SyncError` exception when Git URL contains an embedded username
* [#20977](https://github.com/netbox-community/netbox/issues/20977) - "Run again" button should respect script variable defaults
* [#21115](https://github.com/netbox-community/netbox/issues/21115) - Include `attribute_data` in ModuleType YAML export
* [#21129](https://github.com/netbox-community/netbox/issues/21129) - Store queue name on the Job model to ensure deletion of associated RQ task when a non-default queue is used
* [#21168](https://github.com/netbox-community/netbox/issues/21168) - Fix Application Service cloning to preserve parent object
* [#21173](https://github.com/netbox-community/netbox/issues/21173) - Ensure all plugin menu items are registered regardless of initialization order
* [#21176](https://github.com/netbox-community/netbox/issues/21176) - Remove checkboxes from IP ranges in mixed-type tables
* [#21202](https://github.com/netbox-community/netbox/issues/21202) - Fix scoped form cloning clearing the `scope` field when `scope_type` changes
* [#21214](https://github.com/netbox-community/netbox/issues/21214) - Clean up AutoSyncRecord when detaching from DataSource
* [#21242](https://github.com/netbox-community/netbox/issues/21242) - Navigation menu items for authentication should not require `staff_only` permission
* [#21254](https://github.com/netbox-community/netbox/issues/21254) - Fix `AttributeError` exception when checking for latest release
* [#21262](https://github.com/netbox-community/netbox/issues/21262) - Assigned scope should be replicated when cloning a prefix
* [#21269](https://github.com/netbox-community/netbox/issues/21269) - Fix replication of front/rear port assignments from the module type when installing a module
---
## v4.5.1 (2026-01-20)
### Enhancements

View File

@@ -1,6 +1,7 @@
from django.urls import include, path
from utilities.urls import get_model_urls
from . import views
app_name = 'account'

View File

@@ -2,14 +2,15 @@ import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import update_last_login
from django.contrib.auth.signals import user_logged_in
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render, resolve_url
from django.shortcuts import get_object_or_404, redirect, render, resolve_url
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import urlencode
@@ -18,7 +19,6 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
from account.models import UserToken
from core.models import ObjectChange
from core.tables import ObjectChangeTable
from extras.models import Bookmark
@@ -35,11 +35,13 @@ from utilities.request import safe_for_redirect
from utilities.string import remove_linebreaks
from utilities.views import register_model_view
from .models import UserToken
#
# Login/logout
#
class LoginView(View):
"""
Perform user authentication via the web UI.

View File

@@ -1,2 +1,2 @@
from .serializers_.providers import *
from .serializers_.circuits import *
from .serializers_.providers import *

View File

@@ -4,18 +4,28 @@ from rest_framework import serializers
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
from circuits.constants import CIRCUIT_GROUP_ASSIGNMENT_MEMBER_MODELS, CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import (
Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType, VirtualCircuit,
VirtualCircuitTermination, VirtualCircuitType,
Circuit,
CircuitGroup,
CircuitGroupAssignment,
CircuitTermination,
CircuitType,
VirtualCircuit,
VirtualCircuitTermination,
VirtualCircuitType,
)
from dcim.api.serializers_.device_components import InterfaceSerializer
from dcim.api.serializers_.cables import CabledObjectSerializer
from dcim.api.serializers_.device_components import InterfaceSerializer
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import (
NetBoxModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer,
NetBoxModelSerializer,
OrganizationalModelSerializer,
PrimaryModelSerializer,
WritableNestedSerializer,
)
from netbox.choices import DistanceUnitChoices
from tenancy.api.serializers_.tenants import TenantSerializer
from .providers import ProviderAccountSerializer, ProviderNetworkSerializer, ProviderSerializer
__all__ = (

View File

@@ -5,6 +5,7 @@ from ipam.api.serializers_.asns import ASNSerializer
from ipam.models import ASN
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
from netbox.api.serializers import PrimaryModelSerializer
from .nested import NestedProviderAccountSerializer
__all__ = (

View File

@@ -1,6 +1,6 @@
from netbox.api.routers import NetBoxRouter
from . import views
from . import views
router = NetBoxRouter()
router.APIRootView = views.CircuitsRootView

View File

@@ -4,6 +4,7 @@ from circuits import filtersets
from circuits.models import *
from dcim.api.views import PassThroughPortMixin
from netbox.api.viewsets import NetBoxModelViewSet
from . import serializers

View File

@@ -9,7 +9,8 @@ class CircuitsConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import signals, search # noqa: F401
from . import search, signals # noqa: F401
from .models import CircuitTermination
# Register models

View File

@@ -2,11 +2,11 @@ from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
#
# Circuits
#
class CircuitStatusChoices(ChoiceSet):
key = 'Circuit.status'

View File

@@ -1,6 +1,5 @@
from django.db.models import Q
# models values for ContentTypes which may be CircuitTermination termination types
CIRCUIT_TERMINATION_TERMINATION_TYPES = (
'region', 'sitegroup', 'site', 'location', 'providernetwork',

View File

@@ -9,9 +9,13 @@ from ipam.models import ASN
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
ContentTypeFilter,
MultiValueCharFilter,
MultiValueNumberFilter,
TreeNodeMultipleChoiceFilter,
)
from utilities.filtersets import register_filterset
from .choices import *
from .models import *

View File

@@ -4,7 +4,10 @@ from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from circuits.choices import (
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices,
CircuitCommitRateChoices,
CircuitPriorityChoices,
CircuitStatusChoices,
VirtualCircuitTerminationRoleChoices,
)
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import *
@@ -15,7 +18,10 @@ from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditFor
from tenancy.models import Tenant
from utilities.forms import add_blank_choice, get_field_value
from utilities.forms.fields import (
ColorField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
ColorField,
ContentTypeChoiceField,
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import BulkEditNullBooleanSelect, DatePicker, HTMXSelect, NumberWithOptions

View File

@@ -2,7 +2,10 @@ from django import forms
from django.utils.translation import gettext as _
from circuits.choices import (
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices,
CircuitCommitRateChoices,
CircuitPriorityChoices,
CircuitStatusChoices,
CircuitTerminationSideChoices,
VirtualCircuitTerminationRoleChoices,
)
from circuits.models import *
@@ -10,7 +13,7 @@ from dcim.models import Location, Region, Site, SiteGroup
from ipam.models import ASN
from netbox.choices import DistanceUnitChoices
from netbox.forms import NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm, PrimaryModelFilterSetForm
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
from utilities.forms import add_blank_choice
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.rendering import FieldSet

View File

@@ -4,7 +4,9 @@ from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from circuits.choices import (
CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices, VirtualCircuitTerminationRoleChoices,
CircuitCommitRateChoices,
CircuitTerminationPortSpeedChoices,
VirtualCircuitTerminationRoleChoices,
)
from circuits.constants import *
from circuits.models import *
@@ -14,7 +16,10 @@ from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelF
from tenancy.forms import TenancyForm
from utilities.forms import get_field_value
from utilities.forms.fields import (
ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
ContentTypeChoiceField,
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
SlugField,
)
from utilities.forms.mixins import DistanceValidationMixin
from utilities.forms.rendering import FieldSet, InlineFields

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Annotated, TYPE_CHECKING
from typing import TYPE_CHECKING, Annotated
import strawberry
import strawberry_django

View File

@@ -1,10 +1,10 @@
from datetime import date
from typing import Annotated, TYPE_CHECKING
from typing import TYPE_CHECKING, Annotated
import strawberry
import strawberry_django
from strawberry.scalars import ID
from strawberry_django import BaseFilterLookup, FilterLookup, DateFilterLookup
from strawberry_django import BaseFilterLookup, DateFilterLookup, FilterLookup
from circuits import models
from circuits.graphql.filter_mixins import CircuitTypeFilterMixin
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
from dcim.graphql.filters import InterfaceFilter, LocationFilter, RegionFilter, SiteFilter, SiteGroupFilter
from ipam.graphql.filters import ASNFilter
from netbox.graphql.filter_lookups import IntegerLookup
from .enums import *
__all__ = (

View File

@@ -1,4 +1,4 @@
from typing import Annotated, List, TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Annotated, List, Union
import strawberry
import strawberry_django
@@ -8,6 +8,7 @@ from dcim.graphql.mixins import CabledObjectMixin
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
from netbox.graphql.types import BaseObjectType, ObjectType, OrganizationalObjectType, PrimaryObjectType
from tenancy.graphql.types import TenantType
from .filters import *
if TYPE_CHECKING:

View File

@@ -1,7 +1,8 @@
import django.db.models.deletion
from django.db import migrations, models
import ipam.fields
from utilities.json import CustomFieldJSONEncoder
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):

View File

@@ -1,6 +1,6 @@
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.2.5 on 2023-10-20 21:25
from django.db import migrations
import utilities.fields

View File

@@ -1,8 +1,9 @@
import django.db.models.deletion
import taggit.managers
import utilities.json
from django.db import migrations, models
import utilities.json
class Migration(migrations.Migration):
dependencies = [

View File

@@ -8,10 +8,16 @@ from django.utils.translation import gettext_lazy as _
from circuits.choices import *
from dcim.models import CabledObjectModel
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
from netbox.models.mixins import DistanceMixin
from netbox.models.features import (
ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin,
ContactsMixin,
CustomFieldsMixin,
CustomLinksMixin,
ExportTemplatesMixin,
ImageAttachmentsMixin,
TagsMixin,
)
from netbox.models.mixins import DistanceMixin
from .base import BaseCircuitType
__all__ = (

View File

@@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from circuits.choices import *
from netbox.models import ChangeLoggedModel, PrimaryModel
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin
from .base import BaseCircuitType
__all__ = (

View File

@@ -1,4 +1,5 @@
from netbox.search import SearchIndex, register_search
from . import models

View File

@@ -2,6 +2,7 @@ from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from dcim.signals import rebuild_paths
from .models import CircuitTermination

View File

@@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
from circuits.models import *
from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
from .columns import CommitRateColumn
__all__ = (

View File

@@ -5,7 +5,16 @@ from circuits.filtersets import *
from circuits.models import *
from dcim.choices import InterfaceTypeChoices, LocationStatusChoices
from dcim.models import (
Cable, Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Region, Site, SiteGroup
Cable,
Device,
DeviceRole,
DeviceType,
Interface,
Location,
Manufacturer,
Region,
Site,
SiteGroup,
)
from ipam.models import ASN, RIR
from netbox.choices import DistanceUnitChoices

View File

@@ -1,4 +1,4 @@
from django.test import RequestFactory, tag, TestCase
from django.test import RequestFactory, TestCase, tag
from circuits.models import CircuitTermination
from circuits.tables import CircuitTerminationTable

View File

@@ -1,6 +1,7 @@
from django.urls import include, path
from utilities.urls import get_model_urls
from . import views
app_name = 'circuits'

View File

@@ -5,14 +5,15 @@ from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, B
from netbox.views import generic
from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view
from . import filtersets, forms, tables
from .models import *
#
# Providers
#
@register_model_view(Provider, 'list', path='', detail=False)
class ProviderListView(generic.ObjectListView):
queryset = Provider.objects.annotate(

View File

@@ -2,10 +2,14 @@ import re
import typing
from collections import OrderedDict
from drf_spectacular.extensions import OpenApiSerializerFieldExtension, OpenApiSerializerExtension, _SchemaType
from drf_spectacular.extensions import OpenApiSerializerExtension, OpenApiSerializerFieldExtension, _SchemaType
from drf_spectacular.openapi import AutoSchema
from drf_spectacular.plumbing import (
build_basic_type, build_choice_field, build_media_type_object, build_object_type, get_doc,
build_basic_type,
build_choice_field,
build_media_type_object,
build_object_type,
get_doc,
)
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import Direction

View File

@@ -1,12 +1,13 @@
from rest_framework import serializers
from core.choices import *
from core.models import ObjectChange
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import BaseModelSerializer
from users.api.serializers_.users import UserSerializer
from ...choices import *
from ...models import ObjectChange
__all__ = (
'ObjectChangeSerializer',
)

View File

@@ -1,9 +1,10 @@
from core.choices import *
from core.models import DataFile, DataSource
from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer
from netbox.utils import get_data_backend_choices
from ...choices import *
from ...models import DataFile, DataSource
__all__ = (
'DataFileSerializer',
'DataSourceSerializer',

View File

@@ -1,14 +1,15 @@
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.choices import *
from core.models import Job
from netbox.api.exceptions import SerializerNotFound
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.serializers import BaseModelSerializer
from users.api.serializers_.users import UserSerializer
from utilities.api import get_serializer_for_model
from ...choices import *
from ...models import Job
__all__ = (
'JobSerializer',
)

View File

@@ -5,10 +5,11 @@ from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from core.models import ObjectType
from netbox.api.serializers import BaseModelSerializer
from utilities.views import get_action_url
from ...models import ObjectType
__all__ = (
'ObjectTypeSerializer',
)

View File

@@ -1,4 +1,5 @@
from netbox.api.routers import NetBoxRouter
from . import views
app_name = 'core-api'

View File

@@ -14,15 +14,16 @@ from rest_framework.routers import APIRootView
from rq.job import Job as RQ_Job
from rq.worker import Worker
from core import filtersets
from core.jobs import SyncDataSourceJob
from core.models import *
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.metadata import ContentTypeMetadata
from netbox.api.pagination import LimitOffsetListPagination
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
from utilities.api import IsSuperuser
from .. import filtersets
from ..jobs import SyncDataSourceJob
from ..models import *
from ..utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
from . import serializers

View File

@@ -5,10 +5,11 @@ from django.db import models
from django.db.migrations.operations import AlterModelOptions
from django.utils.translation import gettext as _
from core.events import *
from netbox.events import EventType, EVENT_TYPE_KIND_DANGER, EVENT_TYPE_KIND_SUCCESS, EVENT_TYPE_KIND_WARNING
from netbox.events import EVENT_TYPE_KIND_DANGER, EVENT_TYPE_KIND_SUCCESS, EVENT_TYPE_KIND_WARNING, EventType
from utilities.migration import custom_deconstruct
from .events import *
# Ignore verbose_name & verbose_name_plural Meta options when calculating model migrations
AlterModelOptions.ALTER_OPTION_KEYS.remove('verbose_name')
AlterModelOptions.ALTER_OPTION_KEYS.remove('verbose_name_plural')
@@ -21,11 +22,12 @@ class CoreConfig(AppConfig):
name = "core"
def ready(self):
from core.api import schema # noqa: F401
from core.checks import check_duplicate_indexes # noqa: F401
from netbox.models.features import register_models
from . import data_backends, events, search # noqa: F401
from netbox import context_managers # noqa: F401
from netbox.models.features import register_models
from . import data_backends, events, search # noqa: F401
from .api import schema # noqa: F401
from .checks import check_duplicate_indexes # noqa: F401
# Register models
register_models(*self.get_models())

View File

@@ -1,6 +1,6 @@
from django.core.checks import Error, register, Tags
from django.db.models import Index, UniqueConstraint
from django.apps import apps
from django.core.checks import Error, Tags, register
from django.db.models import Index, UniqueConstraint
__all__ = (
'check_duplicate_indexes',

View File

@@ -2,11 +2,11 @@ from django.utils.translation import gettext_lazy as _
from utilities.choices import ChoiceSet
#
# Data sources
#
class DataSourceStatusChoices(ChoiceSet):
NEW = 'new'
QUEUED = 'queued'

View File

@@ -15,17 +15,31 @@ from netbox.utils import register_data_backend
from utilities.constants import HTTP_PROXY_SUPPORTED_SCHEMAS, HTTP_PROXY_SUPPORTED_SOCK_SCHEMAS
from utilities.proxy import resolve_proxies
from utilities.socks import ProxyPoolManager
from .exceptions import SyncError
__all__ = (
'GitBackend',
'LocalBackend',
'S3Backend',
'url_has_embedded_credentials',
)
logger = logging.getLogger('netbox.data_backends')
def url_has_embedded_credentials(url):
"""
Check if a URL contains embedded credentials (username in the URL).
URLs like 'https://user@bitbucket.org/...' have embedded credentials.
This is used to avoid passing explicit credentials to dulwich when the
URL already contains them, which would cause authentication conflicts.
"""
parsed = urlparse(url)
return bool(parsed.username)
@register_data_backend()
class LocalBackend(DataBackend):
name = 'local'
@@ -102,7 +116,9 @@ class GitBackend(DataBackend):
clone_args['pool_manager'] = ProxyPoolManager(self.socks_proxy)
if self.url_scheme in ('http', 'https'):
if self.params.get('username'):
# Only pass explicit credentials if URL doesn't already contain embedded username
# to avoid credential conflicts (see #20902)
if not url_has_embedded_credentials(self.url) and self.params.get('username'):
clone_args.update(
{
"username": self.params.get('username'),

View File

@@ -1,5 +1,4 @@
import logging
from dataclasses import dataclass, field
from datetime import datetime

View File

@@ -8,6 +8,7 @@ from netbox.utils import get_data_backend_choices
from users.models import User
from utilities.filters import ContentTypeFilter
from utilities.filtersets import register_filterset
from .choices import *
from .models import *

View File

@@ -1,13 +1,14 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from core.choices import JobIntervalChoices
from core.models import *
from netbox.forms import PrimaryModelBulkEditForm
from netbox.utils import get_data_backend_choices
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import BulkEditNullBooleanSelect
from ..choices import JobIntervalChoices
from ..models import *
__all__ = (
'DataSourceBulkEditForm',
)

View File

@@ -1,6 +1,7 @@
from core.models import *
from netbox.forms import PrimaryModelImportForm
from ..models import *
__all__ = (
'DataSourceImportForm',
)

View File

@@ -1,19 +1,23 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from core.choices import *
from core.models import *
from netbox.forms import NetBoxModelFilterSetForm, PrimaryModelFilterSetForm
from netbox.forms.mixins import SavedFiltersMixin
from netbox.utils import get_data_backend_choices
from users.models import User
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
from utilities.forms.fields import (
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
ContentTypeChoiceField,
ContentTypeMultipleChoiceField,
DynamicModelMultipleChoiceField,
TagFilterField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import DateTimePicker
from ..choices import *
from ..models import *
__all__ = (
'ConfigRevisionFilterForm',
'DataFileFilterForm',

View File

@@ -1,9 +1,10 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from core.models import DataFile, DataSource
from utilities.forms.fields import DynamicModelChoiceField
from ..models import DataFile, DataSource
__all__ = (
'SyncedDataMixin',
)

View File

@@ -6,9 +6,7 @@ from django.conf import settings
from django.forms.fields import JSONField as _JSONField
from django.utils.translation import gettext_lazy as _
from core.forms.mixins import SyncedDataMixin
from core.models import *
from netbox.config import get_config, PARAMS
from netbox.config import PARAMS, get_config
from netbox.forms import NetBoxModelForm, PrimaryModelForm
from netbox.registry import registry
from netbox.utils import get_data_backend_choices
@@ -17,6 +15,9 @@ from utilities.forms.fields import JSONField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
from ..forms.mixins import SyncedDataMixin
from ..models import *
__all__ = (
'ConfigRevisionForm',
'DataSourceForm',

View File

@@ -1,6 +1,6 @@
import strawberry
from core.choices import *
from ..choices import *
__all__ = (
'DataSourceStatusEnum',

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Annotated, TYPE_CHECKING
from typing import TYPE_CHECKING, Annotated
import strawberry
import strawberry_django

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Annotated, TYPE_CHECKING
from typing import TYPE_CHECKING, Annotated
import strawberry
import strawberry_django
@@ -7,8 +7,9 @@ from django.contrib.contenttypes.models import ContentType as DjangoContentType
from strawberry.scalars import ID
from strawberry_django import BaseFilterLookup, DatetimeFilterLookup, FilterLookup
from core import models
from netbox.graphql.filters import BaseModelFilter, PrimaryModelFilter
from .. import models
from .enums import *
if TYPE_CHECKING:

View File

@@ -1,14 +1,14 @@
from typing import Annotated, List, TYPE_CHECKING
from typing import TYPE_CHECKING, Annotated, List
import strawberry
import strawberry_django
from django.contrib.contenttypes.models import ContentType
from strawberry.types import Info
from core.models import ObjectChange
from ..models import ObjectChange
if TYPE_CHECKING:
from core.graphql.types import DataFileType, DataSourceType, ObjectChangeType
from .types import DataFileType, DataSourceType, ObjectChangeType
__all__ = (
'ChangelogMixin',

View File

@@ -4,8 +4,9 @@ import strawberry
import strawberry_django
from django.contrib.contenttypes.models import ContentType as DjangoContentType
from core import models
from netbox.graphql.types import BaseObjectType, PrimaryObjectType
from .. import models
from .filters import *
__all__ = (

View File

@@ -8,13 +8,13 @@ from django.core.cache import cache
from django.utils import timezone
from packaging import version
from core.models import Job, ObjectChange
from netbox.config import Config
from netbox.jobs import JobRunner, system_job
from netbox.search.backends import search_backend
from utilities.proxy import resolve_proxies
from .choices import DataSourceStatusChoices, JobIntervalChoices
from .models import DataSource
from .models import DataSource, Job, ObjectChange
class SyncDataSourceJob(JobRunner):

View File

@@ -4,7 +4,6 @@ from django_rq.management.commands.rqworker import Command as _Command
from netbox.registry import registry
DEFAULT_QUEUES = ('high', 'default', 'low')
logger = logging.getLogger('netbox.rqworker')

View File

@@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand, CommandError
from core.choices import DataSourceStatusChoices
from core.models import DataSource
from ...choices import DataSourceStatusChoices
from ...models import DataSource
class Command(BaseCommand):

View File

@@ -1,6 +1,7 @@
import core.models.object_types
from django.db import migrations
import core.models.object_types
class Migration(migrations.Migration):
dependencies = [

View File

@@ -1,4 +1,5 @@
from .object_types import *
from .object_types import * # isort: split
from .change_logging import *
from .config import *
from .data import *

View File

@@ -8,12 +8,12 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel
from core.choices import ObjectChangeActionChoices
from core.querysets import ObjectChangeQuerySet
from netbox.models.features import ChangeLoggingMixin
from netbox.models.features import has_feature
from netbox.models.features import ChangeLoggingMixin, has_feature
from utilities.data import shallow_compare_dict
from ..choices import ObjectChangeActionChoices
from ..querysets import ObjectChangeQuerySet
__all__ = (
'ObjectChange',
)

View File

@@ -1,7 +1,8 @@
from django.core.cache import cache
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from utilities.querysets import RestrictedQuerySet

View File

@@ -19,6 +19,7 @@ from netbox.models import PrimaryModel
from netbox.models.features import JobsMixin
from netbox.registry import registry
from utilities.querysets import RestrictedQuerySet
from ..choices import *
from ..exceptions import SyncError

View File

@@ -4,15 +4,16 @@ from functools import cached_property
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.core.files.storage import storages
from django.db import models
from django.utils.translation import gettext as _
from ..choices import ManagedFileRootPathChoices
from extras.storage import ScriptFileSystemStorage
from netbox.models.features import SyncedDataMixin
from utilities.querysets import RestrictedQuerySet
from ..choices import ManagedFileRootPathChoices
__all__ = (
'ManagedFile',
)

View File

@@ -16,17 +16,18 @@ from django.utils import timezone
from django.utils.translation import gettext as _
from rq.exceptions import InvalidJobOperation
from core.choices import JobStatusChoices
from core.dataclasses import JobLogEntry
from core.events import JOB_COMPLETED, JOB_ERRORED, JOB_FAILED
from core.models import ObjectType
from core.signals import job_end, job_start
from extras.models import Notification
from netbox.models.features import has_feature
from utilities.json import JobLogDecoder
from utilities.querysets import RestrictedQuerySet
from utilities.rqworker import get_queue_for_model
from ..choices import JobStatusChoices
from ..dataclasses import JobLogEntry
from ..events import JOB_COMPLETED, JOB_ERRORED, JOB_FAILED
from ..models import ObjectType
from ..signals import job_end, job_start
__all__ = (
'Job',
)

View File

@@ -1,4 +1,5 @@
from netbox.search import SearchIndex, register_search
from . import models

View File

@@ -3,25 +3,26 @@ from threading import local
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.signals import request_finished
from django.db.models import CASCADE, RESTRICT
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
from django.db.models.signals import m2m_changed, post_migrate, post_save, pre_delete
from django.dispatch import receiver, Signal
from django.core.signals import request_finished
from django.dispatch import Signal, receiver
from django.utils.translation import gettext_lazy as _
from django_prometheus.models import model_deletes, model_inserts, model_updates
from core.choices import JobStatusChoices, ObjectChangeActionChoices
from core.events import *
from core.models import ObjectType
from extras.events import enqueue_event
from extras.models import Tag
from extras.utils import run_validators
from netbox.config import get_config
from netbox.context import current_request, events_queue
from netbox.models.features import ChangeLoggingMixin, get_model_features, model_is_public
from utilities.data import get_config_value_ci
from utilities.exceptions import AbortRequest
from .models import ConfigRevision, DataSource, ObjectChange
from .choices import JobStatusChoices, ObjectChangeActionChoices
from .events import *
from .models import ConfigRevision, DataSource, ObjectChange, ObjectType
__all__ = (
'clear_events',
@@ -168,7 +169,7 @@ def handle_deleted_object(sender, instance, **kwargs):
# to queueing any events for the object being deleted, in case a validation error is
# raised, causing the deletion to fail.
model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
validators = get_config().PROTECTION_RULES.get(model_name, [])
validators = get_config_value_ci(get_config().PROTECTION_RULES, model_name, default=[])
try:
run_validators(instance, validators)
except ValidationError as e:

View File

@@ -2,5 +2,5 @@ from .change_logging import *
from .config import *
from .data import *
from .jobs import *
from .tasks import *
from .plugins import *
from .tasks import *

View File

@@ -1,8 +1,9 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from core.models import ObjectChange
from netbox.tables import NetBoxTable, columns
from ..models import ObjectChange
from .template_code import *
__all__ = (

View File

@@ -1,8 +1,9 @@
from django.utils.translation import gettext_lazy as _
from core.models import ConfigRevision
from netbox.tables import NetBoxTable, columns
from ..models import ConfigRevision
__all__ = (
'ConfigRevisionTable',
)

View File

@@ -1,8 +1,9 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from core.models import *
from netbox.tables import NetBoxTable, PrimaryModelTable, columns
from ..models import *
from .columns import BackendTypeColumn
from .template_code import DATA_SOURCE_SYNC_BUTTON

View File

@@ -2,9 +2,10 @@ import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from netbox.tables import BaseTable, NetBoxTable, columns
from core.constants import JOB_LOG_ENTRY_LEVELS
from core.models import Job
from core.tables.columns import BadgeColumn
from ..constants import JOB_LOG_ENTRY_LEVELS
from ..models import Job
from .columns import BadgeColumn
class JobTable(NetBoxTable):

View File

@@ -2,6 +2,7 @@ import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from netbox.tables import BaseTable, columns
from .template_code import PLUGIN_IS_INSTALLED, PLUGIN_NAME_TEMPLATE
__all__ = (

View File

@@ -2,10 +2,11 @@ import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from django_tables2.utils import A
from core.constants import RQ_TASK_STATUSES
from core.tables.columns import BadgeColumn
from netbox.tables import BaseTable, columns
from ..constants import RQ_TASK_STATUSES
from .columns import BadgeColumn
class BackgroundQueueTable(BaseTable):
name = tables.Column(

View File

@@ -1,17 +1,19 @@
import uuid
from django_rq import get_queue
from django_rq.workers import get_worker
from django.urls import reverse
from django.utils import timezone
from rq.job import Job as RQ_Job, JobStatus
from django_rq import get_queue
from django_rq.workers import get_worker
from rest_framework import status
from rq.job import Job as RQ_Job
from rq.job import JobStatus
from rq.registry import FailedJobRegistry, StartedJobRegistry
from rest_framework import status
from users.constants import TOKEN_PREFIX
from users.models import Token, User
from utilities.testing import APITestCase, APIViewTestCases, TestCase
from utilities.testing.utils import disable_logging
from ..models import *

View File

@@ -7,7 +7,16 @@ from core.choices import ObjectChangeActionChoices
from core.models import ObjectChange, ObjectType
from dcim.choices import InterfaceTypeChoices, ModuleStatusChoices, SiteStatusChoices
from dcim.models import (
Cable, CableTermination, Device, DeviceRole, DeviceType, Manufacturer, Module, ModuleBay, ModuleType, Interface,
Cable,
CableTermination,
Device,
DeviceRole,
DeviceType,
Interface,
Manufacturer,
Module,
ModuleBay,
ModuleType,
Site,
)
from extras.choices import *

View File

@@ -0,0 +1,116 @@
from unittest import skipIf
from unittest.mock import patch
from django.test import TestCase
from core.data_backends import url_has_embedded_credentials
try:
import dulwich # noqa: F401
DULWICH_AVAILABLE = True
except ImportError:
DULWICH_AVAILABLE = False
class URLEmbeddedCredentialsTests(TestCase):
def test_url_with_embedded_username(self):
self.assertTrue(url_has_embedded_credentials('https://myuser@bitbucket.org/workspace/repo.git'))
def test_url_without_embedded_username(self):
self.assertFalse(url_has_embedded_credentials('https://bitbucket.org/workspace/repo.git'))
def test_url_with_username_and_password(self):
self.assertTrue(url_has_embedded_credentials('https://user:pass@bitbucket.org/workspace/repo.git'))
def test_various_providers_with_embedded_username(self):
urls = [
'https://user@bitbucket.org/workspace/repo.git',
'https://user@github.com/owner/repo.git',
'https://deploy-key@gitlab.com/group/project.git',
'http://user@internal-git.example.com/repo.git',
]
for url in urls:
with self.subTest(url=url):
self.assertTrue(url_has_embedded_credentials(url))
def test_various_providers_without_embedded_username(self):
"""Various Git providers without embedded usernames."""
urls = [
'https://bitbucket.org/workspace/repo.git',
'https://github.com/owner/repo.git',
'https://gitlab.com/group/project.git',
'http://internal-git.example.com/repo.git',
]
for url in urls:
with self.subTest(url=url):
self.assertFalse(url_has_embedded_credentials(url))
def test_ssh_url(self):
# git@host:path format doesn't parse as having a username in the traditional sense
self.assertFalse(url_has_embedded_credentials('git@github.com:owner/repo.git'))
def test_file_url(self):
self.assertFalse(url_has_embedded_credentials('file:///path/to/repo'))
@skipIf(not DULWICH_AVAILABLE, "dulwich is not installed")
class GitBackendCredentialIntegrationTests(TestCase):
"""
Integration tests that verify GitBackend correctly applies credential logic.
These tests require dulwich to be installed and verify the full integration
of the credential handling in GitBackend.fetch().
"""
def _get_clone_kwargs(self, url, **params):
from core.data_backends import GitBackend
backend = GitBackend(url=url, **params)
with patch('dulwich.porcelain.clone') as mock_clone, \
patch('dulwich.porcelain.NoneStream'):
try:
with backend.fetch():
pass
except Exception:
pass
if mock_clone.called:
return mock_clone.call_args.kwargs
return {}
def test_url_with_embedded_username_skips_explicit_credentials(self):
kwargs = self._get_clone_kwargs(
url='https://myuser@bitbucket.org/workspace/repo.git',
username='myuser',
password='my-api-key'
)
self.assertEqual(kwargs.get('username'), None)
self.assertEqual(kwargs.get('password'), None)
def test_url_without_embedded_username_passes_explicit_credentials(self):
kwargs = self._get_clone_kwargs(
url='https://bitbucket.org/workspace/repo.git',
username='myuser',
password='my-api-key'
)
self.assertEqual(kwargs.get('username'), 'myuser')
self.assertEqual(kwargs.get('password'), 'my-api-key')
def test_url_with_embedded_username_no_explicit_credentials(self):
kwargs = self._get_clone_kwargs(
url='https://myuser@bitbucket.org/workspace/repo.git'
)
self.assertEqual(kwargs.get('username'), None)
self.assertEqual(kwargs.get('password'), None)
def test_public_repo_no_credentials(self):
kwargs = self._get_clone_kwargs(
url='https://github.com/public/repo.git'
)
self.assertEqual(kwargs.get('username'), None)
self.assertEqual(kwargs.get('password'), None)

View File

@@ -8,6 +8,7 @@ from dcim.models import Site
from ipam.models import IPAddress
from users.models import User
from utilities.testing import BaseFilterSetTests, ChangeLoggedFilterSetTests
from ..choices import *
from ..filtersets import *
from ..models import *

View File

@@ -1,12 +1,12 @@
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock, patch
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase
from core.models import DataSource, Job, ObjectType
from core.choices import ObjectChangeActionChoices
from dcim.models import Site, Location, Device
from core.models import DataSource, Job, ObjectType
from dcim.models import Device, Location, Site
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED

View File

@@ -4,6 +4,7 @@ Unit tests for OpenAPI schema generation.
Refs: #20638
"""
import json
from django.test import TestCase

View File

@@ -8,7 +8,8 @@ from django.utils import timezone
from django_rq import get_queue
from django_rq.settings import QUEUES_MAP
from django_rq.workers import get_worker
from rq.job import Job as RQ_Job, JobStatus
from rq.job import Job as RQ_Job
from rq.job import JobStatus
from rq.registry import DeferredJobRegistry, FailedJobRegistry, FinishedJobRegistry, StartedJobRegistry
from core.choices import ObjectChangeActionChoices

View File

@@ -1,6 +1,7 @@
from django.urls import include, path
from utilities.urls import get_model_urls
from . import views
app_name = 'core'

View File

@@ -1,11 +1,12 @@
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from django_rq.queues import get_queue, get_queue_by_index, get_redis_connection
from django_rq.settings import QUEUES_MAP, QUEUES_LIST
from django_rq.settings import QUEUES_LIST, QUEUES_MAP
from django_rq.utils import get_jobs, stop_jobs
from rq import requeue_job
from rq.exceptions import NoSuchJobError
from rq.job import Job as RQ_Job, JobStatus as RQJobStatus
from rq.job import Job as RQ_Job
from rq.job import JobStatus as RQJobStatus
from rq.registry import (
DeferredJobRegistry,
FailedJobRegistry,

View File

@@ -1,27 +1,28 @@
import json
import platform
from copy import deepcopy
from django import __version__ as django_version
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.cache import cache
from django.db import connection, ProgrammingError
from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.db import ProgrammingError, connection
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from django_rq.queues import get_connection, get_queue_by_index, get_redis_connection
from django_rq.settings import QUEUES_MAP, QUEUES_LIST
from django_rq.settings import QUEUES_LIST, QUEUES_MAP
from django_rq.utils import get_statistics
from rq.exceptions import NoSuchJobError
from rq.job import Job as RQ_Job, JobStatus as RQJobStatus
from rq.job import Job as RQ_Job
from rq.job import JobStatus as RQJobStatus
from rq.worker import Worker
from rq.worker_registration import clean_worker_registry
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job
from netbox.config import get_config, PARAMS
from netbox.config import PARAMS, get_config
from netbox.object_actions import AddObject, BulkDelete, BulkExport, DeleteObject
from netbox.plugins.utils import get_installed_plugins
from netbox.views import generic
@@ -40,17 +41,19 @@ from utilities.views import (
ViewTab,
register_model_view,
)
from . import filtersets, forms, tables
from .jobs import SyncDataSourceJob
from .models import *
from .plugins import get_catalog_plugins, get_local_plugins
from .tables import CatalogPluginTable, JobLogEntryTable, PluginVersionTable
from .utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job
#
# Data sources
#
@register_model_view(DataSource, 'list', path='', detail=False)
class DataSourceListView(generic.ObjectListView):
queryset = DataSource.objects.annotate(
@@ -310,6 +313,22 @@ class ConfigRevisionListView(generic.ObjectListView):
class ConfigRevisionView(generic.ObjectView):
queryset = ConfigRevision.objects.all()
def get_extra_context(self, request, instance):
"""
Retrieve additional context for a given request and instance.
"""
# Copy the revision data to avoid modifying the original
config = deepcopy(instance.data or {})
# Serialize any JSON-based classes
for attr in ['CUSTOM_VALIDATORS', 'DEFAULT_USER_PREFERENCES', 'PROTECTION_RULES']:
if attr in config:
config[attr] = json.dumps(config[attr], cls=ConfigJSONEncoder, indent=4)
return {
'config': config,
}
@register_model_view(ConfigRevision, 'add', detail=False)
class ConfigRevisionEditView(generic.ObjectEditView):
@@ -617,8 +636,8 @@ class SystemView(UserPassesTestMixin, View):
response['Content-Disposition'] = 'attachment; filename="netbox.json"'
return response
# Serialize any CustomValidator classes
for attr in ['CUSTOM_VALIDATORS', 'PROTECTION_RULES']:
# Serialize any JSON-based classes
for attr in ['CUSTOM_VALIDATORS', 'DEFAULT_USER_PREFERENCES', 'PROTECTION_RULES']:
if hasattr(config, attr) and getattr(config, attr, None):
setattr(config, attr, json.dumps(getattr(config, attr), cls=ConfigJSONEncoder, indent=4))

View File

@@ -1,13 +1,13 @@
from .serializers_.cables import *
from .serializers_.sites import *
from .serializers_.racks import *
from .serializers_.device_components import *
from .serializers_.devices import *
from .serializers_.devicetype_components import *
from .serializers_.devicetypes import *
from .serializers_.manufacturers import *
from .serializers_.platforms import *
from .serializers_.roles import *
from .serializers_.devicetypes import *
from .serializers_.devicetype_components import *
from .serializers_.virtualchassis import *
from .serializers_.devices import *
from .serializers_.device_components import *
from .serializers_.power import *
from .serializers_.racks import *
from .serializers_.rackunits import *
from .serializers_.roles import *
from .serializers_.sites import *
from .serializers_.virtualchassis import *

View File

@@ -7,7 +7,10 @@ from dcim.models import Cable, CablePath, CableTermination
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import (
BaseModelSerializer, GenericObjectSerializer, NetBoxModelSerializer, PrimaryModelSerializer,
BaseModelSerializer,
GenericObjectSerializer,
NetBoxModelSerializer,
PrimaryModelSerializer,
)
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model

View File

@@ -5,8 +5,18 @@ from rest_framework import serializers
from dcim.choices import *
from dcim.constants import *
from dcim.models import (
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PortMapping,
PowerOutlet, PowerPort, RearPort, VirtualDeviceContext,
ConsolePort,
ConsoleServerPort,
DeviceBay,
FrontPort,
Interface,
InventoryItem,
ModuleBay,
PortMapping,
PowerOutlet,
PowerPort,
RearPort,
VirtualDeviceContext,
)
from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer
from ipam.api.serializers_.vrfs import VRFSerializer
@@ -20,6 +30,7 @@ from wireless.api.serializers_.nested import NestedWirelessLinkSerializer
from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
from wireless.choices import *
from wireless.models import WirelessLAN
from .base import ConnectedEndpointsSerializer, PortSerializer
from .cables import CabledObjectSerializer
from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer

View File

@@ -15,6 +15,7 @@ from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import PrimaryModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from virtualization.api.serializers_.clusters import ClusterSerializer
from .devicetypes import *
from .nested import NestedDeviceBaySerializer, NestedDeviceSerializer, NestedModuleBaySerializer
from .platforms import PlatformSerializer

View File

@@ -4,14 +4,23 @@ from rest_framework import serializers
from dcim.choices import *
from dcim.constants import *
from dcim.models import (
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, FrontPortTemplate, InterfaceTemplate,
InventoryItemTemplate, ModuleBayTemplate, PortTemplateMapping, PowerOutletTemplate, PowerPortTemplate,
ConsolePortTemplate,
ConsoleServerPortTemplate,
DeviceBayTemplate,
FrontPortTemplate,
InterfaceTemplate,
InventoryItemTemplate,
ModuleBayTemplate,
PortTemplateMapping,
PowerOutletTemplate,
PowerPortTemplate,
RearPortTemplate,
)
from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.gfk_fields import GFKSerializerField
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
from wireless.choices import *
from .base import PortSerializer
from .devicetypes import DeviceTypeSerializer, ModuleTypeSerializer
from .manufacturers import ManufacturerSerializer

View File

@@ -8,6 +8,7 @@ from dcim.models import DeviceType, ModuleType, ModuleTypeProfile
from netbox.api.fields import AttributesField, ChoiceField
from netbox.api.serializers import PrimaryModelSerializer
from netbox.choices import *
from .manufacturers import ManufacturerSerializer
from .platforms import PlatformSerializer

View File

@@ -1,8 +1,8 @@
from drf_spectacular.utils import extend_schema_serializer
from rest_framework import serializers
from netbox.api.serializers import WritableNestedSerializer
from dcim import models
from netbox.api.serializers import WritableNestedSerializer
__all__ = (
'NestedDeviceBaySerializer',

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
from dcim.models import Platform
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from netbox.api.serializers import NestedGroupModelSerializer
from .manufacturers import ManufacturerSerializer
from .nested import NestedPlatformSerializer

Some files were not shown because too many files have changed in this diff Show More