[bug] Settings fix (#13156)

* Fix error when generating default settings

- Prevent database error on conflicting rows
- Can be caused by concurrent user requests

* ignore conflicts for TipResult
This commit is contained in:
Oliver authored and GitHub committed 2026-11-02 12:09:09 +11:00
1 parent 84fa789caf
commit 4fb0226ecc
4 files changed
+54 -4

No files matched your search

+2
View File
@@ -292,6 +292,8 @@ class BaseInvenTreeSetting(UpdatedUserMixin, models.Model):
if not key.startswith('_')
],
batch_size=250,
# Concurrent requests may race to create the same keys
ignore_conflicts=True,
)
except Exception as exc:
logger.exception(
+26
View File
@@ -866,6 +866,32 @@ class UserSettingsApiTest(InvenTreeAPITestCase):
self.get(url, expected_code=200)
def test_build_default_values_race(self):
"""Test that building default values tolerates keys created concurrently."""
key = 'DISPLAY_HISTORY_TAB'
InvenTreeUserSetting.objects.create(key=key, value='True', user=self.user)
# Simulate another request creating the key after the existing keys are checked
mock_qs = mock.Mock()
mock_qs.values_list.return_value = []
with (
mock.patch.object(
InvenTreeUserSetting.objects, 'filter', return_value=mock_qs
),
self.assertNoLogs(logger='inventree', level='ERROR'),
):
InvenTreeUserSetting.build_default_values(user=self.user)
settings = InvenTreeUserSetting.objects.filter(user=self.user)
self.assertEqual(settings.filter(key=key).count(), 1)
self.assertEqual(
set(settings.values_list('key', flat=True)),
{k for k in InvenTreeUserSetting.SETTINGS if not k.startswith('_')},
)
def test_user_setting_invalid(self):
"""Test a user setting with an invalid key."""
url = reverse('api-user-setting-detail', kwargs={'key': 'DONKEY'})
+5 -3
View File
@@ -878,9 +878,11 @@ class TipResult(InvenTree.models.MetadataMixin):
# bulk create
missing_ids = set(known_ids) - existing_ids
if missing_ids:
cls.objects.bulk_create([
cls(user=user, tip_id=tip_id) for tip_id in missing_ids
])
cls.objects.bulk_create(
[cls(user=user, tip_id=tip_id) for tip_id in missing_ids],
# Concurrent requests may race to create the same records
ignore_conflicts=True,
)
# Validate groups
+21 -1
View File
@@ -1,6 +1,7 @@
"""Unit tests for the 'users' app."""
import datetime
from unittest import mock
from django.apps import apps
from django.contrib.auth.models import Group
@@ -10,7 +11,7 @@ from django.urls import reverse
from common.settings import set_global_setting
from InvenTree.helpers_mfa import get_codes
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase, InvenTreeTestCase
from users.models import ApiToken, Owner
from users.models import ApiToken, Owner, TipResult
from users.oauth2_scopes import _roles
from users.ruleset import (
RULESET_CHOICES,
@@ -18,6 +19,7 @@ from users.ruleset import (
get_ruleset_ignore,
get_ruleset_models,
)
from web.tips import KNOWN_IDS
G_RULESETS = get_ruleset_models()
G_RULESETS_IG = get_ruleset_ignore()
@@ -697,6 +699,24 @@ class UserProfileTest(InvenTreeAPITestCase):
profile.refresh_from_db()
self.assertIsNone(profile.primary_group)
def test_tip_backfill_race(self):
"""Test that backfilling tip results tolerates records created concurrently."""
tip_id = KNOWN_IDS[0]
TipResult.objects.create(user=self.user, tip_id=tip_id)
# Simulate another request creating the record after the existing IDs are checked
mock_qs = mock.Mock()
mock_qs.values_list.return_value = []
with mock.patch.object(TipResult.objects, 'filter', return_value=mock_qs):
TipResult.backfill(self.user)
results = TipResult.objects.filter(user=self.user)
self.assertEqual(results.filter(tip_id=tip_id).count(), 1)
self.assertEqual(set(results.values_list('tip_id', flat=True)), set(KNOWN_IDS))
class UserProfileMetadataPermissionTests(InvenTreeAPITestCase):
"""Tests for the generic metadata endpoint against the UserProfile model."""