Compare commits

...

8 Commits
v4.3.3 ... main

Author SHA1 Message Date
Olexandr88
9c2cd66162 Update README.md
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled
2025-07-09 10:53:40 -04:00
github-actions
f61a2964c8 Update source translation strings 2025-07-09 05:04:52 +00:00
Jason Novinger
ee94fb0b94
Closes #19550: Enhancement: Refactor rack elevations template for lazy loading /dcim/rack-elevations/ (#19823)
* Refactor rack elevation template to use htmx for dynamic loading and improved user experience

* rework to prevent dup loading

* Update netbox/templates/dcim/inc/rack_elevation.html

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/templates/dcim/inc/rack_elevation.html

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Move inline styles to styles/custom/racks.css

---------

Co-authored-by: tony.nealon@wholesailnetworks.com <tony.nealon@wholesailnetworks.com>
Co-authored-by: tbotnz <tonynealon1989@gmail.com>
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2025-07-08 11:20:04 -04:00
Harry
8fb8f4c75b
Closes #19571: Create expansion_card.json (#19689)
* Create expansion_card.json

* Update 0206_load_module_type_profiles.py

* Update expansion_card.json

Fixed
2025-07-08 08:27:48 -05:00
github-actions
e33793dc82 Update source translation strings 2025-07-03 05:04:46 +00:00
Jeremy Stretch
3b8841ee3b
Fixes #19806: Introduce JobFailed exception to allow marking background jobs as failed (#19807) 2025-07-02 14:02:49 -05:00
dieck
ea4c205a37 Upgrade documentation: have git fetch new tags
fixes #19778
2025-07-02 13:59:56 -04:00
github-actions
2a5d3abafb Update source translation strings 2025-06-27 05:03:03 +00:00
15 changed files with 133 additions and 61 deletions

View File

@ -6,7 +6,7 @@
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://img.shields.io/github/contributors/netbox-community/netbox?color=blue" alt="Contributors" /></a>
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-15-blue" alt="Languages supported" /></a>
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=main" alt="CI status" /></a>
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/actions/workflows/ci.yml/badge.svg" alt="CI status" /></a>
<p>
<strong><a href="https://netboxlabs.com/community/">NetBox Community</a></strong> |
<strong><a href="https://netboxlabs.com/netbox-cloud/">NetBox Cloud</a></strong> |

View File

@ -2,9 +2,9 @@
NetBox includes the ability to execute certain functions as background tasks. These include:
* [Report](../customization/reports.md) execution
* [Custom script](../customization/custom-scripts.md) execution
* Synchronization of [remote data sources](../integrations/synchronized-data.md)
* Housekeeping tasks
Additionally, NetBox plugins can enqueue their own background tasks. This is accomplished using the [Job model](../models/core/job.md). Background tasks are executed by the `rqworker` process(es).

View File

@ -135,7 +135,7 @@ Check out the desired release by specifying its tag. For example:
```
cd /opt/netbox && \
sudo git fetch && \
sudo git fetch --tags && \
sudo git checkout v4.2.7
```

View File

