Compare commits

...

25 Commits

Author SHA1 Message Date
ifoughali
cf16a29ad3 Style: removed comment 2025-12-05 15:24:35 +01:00
ifoughali
544c97d923 XMerge branch 'closes-20817-Fix-datasource-sync-broken-when-cron-is-set' of https://github.com/ifoughal/netbox into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-12-05 15:23:43 +01:00
ifoughali
77ee6baa23 refactor: moved status update logic from clean() to save() method 2025-12-05 15:23:38 +01:00
Idris Foughali
09d1049267 Merge branch 'netbox-community:main' into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-12-05 14:51:56 +01:00
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
github-actions
922b08c0ff Update source translation strings
Some checks failed
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-02 05:02:22 +00:00
Bapths
84864fa5e1 Closes #20860: Add changlog message support for component object creation (#20898)
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
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-12-01 17:04:21 -06:00
Jeremy Stretch
767dfccd8f Fixes #20888: Pass decimal values for min/max on latitude and longitude fields (#20892)
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
2025-12-01 10:35:44 -08:00
Idris Foughali
93e5f919ba Merge branch 'netbox-community:main' into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-12-01 10:07:15 +01:00
Tom Gamull
dc4bab7477 docs: fix broken bookmarks link in model features table
Some checks failed
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
The bookmarks link was pointing to ../features/customization.md#bookmarks
but the bookmarks section is actually in ../features/user-preferences.md#bookmarks.

This fixes the broken anchor link.
2025-11-26 15:12:52 -05:00
ifoughali
929d024003 Merge branch 'closes-20817-Fix-datasource-sync-broken-when-cron-is-set' of https://github.com/ifoughal/netbox into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-11-26 09:00:22 +01:00
ifoughali
e4b614038e revert: re-added queued status set for datasource object 2025-11-26 09:00:17 +01:00
Idris Foughali
3016b1d90b Merge branch 'netbox-community:main' into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-11-26 08:55:12 +01:00
ifoughali
57b47dc1ea style: use != instead of not in for single SYNCING check 2025-11-26 08:05:20 +01:00
github-actions
60aa952eb1 Update source translation strings
Some checks are pending
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run
2025-11-26 05:02:03 +00:00
ifoughali
da4c669312 Feat: reworked status update logic 2025-11-20 11:27:39 +01:00
ifoughali
71f707b7ac Feat: removed SCHEDULED choice due to redundency with sync interval 2025-11-20 11:26:43 +01:00
ifoughali
e11508dd6c Fix: removed status update from the enqueue method 2025-11-20 10:50:35 +01:00
ifoughali
5b5b5c8909 Revert "Feat: set status as editable field"
This reverts commit b4160ad59b.
2025-11-19 20:18:59 +01:00
Idris Foughali
a49869af42 Feat: removed QUEUED from ready for sync condition 2025-11-19 19:01:01 +00:00
Idris Foughali
2e0ff04f84 Feat: added 2 states for DataSourceStatusChoices 2025-11-19 18:52:27 +00:00
Idris Foughali
bfeba36514 Feat: added status update during save method of DataSourceForm 2025-11-19 18:51:25 +00:00
Idris Foughali
111aca115b Feat: added clean method to set data-source state to Ready or scheduled 2025-11-19 18:51:01 +00:00
Idris Foughali
b4160ad59b Feat: set status as editable field 2025-11-19 18:49:47 +00:00
10 changed files with 361 additions and 302 deletions

View File

