Merge branch 'develop' into jeffg/issue-16793-translations

This commit is contained in:
Jeremy Stretch 2024-07-11 13:51:51 -04:00
commit 2885fc6090
43 changed files with 16188 additions and 15874 deletions

View File

@ -26,7 +26,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: v4.0.6 placeholder: v4.0.7
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -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: v4.0.6 placeholder: v4.0.7
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -16,7 +16,7 @@ Administrators are encouraged to adhere to industry best practices concerning th
## Reporting a Suspected Vulnerability ## Reporting a Suspected Vulnerability
If you believe you've uncovered a security vulnerability and wish to report it confidentially, you may do so via email. Please note that any reported vulnerabilities **MUST** meet all the following conditions: If you believe you've uncovered a security vulnerability and wish to report it confidentially, you may do so by emailing `security@netboxlabs.com`. Please ensure that your report meets all the following conditions:
* Affects the most recent stable release of NetBox, or a current beta release * Affects the most recent stable release of NetBox, or a current beta release
* Affects a NetBox instance installed and configured per the official documentation * Affects a NetBox instance installed and configured per the official documentation
@ -24,7 +24,7 @@ If you believe you've uncovered a security vulnerability and wish to report it c
Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous. Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous.
If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/netbox-community/netbox/security/advisories/new) on GitHub. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project. For any security concerns regarding the community-maintained Docker image for NetBox, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project.
### Bug Bounties ### Bug Bounties

View File