@ -15,7 +15,6 @@ A background job implements a basic [Job](../../models/core/job.md) executor for
```python title="jobs.py"
from netbox.jobs import JobRunner
class MyTestJob(JobRunner):
class Meta:
name = "My Test Job"
@ -25,6 +24,8 @@ class MyTestJob(JobRunner):
# your logic goes here
```
Completed jobs will have their status updated to "completed" by default, or "errored" if an unhandled exception was raised by the `run()` method. To intentionally mark a job as failed, raise the `core.exceptions.JobFailed` exception. (Note that "failed" differs from "errored" in that a failure may be expected under certain conditions, whereas an error is not.)
You can schedule the background job from within your code (e.g. from a model's `save()` method or a view) by calling `MyTestJob.enqueue()`. This method passes through all arguments to `Job.enqueue()`. However, no `name` argument must be passed, as the background job name will be used instead.
!!! tip

View File

@ -1,9 +1,19 @@
from django.core.exceptions import ImproperlyConfigured
class SyncError(Exception):
pass
__all__ = (
'IncompatiblePluginError',
'JobFailed',
'SyncError',
)
class IncompatiblePluginError(ImproperlyConfigured):
pass
class JobFailed(Exception):
pass
class SyncError(Exception):
pass

View File

@ -187,15 +187,14 @@ class Job(models.Model):
"""
Mark the job as completed, optionally specifying a particular termination status.
"""
valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
if status not in valid_statuses:
if status not in JobStatusChoices.TERMINAL_STATE_CHOICES:
raise ValueError(
_("Invalid status for job termination. Choices are: {choices}").format(
choices=', '.join(valid_statuses)
choices=', '.join(JobStatusChoices.TERMINAL_STATE_CHOICES)
)
)
# Mark the job as completed
# Set the job's status and completion time
self.status = status
if error:
self.error = error

View File

@ -19,7 +19,8 @@ def load_initial_data(apps, schema_editor):
'gpu',
'hard_disk',
'memory',
'power_supply'
'power_supply',
'expansion_card'
)
for name in initial_profiles:

View File

@ -0,0 +1,15 @@
{
"name": "Expansion card",
"schema": {
"properties": {
"connector_type": {
"type": "string",
"description": "Connector type e.g. PCIe x4"
},
"bandwidth": {
"type": "integer",
"description": "Total Bandwidth for this module"
}
}
}
}

View File

