From ad054fc69400e556d9966962cd69a45bc76f5e61 Mon Sep 17 00:00:00 2001 From: mburggraf Date: Tue, 14 Jul 2026 19:46:53 +0200 Subject: [PATCH] Fixes #22513: Make JournalEntry.created_by immutable after creation (#22547) --- netbox/extras/api/serializers_/journaling.py | 9 +++++++ netbox/extras/forms/bulk_import.py | 6 +++++ netbox/extras/tests/query_counts.json | 2 +- netbox/extras/tests/test_api.py | 25 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/netbox/extras/api/serializers_/journaling.py b/netbox/extras/api/serializers_/journaling.py index 03ec34451..1824ba0bc 100644 --- a/netbox/extras/api/serializers_/journaling.py +++ b/netbox/extras/api/serializers_/journaling.py @@ -38,6 +38,15 @@ class JournalEntrySerializer(NetBoxModelSerializer): ] brief_fields = ('id', 'url', 'display', 'created') + def get_fields(self): + fields = super().get_fields() + + # Make created_by field read-only if updating an existing JournalEntry. + if self.instance is not None: + fields['created_by'].read_only = True + + return fields + def validate(self, data): # Validate that the parent object exists diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 78f6d8c0b..d5a047961 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -344,6 +344,12 @@ class JournalEntryImportForm(NetBoxModelImportForm): 'assigned_object_type', 'assigned_object_id', 'created_by', 'kind', 'comments', 'tags' ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # If not creating a new JournalEntry, disable the created_by field + if self.instance and not self.instance._state.adding: + self.fields['created_by'].disabled = True + class NotificationGroupImportForm(CSVModelForm): users = CSVModelMultipleChoiceField( diff --git a/netbox/extras/tests/query_counts.json b/netbox/extras/tests/query_counts.json index a6807bd29..84a5223d2 100644 --- a/netbox/extras/tests/query_counts.json +++ b/netbox/extras/tests/query_counts.json @@ -19,7 +19,7 @@ "exporttemplate:list_objects_with_permission": 19, "imageattachment:api_list_objects": 11, "imageattachment:list_objects_with_permission": 21, - "journalentry:api_list_objects": 15, + "journalentry:api_list_objects": 16, "journalentry:list_objects_with_permission": 24, "notification:api_list_objects": 12, "notificationgroup:api_list_objects": 11, diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 1239a0e9f..d278a5845 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -926,6 +926,12 @@ class JournalEntryTestCase(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): + users = ( + User(username='User 1'), + User(username='User 2'), + ) + User.objects.bulk_create(users) + user = User.objects.first() site = Site.objects.create(name='Site 1', slug='site-1') @@ -966,6 +972,25 @@ class JournalEntryTestCase(APIViewTestCases.APIViewTestCase): }, ] + def test_immutable_created_by(self): + """ + Verify that created_by can't be changed for existing objects + """ + entry = JournalEntry.objects.first() + created_by_before = entry.created_by_id + # select user different from the one currently set + change_user = User.objects.exclude(pk=created_by_before).only('id').first() + + url = reverse('extras-api:journalentry-detail', kwargs={'pk': entry.pk}) + self.add_permissions('extras.change_journalentry') + response = self.client.patch(url, {'created_by': change_user.id}, format='json', **self.header) + + self.assertEqual(response.status_code, 200) + + entry.refresh_from_db() + created_by_after = entry.created_by_id + self.assertEqual(created_by_before, created_by_after) + class ConfigContextProfileTestCase(APIViewTestCases.APIViewTestCase): model = ConfigContextProfile