Compare commits

...

63 Commits

Author SHA1 Message Date
Jeremy Stretch
09d6b9c62f Merge pull request #17157 from netbox-community/develop
Release v4.0.9
2024-08-14 10:23:47 -04:00
Jeremy Stretch
4747cdef0b Bump version to v4.0.9 for release 2024-08-14 10:06:44 -04:00
Jeremy Stretch
f3f1aa3841 Release v4.0.9 2024-08-14 09:30:41 -04:00
transifex-integration[bot]
727cb65c50 Updates for project NetBox (#17153)
* Translate django.po in ru

100% translated source file: 'django.po'
on 'ru'.

* Translate django.po in de

100% translated source file: 'django.po'
on 'de'.

* Translate django.po in ja [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'ja'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in fr [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'fr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in es [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'es'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in pt [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'pt'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in tr [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'tr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in it [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'it'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in zh [Manual Sync]

99% of minimum 1% translated source file: 'django.po'
on 'zh'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in pl [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'pl'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in nl [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'nl'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in uk [Manual Sync]

99% of minimum 1% translated source file: 'django.po'
on 'uk'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in cs [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'cs'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in da [Manual Sync]

98% of minimum 1% translated source file: 'django.po'
on 'da'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

* Translate django.po in zh

100% translated source file: 'django.po'
on 'zh'.

* Translate django.po in cs

100% translated source file: 'django.po'
on 'cs'.

* Translate django.po in da

100% translated source file: 'django.po'
on 'da'.

* Translate django.po in nl

100% translated source file: 'django.po'
on 'nl'.

* Translate django.po in fr

100% translated source file: 'django.po'
on 'fr'.

* Translate django.po in it

100% translated source file: 'django.po'
on 'it'.

* Translate django.po in ja

100% translated source file: 'django.po'
on 'ja'.

* Translate django.po in pl

100% translated source file: 'django.po'
on 'pl'.

* Translate django.po in pt

100% translated source file: 'django.po'
on 'pt'.

* Translate django.po in es

100% translated source file: 'django.po'
on 'es'.

* Translate django.po in tr

100% translated source file: 'django.po'
on 'tr'.

* Translate django.po in uk

100% translated source file: 'django.po'
on 'uk'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-08-14 09:28:28 -04:00
Arthur Hanson
872af72b8e 16073 set default custom fields on CSV import (#17152)
* 16073 set default custom fields on CSV import

* 16073 add test case

* Remove second for loop

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-08-14 08:22:14 -04:00
Jeremy Stretch
5a9f9af2fa Fixes #16871: Sanitize device ID when bulk editing components to prevent exception 2024-08-14 07:43:10 -04:00
github-actions
09d36469dd Update source translation strings 2024-08-14 05:02:17 +00:00
Jeremy Stretch
8789aaaa39 Changelog for #16692, #17006, #17124, #17131, #17144 2024-08-13 16:12:58 -04:00
Jeremy Stretch
d5c1a5acda Fixes #17144: Avoid displaying duplicate pop-up messages 2024-08-13 15:36:15 -04:00
PieterL75
6feb8bf0e3 add 'vlan' to prefix bulk edit (#17142)
* add 'vlan' to prefix bulk edit

* Move VLAN fields to a separate field set in bulk edit form

---------

Co-authored-by: Pieter Lambrecht <pieter.lambrecht@accenture.com>
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-08-13 09:54:07 -04:00
Jeremy Stretch
9e54cfe340 Fixes #17131: Fix exception when creating object-type custom field without selecting related object type 2024-08-13 08:14:16 -04:00
github-actions
6a663e2a3e Update source translation strings 2024-08-13 05:02:12 +00:00
Matthew Mehrtens
7c9a77b77f 17006 Add Wi-Fi 7 IEEE 802.11be (#17125)
* Add .devcontainer to .gitignore

* Closes #17006: Add Wi-Fi 7 IEEE 802.11be

* Revert out-of-scope change

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-08-12 08:03:46 -04:00
github-actions
81fe12a7d9 Update source translation strings 2024-08-11 05:02:11 +00:00
Jeremy Stretch
9c7002f691 Fixes #17124: BaseTable should follow reverse one-to-one relationships when prefetching related objects 2024-08-10 11:58:14 -04:00
Jeremy Stretch
20967bf88d Changelog for #13459, #16176, #17038, #17064 2024-08-10 11:49:34 -04:00
Arthur Hanson
34d20fccd5 17036 international messages (#17041)
* 17036 international messages

* 17036 fix typo

* 17036 fix _

* Misc cleanup & fixes

* More cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-08-10 11:47:06 -04:00
Arthur Hanson
f6c1642116 16176 Add UI for multi-termination cables 2024-08-10 10:30:56 -04:00
Arthur Hanson
b7b0ab16f5 17064 fix markdown padding for first line 2024-08-10 10:28:01 -04:00
Arthur Hanson
6ae3af2f26 13459 Fix OpenAPI type for TreeNodeMultipleChoiceFilter (#17095)
* 13459 Correct OpenAPI type for TreeNodeMultipleChoiceFilter

* 13459 Correct OpenAPI type for TreeNodeMultipleChoiceFilter
2024-08-10 10:24:02 -04:00
Jeremy Stretch
6c845bd5de Update CONTRIBUTING.md
Add warning about intentionally submitting duplicate issues
2024-08-06 07:53:43 -04:00
github-actions
597fc926c0 Update source translation strings 2024-08-02 05:02:14 +00:00
bubu
fa2b3bcfcc Adjust HTML template for Chinese translation order. 2024-08-01 09:08:06 -04:00
Arthur Hanson
dc173a5508 17038 fix export system data 2024-08-01 08:47:26 -04:00
Arthur Hanson
408f8b4964 17054 upgrade sass to upgrade braces 2024-08-01 08:43:50 -04:00
github-actions
f949aa334b Update source translation strings 2024-07-27 05:02:14 +00:00
Jeremy Stretch
8bfcb1c816 PRVB 2024-07-26 16:25:32 -04:00
Jeremy Stretch
630c6fb43d Merge branch 'master' into develop 2024-07-26 16:24:55 -04:00
Jeremy Stretch
7b5e8d5f2a Update static assets 2024-07-26 15:47:57 -04:00
Jeremy Stretch
303c1ce00c Release v4.0.8 2024-07-26 15:37:05 -04:00
Jeremy Stretch
b4240cdd67 Update import statements for Strawberry 0.236.0 2024-07-26 15:35:08 -04:00
transifex-integration[bot]
1e7a71969e Updates for project NetBox (#17004)
* Translate django.po in de

100% translated source file: 'django.po'
on 'de'.

* Translate django.po in pt

100% translated source file: 'django.po'
on 'pt'.

* Translate django.po in zh

100% translated source file: 'django.po'
on 'zh'.

* Translate django.po in pl

100% translated source file: 'django.po'
on 'pl'.

* Translate django.po in ja

100% translated source file: 'django.po'
on 'ja'.

* Translate django.po in nl

100% translated source file: 'django.po'
on 'nl'.

* Translate django.po in cs

100% translated source file: 'django.po'
on 'cs'.

* Translate django.po in uk

100% translated source file: 'django.po'
on 'uk'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-07-26 15:34:47 -04:00
Jonathan Senecal
f3d1924453 Use provider_id instead of account_id in url_params 2024-07-26 15:10:25 -04:00
Jeremy Stretch
4d55d7d964 Changelog for #16933, #16943, #16964 2024-07-26 08:01:08 -04:00
Jeremy Stretch
d8c7282fdb Fixes #16964: Ensure configured password validators are enforced (#16990)
* Closes #16964: Validate password when creating a new user or updating password for an existing user

* Add serializer validation & tests

---------

Co-authored-by: Nishant Gaglani <nishantgaglani@gmail.com>
2024-07-26 07:58:14 -04:00
Jeremy Stretch
cc72a58c1e Remove jeffgdotorg from triage rotation 2024-07-24 17:15:53 -04:00
github-actions
36df9228a6 Update source translation strings 2024-07-20 05:02:12 +00:00
Jeremy Stretch
424dda5be6 Closes #16933: Enable toggling true/false marks on BooleanColumn 2024-07-19 07:54:41 -04:00
Jeremy Stretch
3028f262cc Closes #16943: Expand navigation breadcrumbs on job view to include parent object 2024-07-19 07:52:27 -04:00
github-actions
11cadf3a8a Update source translation strings 2024-07-19 05:02:01 +00:00
Jeremy Stretch
954d0cfcd0 Closes #16929: Add version & user details as data attributes (#16939)
* Closes #16929: Add version & user details as data attributes

* Fix typo
2024-07-18 12:39:23 -04:00
Benjamin Dale
0830ebb34a Update CONTRIBUTING.md
The GitHub reactions icon has been moved from the top right to the bottom left of messages in Issues - I was going insane trying to find it, so this might help someone in the future ; )
2024-07-16 20:12:17 -04:00
Jeremy Stretch
95cb7b2c34 Changelog for #16402, #16536, #16624, #16819 2024-07-15 09:12:36 -04:00
Arthur Hanson
dde77c83b4 16819 highlight parent device in rack (#16881)
* 16819 highlight parent device in rack

* 16819 review changes
2024-07-15 09:09:26 -04:00
Eric Oswald
e216bebd41 Fix incorrect import in rest-api.md 2024-07-13 10:28:43 -04:00
Thomas Fargeix
d39acfd3f2 Fixes 16536 - Fix filtering of device component by device role (#16553)
* Fixes 16536 - Fix filtering of device component by device role

Rename role and role_id fields to device_role and device_role_id in
DeviceComponentFilterSet

* Update tests for DeviceComponentFilterSet

* Use device_role filter name for DeviceComponentFilterSetTests

* Add test_device_role test in InventoryItemTestCase
2024-07-12 09:13:33 -04:00
Fabian Geisberger
4ea4e57f33 Fixes #16624: Set allow_null=True on method fields that can return None 2024-07-12 09:03:40 -04:00
Arthur Hanson
377543cd9c 16402 remove links from script result table 2024-07-12 08:31:32 -04:00
github-actions
a8827c8472 Update source translation strings 2024-07-12 05:02:03 +00:00
Jeremy Stretch
b2ca6df50a Changelog for #14640, #14792, #15660, #15696, #16793 2024-07-11 14:21:41 -04:00
transifex-integration[bot]
ab6ddd50a8 Updates for project NetBox (#16888)
* Translate django.po in pt

100% reviewed source file: 'django.po'
on 'pt'.

* Translate django.po in es

100% translated source file: 'django.po'
on 'es'.

* Translate django.po in de

100% translated source file: 'django.po'
on 'de'.

* Translate django.po in fr

100% translated source file: 'django.po'
on 'fr'.

* Translate django.po in ru

100% translated source file: 'django.po'
on 'ru'.

* Translate django.po in ja

100% translated source file: 'django.po'
on 'ja'.

* Translate django.po in it

100% translated source file: 'django.po'
on 'it'.

* Translate django.po in cs

100% translated source file: 'django.po'
on 'cs'.

* Translate django.po in zh

100% translated source file: 'django.po'
on 'zh'.

* Translate django.po in nl

100% translated source file: 'django.po'
on 'nl'.

* Translate django.po in da

100% translated source file: 'django.po'
on 'da'.

* Translate django.po in uk

100% translated source file: 'django.po'
on 'uk'.

* Translate django.po in pl

100% translated source file: 'django.po'
on 'pl'.

* Translate django.po in tr

100% translated source file: 'django.po'
on 'tr'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-07-11 14:20:06 -04:00
Jeff Gehlbach
499da4fdcf Added CS, DA, IT, NL, and PL, minus the .po and .mo starting point files (#16810)
* Added CS, DA, IT, NL, and PL, minus the .po and .mo starting point files

* Add initial PO files for new languages

* Revert updates to EN django.po

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-07-11 14:04:36 -04:00
github-actions
4fa396716e Update source translation strings 2024-07-11 05:02:02 +00:00
Jeremy Stretch
6f3a2a599f Changelog for #15375, #16357, #16760, #16838, #16867 2024-07-10 09:16:23 -04:00
Jeremy Stretch
960d2b82b7 Update contact email 2024-07-10 09:14:20 -04:00
the.friendly.net
f2e1de027f Fixes #16760: datasource git on local file system fails (#16872)
* Fixes #16760: datasource git on local file system fails

* Fixes #16760: datasource git on local file system fails

* Set depth & quiet parameters only if using a remote URL

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2024-07-10 09:10:28 -04:00
Jeff Gehlbach
bf97138c78 Small additions and tweaks to release checklist from releasing v4.0.7 2024-07-10 08:45:36 -04:00
Arthur Hanson
30d711d24a 16838 show extra_buttons if no actions defined 2024-07-10 08:43:27 -04:00
Arthur Hanson
2a8bec1cbf 16867 render dashboard if model no longer available 2024-07-10 08:39:40 -04:00
Arthur Hanson
013139aa20 16357 clone tenant and type for cable 2024-07-10 08:34:48 -04:00
Théophile Bastian
4ca1494127 SSO: custom name for identity providers (#16732) 2024-07-10 13:09:03 +07:00
github-actions
70311a9db5 Update source translation strings 2024-07-10 05:02:10 +00:00
Jeremy Stretch
dd413b248a PRVB 2024-07-09 13:49:24 -04:00
46 changed files with 7247 additions and 4634 deletions

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ NetBox users are welcome to participate in either role, on stage or in the crowd
* First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases) of NetBox. If you're running an older version, it's likely that the bug has already been fixed.
* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the bottom left corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated.
* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the bottom left corner of the issue and add a thumbs up ( :thumbsup: ). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated.
* If you can't find any existing issues (open or closed) that seem to match yours, you're welcome to [submit a new bug report](https://github.com/netbox-community/netbox/issues/new?label=type%3A+bug&template=bug_report.yaml). Be sure to complete the entire report template, including detailed steps that someone triaging your issue can follow to confirm the reported behavior. (If we're not able to replicate the bug based on the information provided, we'll ask for additional detail.)
@@ -56,7 +56,9 @@ intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Poli
## :bulb: Feature Requests
* First, check the GitHub [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the feature you have in mind has already been proposed. If you happen to find an open feature request that matches your idea, click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This ensures that the issue has a better chance of receiving attention. Also feel free to add a comment with any additional justification for the feature.
* First, check the GitHub [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the feature you have in mind has already been proposed. If you happen to find an open feature request that matches your idea, click "add a reaction" in the top right corner of the issue and add a thumbs up ( :thumbsup: ). This ensures that the issue has a better chance of receiving attention. Also feel free to add a comment with any additional justification for the feature.
* Please don't submit duplicate issues! Sometimes we reject feature requests, for various reasons. Even if you disagree with those reasons, please **do not** submit a duplicate feature request. It is very disrepectful of the maintainers' time, and you may be barred from opening future issues.
* If you have a rough idea that's not quite ready for formal submission yet, start a [GitHub discussion](https://github.com/netbox-community/netbox/discussions) instead. This is a great way to test the viability and narrow down the scope of a new feature prior to submitting a formal proposal, and can serve to generate interest in your idea from other community members.

View File

@@ -10,7 +10,7 @@ django-cors-headers
# https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst
# Pinned for DNS looukp bug; see https://github.com/netbox-community/netbox/issues/16454
# and https://github.com/jazzband/django-debug-toolbar/issues/1927
django-debug-toolbar==4.3.0
django-debug-toolbar
# Library for writing reusable URL query filters
# https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst

View File

@@ -377,6 +377,7 @@
"ieee802.11ad",
"ieee802.11ax",
"ieee802.11ay",
"ieee802.11be",
"ieee802.15.1",
"other-wireless",
"gsm",

View File

@@ -1,5 +1,26 @@
# NetBox v4.0
## v4.0.9 (2024-08-14)
### Enhancements
* [#16692](https://github.com/netbox-community/netbox/issues/16692) - Enable modifying VLAN assignment while bulk editing prefixes
* [#17006](https://github.com/netbox-community/netbox/issues/17006) - Add IEEE 802.11be interface type
### Bug Fixes
* [#13459](https://github.com/netbox-community/netbox/issues/13459) - Correct OpenAPI schema type for `TreeNodeMultipleChoiceFilter`
* [#16073](https://github.com/netbox-community/netbox/issues/16073) - Respect default values for custom fields during bulk import of objects
* [#16176](https://github.com/netbox-community/netbox/issues/16176) - Restore ability to select multiple terminating devices when connecting a cable
* [#16871](https://github.com/netbox-community/netbox/issues/16871) - Sanitize device ID query parameter when bulk editing components to prevent exception
* [#17038](https://github.com/netbox-community/netbox/issues/17038) - Fix AttributeError exception when attempting to export system status data
* [#17064](https://github.com/netbox-community/netbox/issues/17064) - Fix misaligned text within rendered Markdown code blocks
* [#17124](https://github.com/netbox-community/netbox/issues/17124) - `BaseTable` should follow reverse one-to-one relationships when prefetching related objects
* [#17131](https://github.com/netbox-community/netbox/issues/17131) - Fix exception when creating object-type custom field without selecting related object type
* [#17144](https://github.com/netbox-community/netbox/issues/17144) - Avoid showing duplicated pop-up messages
---
## v4.0.8 (2024-07-26)
### Enhancements

View File

@@ -109,7 +109,7 @@ class LoginView(View):
# Authenticate user
auth_login(request, form.get_user())
logger.info(f"User {request.user} successfully authenticated")
messages.success(request, f"Logged in as {request.user}.")
messages.success(request, _("Logged in as {user}.").format(user=request.user))
# Ensure the user has a UserConfig defined. (This should normally be handled by
# create_userconfig() on user creation.)
@@ -159,7 +159,7 @@ class LogoutView(View):
username = request.user
auth_logout(request)
logger.info(f"User {username} has logged out")
messages.info(request, "You have logged out.")
messages.info(request, _("You have logged out."))
# Delete session key & language cookies (if set) upon logout
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
@@ -234,7 +234,7 @@ class ChangePasswordView(LoginRequiredMixin, View):
def get(self, request):
# LDAP users cannot change their password here
if getattr(request.user, 'ldap_username', None):
messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
messages.warning(request, _("LDAP-authenticated user credentials cannot be changed within NetBox."))
return redirect('account:profile')
form = PasswordChangeForm(user=request.user)
@@ -249,7 +249,7 @@ class ChangePasswordView(LoginRequiredMixin, View):
if form.is_valid():
form.save()
update_session_auth_hash(request, form.user)
messages.success(request, "Your password has been changed successfully.")
messages.success(request, _("Your password has been changed successfully."))
return redirect('account:profile')
return render(request, self.template_name, {

View File

@@ -1,6 +1,7 @@
from django.contrib import messages
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import gettext_lazy as _
from dcim.views import PathTraceView
from netbox.views import generic
@@ -326,7 +327,9 @@ class CircuitSwapTerminations(generic.ObjectEditView):
# Circuit must have at least one termination to swap
if not circuit.termination_a and not circuit.termination_z:
messages.error(request, "No terminations have been defined for circuit {}.".format(circuit))
messages.error(request, _(
"No terminations have been defined for circuit {circuit}."
).format(circuit=circuit))
return redirect('circuits:circuit', pk=circuit.pk)
return render(request, 'circuits/circuit_terminations_swap.html', {
@@ -374,7 +377,7 @@ class CircuitSwapTerminations(generic.ObjectEditView):
circuit.termination_z = None
circuit.save()
messages.success(request, f"Swapped terminations for circuit {circuit}.")
messages.success(request, _("Swapped terminations for circuit {circuit}.").format(circuit=circuit))
return redirect('circuits:circuit', pk=circuit.pk)
return render(request, 'circuits/circuit_terminations_swap.html', {

View File

@@ -76,7 +76,10 @@ class DataSourceSyncView(BaseObjectView):
datasource = get_object_or_404(self.queryset, pk=pk)
job = datasource.enqueue_sync_job(request)
messages.success(request, f"Queued job #{job.pk} to sync {datasource}")
messages.success(
request,
_("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource)
)
return redirect(datasource.get_absolute_url())
@@ -235,7 +238,7 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View):
candidate_config = get_object_or_404(ConfigRevision, pk=pk)
candidate_config.activate()
messages.success(request, f"Restored configuration revision #{pk}")
messages.success(request, _("Restored configuration revision #{id}").format(id=pk))
return redirect(candidate_config.get_absolute_url())
@@ -379,9 +382,9 @@ class BackgroundTaskDeleteView(BaseRQView):
# Remove job id from queue and delete the actual job
queue.connection.lrem(queue.key, 0, job.id)
job.delete()
messages.success(request, f'Deleted job {job_id}')
messages.success(request, _('Job {id} has been deleted.').format(id=job_id))
else:
messages.error(request, f'Error deleting job: {form.errors[0]}')
messages.error(request, _('Error deleting job {id}: {error}').format(id=job_id, error=form.errors[0]))
return redirect(reverse('core:background_queue_list'))
@@ -394,13 +397,13 @@ class BackgroundTaskRequeueView(BaseRQView):
try:
job = RQ_Job.fetch(job_id, connection=get_redis_connection(config['connection_config']),)
except NoSuchJobError:
raise Http404(_("Job {job_id} not found").format(job_id=job_id))
raise Http404(_("Job {id} not found.").format(id=job_id))
queue_index = QUEUES_MAP[job.origin]
queue = get_queue_by_index(queue_index)
requeue_job(job_id, connection=queue.connection, serializer=queue.serializer)
messages.success(request, f'You have successfully requeued: {job_id}')
messages.success(request, _('Job {id} has been re-enqueued.').format(id=job_id))
return redirect(reverse('core:background_task', args=[job_id]))
@@ -412,7 +415,7 @@ class BackgroundTaskEnqueueView(BaseRQView):
try:
job = RQ_Job.fetch(job_id, connection=get_redis_connection(config['connection_config']),)
except NoSuchJobError:
raise Http404(_("Job {job_id} not found").format(job_id=job_id))
raise Http404(_("Job {id} not found.").format(id=job_id))
queue_index = QUEUES_MAP[job.origin]
queue = get_queue_by_index(queue_index)
@@ -435,7 +438,7 @@ class BackgroundTaskEnqueueView(BaseRQView):
registry = ScheduledJobRegistry(queue.name, queue.connection)
registry.remove(job)
messages.success(request, f'You have successfully enqueued: {job_id}')
messages.success(request, _('Job {id} has been enqueued.').format(id=job_id))
return redirect(reverse('core:background_task', args=[job_id]))
@@ -452,11 +455,11 @@ class BackgroundTaskStopView(BaseRQView):
queue_index = QUEUES_MAP[job.origin]
queue = get_queue_by_index(queue_index)
stopped, _ = stop_jobs(queue, job_id)
if len(stopped) == 1:
messages.success(request, f'You have successfully stopped {job_id}')
stopped_jobs = stop_jobs(queue, job_id)[0]
if len(stopped_jobs) == 1:
messages.success(request, _('Job {id} has been stopped.').format(id=job_id))
else:
messages.error(request, f'Failed to stop {job_id}')
messages.error(request, _('Failed to stop job {id}').format(id=job_id))
return redirect(reverse('core:background_task', args=[job_id]))
@@ -559,13 +562,14 @@ class SystemView(UserPassesTestMixin, View):
# Raw data export
if 'export' in request.GET:
params = [param.name for param in PARAMS]
data = {
**stats,
'plugins': {
plugin.name: plugin.version for plugin in plugins
},
'config': {
k: config.data[k] for k in sorted(config.data)
k: getattr(config, k) for k in sorted(params)
},
}
response = HttpResponse(json.dumps(data, indent=4), content_type='text/json')

View File

@@ -886,6 +886,7 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_80211AD = 'ieee802.11ad'
TYPE_80211AX = 'ieee802.11ax'
TYPE_80211AY = 'ieee802.11ay'
TYPE_80211BE = 'ieee802.11be'
TYPE_802151 = 'ieee802.15.1'
TYPE_OTHER_WIRELESS = 'other-wireless'
@@ -1057,6 +1058,7 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_80211AD, 'IEEE 802.11ad'),
(TYPE_80211AX, 'IEEE 802.11ax'),
(TYPE_80211AY, 'IEEE 802.11ay'),
(TYPE_80211BE, 'IEEE 802.11be'),
(TYPE_802151, 'IEEE 802.15.1 (Bluetooth)'),
(TYPE_OTHER_WIRELESS, 'Other (Wireless)'),
)

View File

@@ -49,6 +49,7 @@ WIRELESS_IFACE_TYPES = [
InterfaceTypeChoices.TYPE_80211AD,
InterfaceTypeChoices.TYPE_80211AX,
InterfaceTypeChoices.TYPE_80211AY,
InterfaceTypeChoices.TYPE_80211BE,
InterfaceTypeChoices.TYPE_802151,
InterfaceTypeChoices.TYPE_OTHER_WIRELESS,
]

View File

@@ -1188,12 +1188,17 @@ class ComponentBulkEditForm(NetBoxModelBulkEditForm):
required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, *args, initial=None, **kwargs):
try:
self.device_id = int(initial.get('device'))
except (TypeError, ValueError):
self.device_id = None
super().__init__(*args, initial=initial, **kwargs)
# Limit module queryset to Modules which belong to the parent Device
if 'device' in self.initial:
device = Device.objects.filter(pk=self.initial['device']).first()
if self.device_id:
device = Device.objects.filter(pk=self.device_id).first()
self.fields['module'].queryset = Module.objects.filter(device=device)
else:
self.fields['module'].choices = ()
@@ -1201,8 +1206,8 @@ class ComponentBulkEditForm(NetBoxModelBulkEditForm):
class ConsolePortBulkEditForm(
form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),
@@ -1218,8 +1223,8 @@ class ConsolePortBulkEditForm(
class ConsoleServerPortBulkEditForm(
form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),
@@ -1235,8 +1240,8 @@ class ConsoleServerPortBulkEditForm(
class PowerPortBulkEditForm(
form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),
@@ -1253,8 +1258,8 @@ class PowerPortBulkEditForm(
class PowerOutletBulkEditForm(
form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),
@@ -1273,8 +1278,8 @@ class PowerOutletBulkEditForm(
super().__init__(*args, **kwargs)
# Limit power_port queryset to PowerPorts which belong to the parent Device
if 'device' in self.initial:
device = Device.objects.filter(pk=self.initial['device']).first()
if self.device_id:
device = Device.objects.filter(pk=self.device_id).first()
self.fields['power_port'].queryset = PowerPort.objects.filter(device=device)
else:
self.fields['power_port'].choices = ()
@@ -1282,12 +1287,12 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm(
ComponentBulkEditForm,
form_from_model(Interface, [
'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'wireless_lans'
]),
ComponentBulkEditForm
])
):
enabled = forms.NullBooleanField(
label=_('Enabled'),
@@ -1416,8 +1421,8 @@ class InterfaceBulkEditForm(
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'device' in self.initial:
device = Device.objects.filter(pk=self.initial['device']).first()
if self.device_id:
device = Device.objects.filter(pk=self.device_id).first()
# Restrict parent/bridge/LAG interface assignment by device
self.fields['parent'].widget.add_query_param('virtual_chassis_member_id', device.pk)
@@ -1480,8 +1485,8 @@ class InterfaceBulkEditForm(
class FrontPortBulkEditForm(
form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),
@@ -1497,8 +1502,8 @@ class FrontPortBulkEditForm(
class RearPortBulkEditForm(
form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
ComponentBulkEditForm
ComponentBulkEditForm,
form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description'])
):
mark_connected = forms.NullBooleanField(
label=_('Mark connected'),

View File

@@ -19,7 +19,7 @@ def get_cable_form(a_type, b_type):
# Device component
if hasattr(term_cls, 'device'):
attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(
attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
label=_('Device'),
required=False,
@@ -33,6 +33,7 @@ def get_cable_form(a_type, b_type):
label=term_cls._meta.verbose_name.title(),
context={
'disabled': '_occupied',
'parent': 'device',
},
query_params={
'device_id': f'$termination_{cable_end}_device',
@@ -43,7 +44,7 @@ def get_cable_form(a_type, b_type):
# PowerFeed
elif term_cls == PowerFeed:
attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
label=_('Power Panel'),
required=False,
@@ -57,6 +58,7 @@ def get_cable_form(a_type, b_type):
label=_('Power Feed'),
context={
'disabled': '_occupied',
'parent': 'powerpanel',
},
query_params={
'power_panel_id': f'$termination_{cable_end}_powerpanel',
@@ -66,7 +68,7 @@ def get_cable_form(a_type, b_type):
# CircuitTermination
elif term_cls == CircuitTermination:
attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
attrs[f'termination_{cable_end}_circuit'] = DynamicModelMultipleChoiceField(
queryset=Circuit.objects.all(),
label=_('Circuit'),
selector=True,
@@ -79,6 +81,7 @@ def get_cable_form(a_type, b_type):
label=_('Side'),
context={
'disabled': '_occupied',
'parent': 'circuit',
},
query_params={
'circuit_id': f'$termination_{cable_end}_circuit',

View File

@@ -2059,7 +2059,7 @@ class DeviceRenderConfigView(generic.ObjectView):
try:
rendered_config = config_template.render(context=context_data)
except TemplateError as e:
messages.error(request, f"An error occurred while rendering the template: {e}")
messages.error(request, _("An error occurred while rendering the template: {error}").format(error=e))
rendered_config = traceback.format_exc()
return {
@@ -2823,7 +2823,13 @@ class DeviceBayPopulateView(generic.ObjectEditView):
device_bay.snapshot()
device_bay.installed_device = form.cleaned_data['installed_device']
device_bay.save()
messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
messages.success(
request,
_("Installed device {device} in bay {device_bay}.").format(
device=device_bay.installed_device,
device_bay=device_bay
)
)
return_url = self.get_return_url(request)
return redirect(return_url)
@@ -2858,7 +2864,13 @@ class DeviceBayDepopulateView(generic.ObjectEditView):
removed_device = device_bay.installed_device
device_bay.installed_device = None
device_bay.save()
messages.success(request, f"{removed_device} has been removed from {device_bay}.")
messages.success(
request,
_("Removed device {device} from bay {device_bay}.").format(
device=removed_device,
device_bay=device_bay
)
)
return_url = self.get_return_url(request, device_bay.device)
return redirect(return_url)
@@ -3426,7 +3438,7 @@ class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMix
membership_form.save()
messages.success(request, mark_safe(
f'Added member <a href="{device.get_absolute_url()}">{escape(device)}</a>'
_('Added member <a href="{url}">{escape(device)}</a>').format(url=device.get_absolute_url())
))
if '_addanother' in request.POST:
@@ -3471,7 +3483,10 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
# Protect master device from being removed
virtual_chassis = VirtualChassis.objects.filter(master=device).first()
if virtual_chassis is not None:
messages.error(request, f'Unable to remove master device {device} from the virtual chassis.')
messages.error(
request,
_('Unable to remove master device {device} from the virtual chassis.').format(device=device)
)
return redirect(device.get_absolute_url())
if form.is_valid():
@@ -3483,7 +3498,10 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
device.vc_priority = None
device.save()
msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
msg = _('Removed {device} from virtual chassis {chassis}').format(
device=device,
chassis=device.virtual_chassis
)
messages.success(request, msg)
return redirect(self.get_return_url(request, device))

View File

@@ -352,13 +352,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
if not self.related_object_type:
raise ValidationError({
'object_type': _("Object fields must define an object type.")
'related_object_type': _("Object fields must define an object type.")
})
elif self.related_object_type:
raise ValidationError({
'object_type': _(
"{type} fields may not define an object type.")
.format(type=self.get_type_display())
'type': _("{type} fields may not define an object type.") .format(type=self.get_type_display())
})
def serialize(self, value):

View File

@@ -221,6 +221,19 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
'group_id': '$site_group',
}
)
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label=_('VLAN Group')
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
label=_('VLAN'),
query_params={
'group_id': '$vlan_group',
}
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
@@ -269,9 +282,10 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
FieldSet('tenant', 'status', 'role', 'description'),
FieldSet('region', 'site_group', 'site', name=_('Site')),
FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
FieldSet('vlan_group', 'vlan', name=_('VLAN Assignment')),
)
nullable_fields = (
'site', 'vrf', 'tenant', 'role', 'description', 'comments',
'site', 'vlan', 'vrf', 'tenant', 'role', 'description', 'comments',
)

View File

@@ -25,7 +25,7 @@ from utilities.string import trailing_slash
# Environment setup
#
VERSION = '4.0.8'
VERSION = '4.0.9'
HOSTNAME = platform.node()
# Set the base directory two levels up
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

View File

@@ -6,6 +6,7 @@ from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import RelatedField
from django.db.models.fields.reverse_related import ManyToOneRel
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.safestring import mark_safe
@@ -102,7 +103,7 @@ class BaseTable(tables.Table):
field = model._meta.get_field(field_name)
except FieldDoesNotExist:
break
if isinstance(field, RelatedField):
if isinstance(field, (RelatedField, ManyToOneRel)):
# Follow ForeignKeys to the related model
prefetch_path.append(field_name)
model = field.remote_field.model

View File

@@ -2,6 +2,7 @@ from django.test import override_settings
from core.models import ObjectType
from dcim.models import *
from extras.models import CustomField
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
from users.models import ObjectPermission
from utilities.testing import ModelViewTestCase, create_tags
@@ -116,3 +117,28 @@ class CSVImportTestCase(ModelViewTestCase):
# Test POST with permission
self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
self.assertEqual(Region.objects.count(), 0)
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_custom_field_defaults(self):
self.add_permissions('dcim.add_region')
csv_data = [
'name,slug,description',
'Region 1,region-1,abc',
]
data = {
'format': ImportFormatChoices.CSV,
'data': self._get_csv_data(csv_data),
'csv_delimiter': CSVDelimiterChoices.AUTO,
}
cf = CustomField.objects.create(
name='tcf',
type='text',
required=False,
default='def-cf-text'
)
cf.object_types.set([ObjectType.objects.get_for_model(self.model)])
self.assertHttpStatus(self.client.post(self._get_url('import'), data), 302)
region = Region.objects.get(slug='region-1')
self.assertEqual(region.cf['tcf'], 'def-cf-text')

View File

@@ -4,6 +4,7 @@ from copy import deepcopy
from django.contrib import messages
from django.contrib.contenttypes.fields import GenericRel
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
from django.db import transaction, IntegrityError
from django.db.models import ManyToManyField, ProtectedError, RestrictedError
@@ -17,7 +18,8 @@ from django.utils.translation import gettext as _
from django_tables2.export import TableExport
from core.models import ObjectType
from extras.models import ExportTemplate
from extras.choices import CustomFieldUIEditableChoices
from extras.models import CustomField, ExportTemplate
from extras.signals import clear_events
from utilities.error_handlers import handle_protectederror
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
@@ -106,7 +108,13 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
try:
return template.render_to_response(self.queryset)
except Exception as e:
messages.error(request, f"There was an error rendering the selected export template ({template.name}): {e}")
messages.error(
request,
_("There was an error rendering the selected export template ({template}): {error}").format(
template=template.name,
error=e
)
)
# Strip the `export` param and redirect user to the filtered objects list
query_params = request.GET.copy()
query_params.pop('export')
@@ -409,6 +417,17 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
if instance.pk and hasattr(instance, 'snapshot'):
instance.snapshot()
else:
# For newly created objects, apply any default custom field values
custom_fields = CustomField.objects.filter(
object_types=ContentType.objects.get_for_model(self.queryset.model),
ui_editable=CustomFieldUIEditableChoices.YES
)
for cf in custom_fields:
field_name = f'cf_{cf.name}'
if field_name not in record:
record[field_name] = cf.default
# Instantiate the model form for the object
model_form_kwargs = {
'data': record,
@@ -668,7 +687,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
# Retrieve objects being edited
table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
if not table.rows:
messages.warning(request, "No {} were selected.".format(model._meta.verbose_name_plural))
messages.warning(
request,
_("No {object_type} were selected.").format(object_type=model._meta.verbose_name_plural)
)
return redirect(self.get_return_url(request))
return render(request, self.template_name, {
@@ -745,8 +767,13 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
if self.queryset.filter(pk__in=renamed_pks).count() != len(selected_objects):
raise PermissionsViolation
model_name = self.queryset.model._meta.verbose_name_plural
messages.success(request, f"Renamed {len(selected_objects)} {model_name}")
messages.success(
request,
_("Renamed {count} {object_type}").format(
count=len(selected_objects),
object_type=self.queryset.model._meta.verbose_name_plural
)
)
return redirect(self.get_return_url(request))
except (AbortRequest, PermissionsViolation) as e:
@@ -838,7 +865,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
messages.error(request, mark_safe(e.message))
return redirect(self.get_return_url(request))
msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
msg = _("Deleted {count} {object_type}").format(
count=deleted_count,
object_type=model._meta.verbose_name_plural
)
logger.info(msg)
messages.success(request, msg)
return redirect(self.get_return_url(request))
@@ -855,7 +885,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
# Retrieve objects being deleted
table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
if not table.rows:
messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
messages.warning(
request,
_("No {object_type} were selected.").format(object_type=model._meta.verbose_name_plural)
)
return redirect(self.get_return_url(request))
return render(request, self.template_name, {
@@ -900,7 +933,10 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
selected_objects = self.parent_model.objects.filter(pk__in=pk_list)
if not selected_objects:
messages.warning(request, "No {} were selected.".format(self.parent_model._meta.verbose_name_plural))
messages.warning(
request,
_("No {object_type} were selected.").format(object_type=self.parent_model._meta.verbose_name_plural)
)
return redirect(self.get_return_url(request))
table = self.table(selected_objects, orderable=False)

View File

@@ -202,11 +202,14 @@ class ObjectSyncDataView(View):
obj = get_object_or_404(qs, **kwargs)
if not obj.data_file:
messages.error(request, f"Unable to synchronize data: No data file set.")
messages.error(request, _("Unable to synchronize data: No data file set."))
return redirect(obj.get_absolute_url())
obj.sync(save=True)
messages.success(request, f"Synchronized data for {model._meta.verbose_name} {obj}.")
messages.success(request, _("Synchronized data for {object_type} {object}.").format(
object_type=model._meta.verbose_name,
object=obj
))
return redirect(obj.get_absolute_url())
@@ -228,7 +231,9 @@ class BulkSyncDataView(GetReturnURLMixin, BaseMultiObjectView):
for obj in selected_objects:
obj.sync(save=True)
model_name = self.queryset.model._meta.verbose_name_plural
messages.success(request, f"Synced {len(selected_objects)} {model_name}")
messages.success(request, _("Synced {count} {object_type}").format(
count=len(selected_objects),
object_type=self.queryset.model._meta.verbose_name_plural
))
return redirect(self.get_return_url(request))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,9 @@ export function initMessages(): void {
for (const element of elements) {
if (element !== null) {
const toast = new Toast(element);
toast.show();
if (!toast.isShown()) {
toast.show();
}
}
}
}

View File

@@ -44,3 +44,7 @@ table a {
[data-bs-theme=dark] ::selection {
background-color: rgba(var(--tblr-primary-rgb),.48)
}
pre code {
padding: unset;
}

View File

@@ -867,13 +867,20 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.2, braces@~3.0.2:
braces@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
braces@~3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
@@ -1520,10 +1527,10 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
fill-range@^7.0.1, fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
@@ -1816,9 +1823,9 @@ ignore@^5.2.0:
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
immutable@^4.0.0:
version "4.3.6"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447"
integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
import-fresh@^3.2.1:
version "3.3.0"

View File

@@ -24,7 +24,12 @@
</div>
{% endblock page-header %}
{% block title %}{{ status|capfirst }} {% trans "Workers in " %}{{ queue.name }}{% endblock %}
{% block title %}
{{ status|capfirst }}
{% blocktrans trimmed with queue_name=queue.name %}
Workers in {{ queue_name }}
{% endblocktrans %}
{% endblock %}
{% block controls %}{% endblock %}

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django_filters.constants import EMPTY_VALUES
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
__all__ = (
'ContentTypeFilter',
@@ -116,6 +116,7 @@ class MultiValueWWNFilter(django_filters.MultipleChoiceFilter):
field_class = multivalue_field_factory(forms.CharField)
@extend_schema_field(OpenApiTypes.STR)
class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
"""
Filters for a set of Models, including all descendant models within a Tree. Example: [<Region: R1>,<Region: R2>]

View File

@@ -271,8 +271,9 @@ class ClusterAddDevicesView(generic.ObjectEditView):
device.cluster = cluster
device.save()
messages.success(request, "Added {} devices to cluster {}".format(
len(device_pks), cluster
messages.success(request, _("Added {count} devices to cluster {cluster}").format(
count=len(device_pks),
cluster=cluster
))
return redirect(cluster.get_absolute_url())
@@ -305,8 +306,9 @@ class ClusterRemoveDevicesView(generic.ObjectEditView):
device.cluster = None
device.save()
messages.success(request, "Removed {} devices from cluster {}".format(
len(device_pks), cluster
messages.success(request, _("Removed {count} devices from cluster {cluster}").format(
count=len(device_pks),
cluster=cluster
))
return redirect(cluster.get_absolute_url())
@@ -444,7 +446,7 @@ class VirtualMachineRenderConfigView(generic.ObjectView):
try:
rendered_config = config_template.render(context=context_data)
except TemplateError as e:
messages.error(request, f"An error occurred while rendering the template: {e}")
messages.error(request, _("An error occurred while rendering the template: {error}").format(error=e))
rendered_config = traceback.format_exc()
return {

View File

@@ -1,14 +1,14 @@
Django==5.0.7
Django==5.0.8
django-cors-headers==4.4.0
django-debug-toolbar==4.3.0
django-debug-toolbar==4.4.6
django-filter==24.2
django-htmx==1.18.0
django-htmx==1.19.0
django-graphiql-debug-toolbar==0.2.0
django-mptt==0.16.0
django-pglocks==1.0.4
django-prometheus==2.3.1
django-redis==5.4.0
django-rich==1.9.0
django-rich==1.10.0
django-rq==2.10.2
django-taggit==5.0.1
django-tables2==2.7.0
@@ -17,7 +17,7 @@ djangorestframework==3.15.2
drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.7.1
feedparser==6.0.11
gunicorn==22.0.0
gunicorn==23.0.0
Jinja2==3.1.4
Markdown==3.6
mkdocs-material==9.5.30
@@ -26,7 +26,7 @@ netaddr==1.3.0
nh3==0.2.18
Pillow==10.4.0
psycopg[c,pool]==3.2.1
PyYAML==6.0.1
PyYAML==6.0.2
requests==2.32.3
social-auth-app-django==5.4.2
social-auth-core==4.5.4