mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-12 20:07:42 +01:00
Compare commits
33 Commits
20490-rest
...
21407-ruff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90717f7c24 | ||
|
|
6c383f293c | ||
|
|
5bf516c63d | ||
|
|
7df062d590 | ||
|
|
4b22be03a0 | ||
|
|
24769ce127 | ||
|
|
164e9db98d | ||
|
|
23f1c86e9c | ||
|
|
02ffdd9d5d | ||
|
|
5013297326 | ||
|
|
584e0a9b8c | ||
|
|
3ac9d0b8bf | ||
|
|
b387ea5f58 | ||
|
|
ba9f6bf359 | ||
|
|
ee6cbdcefe | ||
|
|
de1c5120dd | ||
|
|
87d2e02c85 | ||
|
|
cbbc4f74b8 | ||
|
|
be5bd74d4e | ||
|
|
cf12bb5bf5 | ||
|
|
c060eef1d8 | ||
|
|
96f0debe6e | ||
|
|
b26c7f34cd | ||
|
|
d6428c6aa4 | ||
|
|
e3eca98897 | ||
|
|
cdc735fe41 | ||
|
|
aa4a9da955 | ||
|
|
5c6fc2fb6f | ||
|
|
ad29cb2d66 | ||
|
|
bec5ecf6a9 | ||
|
|
c98f55dbd2 | ||
|
|
dfe20532a1 | ||
|
|
43ae52089f |
@@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/03-performance.yaml
vendored
2
.github/ISSUE_TEMPLATE/03-performance.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
4830
contrib/openapi.json
4830
contrib/openapi.json
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from utilities.urls import get_model_urls
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'account'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from .serializers_.providers import *
|
||||
from .serializers_.circuits import *
|
||||
from .serializers_.providers import *
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from netbox.api.routers import NetBoxRouter
|
||||
from . import views
|
||||
|
||||
from . import views
|
||||
|
||||
router = NetBoxRouter()
|
||||
router.APIRootView = views.CircuitsRootView
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,11 +2,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
|
||||
#
|
||||
# Circuits
|
||||
#
|
||||
|
||||
|
||||
class CircuitStatusChoices(ChoiceSet):
|
||||
key = 'Circuit.status'
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 *
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Generated by Django 4.2.5 on 2023-10-20 21:25
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import utilities.fields
|
||||
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from netbox.search import SearchIndex, register_search
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from utilities.urls import get_model_urls
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'circuits'
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from netbox.api.routers import NetBoxRouter
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'core-api'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from core.models import *
|
||||
from netbox.forms import PrimaryModelImportForm
|
||||
|
||||
from ..models import *
|
||||
|
||||
__all__ = (
|
||||
'DataSourceImportForm',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import strawberry
|
||||
|
||||
from core.choices import *
|
||||
from ..choices import *
|
||||
|
||||
__all__ = (
|
||||
'DataSourceStatusEnum',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import core.models.object_types
|
||||
from django.db import migrations
|
||||
|
||||
import core.models.object_types
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .object_types import *
|
||||
from .object_types import * # isort: split
|
||||
|
||||
from .change_logging import *
|
||||
from .config import *
|
||||
from .data import *
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from netbox.search import SearchIndex, register_search
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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__ = (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 *
|
||||
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
116
netbox/core/tests/test_data_backends.py
Normal file
116
netbox/core/tests/test_data_backends.py
Normal 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)
|
||||
@@ -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 *
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Unit tests for OpenAPI schema generation.
|
||||
Refs: #20638
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from utilities.urls import get_model_urls
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'core'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user