Compare commits

..

1 Commits

Author SHA1 Message Date
Arthur
8cbfe94fba fix performance regression for Site save, use bulk_update for cached fields 2026-01-14 16:30:40 -08:00
6 changed files with 24 additions and 43 deletions

View File

@@ -733,10 +733,9 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
)
module_bay = DynamicModelChoiceField(
label=_('Module bay'),
queryset=ModuleBay.objects.order_by('name'),
queryset=ModuleBay.objects.all(),
query_params={
'device_id': '$device',
'ordering': 'name',
'device_id': '$device'
},
context={
'disabled': 'installed_module',

View File

@@ -211,12 +211,16 @@ def sync_cached_scope_fields(instance, created, **kwargs):
for model in (Prefix, Cluster, WirelessLAN):
qs = model.objects.filter(**filters)
# Bulk update cached fields to avoid O(N) performance issues with large datasets.
# This does not trigger post_save signals, avoiding spurious change log entries.
objects_to_update = []
for obj in qs:
# Recompute cache using the same logic as save()
obj.cache_related_objects()
obj.save(update_fields=[
'_location',
'_site',
'_site_group',
'_region',
])
objects_to_update.append(obj)
if objects_to_update:
model.objects.bulk_update(
objects_to_update,
['_location', '_site', '_site_group', '_region']
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -75,16 +75,10 @@ export class DynamicTomSelect extends TomSelect {
load(value: string) {
const self = this;
// Save current selection before clearing
const currentValue = self.getValue();
// Automatically clear any cached options. (Only options included
// in the API response should be present.)
self.clearOptions();
// Clear user_options to prevent the pre-selected option from being treated specially
(self as any).user_options = {};
// Populate the null option (if any) if not searching
if (self.nullOption && !value) {
self.addOption(self.nullOption);
@@ -104,29 +98,16 @@ export class DynamicTomSelect extends TomSelect {
.then(response => response.json())
.then(apiData => {
const results: Dict[] = apiData.results;
// Add options and manually set $order to ensure correct sorting
results.forEach((result, index) => {
const options: Dict[] = [];
for (const result of results) {
const option = self.getOptionFromData(result);
self.addOption(option);
// Set $order after addOption() to override any special handling of pre-selected items
const key = option[self.settings.valueField as string] as string;
if (self.options[key]) {
(self.options[key] as any).$order = index;
}
});
self.loading--;
if (self.loading === 0) {
self.wrapper.classList.remove(self.settings.loadingClass as string);
options.push(option);
}
// Restore the current selection
if (currentValue && !self.items.includes(currentValue as string)) {
self.items.push(currentValue as string);
}
self.refreshOptions(false);
return options;
})
// Pass the options to the callback function
.then(options => {
self.loadCallback(options, []);
})
.catch(() => {
self.loadCallback([], []);

View File

@@ -49,9 +49,6 @@ export function initDynamicSelects(): void {
labelField: LABEL_FIELD,
maxOptions: MAX_OPTIONS,
// Preserve API response order
sortField: '$order',
// Disable local search (search is performed on the backend)
searchField: [],