@ -8,6 +8,7 @@ from django_pglocks import advisory_lock
from rq.timeouts import JobTimeoutException
from core.choices import JobStatusChoices
from core.exceptions import JobFailed
from core.models import Job, ObjectType
from netbox.constants import ADVISORY_LOCK_KEYS
from netbox.registry import registry
@ -73,15 +74,21 @@ class JobRunner(ABC):
This method is called by the Job Scheduler to handle the execution of all job commands. It will maintain the
job's metadata and handle errors. For periodic jobs, a new job is automatically scheduled using its `interval`.
"""
logger = logging.getLogger('netbox.jobs')
try:
job.start()
cls(job).run(*args, **kwargs)
job.terminate()
except JobFailed:
logger.warning(f"Job {job} failed")
job.terminate(status=JobStatusChoices.STATUS_FAILED)
except Exception as e:
job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))
if type(e) is JobTimeoutException:
logging.error(e)
logger.error(e)
# If the executed job is a periodic job, schedule its next execution at the specified interval.
finally:

View File

@ -7,11 +7,15 @@ from django_rq import get_queue
from ..jobs import *
from core.models import DataSource, Job
from core.choices import JobStatusChoices
from core.exceptions import JobFailed
from utilities.testing import disable_warnings
class TestJobRunner(JobRunner):
def run(self, *args, **kwargs):
pass
if kwargs.get('make_fail', False):
raise JobFailed()
class JobRunnerTestCase(TestCase):
@ -49,6 +53,12 @@ class JobRunnerTest(JobRunnerTestCase):
self.assertEqual(job.status, JobStatusChoices.STATUS_COMPLETED)
def test_handle_failed(self):
with disable_warnings('netbox.jobs'):
job = TestJobRunner.enqueue(immediate=True, make_fail=True)
self.assertEqual(job.status, JobStatusChoices.STATUS_FAILED)
def test_handle_errored(self):
class ErroredJobRunner(TestJobRunner):
EXP = Exception('Test error')

Binary file not shown.

View File

@ -0,0 +1,4 @@
.rack-loading-container {
min-height: 200px;
margin-left: 30px;
}

View File

@ -27,3 +27,4 @@
@import 'custom/markdown';
@import 'custom/misc';
@import 'custom/notifications';
@import 'custom/racks';

View File

@ -1,6 +1,17 @@
{% load i18n %}
<div style="margin-left: -30px">
<object data="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" class="rack_elevation" aria-label="{% trans "Rack elevation" %}"></object>
<div
hx-get="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{ face }}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}"
hx-trigger="intersect"
hx-swap="outerHTML"
aria-label="{% trans "Rack elevation" %}"
>
<div class="d-flex justify-content-center align-items-center rack-loading-container">
<div class="spinner-border" role="status">
<span class="visually-hidden">{% trans "Loading..." %}</span>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
<a class="btn btn-outline-primary" href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" hx-boost="false">

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-26 05:02+0000\n"
"POT-Creation-Date: 2025-07-09 05:04+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,7 +20,7 @@ msgstr ""
#: netbox/account/tables.py:27 netbox/templates/account/token.html:22
#: netbox/templates/users/token.html:17 netbox/users/forms/bulk_import.py:39
#: netbox/users/forms/model_forms.py:112
#: netbox/users/forms/model_forms.py:113
msgid "Key"
msgstr ""
@ -57,7 +57,7 @@ msgstr ""
#: netbox/account/tables.py:45 netbox/templates/account/token.html:55
#: netbox/templates/users/token.html:47 netbox/users/forms/bulk_edit.py:122
#: netbox/users/forms/model_forms.py:124
#: netbox/users/forms/model_forms.py:125
msgid "Allowed IPs"
msgstr ""
@ -705,7 +705,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:852 netbox/dcim/tables/power.py:77
#: netbox/dcim/tables/racks.py:141 netbox/extras/forms/bulk_import.py:42
#: netbox/extras/tables/tables.py:449 netbox/extras/tables/tables.py:509
#: netbox/netbox/tables/tables.py:272 netbox/templates/circuits/circuit.html:30
#: netbox/netbox/tables/tables.py:274 netbox/templates/circuits/circuit.html:30
#: netbox/templates/circuits/virtualcircuit.html:39
#: netbox/templates/circuits/virtualcircuittermination.html:64
#: netbox/templates/core/datasource.html:38 netbox/templates/dcim/cable.html:15
@ -804,7 +804,7 @@ msgstr ""
#: netbox/templates/vpn/l2vpn.html:26 netbox/templates/vpn/tunnel.html:25
#: netbox/templates/wireless/wirelesslan.html:22
#: netbox/templates/wireless/wirelesslink.html:17
#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:194
#: netbox/users/forms/filtersets.py:32 netbox/users/forms/model_forms.py:195
#: netbox/virtualization/forms/bulk_edit.py:71
#: netbox/virtualization/forms/bulk_edit.py:100
#: netbox/virtualization/forms/bulk_import.py:55
@ -972,7 +972,7 @@ msgstr ""
#: netbox/ipam/forms/filtersets.py:406 netbox/ipam/forms/filtersets.py:492
#: netbox/ipam/forms/filtersets.py:505 netbox/ipam/forms/filtersets.py:530
#: netbox/ipam/forms/filtersets.py:601 netbox/ipam/forms/filtersets.py:619
#: netbox/netbox/tables/tables.py:288 netbox/templates/dcim/moduletype.html:68
#: netbox/netbox/tables/tables.py:290 netbox/templates/dcim/moduletype.html:68
#: netbox/virtualization/forms/filtersets.py:46
#: netbox/virtualization/forms/filtersets.py:109
#: netbox/virtualization/forms/filtersets.py:204
@ -1369,7 +1369,7 @@ msgstr ""
#: netbox/templates/extras/configcontext.html:60
#: netbox/templates/ipam/ipaddress.html:59
#: netbox/templates/ipam/vlan_edit.html:42
#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:314
#: netbox/tenancy/forms/filtersets.py:87 netbox/users/forms/model_forms.py:315
msgid "Assignment"
msgstr ""
@ -2179,7 +2179,7 @@ msgstr ""
#: netbox/core/data_backends.py:56 netbox/templates/account/base.html:23
#: netbox/templates/account/password.html:12
#: netbox/users/forms/model_forms.py:170
#: netbox/users/forms/model_forms.py:171
msgid "Password"
msgstr ""
@ -2231,7 +2231,7 @@ msgstr ""
#: netbox/extras/forms/filtersets.py:335 netbox/extras/tables/tables.py:166
#: netbox/extras/tables/tables.py:267 netbox/extras/tables/tables.py:300
#: netbox/extras/tables/tables.py:459 netbox/netbox/preferences.py:22
#: netbox/templates/core/datasource.html:42
#: netbox/netbox/preferences.py:61 netbox/templates/core/datasource.html:42
#: netbox/templates/dcim/interface.html:61
#: netbox/templates/extras/customlink.html:17
#: netbox/templates/extras/eventrule.html:17
@ -2346,7 +2346,7 @@ msgstr ""
#: netbox/templates/users/user.html:4 netbox/templates/users/user.html:12
#: netbox/users/filtersets.py:107 netbox/users/filtersets.py:174
#: netbox/users/forms/filtersets.py:84 netbox/users/forms/filtersets.py:125
#: netbox/users/forms/model_forms.py:155 netbox/users/forms/model_forms.py:192
#: netbox/users/forms/model_forms.py:156 netbox/users/forms/model_forms.py:193
#: netbox/users/tables.py:19
msgid "User"
msgstr ""
@ -2449,7 +2449,7 @@ msgstr ""
#: netbox/core/forms/model_forms.py:170 netbox/dcim/forms/filtersets.py:752
#: netbox/templates/core/inc/config_data.html:127
#: netbox/users/forms/model_forms.py:64
#: netbox/users/forms/model_forms.py:65
msgid "Miscellaneous"
msgstr ""
@ -2738,12 +2738,12 @@ msgstr ""
msgid "Jobs cannot be assigned to this object type ({type})."
msgstr ""
#: netbox/core/models/jobs.py:193
#: netbox/core/models/jobs.py:192
#, python-brace-format
msgid "Invalid status for job termination. Choices are: {choices}"
msgstr ""
#: netbox/core/models/jobs.py:235
#: netbox/core/models/jobs.py:234
msgid ""
"enqueue() cannot be called with values for both schedule_at and immediate."
msgstr ""
@ -2763,7 +2763,7 @@ msgstr ""
#: netbox/extras/tables/tables.py:341 netbox/extras/tables/tables.py:373
#: netbox/extras/tables/tables.py:453 netbox/extras/tables/tables.py:514
#: netbox/extras/tables/tables.py:637 netbox/extras/tables/tables.py:677
#: netbox/extras/tables/tables.py:731 netbox/netbox/tables/tables.py:276
#: netbox/extras/tables/tables.py:731 netbox/netbox/tables/tables.py:278
#: netbox/templates/core/objectchange.html:58
#: netbox/templates/extras/eventrule.html:78
#: netbox/templates/extras/journalentry.html:18
@ -2801,7 +2801,7 @@ msgstr ""
#: netbox/core/tables/jobs.py:10 netbox/core/tables/tasks.py:76
#: netbox/dcim/tables/devicetypes.py:169 netbox/extras/tables/tables.py:230
#: netbox/extras/tables/tables.py:504 netbox/extras/tables/tables.py:702
#: netbox/netbox/tables/tables.py:221
#: netbox/netbox/tables/tables.py:223
#: netbox/templates/dcim/virtualchassis_edit.html:56
#: netbox/utilities/forms/forms.py:73 netbox/wireless/tables/wirelesslink.py:16
msgid "ID"
@ -3381,8 +3381,9 @@ msgid "Three-phase"
msgstr ""
#: netbox/dcim/choices.py:1657 netbox/extras/choices.py:53
#: netbox/netbox/preferences.py:21 netbox/templates/extras/customfield.html:78
#: netbox/vpn/choices.py:20 netbox/wireless/choices.py:27
#: netbox/netbox/preferences.py:21 netbox/netbox/preferences.py:60
#: netbox/templates/extras/customfield.html:78 netbox/vpn/choices.py:20
#: netbox/wireless/choices.py:27
msgid "Disabled"
msgstr ""
@ -8201,7 +8202,7 @@ msgstr ""
#: netbox/extras/forms/model_forms.py:254
#: netbox/extras/forms/model_forms.py:297
#: netbox/extras/forms/model_forms.py:450
#: netbox/extras/forms/model_forms.py:567 netbox/users/forms/model_forms.py:276
#: netbox/extras/forms/model_forms.py:567 netbox/users/forms/model_forms.py:277
msgid "Object types"
msgstr ""
@ -8297,8 +8298,8 @@ msgstr ""
#: netbox/extras/forms/bulk_import.py:275
#: netbox/extras/forms/model_forms.py:398 netbox/netbox/navigation/menu.py:413
#: netbox/templates/extras/notificationgroup.html:41
#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:236
#: netbox/users/forms/model_forms.py:248 netbox/users/forms/model_forms.py:300
#: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:237
#: netbox/users/forms/model_forms.py:249 netbox/users/forms/model_forms.py:301
#: netbox/users/tables.py:102
msgid "Users"
msgstr ""
@ -8314,8 +8315,8 @@ msgstr ""
#: netbox/templates/tenancy/contact.html:21
#: netbox/tenancy/forms/bulk_edit.py:139 netbox/tenancy/forms/filtersets.py:78
#: netbox/tenancy/forms/model_forms.py:99 netbox/tenancy/tables/contacts.py:64
#: netbox/users/forms/model_forms.py:181 netbox/users/forms/model_forms.py:193
#: netbox/users/forms/model_forms.py:305 netbox/users/tables.py:35
#: netbox/users/forms/model_forms.py:182 netbox/users/forms/model_forms.py:194
#: netbox/users/forms/model_forms.py:306 netbox/users/tables.py:35
#: netbox/users/tables.py:106
msgid "Groups"
msgstr ""
@ -10212,7 +10213,7 @@ msgid ""
"One of parent or parent_object_id must be included with parent_object_type"
msgstr ""
#: netbox/ipam/forms/bulk_import.py:638
#: netbox/ipam/forms/bulk_import.py:641
#, python-brace-format
msgid "{ip} is not assigned to this parent."
msgstr ""
@ -11834,9 +11835,9 @@ msgstr ""
msgid "API Tokens"
msgstr ""
#: netbox/netbox/navigation/menu.py:460 netbox/users/forms/model_forms.py:187
#: netbox/users/forms/model_forms.py:195 netbox/users/forms/model_forms.py:242
#: netbox/users/forms/model_forms.py:249
#: netbox/netbox/navigation/menu.py:460 netbox/users/forms/model_forms.py:188
#: netbox/users/forms/model_forms.py:196 netbox/users/forms/model_forms.py:243
#: netbox/users/forms/model_forms.py:250
msgid "Permissions"
msgstr ""
@ -11959,11 +11960,19 @@ msgstr ""
msgid "Where the paginator controls will be displayed relative to a table"
msgstr ""
#: netbox/netbox/preferences.py:60
#: netbox/netbox/preferences.py:58
msgid "Striped table rows"
msgstr ""
#: netbox/netbox/preferences.py:63
msgid "Render table rows with alternating colors to increase readability"
msgstr ""
#: netbox/netbox/preferences.py:68
msgid "Data format"
msgstr ""
#: netbox/netbox/preferences.py:65
#: netbox/netbox/preferences.py:73
msgid "The preferred syntax for displaying generic data within the UI"
msgstr ""
@ -12062,12 +12071,12 @@ msgstr ""
msgid "No {model_name} found"
msgstr ""
#: netbox/netbox/tables/tables.py:281
#: netbox/netbox/tables/tables.py:283
#: netbox/templates/generic/bulk_import.html:117
msgid "Field"
msgstr ""
#: netbox/netbox/tables/tables.py:284
#: netbox/netbox/tables/tables.py:286
msgid "Value"
msgstr ""
@ -13012,7 +13021,7 @@ msgid "Cable Trace for %(object_type)s %(object)s"
msgstr ""
#: netbox/templates/dcim/cable_trace.html:24
#: netbox/templates/dcim/inc/rack_elevation.html:7
#: netbox/templates/dcim/inc/rack_elevation.html:18
msgid "Download SVG"
msgstr ""
@ -13415,10 +13424,14 @@ msgstr ""
msgid "Descending Units"
msgstr ""
#: netbox/templates/dcim/inc/rack_elevation.html:3
#: netbox/templates/dcim/inc/rack_elevation.html:7
msgid "Rack elevation"
msgstr ""
#: netbox/templates/dcim/inc/rack_elevation.html:11
msgid "Loading..."
msgstr ""
#: netbox/templates/dcim/interface.html:17
msgid "Add Child Interface"
msgstr ""
@ -13716,7 +13729,7 @@ msgstr ""
#: netbox/templates/dcim/virtualchassis_add_member.html:27
#: netbox/templates/generic/object_edit.html:78
#: netbox/templates/users/objectpermission.html:31
#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:312
#: netbox/users/forms/filtersets.py:67 netbox/users/forms/model_forms.py:313
msgid "Actions"
msgstr ""
@ -14874,7 +14887,7 @@ msgid "View"
msgstr ""
#: netbox/templates/users/objectpermission.html:52
#: netbox/users/forms/model_forms.py:315
#: netbox/users/forms/model_forms.py:316
msgid "Constraints"
msgstr ""
@ -15358,60 +15371,60 @@ msgstr ""
msgid "Can Delete"
msgstr ""
#: netbox/users/forms/model_forms.py:62
#: netbox/users/forms/model_forms.py:63
msgid "User Interface"
msgstr ""
#: netbox/users/forms/model_forms.py:114
#: netbox/users/forms/model_forms.py:115
msgid ""
"Keys must be at least 40 characters in length. <strong>Be sure to record "
"your key</strong> prior to submitting this form, as it may no longer be "
"accessible once the token has been created."
msgstr ""
#: netbox/users/forms/model_forms.py:126
#: netbox/users/forms/model_forms.py:127
msgid ""
"Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for "
"no restrictions. Example: <code>10.1.1.0/24,192.168.10.16/32,2001:"
"db8:1::/64</code>"
msgstr ""
#: netbox/users/forms/model_forms.py:175
#: netbox/users/forms/model_forms.py:176
msgid "Confirm password"
msgstr ""
#: netbox/users/forms/model_forms.py:178
#: netbox/users/forms/model_forms.py:179
msgid "Enter the same password as before, for verification."
msgstr ""
#: netbox/users/forms/model_forms.py:227
#: netbox/users/forms/model_forms.py:228
msgid "Passwords do not match! Please check your input and try again."
msgstr ""
#: netbox/users/forms/model_forms.py:294
#: netbox/users/forms/model_forms.py:295
msgid "Additional actions"
msgstr ""
#: netbox/users/forms/model_forms.py:297
#: netbox/users/forms/model_forms.py:298
msgid "Actions granted in addition to those listed above"
msgstr ""
#: netbox/users/forms/model_forms.py:313
#: netbox/users/forms/model_forms.py:314
msgid "Objects"
msgstr ""
#: netbox/users/forms/model_forms.py:325
#: netbox/users/forms/model_forms.py:326
msgid ""
"JSON expression of a queryset filter that will return only permitted "
"objects. Leave null to match all objects of this type. A list of multiple "
"objects will result in a logical OR operation."
msgstr ""
#: netbox/users/forms/model_forms.py:364
#: netbox/users/forms/model_forms.py:365
msgid "At least one action must be selected."
msgstr ""
#: netbox/users/forms/model_forms.py:382
#: netbox/users/forms/model_forms.py:383
#, python-brace-format
msgid "Invalid filter for {model}: {error}"
msgstr ""