@ -40,3 +40,22 @@ REMOTE_AUTH_BACKEND = 'social_core.backends.google.GoogleOAuth2'
NetBox supports single sign-on authentication via the [python-social-auth](https://github.com/python-social-auth) library. To enable SSO, specify the path to the desired authentication backend within the `social_core` Python package. Please see the complete list of [supported authentication backends](https://github.com/python-social-auth/social-core/tree/master/social_core/backends) for the available options. NetBox supports single sign-on authentication via the [python-social-auth](https://github.com/python-social-auth) library. To enable SSO, specify the path to the desired authentication backend within the `social_core` Python package. Please see the complete list of [supported authentication backends](https://github.com/python-social-auth/social-core/tree/master/social_core/backends) for the available options.
Most remote authentication backends require some additional configuration through settings prefixed with `SOCIAL_AUTH_`. These will be automatically imported from NetBox's `configuration.py` file. Additionally, the [authentication pipeline](https://python-social-auth.readthedocs.io/en/latest/pipeline.html) can be customized via the `SOCIAL_AUTH_PIPELINE` parameter. (NetBox's default pipeline is defined in `netbox/settings.py` for your reference.) Most remote authentication backends require some additional configuration through settings prefixed with `SOCIAL_AUTH_`. These will be automatically imported from NetBox's `configuration.py` file. Additionally, the [authentication pipeline](https://python-social-auth.readthedocs.io/en/latest/pipeline.html) can be customized via the `SOCIAL_AUTH_PIPELINE` parameter. (NetBox's default pipeline is defined in `netbox/settings.py` for your reference.)
#### Configuring the SSO module's appearance
The way a remote authentication backend is displayed to the user on the login
page may be adjusted via the `SOCIAL_AUTH_BACKEND_ATTRS` parameter, defaulting
to an empty dictionary. This dictionary maps a `social_core` module's name (ie.
`REMOTE_AUTH_BACKEND.name`) to a couple of parameters, `(display_name, icon)`.
The `display_name` is the name displayed to the user on the login page. The
icon may either be the URL of an icon; refer to a [Material Design
Icons](https://github.com/google/material-design-icons) icon's name; or be
`None` for no icon.
For instance, the OIDC backend may be customized with
```python
SOCIAL_AUTH_BACKEND_ATTRS = {
'oidc': ("My awesome SSO", "login"),
}
```

View File

@ -31,6 +31,17 @@ The sampling rate for errors. Must be a value between 0 (disabled) and 1.0 (repo
--- ---
## SENTRY_SEND_DEFAULT_PII
Default: False
Maps to the Sentry SDK's [`send_default_pii`](https://docs.sentry.io/platforms/python/configuration/options/#send-default-pii) parameter. If enabled, certain personally identifiable information (PII) is added.
!!! warning "Sensitive data"
If you enable this option, be aware that sensitive data such as cookies and authentication tokens will be logged.
---
## SENTRY_TAGS ## SENTRY_TAGS
An optional dictionary of tag names and values to apply to Sentry error reports.For example: An optional dictionary of tag names and values to apply to Sentry error reports.For example:

View File

@ -177,7 +177,7 @@ The dotted path to the desired search backend class. `CachedValueSearchBackend`
Default: None (local storage) Default: None (local storage)
The backend storage engine for handling uploaded files (e.g. image attachments). NetBox supports integration with the [`django-storages`](https://django-storages.readthedocs.io/en/stable/) package, which provides backends for several popular file storage services. If not configured, local filesystem storage will be used. The backend storage engine for handling uploaded files (e.g. image attachments). NetBox supports integration with the [`django-storages`](https://django-storages.readthedocs.io/en/stable/) and [`django-storage-swift`](https://github.com/dennisv/django-storage-swift) packages, which provide backends for several popular file storage services. If not configured, local filesystem storage will be used.
The configuration parameters for the specified storage backend are defined under the `STORAGE_CONFIG` setting. The configuration parameters for the specified storage backend are defined under the `STORAGE_CONFIG` setting.
@ -187,7 +187,7 @@ The configuration parameters for the specified storage backend are defined under
Default: Empty Default: Empty
A dictionary of configuration parameters for the storage backend configured as `STORAGE_BACKEND`. The specific parameters to be used here are specific to each backend; see the [`django-storages` documentation](https://django-storages.readthedocs.io/en/stable/) for more detail. A dictionary of configuration parameters for the storage backend configured as `STORAGE_BACKEND`. The specific parameters to be used here are specific to each backend; see the documentation for your selected backend ([`django-storages`](https://django-storages.readthedocs.io/en/stable/) or [`django-storage-swift`](https://github.com/dennisv/django-storage-swift)) for more detail.
If `STORAGE_BACKEND` is not defined, this setting will be ignored. If `STORAGE_BACKEND` is not defined, this setting will be ignored.

View File

@ -113,7 +113,7 @@ Create a [new release](https://github.com/netbox-community/netbox/releases/new)
* **Tag:** Current version (e.g. `v3.3.1`) * **Tag:** Current version (e.g. `v3.3.1`)
* **Target:** `master` * **Target:** `master`
* **Title:** Version and date (e.g. `v3.3.1 - 2022-08-25`) * **Title:** Version and date (e.g. `v3.3.1 - 2022-08-25`)
* **Description:** Copy from the pull request body * **Description:** Copy from the pull request body, then promote the `###` headers to `##` ones
Once created, the release will become available for users to install. Once created, the release will become available for users to install.
@ -135,6 +135,6 @@ First, run the `build-site` action, by navigating to Actions > build-site > Run
Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag. Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag.
Clear the CDN cache from the [Kinsta](https://my.kinsta.com/) portal. Navigate to _Sites_ / _NetBox Labs_ / _Live_, select _CDN_ in the left-nav, click the _Clear CDN cache_ button, and confirm the clear operation. Clear the CDN cache from the [Kinsta](https://my.kinsta.com/) portal. Navigate to _Sites_ / _NetBox Labs_ / _Live_, select _Cache_ in the left-nav, click the _Clear Cache_ button, and confirm the clear operation.
Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated. Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated.

View File

@ -20,6 +20,8 @@ Then, commit the change and push to the `develop` branch on GitHub. Any new stri
Typically, translated strings need to be updated only as part of the NetBox [release process](./release-checklist.md). Typically, translated strings need to be updated only as part of the NetBox [release process](./release-checklist.md).
Check the Transifex dashboard for languages that are not marked _ready for use_, being sure to click _Show all languages_ if it appears at the bottom of the list. Use machine translation to round out any not-ready languages. It's not necessary to review the machine translation immediately as the translation teams will handle that aspect; the goal at this stage is to get translations included in the Transifex pull request.
To update translated strings, start by initiating a sync from Transifex. From the Transifex dashboard, navigate to Settings > Integrations > GitHub > Manage, and click the **Manual Sync** button at top right. To update translated strings, start by initiating a sync from Transifex. From the Transifex dashboard, navigate to Settings > Integrations > GitHub > Manage, and click the **Manual Sync** button at top right.
![Transifex manual sync](../media/development/transifex_sync.png) ![Transifex manual sync](../media/development/transifex_sync.png)

View File

@ -1,27 +1,53 @@
# NetBox v4.0 # NetBox v4.0
## v4.0.7 (FUTURE) ## v4.0.8 (FUTURE)
### Enhancements ### Enhancements
* [#15375](https://github.com/netbox-community/netbox/issues/15375) - Enable customization of SSO backend name & icon
### Bug Fixes
* [#16357](https://github.com/netbox-community/netbox/issues/16357) - Replicate assigned type & tenant for cable when clicking "create an add another"
* [#16760](https://github.com/netbox-community/netbox/issues/16760) - Fix data source syncing using git via a local path
* [#16838](https://github.com/netbox-community/netbox/issues/16838) - ActionsColumn should render extra buttons even when no stock actions are enabled
* [#16867](https://github.com/netbox-community/netbox/issues/16867) - Fix exception when a dashboard list widget references a model which has been removed
---
## v4.0.7 (2024-07-09)
### Enhancements
* [#14554](https://github.com/netbox-community/netbox/issues/14554) - Add support for [django-storage-swift](https://github.com/dennisv/django-storage-swift) storage backend
* [#16424](https://github.com/netbox-community/netbox/issues/16424) - Enable filtering of devices by cluster and cluster group * [#16424](https://github.com/netbox-community/netbox/issues/16424) - Enable filtering of devices by cluster and cluster group
* [#16716](https://github.com/netbox-community/netbox/issues/16716) - Display NAT address (if any) for OOB IP address under device view
* [#16725](https://github.com/netbox-community/netbox/issues/16725) - Always position the admin section last in the navigation menu * [#16725](https://github.com/netbox-community/netbox/issues/16725) - Always position the admin section last in the navigation menu
* [#15660](https://github.com/netbox-community/netbox/issues/15660) - Add Czech language support * [#16791](https://github.com/netbox-community/netbox/issues/16791) - Add 200 & 400 Gbps selections for circuit termination port speed
* [#15696](https://github.com/netbox-community/netbox/issues/15696) - Add Danish language support * [#16802](https://github.com/netbox-community/netbox/issues/16802) - Introduce `SENTRY_SEND_DEFAULT_PII` configuration parameter and disable PII export by default
* [#16793](https://github.com/netbox-community/netbox/issues/16793) - Add Italian language support * [#16817](https://github.com/netbox-community/netbox/issues/16817) - Add 200 & 400 Gbps selections for circuit commit rate
* [#14640](https://github.com/netbox-community/netbox/issues/14640) - Add Dutch language support
* [#14792](https://github.com/netbox-community/netbox/issues/14792) - Add Polish language support
### Bug Fixes ### Bug Fixes
* [#16523](https://github.com/netbox-community/netbox/issues/16523) - Restore highlighting of current device in virtual chassis members panel * [#16523](https://github.com/netbox-community/netbox/issues/16523) - Restore highlighting of current device in virtual chassis members panel
* [#16654](https://github.com/netbox-community/netbox/issues/16654) - Fix parent item assignment for inventory item bulk import * [#16654](https://github.com/netbox-community/netbox/issues/16654) - Fix parent item assignment for inventory item bulk import
* [#16657](https://github.com/netbox-community/netbox/issues/16657) - Fix translation of object types in global search * [#16657](https://github.com/netbox-community/netbox/issues/16657) - Fix translation of object types in global search
* [#16679](https://github.com/netbox-community/netbox/issues/16679) - Avoid overwriting custom JSON fields during bulk edit
* [#16689](https://github.com/netbox-community/netbox/issues/16689) - System configuration view should reflect static parameters when no config revisions exist * [#16689](https://github.com/netbox-community/netbox/issues/16689) - System configuration view should reflect static parameters when no config revisions exist
* [#16714](https://github.com/netbox-community/netbox/issues/16714) - Fix cloning of device types with 0U height * [#16714](https://github.com/netbox-community/netbox/issues/16714) - Fix cloning of device types with 0U height
* [#16721](https://github.com/netbox-community/netbox/issues/16721) - Fix errant API request after deselecting a rack in device edit form
* [#16723](https://github.com/netbox-community/netbox/issues/16723) - Fix escaping of path to virtual environment in `upgrade.sh` * [#16723](https://github.com/netbox-community/netbox/issues/16723) - Fix escaping of path to virtual environment in `upgrade.sh`
* [#16735](https://github.com/netbox-community/netbox/issues/16735) - Object list "results" tab should show a count of zero when empty * [#16735](https://github.com/netbox-community/netbox/issues/16735) - Object list "results" tab should show a count of zero when empty
* [#16747](https://github.com/netbox-community/netbox/issues/16747) - Avoid clearing entire search cache when manually reindexing specific apps/models * [#16747](https://github.com/netbox-community/netbox/issues/16747) - Avoid clearing entire search cache when manually reindexing specific apps/models
* [#16758](https://github.com/netbox-community/netbox/issues/16758) - Ensure manually selected lagnuage persists across browser sessions
* [#16779](https://github.com/netbox-community/netbox/issues/16779) - Fix saved filter selection for child object lists
* [#16780](https://github.com/netbox-community/netbox/issues/16780) - IKE proposal created via REST API should not require authentication_algorithm
* [#16796](https://github.com/netbox-community/netbox/issues/16796) - Allow assignment of VM with no site to a cluster with a site
* [#16806](https://github.com/netbox-community/netbox/issues/16806) - Fix redirect URL when creating contact assignments with "add another" button
* [#16807](https://github.com/netbox-community/netbox/issues/16807) - Fix layout of VLAN edit form when custom fields are present
* [#16808](https://github.com/netbox-community/netbox/issues/16808) - Fix event rule triggering in scenario where objects are updated immediately prior to deletion
* [#16813](https://github.com/netbox-community/netbox/issues/16813) - Fix AttributeError exception when filtering bookmarks in dashboard widget by object type
* [#16843](https://github.com/netbox-community/netbox/issues/16843) - Permit creation of IKE policies via REST API without specifying an IKE mode
--- ---

View File

@ -44,10 +44,20 @@ class LoginView(View):
return super().dispatch(*args, **kwargs) return super().dispatch(*args, **kwargs)
def gen_auth_data(self, name, url, params): def gen_auth_data(self, name, url, params):
display_name, icon_name = get_auth_backend_display(name) display_name, icon_source = get_auth_backend_display(name)
icon_name = None
icon_img = None
if icon_source:
if '://' in icon_source:
icon_img = icon_source
else:
icon_name = icon_source
return { return {
'display_name': display_name, 'display_name': display_name,
'icon_name': icon_name, 'icon_name': icon_name,
'icon_img': icon_img,
'url': f'{url}?{urlencode(params)}', 'url': f'{url}?{urlencode(params)}',
} }
@ -111,7 +121,7 @@ class LoginView(View):
# Set the user's preferred language (if any) # Set the user's preferred language (if any)
if language := request.user.config.get('locale.language'): if language := request.user.config.get('locale.language'):
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language, max_age=request.session.get_expiry_age())
return response return response
@ -206,7 +216,7 @@ class UserConfigView(LoginRequiredMixin, View):
# Set/clear language cookie # Set/clear language cookie
if language := form.cleaned_data['locale.language']: if language := form.cleaned_data['locale.language']:
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language, max_age=request.session.get_expiry_age())
else: else:
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME) response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)

View File

@ -38,6 +38,8 @@ class CircuitCommitRateChoices(ChoiceSet):
(25000000, '25 Gbps'), (25000000, '25 Gbps'),
(40000000, '40 Gbps'), (40000000, '40 Gbps'),
(100000000, '100 Gbps'), (100000000, '100 Gbps'),
(200000000, '200 Gbps'),
(400000000, '400 Gbps'),
(1544, 'T1 (1.544 Mbps)'), (1544, 'T1 (1.544 Mbps)'),
(2048, 'E1 (2.048 Mbps)'), (2048, 'E1 (2.048 Mbps)'),
] ]

View File

@ -84,9 +84,7 @@ class GitBackend(DataBackend):
clone_args = { clone_args = {
"branch": self.params.get('branch'), "branch": self.params.get('branch'),
"config": self.config, "config": self.config,
"depth": 1,
"errstream": porcelain.NoneStream(), "errstream": porcelain.NoneStream(),
"quiet": True,
} }
if self.url_scheme in ('http', 'https'): if self.url_scheme in ('http', 'https'):
@ -97,6 +95,9 @@ class GitBackend(DataBackend):
"password": self.params.get('password'), "password": self.params.get('password'),
} }
) )
if self.url_scheme:
clone_args["quiet"] = True
clone_args["depth"] = 1
logger.debug(f"Cloning git repo: {self.url}") logger.debug(f"Cloning git repo: {self.url}")
try: try:

View File

@ -88,6 +88,8 @@ class Cable(PrimaryModel):
null=True null=True
) )
clone_fields = ('tenant', 'type',)
class Meta: class Meta:
ordering = ('pk',) ordering = ('pk',)
verbose_name = _('cable') verbose_name = _('cable')

View File

@ -251,6 +251,10 @@ class ObjectListWidget(DashboardWidget):
def render(self, request): def render(self, request):
app_label, model_name = self.config['model'].split('.') app_label, model_name = self.config['model'].split('.')
model = ObjectType.objects.get_by_natural_key(app_label, model_name).model_class() model = ObjectType.objects.get_by_natural_key(app_label, model_name).model_class()
if not model:
logger.debug(f"Dashboard Widget model_class not found: {app_label}:{model_name}")
return
viewname = get_viewname(model, action='list') viewname = get_viewname(model, action='list')
# Evaluate user's permission. Note that this controls only whether the HTMX element is # Evaluate user's permission. Note that this controls only whether the HTMX element is
@ -381,17 +385,17 @@ class BookmarksWidget(DashboardWidget):
if request.user.is_anonymous: if request.user.is_anonymous:
bookmarks = list() bookmarks = list()
else: else:
user_bookmarks = Bookmark.objects.filter(user=request.user) bookmarks = Bookmark.objects.filter(user=request.user)
if self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_AZ:
bookmarks = sorted(user_bookmarks, key=lambda bookmark: bookmark.__str__().lower())
elif self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_ZA:
bookmarks = sorted(user_bookmarks, key=lambda bookmark: bookmark.__str__().lower(), reverse=True)
else:
bookmarks = user_bookmarks.order_by(self.config['order_by'])
if object_types := self.config.get('object_types'): if object_types := self.config.get('object_types'):
models = get_models_from_content_types(object_types) models = get_models_from_content_types(object_types)
content_types = ObjectType.objects.get_for_models(*models).values() content_types = ObjectType.objects.get_for_models(*models).values()
bookmarks = bookmarks.filter(object_type__in=content_types) bookmarks = bookmarks.filter(object_type__in=content_types)
if self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_AZ:
bookmarks = sorted(bookmarks, key=lambda bookmark: bookmark.__str__().lower())
elif self.config['order_by'] == BookmarkOrderingChoices.ORDERING_ALPHABETICAL_ZA:
bookmarks = sorted(bookmarks, key=lambda bookmark: bookmark.__str__().lower(), reverse=True)
else:
bookmarks = bookmarks.order_by(self.config['order_by'])
if max_items := self.config.get('max_items'): if max_items := self.config.get('max_items'):
bookmarks = bookmarks[:max_items] bookmarks = bookmarks[:max_items]

View File

@ -63,6 +63,9 @@ def enqueue_object(queue, instance, user, request_id, action):
if key in queue: if key in queue:
queue[key]['data'] = serialize_for_event(instance) queue[key]['data'] = serialize_for_event(instance)
queue[key]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange'] queue[key]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange']
# If the object is being deleted, update any prior "update" event to "delete"
if action == ObjectChangeActionChoices.ACTION_DELETE:
queue[key]['event'] = action
else: else:
queue[key] = { queue[key] = {
'content_type': ContentType.objects.get_for_model(instance), 'content_type': ContentType.objects.get_for_model(instance),

View File

@ -390,13 +390,36 @@ class EventRuleTest(APITestCase):
request.id = uuid.uuid4() request.id = uuid.uuid4()
request.user = self.user request.user = self.user
self.assertEqual(self.queue.count, 0, msg="Unexpected jobs found in queue") # Test create & update
with event_tracking(request): with event_tracking(request):
site = Site(name='Site 1', slug='site-1') site = Site(name='Site 1', slug='site-1')
site.save() site.save()
site.description = 'foo'
# Save the site a second time
site.save() site.save()
self.assertEqual(self.queue.count, 1, msg="Duplicate jobs found in queue") self.assertEqual(self.queue.count, 1, msg="Duplicate jobs found in queue")
job = self.queue.get_jobs()[0]
self.assertEqual(job.kwargs['event'], ObjectChangeActionChoices.ACTION_CREATE)
self.queue.empty()
# Test multiple updates
site = Site.objects.create(name='Site 2', slug='site-2')
with event_tracking(request):
site.description = 'foo'
site.save()
site.description = 'bar'
site.save()
self.assertEqual(self.queue.count, 1, msg="Duplicate jobs found in queue")
job = self.queue.get_jobs()[0]
self.assertEqual(job.kwargs['event'], ObjectChangeActionChoices.ACTION_UPDATE)
self.queue.empty()
# Test update & delete
site = Site.objects.create(name='Site 3', slug='site-3')
with event_tracking(request):
site.description = 'foo'
site.save()
site.delete()
self.assertEqual(self.queue.count, 1, msg="Duplicate jobs found in queue")
job = self.queue.get_jobs()[0]
self.assertEqual(job.kwargs['event'], ObjectChangeActionChoices.ACTION_DELETE)
self.queue.empty()

View File

@ -49,12 +49,15 @@ AUTH_BACKEND_ATTRS = {
'okta-openidconnect': ('Okta (OIDC)', None), 'okta-openidconnect': ('Okta (OIDC)', None),
'salesforce-oauth2': ('Salesforce', 'salesforce'), 'salesforce-oauth2': ('Salesforce', 'salesforce'),
} }
# Override with potential user configuration
AUTH_BACKEND_ATTRS.update(getattr(settings, 'SOCIAL_AUTH_BACKEND_ATTRS', {}))
def get_auth_backend_display(name): def get_auth_backend_display(name):
""" """
Return the user-friendly name and icon name for a remote authentication backend, if known. Defaults to the Return the user-friendly name and icon name for a remote authentication backend, if
raw backend name and no icon. known. Obtained from the defaults dictionary AUTH_BACKEND_ATTRS, overridden by the
setting `SOCIAL_AUTH_BACKEND_ATTRS`. Defaults to the raw backend name and no icon.
""" """
return AUTH_BACKEND_ATTRS.get(name, (name, None)) return AUTH_BACKEND_ATTRS.get(name, (name, None))

View File

@ -47,6 +47,11 @@ class CoreMiddleware:
with event_tracking(request): with event_tracking(request):
response = self.get_response(request) response = self.get_response(request)
# Check if language cookie should be renewed
if request.user.is_authenticated and settings.SESSION_SAVE_EVERY_REQUEST:
if language := request.user.config.get('locale.language'):
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language, max_age=request.session.get_expiry_age())
# Attach the unique request ID as an HTTP header. # Attach the unique request ID as an HTTP header.
response['X-Request-ID'] = request.id response['X-Request-ID'] = request.id

View File

@ -25,7 +25,7 @@ from utilities.string import trailing_slash
# Environment setup # Environment setup
# #
VERSION = '4.0.7-dev' VERSION = '4.0.8-dev'
HOSTNAME = platform.node() HOSTNAME = platform.node()
# Set the base directory two levels up # Set the base directory two levels up
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -147,6 +147,7 @@ SECURE_SSL_REDIRECT = getattr(configuration, 'SECURE_SSL_REDIRECT', False)
SENTRY_DSN = getattr(configuration, 'SENTRY_DSN', None) SENTRY_DSN = getattr(configuration, 'SENTRY_DSN', None)
SENTRY_ENABLED = getattr(configuration, 'SENTRY_ENABLED', False) SENTRY_ENABLED = getattr(configuration, 'SENTRY_ENABLED', False)
SENTRY_SAMPLE_RATE = getattr(configuration, 'SENTRY_SAMPLE_RATE', 1.0) SENTRY_SAMPLE_RATE = getattr(configuration, 'SENTRY_SAMPLE_RATE', 1.0)
SENTRY_SEND_DEFAULT_PII = getattr(configuration, 'SENTRY_SEND_DEFAULT_PII', False)
SENTRY_TAGS = getattr(configuration, 'SENTRY_TAGS', {}) SENTRY_TAGS = getattr(configuration, 'SENTRY_TAGS', {})
SENTRY_TRACES_SAMPLE_RATE = getattr(configuration, 'SENTRY_TRACES_SAMPLE_RATE', 0) SENTRY_TRACES_SAMPLE_RATE = getattr(configuration, 'SENTRY_TRACES_SAMPLE_RATE', 0)
SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid') SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid')
@ -225,6 +226,23 @@ if STORAGE_BACKEND is not None:
return globals().get(name, default) return globals().get(name, default)
storages.utils.setting = _setting storages.utils.setting = _setting
# django-storage-swift
elif STORAGE_BACKEND == 'swift.storage.SwiftStorage':
try:
import swift.utils # type: ignore
except ModuleNotFoundError as e:
if getattr(e, 'name') == 'swift':
raise ImproperlyConfigured(
f"STORAGE_BACKEND is set to {STORAGE_BACKEND} but django-storage-swift is not present. "
"It can be installed by running 'pip install django-storage-swift'."
)
raise e
# Load all SWIFT_* settings from the user configuration
for param, value in STORAGE_CONFIG.items():
if param.startswith('SWIFT_'):
globals()[param] = value
if STORAGE_CONFIG and STORAGE_BACKEND is None: if STORAGE_CONFIG and STORAGE_BACKEND is None:
warnings.warn( warnings.warn(
"STORAGE_CONFIG has been set in configuration.py but STORAGE_BACKEND is not defined. STORAGE_CONFIG will be " "STORAGE_CONFIG has been set in configuration.py but STORAGE_BACKEND is not defined. STORAGE_CONFIG will be "
@ -536,7 +554,7 @@ if SENTRY_ENABLED:
release=VERSION, release=VERSION,
sample_rate=SENTRY_SAMPLE_RATE, sample_rate=SENTRY_SAMPLE_RATE,
traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE, traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
send_default_pii=True, send_default_pii=SENTRY_SEND_DEFAULT_PII,
http_proxy=HTTP_PROXIES.get('http') if HTTP_PROXIES else None, http_proxy=HTTP_PROXIES.get('http') if HTTP_PROXIES else None,
https_proxy=HTTP_PROXIES.get('https') if HTTP_PROXIES else None https_proxy=HTTP_PROXIES.get('https') if HTTP_PROXIES else None
) )

View File

@ -249,7 +249,7 @@ class ActionsColumn(tables.Column):
def render(self, record, table, **kwargs): def render(self, record, table, **kwargs):
# Skip dummy records (e.g. available VLANs) or those with no actions # Skip dummy records (e.g. available VLANs) or those with no actions
if not getattr(record, 'pk', None) or not self.actions: if not getattr(record, 'pk', None) or not (self.actions or self.extra_buttons):
return '' return ''
model = table.Meta.model model = table.Meta.model

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,7 @@
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"flatpickr": "4.6.13", "flatpickr": "4.6.13",
"gridstack": "10.2.1", "gridstack": "10.3.0",
"htmx.org": "1.9.12", "htmx.org": "1.9.12",
"query-string": "9.0.0", "query-string": "9.0.0",
"sass": "1.77.6", "sass": "1.77.6",

View File

@ -74,20 +74,25 @@ export class DynamicTomSelect extends TomSelect {
load(value: string) { load(value: string) {
const self = this; const self = this;
const url = self.getRequestUrl(value);
// Automatically clear any cached options. (Only options included // Automatically clear any cached options. (Only options included
// in the API response should be present.) // in the API response should be present.)
self.clearOptions(); self.clearOptions();
addClasses(self.wrapper, self.settings.loadingClass);
self.loading++;
// Populate the null option (if any) if not searching // Populate the null option (if any) if not searching
if (self.nullOption && !value) { if (self.nullOption && !value) {
self.addOption(self.nullOption); self.addOption(self.nullOption);
} }
// Get the API request URL. If none is provided, abort as no request can be made.
const url = self.getRequestUrl(value);
if (!url) {
return;
}
addClasses(self.wrapper, self.settings.loadingClass);
self.loading++;
// Make the API request // Make the API request
fetch(url) fetch(url)
.then(response => response.json()) .then(response => response.json())
@ -129,6 +134,9 @@ export class DynamicTomSelect extends TomSelect {
for (const result of this.api_url.matchAll(new RegExp(`({{${key}}})`, 'g'))) { for (const result of this.api_url.matchAll(new RegExp(`({{${key}}})`, 'g'))) {
if (value) { if (value) {
url = replaceAll(url, result[1], value.toString()); url = replaceAll(url, result[1], value.toString());
} else {
// No value is available to replace the token; abort.
return '';
} }
} }
} }

View File

@ -1754,10 +1754,10 @@ graphql@16.8.1:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gridstack@10.2.1: gridstack@10.3.0:
version "10.2.1" version "10.3.0"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-10.2.1.tgz#3ce6119ae86cfb0a533c5f0d15b03777a55384ca" resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-10.3.0.tgz#8fa065f896d0a880c5c54c24d189f3197184488a"
integrity sha512-UAPKnIvd9sIqPDFMtKMqj0G5GDj8MUFPcelRJq7FzQFSxSYBblKts/Gd52iEJg0EvTFP51t6ZuMWGx0pSSFBdw== integrity sha512-eGKsmU2TppV4coyDu9IIdIkm4qjgLLdjlEOFwQyQMuSwfOpzSfLdPc8du0HuebGr7CvAIrJxN4lBOmGrWSBg9g==
has-bigints@^1.0.1, has-bigints@^1.0.2: has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2" version "1.0.2"

View File

@ -78,7 +78,8 @@
{% for backend in auth_backends %} {% for backend in auth_backends %}
<div class="col"> <div class="col">
<a href="{{ backend.url }}" class="btn w-100"> <a href="{{ backend.url }}" class="btn w-100">
{% if backend.icon_name %}<i class="mdi mdi-{{ backend.icon_name }}"></i>{% endif %} {% if backend.icon_name %}<i class="mdi mdi-{{ backend.icon_name }}"></i>
{% elif backend.icon_img %}<img src="{{ backend.icon_img }}" height="24" class="me-2" />{% endif %}
{{ backend.display_name }} {{ backend.display_name }}
</a> </a>
</div> </div>

View File

@ -365,7 +365,7 @@ class ContactAssignmentEditView(generic.ObjectEditView):
def get_extra_addanother_params(self, request): def get_extra_addanother_params(self, request):
return { return {
'content_type': request.GET.get('content_type'), 'object_type': request.GET.get('object_type'),
'object_id': request.GET.get('object_id'), 'object_id': request.GET.get('object_id'),
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -179,8 +179,8 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
'cluster': _('A virtual machine must be assigned to a site and/or cluster.') 'cluster': _('A virtual machine must be assigned to a site and/or cluster.')
}) })
# Validate site for cluster & device # Validate site for cluster & VM
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: if self.cluster and self.site and self.cluster.site and self.cluster.site != self.site:
raise ValidationError({ raise ValidationError({
'cluster': _( 'cluster': _(
'The selected cluster ({cluster}) is not assigned to this site ({site}).' 'The selected cluster ({cluster}) is not assigned to this site ({site}).'

View File

@ -25,7 +25,8 @@ class IKEProposalSerializer(NetBoxModelSerializer):
choices=EncryptionAlgorithmChoices choices=EncryptionAlgorithmChoices
) )
authentication_algorithm = ChoiceField( authentication_algorithm = ChoiceField(
choices=AuthenticationAlgorithmChoices choices=AuthenticationAlgorithmChoices,
required=False
) )
group = ChoiceField( group = ChoiceField(
choices=DHGroupChoices choices=DHGroupChoices
@ -49,7 +50,8 @@ class IKEPolicySerializer(NetBoxModelSerializer):
choices=IKEVersionChoices choices=IKEVersionChoices
) )
mode = ChoiceField( mode = ChoiceField(
choices=IKEModeChoices choices=IKEModeChoices,
required=False
) )
proposals = SerializedPKRelatedField( proposals = SerializedPKRelatedField(
queryset=IKEProposal.objects.all(), queryset=IKEProposal.objects.all(),

View File

@ -1,4 +1,4 @@
Django==5.0.6 Django==5.0.7
django-cors-headers==4.4.0 django-cors-headers==4.4.0
django-debug-toolbar==4.3.0 django-debug-toolbar==4.3.0
django-filter==24.2 django-filter==24.2
@ -12,26 +12,26 @@ django-rich==1.9.0
django-rq==2.10.2 django-rq==2.10.2
django-taggit==5.0.1 django-taggit==5.0.1
django-tables2==2.7.0 django-tables2==2.7.0
django-timezone-field==6.1.0 django-timezone-field==7.0
djangorestframework==3.15.2 djangorestframework==3.15.2
drf-spectacular==0.27.2 drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.6.1 drf-spectacular-sidecar==2024.7.1
feedparser==6.0.11 feedparser==6.0.11
gunicorn==22.0.0 gunicorn==22.0.0
Jinja2==3.1.4 Jinja2==3.1.4
Markdown==3.6 Markdown==3.6
mkdocs-material==9.5.27 mkdocs-material==9.5.28
mkdocstrings[python-legacy]==0.25.1 mkdocstrings[python-legacy]==0.25.1
netaddr==1.3.0 netaddr==1.3.0
nh3==0.2.17 nh3==0.2.18
Pillow==10.3.0 Pillow==10.4.0
psycopg[c,pool]==3.1.19 psycopg[c,pool]==3.2.1
PyYAML==6.0.1 PyYAML==6.0.1
requests==2.32.3 requests==2.32.3
social-auth-app-django==5.4.1 social-auth-app-django==5.4.1
social-auth-core==4.5.4 social-auth-core==4.5.4
strawberry-graphql==0.235.0 strawberry-graphql==0.235.2
strawberry-graphql-django==0.44.2 strawberry-graphql-django==0.46.1
svgwrite==1.4.3 svgwrite==1.4.3
tablib==3.6.1 tablib==3.6.1
tzdata==2024.1 tzdata==2024.1