Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions
da1e0f4b53 Update source translation strings
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
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
2025-12-04 05:02:04 +00:00
Arthur Hanson
7f39f75d3d Fixes #20878: Use database routing when running script (#20879) 2025-12-03 17:47:31 -06:00
3 changed files with 31 additions and 33 deletions

View File

@@ -472,30 +472,14 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
required=False,
help_text=_('Unit for module weight')
)
attribute_data = forms.JSONField(
label=_('Attributes'),
required=False,
help_text=_('Attribute values for the assigned profile, passed as a dictionary')
)
class Meta:
model = ModuleType
fields = [
'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'profile',
'attribute_data', 'comments', 'tags',
'comments', 'tags'
]
def clean(self):
super().clean()
# Attribute data may be included only if a profile is specified
if self.cleaned_data.get('attribute_data') and not self.cleaned_data.get('profile'):
raise forms.ValidationError(_("Profile must be specified if attribute data is provided."))
# Default attribute_data to an empty dictionary if a profile is specified (to enforce schema validation)
if self.cleaned_data.get('profile') and not self.cleaned_data.get('attribute_data'):
self.cleaned_data['attribute_data'] = {}
class DeviceRoleImportForm(NetBoxModelImportForm):
parent = CSVModelChoiceField(

View File

@@ -2,11 +2,14 @@ import logging
import traceback
from contextlib import ExitStack
from django.db import transaction
from django.db import router, transaction
from django.db import DEFAULT_DB_ALIAS
from django.utils.translation import gettext as _
from core.signals import clear_events
from dcim.models import Device
from extras.models import Script as ScriptModel
from netbox.context_managers import event_tracking
from netbox.jobs import JobRunner
from netbox.registry import registry
from utilities.exceptions import AbortScript, AbortTransaction
@@ -42,10 +45,21 @@ class ScriptJob(JobRunner):
# A script can modify multiple models so need to do an atomic lock on
# both the default database (for non ChangeLogged models) and potentially
# any other database (for ChangeLogged models)
with transaction.atomic():
script.output = script.run(data, commit)
if not commit:
raise AbortTransaction()
changeloged_db = router.db_for_write(Device)
with transaction.atomic(using=DEFAULT_DB_ALIAS):
# If branch database is different from default, wrap in a second atomic transaction
# Note: Don't add any extra code between the two atomic transactions,
# otherwise the changes might get committed to the default database
# if there are any raised exceptions.
if changeloged_db != DEFAULT_DB_ALIAS:
with transaction.atomic(using=changeloged_db):
script.output = script.run(data, commit)
if not commit:
raise AbortTransaction()
else:
script.output = script.run(data, commit)
if not commit:
raise AbortTransaction()
except AbortTransaction:
script.log_info(message=_("Database changes have been reverted automatically."))
if script.failed:
@@ -108,14 +122,14 @@ class ScriptJob(JobRunner):
script.request = request
self.logger.debug(f"Request ID: {request.id if request else None}")
# Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
# change logging, event rules, etc.
if commit:
self.logger.info("Executing script (commit enabled)")
with ExitStack() as stack:
for request_processor in registry['request_processors']:
stack.enter_context(request_processor(request))
self.run_script(script, request, data, commit)
else:
self.logger.warning("Executing script (commit disabled)")
with ExitStack() as stack:
for request_processor in registry['request_processors']:
if not commit and request_processor is event_tracking:
continue
stack.enter_context(request_processor(request))
self.run_script(script, request, data, commit)

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-02 05:02+0000\n"
"POT-Creation-Date: 2025-12-04 05:01+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"
@@ -8985,19 +8985,19 @@ msgstr ""
msgid "Interval at which this script is re-run (in minutes)"
msgstr ""
#: netbox/extras/jobs.py:50
#: netbox/extras/jobs.py:64
msgid "Database changes have been reverted automatically."
msgstr ""
#: netbox/extras/jobs.py:56
#: netbox/extras/jobs.py:70
msgid "Script aborted with error: "
msgstr ""
#: netbox/extras/jobs.py:67
#: netbox/extras/jobs.py:81
msgid "An exception occurred: "
msgstr ""
#: netbox/extras/jobs.py:73
#: netbox/extras/jobs.py:87
msgid "Database changes have been reverted due to error."
msgstr ""