mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into feature
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.4.7
|
placeholder: v3.4.8
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.4.7
|
placeholder: v3.4.8
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# HTML sanitizer
|
# HTML sanitizer
|
||||||
# https://github.com/mozilla/bleach
|
# https://github.com/mozilla/bleach/blob/main/CHANGES
|
||||||
bleach<6.0
|
bleach<6.0
|
||||||
|
|
||||||
# Python client for Amazon AWS API
|
# Python client for Amazon AWS API
|
||||||
@ -7,55 +7,55 @@ bleach<6.0
|
|||||||
boto3
|
boto3
|
||||||
|
|
||||||
# The Python web framework on which NetBox is built
|
# The Python web framework on which NetBox is built
|
||||||
# https://github.com/django/django
|
# https://docs.djangoproject.com/en/stable/releases/
|
||||||
Django<4.2
|
Django<4.2
|
||||||
|
|
||||||
# Django middleware which permits cross-domain API requests
|
# Django middleware which permits cross-domain API requests
|
||||||
# https://github.com/OttoYiu/django-cors-headers
|
# https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
|
|
||||||
# Runtime UI tool for debugging Django
|
# Runtime UI tool for debugging Django
|
||||||
# https://github.com/jazzband/django-debug-toolbar
|
# https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst
|
||||||
django-debug-toolbar
|
django-debug-toolbar
|
||||||
|
|
||||||
# Library for writing reusable URL query filters
|
# Library for writing reusable URL query filters
|
||||||
# https://github.com/carltongibson/django-filter
|
# https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
|
||||||
django-filter
|
django-filter
|
||||||
|
|
||||||
# Django debug toolbar extension with support for GraphiQL
|
# Django debug toolbar extension with support for GraphiQL
|
||||||
# https://github.com/flavors/django-graphiql-debug-toolbar/
|
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
|
||||||
django-graphiql-debug-toolbar
|
django-graphiql-debug-toolbar
|
||||||
|
|
||||||
# Modified Preorder Tree Traversal (recursive nesting of objects)
|
# Modified Preorder Tree Traversal (recursive nesting of objects)
|
||||||
# https://github.com/django-mptt/django-mptt
|
# https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst
|
||||||
django-mptt
|
django-mptt
|
||||||
|
|
||||||
# Context managers for PostgreSQL advisory locks
|
# Context managers for PostgreSQL advisory locks
|
||||||
# https://github.com/Xof/django-pglocks
|
# https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt
|
||||||
django-pglocks
|
django-pglocks
|
||||||
|
|
||||||
# Prometheus metrics library for Django
|
# Prometheus metrics library for Django
|
||||||
# https://github.com/korfuri/django-prometheus
|
# https://github.com/korfuri/django-prometheus/blob/master/CHANGELOG.md
|
||||||
django-prometheus
|
django-prometheus
|
||||||
|
|
||||||
# Django caching backend using Redis
|
# Django caching backend using Redis
|
||||||
# https://github.com/jazzband/django-redis
|
# https://github.com/jazzband/django-redis/blob/master/CHANGELOG.rst
|
||||||
django-redis
|
django-redis
|
||||||
|
|
||||||
# Django extensions for Rich (terminal text rendering)
|
# Django extensions for Rich (terminal text rendering)
|
||||||
# https://github.com/adamchainz/django-rich
|
# https://github.com/adamchainz/django-rich/blob/main/CHANGELOG.rst
|
||||||
django-rich
|
django-rich
|
||||||
|
|
||||||
# Django integration for RQ (Reqis queuing)
|
# Django integration for RQ (Reqis queuing)
|
||||||
# https://github.com/rq/django-rq
|
# https://github.com/rq/django-rq/blob/master/CHANGELOG.md
|
||||||
django-rq
|
django-rq
|
||||||
|
|
||||||
# Abstraction models for rendering and paginating HTML tables
|
# Abstraction models for rendering and paginating HTML tables
|
||||||
# https://github.com/jieter/django-tables2
|
# https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
|
||||||
django-tables2
|
django-tables2
|
||||||
|
|
||||||
# User-defined tags for objects
|
# User-defined tags for objects
|
||||||
# https://github.com/alex/django-taggit
|
# https://github.com/jazzband/django-taggit/blob/master/CHANGELOG.rst
|
||||||
django-taggit
|
django-taggit
|
||||||
|
|
||||||
# A Django field for representing time zones
|
# A Django field for representing time zones
|
||||||
@ -63,11 +63,11 @@ django-taggit
|
|||||||
django-timezone-field
|
django-timezone-field
|
||||||
|
|
||||||
# A REST API framework for Django projects
|
# A REST API framework for Django projects
|
||||||
# https://github.com/encode/django-rest-framework
|
# https://www.django-rest-framework.org/community/release-notes/
|
||||||
djangorestframework
|
djangorestframework
|
||||||
|
|
||||||
# Sane and flexible OpenAPI 3 schema generation for Django REST framework.
|
# Sane and flexible OpenAPI 3 schema generation for Django REST framework.
|
||||||
# https://github.com/tfranzel/drf-spectacular
|
# https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst
|
||||||
drf-spectacular
|
drf-spectacular
|
||||||
|
|
||||||
# Serve self-contained distribution builds of Swagger UI and Redoc with Django.
|
# Serve self-contained distribution builds of Swagger UI and Redoc with Django.
|
||||||
@ -75,23 +75,23 @@ drf-spectacular
|
|||||||
drf-spectacular-sidecar
|
drf-spectacular-sidecar
|
||||||
|
|
||||||
# RSS feed parser
|
# RSS feed parser
|
||||||
# https://github.com/kurtmckee/feedparser
|
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
|
||||||
feedparser
|
feedparser
|
||||||
|
|
||||||
# Django wrapper for Graphene (GraphQL support)
|
# Django wrapper for Graphene (GraphQL support)
|
||||||
# https://github.com/graphql-python/graphene-django
|
# https://github.com/graphql-python/graphene-django/releases
|
||||||
graphene_django
|
graphene_django
|
||||||
|
|
||||||
# WSGI HTTP server
|
# WSGI HTTP server
|
||||||
# https://gunicorn.org/
|
# https://docs.gunicorn.org/en/latest/news.html
|
||||||
gunicorn
|
gunicorn
|
||||||
|
|
||||||
# Platform-agnostic template rendering engine
|
# Platform-agnostic template rendering engine
|
||||||
# https://github.com/pallets/jinja
|
# https://jinja.palletsprojects.com/changes/
|
||||||
Jinja2
|
Jinja2
|
||||||
|
|
||||||
# Simple markup language for rendering HTML
|
# Simple markup language for rendering HTML
|
||||||
# https://github.com/Python-Markdown/markdown
|
# https://python-markdown.github.io/change_log/
|
||||||
# mkdocs currently requires Markdown v3.3
|
# mkdocs currently requires Markdown v3.3
|
||||||
Markdown<3.4
|
Markdown<3.4
|
||||||
|
|
||||||
@ -100,50 +100,50 @@ Markdown<3.4
|
|||||||
markdown-include
|
markdown-include
|
||||||
|
|
||||||
# MkDocs Material theme (for documentation build)
|
# MkDocs Material theme (for documentation build)
|
||||||
# https://github.com/squidfunk/mkdocs-material
|
# https://squidfunk.github.io/mkdocs-material/changelog/
|
||||||
mkdocs-material
|
mkdocs-material
|
||||||
|
|
||||||
# Introspection for embedded code
|
# Introspection for embedded code
|
||||||
# https://github.com/mkdocstrings/mkdocstrings
|
# https://github.com/mkdocstrings/mkdocstrings/blob/master/CHANGELOG.md
|
||||||
mkdocstrings[python-legacy]
|
mkdocstrings[python-legacy]
|
||||||
|
|
||||||
# Library for manipulating IP prefixes and addresses
|
# Library for manipulating IP prefixes and addresses
|
||||||
# https://github.com/netaddr/netaddr
|
# https://github.com/netaddr/netaddr/blob/master/CHANGELOG
|
||||||
netaddr
|
netaddr
|
||||||
|
|
||||||
# Fork of PIL (Python Imaging Library) for image processing
|
# Fork of PIL (Python Imaging Library) for image processing
|
||||||
# https://github.com/python-pillow/Pillow
|
# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
|
||||||
Pillow
|
Pillow
|
||||||
|
|
||||||
# PostgreSQL database adapter for Python
|
# PostgreSQL database adapter for Python
|
||||||
# https://github.com/psycopg/psycopg2
|
# https://www.psycopg.org/docs/news.html
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
|
|
||||||
# YAML rendering library
|
# YAML rendering library
|
||||||
# https://github.com/yaml/pyyaml
|
# https://github.com/yaml/pyyaml/blob/master/CHANGES
|
||||||
PyYAML
|
PyYAML
|
||||||
|
|
||||||
# Sentry SDK
|
# Sentry SDK
|
||||||
# https://github.com/getsentry/sentry-python
|
# https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md
|
||||||
sentry-sdk
|
sentry-sdk
|
||||||
|
|
||||||
# Social authentication framework
|
# Social authentication framework
|
||||||
# https://github.com/python-social-auth/social-core
|
# https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
|
||||||
social-auth-core
|
social-auth-core
|
||||||
|
|
||||||
# Django app for social-auth-core
|
# Django app for social-auth-core
|
||||||
# https://github.com/python-social-auth/social-app-django
|
# https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
|
||||||
# See https://github.com/python-social-auth/social-app-django/issues/429
|
# See https://github.com/python-social-auth/social-app-django/issues/429
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
|
|
||||||
# SVG image rendering (used for rack elevations)
|
# SVG image rendering (used for rack elevations)
|
||||||
# https://github.com/mozman/svgwrite
|
# hhttps://github.com/mozman/svgwrite/blob/master/NEWS.rst
|
||||||
svgwrite
|
svgwrite
|
||||||
|
|
||||||
# Tabular dataset library (for table-based exports)
|
# Tabular dataset library (for table-based exports)
|
||||||
# https://github.com/jazzband/tablib
|
# https://github.com/jazzband/tablib/blob/master/HISTORY.md
|
||||||
tablib
|
tablib
|
||||||
|
|
||||||
# Timezone data (required by django-timezone-field on Python 3.9+)
|
# Timezone data (required by django-timezone-field on Python 3.9+)
|
||||||
# https://github.com/python/tzdata
|
# https://github.com/python/tzdata/blob/master/NEWS.md
|
||||||
tzdata
|
tzdata
|
||||||
|
@ -18,4 +18,4 @@ interface.
|
|||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
|
||||||
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Set this to `True` **only** if you are actively developing the NetBox code base.
|
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Additionally, enabling this setting disables the debug warning banner in the UI. Set this to `True` **only** if you are actively developing the NetBox code base.
|
||||||
|
@ -130,7 +130,7 @@ Once you have created a report, it will appear in the reports list. Initially, r
|
|||||||
!!! note
|
!!! note
|
||||||
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
|
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Via the Web UI
|
### Via the Web UI
|
||||||
|
|
||||||
|
@ -586,6 +586,15 @@ Additionally, a token can be set to expire at a specific time. This can be usefu
|
|||||||
|
|
||||||
Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.)
|
Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.)
|
||||||
|
|
||||||
|
#### Creating Tokens for Other Users
|
||||||
|
|
||||||
|
It is possible to provision authentication tokens for other users via the REST API. To do, so the requesting user must have the `users.grant_token` permission assigned. While all users have inherent permission to create their own tokens, this permission is required to enable the creation of tokens for other users.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
!!! warning "Exercise Caution"
|
||||||
|
The ability to create tokens on behalf of other users enables the requestor to access the created token. This ability is intended e.g. for the provisioning of tokens by automated services, and should be used with extreme caution to avoid a security compromise.
|
||||||
|
|
||||||
### Authenticating to the API
|
### Authenticating to the API
|
||||||
|
|
||||||
An authentication token is attached to a request by setting the `Authorization` header to the string `Token` followed by a space and the user's token:
|
An authentication token is attached to a request by setting the `Authorization` header to the string `Token` followed by a space and the user's token:
|
||||||
|
BIN
docs/media/admin_ui_grant_permission.png
Normal file
BIN
docs/media/admin_ui_grant_permission.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
@ -1,16 +1,30 @@
|
|||||||
# NetBox v3.4
|
# NetBox v3.4
|
||||||
|
|
||||||
## v3.4.8 (FUTURE)
|
## v3.4.9 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.4.8 (2023-04-12)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
|
* [#10414](https://github.com/netbox-community/netbox/issues/10414) - Enable general purpose image attachments for device types
|
||||||
|
* [#10600](https://github.com/netbox-community/netbox/issues/10600) - Allow custom object fields to reference a user or group
|
||||||
|
* [#11015](https://github.com/netbox-community/netbox/issues/11015) - Remove unit from commit rate column header in circuits table
|
||||||
|
* [#11431](https://github.com/netbox-community/netbox/issues/11431) - Disallow changing custom field type after creation
|
||||||
|
* [#11453](https://github.com/netbox-community/netbox/issues/11453) - Display a warning banner when `DEBUG` is enabled
|
||||||
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
|
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
|
||||||
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
|
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
|
||||||
|
* [#12207](https://github.com/netbox-community/netbox/issues/12207) - Introduce the `grant_token` permission for controlling the creation of API tokens on behalf of other users
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#10221](https://github.com/netbox-community/netbox/issues/10221) - Validate generic foreign key relations assigned via REST API requests
|
||||||
|
* [#11432](https://github.com/netbox-community/netbox/issues/11432) - Prevent existing components & component templates from being reassigned to different devices/device types via the REST API
|
||||||
|
* [#11454](https://github.com/netbox-community/netbox/issues/11454) - Raise validation error if generic foreign key assignment does not specify both object type and ID
|
||||||
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
|
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
|
||||||
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
|
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
|
||||||
|
* [#12040](https://github.com/netbox-community/netbox/issues/12040) - Display relevant UI tab upon bulk import validation failure
|
||||||
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
|
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
|
||||||
* [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters
|
* [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters
|
||||||
* [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships
|
* [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships
|
||||||
@ -18,6 +32,7 @@
|
|||||||
* [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device
|
* [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device
|
||||||
* [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models
|
* [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models
|
||||||
* [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields
|
* [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields
|
||||||
|
* [#12227](https://github.com/netbox-community/netbox/issues/12227) - Fix tenant assignment on bulk import of L2VPNs
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -64,7 +64,9 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
template_code=CIRCUITTERMINATION_LINK,
|
template_code=CIRCUITTERMINATION_LINK,
|
||||||
verbose_name='Side Z'
|
verbose_name='Side Z'
|
||||||
)
|
)
|
||||||
commit_rate = CommitRateColumn()
|
commit_rate = CommitRateColumn(
|
||||||
|
verbose_name='Commit Rate'
|
||||||
|
)
|
||||||
comments = columns.MarkdownColumn()
|
comments = columns.MarkdownColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='circuits:circuit_list'
|
url_name='circuits:circuit_list'
|
||||||
|
@ -1694,12 +1694,14 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
field_name='terminations__termination_type'
|
field_name='terminations__termination_type'
|
||||||
)
|
)
|
||||||
termination_a_id = MultiValueNumberFilter(
|
termination_a_id = MultiValueNumberFilter(
|
||||||
|
method='filter_by_cable_end_a',
|
||||||
field_name='terminations__termination_id'
|
field_name='terminations__termination_id'
|
||||||
)
|
)
|
||||||
termination_b_type = ContentTypeFilter(
|
termination_b_type = ContentTypeFilter(
|
||||||
field_name='terminations__termination_type'
|
field_name='terminations__termination_type'
|
||||||
)
|
)
|
||||||
termination_b_id = MultiValueNumberFilter(
|
termination_b_id = MultiValueNumberFilter(
|
||||||
|
method='filter_by_cable_end_b',
|
||||||
field_name='terminations__termination_id'
|
field_name='terminations__termination_id'
|
||||||
)
|
)
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
@ -1757,6 +1759,18 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
# Supported objects: device, rack, location, site
|
# Supported objects: device, rack, location, site
|
||||||
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
|
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
|
||||||
|
|
||||||
|
def filter_by_cable_end(self, queryset, name, value, side):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return queryset.filter(**{f'{name}__in': value, 'terminations__cable_end': side}).distinct()
|
||||||
|
|
||||||
|
def filter_by_cable_end_a(self, queryset, name, value):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_A)
|
||||||
|
|
||||||
|
def filter_by_cable_end_b(self, queryset, name, value):
|
||||||
|
# Filter by termination id and cable_end type
|
||||||
|
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B)
|
||||||
|
|
||||||
|
|
||||||
class CableTerminationFilterSet(BaseFilterSet):
|
class CableTerminationFilterSet(BaseFilterSet):
|
||||||
termination_type = ContentTypeFilter()
|
termination_type = ContentTypeFilter()
|
||||||
|
@ -119,6 +119,12 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Cache the original DeviceType ID for reference under clean()
|
||||||
|
self._original_device_type = self.device_type_id
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
def to_objectchange(self, action):
|
||||||
objectchange = super().to_objectchange(action)
|
objectchange = super().to_objectchange(action)
|
||||||
if self.device_type is not None:
|
if self.device_type is not None:
|
||||||
@ -130,6 +136,11 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
if self.pk is not None and self._original_device_type != self.device_type_id:
|
||||||
|
raise ValidationError({
|
||||||
|
"device_type": "Component templates cannot be moved to a different device type."
|
||||||
|
})
|
||||||
|
|
||||||
# A component template must belong to a DeviceType *or* to a ModuleType
|
# A component template must belong to a DeviceType *or* to a ModuleType
|
||||||
if self.device_type and self.module_type:
|
if self.device_type and self.module_type:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
|
@ -78,6 +78,12 @@ class ComponentModel(NetBoxModel):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Cache the original Device ID for reference under clean()
|
||||||
|
self._original_device = self.device_id
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.label:
|
if self.label:
|
||||||
return f"{self.name} ({self.label})"
|
return f"{self.name} ({self.label})"
|
||||||
@ -88,6 +94,14 @@ class ComponentModel(NetBoxModel):
|
|||||||
objectchange.related_object = self.device
|
objectchange.related_object = self.device
|
||||||
return objectchange
|
return objectchange
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
if self.pk is not None and self._original_device != self.device_id:
|
||||||
|
raise ValidationError({
|
||||||
|
"device": "Components cannot be moved to a different device."
|
||||||
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_object(self):
|
def parent_object(self):
|
||||||
return self.device
|
return self.device
|
||||||
|
@ -128,6 +128,10 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
images = GenericRelation(
|
||||||
|
to='extras.ImageAttachment'
|
||||||
|
)
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
||||||
)
|
)
|
||||||
|
@ -104,6 +104,12 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
'last_updated',
|
'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def validate_type(self, value):
|
||||||
|
if self.instance and self.instance.type != value:
|
||||||
|
raise serializers.ValidationError('Changing the type of custom fields is not supported.')
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
@extend_schema_field(OpenApiTypes.STR)
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_data_type(self, obj):
|
def get_data_type(self, obj):
|
||||||
types = CustomFieldTypeChoices
|
types = CustomFieldTypeChoices
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
# TODO: Come up with a canonical way to register suitable models
|
# TODO: Come up with a canonical way to register suitable models
|
||||||
limit_choices_to=FeatureQuery('webhooks'),
|
limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Type of the related object (for object/multi-object fields only)")
|
help_text=_("Type of the related object (for object/multi-object fields only)")
|
||||||
)
|
)
|
||||||
@ -63,6 +64,13 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable changing the type of a CustomField as it almost universally causes errors if custom field data is already present.
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['type'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||||
content_types = ContentTypeMultipleChoiceField(
|
content_types = ContentTypeMultipleChoiceField(
|
||||||
|
@ -103,6 +103,11 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
|||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
update_data = {
|
||||||
|
'content_types': ['dcim.device'],
|
||||||
|
'name': 'New_Name',
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -456,7 +456,8 @@ class L2VPNImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPN
|
model = L2VPN
|
||||||
fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments', 'tags')
|
fields = ('identifier', 'name', 'slug', 'tenant', 'type', 'description',
|
||||||
|
'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationImportForm(NetBoxModelImportForm):
|
class L2VPNTerminationImportForm(NetBoxModelImportForm):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
@ -58,6 +59,33 @@ class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Validate the model for GenericForeignKey fields to ensure that the content type and object ID exist.
|
||||||
|
"""
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
for field in self._meta.get_fields():
|
||||||
|
if isinstance(field, GenericForeignKey):
|
||||||
|
ct_value = getattr(self, field.ct_field)
|
||||||
|
fk_value = getattr(self, field.fk_field)
|
||||||
|
|
||||||
|
if ct_value is None and fk_value is not None:
|
||||||
|
raise ValidationError({
|
||||||
|
field.ct_field: "This field cannot be null.",
|
||||||
|
})
|
||||||
|
if fk_value is None and ct_value is not None:
|
||||||
|
raise ValidationError({
|
||||||
|
field.fk_field: "This field cannot be null.",
|
||||||
|
})
|
||||||
|
|
||||||
|
if ct_value and fk_value:
|
||||||
|
klass = getattr(self, field.ct_field).model_class()
|
||||||
|
if not klass.objects.filter(pk=fk_value).exists():
|
||||||
|
raise ValidationError({
|
||||||
|
field.fk_field: f"Related object not found using the provided value: {fk_value}."
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class PrimaryModel(NetBoxModel):
|
class PrimaryModel(NetBoxModel):
|
||||||
"""
|
"""
|
||||||
|
@ -70,10 +70,17 @@ Blocks:
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if settings.DEBUG and not settings.DEVELOPER %}
|
||||||
|
<div class="alert alert-warning text-center mx-3" role="alert">
|
||||||
|
<strong><i class="mdi mdi-alert"></i> Debug mode is enabled.</strong>
|
||||||
|
Performance may be limited. Debugging should never be enabled on a production system.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if config.MAINTENANCE_MODE %}
|
{% if config.MAINTENANCE_MODE %}
|
||||||
<div class="alert alert-warning text-center mx-3" role="alert">
|
<div class="alert alert-warning text-center mx-3" role="alert">
|
||||||
<h4><i class="mdi mdi-alert"></i> Maintenance Mode</h4>
|
<h5><i class="mdi mdi-alert"></i> Maintenance Mode</h5>
|
||||||
<span>NetBox is currently in maintenance mode. Functionality may be limited.</span>
|
NetBox is currently in maintenance mode. Functionality may be limited.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@
|
|||||||
{% include 'inc/panels/related_objects.html' %}
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
|
{% include 'inc/panels/image_attachments.html' %}
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,17 +15,17 @@ Context:
|
|||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="import-form-tab" data-bs-toggle="tab" data-bs-target="#import-form" type="button" role="tab" aria-controls="import-form" aria-selected="true">
|
<button class="nav-link active" id="import-form-tab" data-bs-toggle="tab" data-bs-target="#import-form" data-href="#tab_import-form" type="button" role="tab" aria-controls="import-form" aria-selected="true">
|
||||||
Direct Import
|
Direct Import
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="upload-form-tab" data-bs-toggle="tab" data-bs-target="#upload-form" type="button" role="tab" aria-controls="upload-form" aria-selected="false">
|
<button class="nav-link" id="upload-form-tab" data-bs-toggle="tab" data-bs-target="#upload-form" data-href="#tab_upload-form" type="button" role="tab" aria-controls="upload-form" aria-selected="false">
|
||||||
Upload File
|
Upload File
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="datafile-form-tab" data-bs-toggle="tab" data-bs-target="#datafile-form" type="button" role="tab" aria-controls="datafile-form" aria-selected="false">
|
<button class="nav-link" id="datafile-form-tab" data-bs-toggle="tab" data-bs-target="#datafile-form" data-href="#tab_datafile-form" type="button" role="tab" aria-controls="datafile-form" aria-selected="false">
|
||||||
Data File
|
Data File
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from netbox.api.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField
|
from netbox.api.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import ValidatedModelSerializer
|
from netbox.api.serializers import ValidatedModelSerializer
|
||||||
@ -94,6 +95,16 @@ class TokenSerializer(ValidatedModelSerializer):
|
|||||||
data['key'] = Token.generate_key()
|
data['key'] = Token.generate_key()
|
||||||
return super().to_internal_value(data)
|
return super().to_internal_value(data)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
|
||||||
|
# If the Token is being created on behalf of another user, enforce the grant_token permission.
|
||||||
|
request = self.context.get('request')
|
||||||
|
token_user = data.get('user')
|
||||||
|
if token_user and token_user != request.user and not request.user.has_perm('users.grant_token'):
|
||||||
|
raise PermissionDenied("This user does not have permission to create tokens for other users.")
|
||||||
|
|
||||||
|
return super().validate(data)
|
||||||
|
|
||||||
|
|
||||||
class TokenProvisionSerializer(serializers.Serializer):
|
class TokenProvisionSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField()
|
username = serializers.CharField()
|
||||||
|
@ -153,6 +153,26 @@ class TokenTest(
|
|||||||
response = self.client.post(url, data, format='json', **self.header)
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_provision_token_other_user(self):
|
||||||
|
"""
|
||||||
|
Test provisioning a Token for a different User with & without the grant_token permission.
|
||||||
|
"""
|
||||||
|
self.add_permissions('users.add_token')
|
||||||
|
user2 = User.objects.create_user(username='testuser2')
|
||||||
|
data = {
|
||||||
|
'user': user2.id,
|
||||||
|
}
|
||||||
|
url = reverse('users-api:token-list')
|
||||||
|
|
||||||
|
# Attempt to create a new Token for User2 *without* the grant_token permission
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
# Assign grant_token permission and successfully create a new Token for User2
|
||||||
|
self.add_permissions('users.grant_token')
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
|
|
||||||
class ObjectPermissionTest(
|
class ObjectPermissionTest(
|
||||||
# No GraphQL support for ObjectPermission
|
# No GraphQL support for ObjectPermission
|
||||||
|
@ -48,6 +48,10 @@ def get_viewname(model, action=None, rest_api=False):
|
|||||||
if is_plugin:
|
if is_plugin:
|
||||||
viewname = f'plugins-api:{app_label}-api:{model_name}'
|
viewname = f'plugins-api:{app_label}-api:{model_name}'
|
||||||
else:
|
else:
|
||||||
|
# Alter the app_label for group and user model_name to point to users app
|
||||||
|
if app_label == 'auth' and model_name in ['group', 'user']:
|
||||||
|
app_label = 'users'
|
||||||
|
|
||||||
viewname = f'{app_label}-api:{model_name}'
|
viewname = f'{app_label}-api:{model_name}'
|
||||||
# Append the action, if any
|
# Append the action, if any
|
||||||
if action:
|
if action:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
bleach==5.0.1
|
bleach==5.0.1
|
||||||
boto3==1.26.91
|
boto3==1.26.91
|
||||||
Django==4.1.7
|
Django==4.1.8
|
||||||
django-cors-headers==3.14.0
|
django-cors-headers==3.14.0
|
||||||
django-debug-toolbar==3.8.1
|
django-debug-toolbar==4.0.0
|
||||||
django-filter==23.1
|
django-filter==23.1
|
||||||
django-graphiql-debug-toolbar==0.2.0
|
django-graphiql-debug-toolbar==0.2.0
|
||||||
django-mptt==0.14
|
django-mptt==0.14
|
||||||
@ -22,18 +22,18 @@ graphene-django==3.0.0
|
|||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
Markdown==3.3.7
|
Markdown==3.3.7
|
||||||
mkdocs-material==9.1.4
|
mkdocs-material==9.1.6
|
||||||
mkdocstrings[python-legacy]==0.20.0
|
mkdocstrings[python-legacy]==0.21.2
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==9.4.0
|
Pillow==9.5.0
|
||||||
psycopg2-binary==2.9.5
|
psycopg2-binary==2.9.6
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
sentry-sdk==1.18.0
|
sentry-sdk==1.19.1
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core[openidconnect]==4.4.0
|
social-auth-core[openidconnect]==4.4.1
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
tablib==3.4.0
|
tablib==3.4.0
|
||||||
tzdata==2023.2
|
tzdata==2023.3
|
||||||
|
|
||||||
# Workaround for #7401
|
# Workaround for #7401
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
|
Reference in New Issue
Block a user