@@ -12,7 +12,7 @@ Depending on its classification, each NetBox model may support various features
| Feature | Feature Mixin | Registry Key | Description |
|------------------------------------------------------------|-------------------------|---------------------|-----------------------------------------------------------------------------------------|
| [Bookmarks](../features/customization.md#bookmarks) | `BookmarksMixin` | `bookmarks` | These models can be bookmarked natively in the user interface |
| [Bookmarks](../features/user-preferences.md#bookmarks) | `BookmarksMixin` | `bookmarks` | These models can be bookmarked natively in the user interface |
| [Change logging](../features/change-logging.md) | `ChangeLoggingMixin` | `change_logging` | Changes to these objects are automatically recorded in the change log |
| Cloning | `CloningMixin` | `cloning` | Provides the `clone()` method to prepare a copy |
| [Contacts](../features/contacts.md) | `ContactsMixin` | `contacts` | Contacts can be associated with these models |

View File

@@ -13,6 +13,7 @@ class DataSourceStatusChoices(ChoiceSet):
SYNCING = 'syncing'
COMPLETED = 'completed'
FAILED = 'failed'
READY = 'ready'
CHOICES = (
(NEW, _('New'), 'blue'),
@@ -20,6 +21,7 @@ class DataSourceStatusChoices(ChoiceSet):
(SYNCING, _('Syncing'), 'cyan'),
(COMPLETED, _('Completed'), 'green'),
(FAILED, _('Failed'), 'red'),
(READY, _('Ready'), 'green'),
)

View File

@@ -16,6 +16,7 @@ from utilities.forms import get_field_value
from utilities.forms.fields import CommentField, JSONField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
from core.choices import DataSourceStatusChoices
__all__ = (
'ConfigRevisionForm',
@@ -79,14 +80,28 @@ class DataSourceForm(NetBoxModelForm):
if self.instance and self.instance.parameters:
self.fields[field_name].initial = self.instance.parameters.get(name)
def save(self, *args, **kwargs):
def save(self, *args, **kwargs):
parameters = {}
for name in self.fields:
if name.startswith('backend_'):
parameters[name[8:]] = self.cleaned_data[name]
self.instance.parameters = parameters
# Determine initial status based on new/existing instance
if not self.instance.pk:
# New instance
object_status = DataSourceStatusChoices.NEW
else:
# Existing instance
if not self.cleaned_data.get("sync_interval"):
object_status = DataSourceStatusChoices.READY
else:
object_status = self.instance.status
# # Final override only if the user explicitly provided a status
self.instance.status = object_status
return super().save(*args, **kwargs)

View File

@@ -111,10 +111,7 @@ class DataSource(JobsMixin, PrimaryModel):
@property
def ready_for_sync(self):
return self.enabled and self.status not in (
DataSourceStatusChoices.QUEUED,
DataSourceStatusChoices.SYNCING
)
return self.enabled and self.status != DataSourceStatusChoices.SYNCING
def clean(self):
super().clean()

View File

@@ -1,3 +1,5 @@
import decimal
import django.core.validators
from django.db import migrations, models
@@ -17,8 +19,8 @@ class Migration(migrations.Migration):
max_digits=8,
null=True,
validators=[
django.core.validators.MinValueValidator(-90.0),
django.core.validators.MaxValueValidator(90.0),
django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
],
),
),
@@ -31,8 +33,8 @@ class Migration(migrations.Migration):
max_digits=9,
null=True,
validators=[
django.core.validators.MinValueValidator(-180.0),
django.core.validators.MaxValueValidator(180.0),
django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
],
),
),
@@ -45,8 +47,8 @@ class Migration(migrations.Migration):
max_digits=8,
null=True,
validators=[
django.core.validators.MinValueValidator(-90.0),
django.core.validators.MaxValueValidator(90.0),
django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
],
),
),
@@ -59,8 +61,8 @@ class Migration(migrations.Migration):
max_digits=9,
null=True,
validators=[
django.core.validators.MinValueValidator(-180.0),
django.core.validators.MaxValueValidator(180.0),
django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
],
),
),

View File

@@ -646,7 +646,10 @@ class Device(
decimal_places=6,
blank=True,
null=True,
validators=[MinValueValidator(-90.0), MaxValueValidator(90.0)],
validators=[
MinValueValidator(decimal.Decimal('-90.0')),
MaxValueValidator(decimal.Decimal('90.0'))
],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
longitude = models.DecimalField(
@@ -655,7 +658,10 @@ class Device(
decimal_places=6,
blank=True,
null=True,
validators=[MinValueValidator(-180.0), MaxValueValidator(180.0)],
validators=[
MinValueValidator(decimal.Decimal('-180.0')),
MaxValueValidator(decimal.Decimal('180.0'))
],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
services = GenericRelation(

View File

@@ -1,3 +1,5 @@
import decimal
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
@@ -211,7 +213,10 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
validators=[MinValueValidator(-90.0), MaxValueValidator(90.0)],
validators=[
MinValueValidator(decimal.Decimal('-90.0')),
MaxValueValidator(decimal.Decimal('90.0'))
],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)
longitude = models.DecimalField(
@@ -220,7 +225,10 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
validators=[MinValueValidator(-180.0), MaxValueValidator(180.0)],
validators=[
MinValueValidator(decimal.Decimal('-180.0')),
MaxValueValidator(decimal.Decimal('180.0'))
],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)

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

@@ -559,6 +559,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
form.instance._replicated_base = hasattr(self.form, "replication_fields")
if form.is_valid():
changelog_message = form.cleaned_data.pop('changelog_message', '')
new_components = []
data = deepcopy(request.POST)
pattern_count = len(form.cleaned_data[self.form.replication_fields[0]])
@@ -585,6 +586,9 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
# Create the new components
new_objs = []
for component_form in new_components:
# Record changelog message (if any)
if changelog_message:
component_form.instance._changelog_message = changelog_message
obj = component_form.save()
new_objs.append(obj)

File diff suppressed because it is too large Load Diff