mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: add update availability filter to model list
Add a new filter option to show only models with available updates across all supported languages. This includes: - Adding "updates" filter translations in all locale files (de, en, es, fr, he, ja, ko) - Extending BaseModelApiClient to handle update_available_only query parameter - Implementing update filter button in PageControls component with event listeners - Adding corresponding CSS styles for active filter state The feature allows users to quickly identify and focus on models that have updates available, improving the update management workflow.
This commit is contained in:
@@ -432,6 +432,10 @@
|
||||
"favorites": {
|
||||
"title": "Nur Favoriten anzeigen",
|
||||
"action": "Favoriten"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Nur Modelle mit verfügbaren Updates anzeigen",
|
||||
"action": "Updates"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "Show Favorites Only",
|
||||
"action": "Favorites"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Show models with updates available",
|
||||
"action": "Updates"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "Mostrar solo favoritos",
|
||||
"action": "Favoritos"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Mostrar solo modelos con actualizaciones disponibles",
|
||||
"action": "Actualizaciones"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "Afficher uniquement les favoris",
|
||||
"action": "Favoris"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Afficher uniquement les modèles avec des mises à jour disponibles",
|
||||
"action": "Mises à jour"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "הצג מועדפים בלבד",
|
||||
"action": "מועדפים"
|
||||
},
|
||||
"updates": {
|
||||
"title": "הצג רק דגמים עם עדכונים זמינים",
|
||||
"action": "עדכונים"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "お気に入りのみ表示",
|
||||
"action": "お気に入り"
|
||||
},
|
||||
"updates": {
|
||||
"title": "アップデート可能なモデルのみ表示",
|
||||
"action": "アップデート"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "즐겨찾기만 보기",
|
||||
"action": "즐겨찾기"
|
||||
},
|
||||
"updates": {
|
||||
"title": "업데이트 가능한 모델만 표시",
|
||||
"action": "업데이트"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "Показать только избранное",
|
||||
"action": "Избранное"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Показывать только модели с доступными обновлениями",
|
||||
"action": "Обновления"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "仅显示收藏",
|
||||
"action": "收藏"
|
||||
},
|
||||
"updates": {
|
||||
"title": "仅显示可用更新的模型",
|
||||
"action": "更新"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -431,6 +431,10 @@
|
||||
"favorites": {
|
||||
"title": "僅顯示收藏",
|
||||
"action": "收藏"
|
||||
},
|
||||
"updates": {
|
||||
"title": "僅顯示可用更新的模型",
|
||||
"action": "更新"
|
||||
}
|
||||
},
|
||||
"bulkOperations": {
|
||||
|
||||
@@ -166,10 +166,7 @@ class ModelListingHandler:
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
has_update = request.query.get("has_update", "false")
|
||||
has_update_filter = (
|
||||
has_update.lower() in {"1", "true", "yes"} if isinstance(has_update, str) else False
|
||||
)
|
||||
update_available_only = request.query.get("update_available_only", "false").lower() == "true"
|
||||
|
||||
return {
|
||||
"page": page,
|
||||
@@ -183,7 +180,7 @@ class ModelListingHandler:
|
||||
"search_options": search_options,
|
||||
"hash_filters": hash_filters,
|
||||
"favorites_only": favorites_only,
|
||||
"has_update": has_update_filter,
|
||||
"update_available_only": update_available_only,
|
||||
**self._parse_specific_params(request),
|
||||
}
|
||||
|
||||
|
||||
@@ -63,10 +63,11 @@ class BaseModelService(ABC):
|
||||
search_options: dict = None,
|
||||
hash_filters: dict = None,
|
||||
favorites_only: bool = False,
|
||||
has_update: bool = False,
|
||||
update_available_only: bool = False,
|
||||
**kwargs,
|
||||
) -> Dict:
|
||||
"""Get paginated and filtered model data"""
|
||||
|
||||
sort_params = self.cache_repository.parse_sort(sort_by)
|
||||
sorted_data = await self.cache_repository.fetch_sorted(sort_params)
|
||||
|
||||
@@ -92,14 +93,23 @@ class BaseModelService(ABC):
|
||||
|
||||
filtered_data = await self._apply_specific_filters(filtered_data, **kwargs)
|
||||
|
||||
if has_update:
|
||||
filtered_data = await self._apply_update_filter(filtered_data)
|
||||
annotated_for_filter: Optional[List[Dict]] = None
|
||||
if update_available_only:
|
||||
annotated_for_filter = await self._annotate_update_flags(filtered_data)
|
||||
filtered_data = [
|
||||
item for item in annotated_for_filter
|
||||
if item.get('update_available')
|
||||
]
|
||||
|
||||
paginated = self._paginate(filtered_data, page, page_size)
|
||||
paginated['items'] = await self._annotate_update_flags(
|
||||
paginated['items'],
|
||||
assume_true=has_update,
|
||||
)
|
||||
|
||||
if update_available_only:
|
||||
# Items already include update flags thanks to the pre-filter annotation.
|
||||
paginated['items'] = list(paginated['items'])
|
||||
else:
|
||||
paginated['items'] = await self._annotate_update_flags(
|
||||
paginated['items'],
|
||||
)
|
||||
return paginated
|
||||
|
||||
|
||||
@@ -160,92 +170,19 @@ class BaseModelService(ABC):
|
||||
"""Apply model-specific filters - to be overridden by subclasses if needed"""
|
||||
return data
|
||||
|
||||
async def _apply_update_filter(self, data: List[Dict]) -> List[Dict]:
|
||||
"""Filter models to those with remote updates available when requested."""
|
||||
if not data:
|
||||
return []
|
||||
if self.update_service is None:
|
||||
logger.warning(
|
||||
"Requested has_update filter for %s models but update service is unavailable",
|
||||
self.model_type,
|
||||
)
|
||||
return []
|
||||
|
||||
id_to_items: Dict[int, List[Dict]] = {}
|
||||
ordered_ids: List[int] = []
|
||||
for item in data:
|
||||
model_id = self._extract_model_id(item)
|
||||
if model_id is None:
|
||||
continue
|
||||
if model_id not in id_to_items:
|
||||
id_to_items[model_id] = []
|
||||
ordered_ids.append(model_id)
|
||||
id_to_items[model_id].append(item)
|
||||
|
||||
if not ordered_ids:
|
||||
return []
|
||||
|
||||
resolved: Optional[Dict[int, bool]] = None
|
||||
bulk_method = getattr(self.update_service, "has_updates_bulk", None)
|
||||
if callable(bulk_method):
|
||||
try:
|
||||
resolved = await bulk_method(self.model_type, ordered_ids)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"Failed to resolve update status in bulk for %s models (%s): %s",
|
||||
self.model_type,
|
||||
ordered_ids,
|
||||
exc,
|
||||
exc_info=True,
|
||||
)
|
||||
resolved = None
|
||||
|
||||
if resolved is None:
|
||||
tasks = [
|
||||
self.update_service.has_update(self.model_type, model_id)
|
||||
for model_id in ordered_ids
|
||||
]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
resolved = {}
|
||||
for model_id, result in zip(ordered_ids, results):
|
||||
if isinstance(result, Exception):
|
||||
logger.error(
|
||||
"Failed to resolve update status for model %s (%s): %s",
|
||||
model_id,
|
||||
self.model_type,
|
||||
result,
|
||||
)
|
||||
continue
|
||||
resolved[model_id] = bool(result)
|
||||
|
||||
filtered: List[Dict] = []
|
||||
for item in data:
|
||||
model_id = self._extract_model_id(item)
|
||||
if model_id is not None and resolved.get(model_id, False):
|
||||
filtered.append(item)
|
||||
return filtered
|
||||
|
||||
async def _annotate_update_flags(
|
||||
self,
|
||||
items: List[Dict],
|
||||
*,
|
||||
assume_true: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""Attach an update_available flag to each response item.
|
||||
|
||||
Items without a civitai model id default to False. When the caller already
|
||||
filtered for updates we can skip the lookup and mark everything True.
|
||||
Items without a civitai model id default to False.
|
||||
"""
|
||||
if not items:
|
||||
return []
|
||||
|
||||
annotated = [dict(item) for item in items]
|
||||
|
||||
if assume_true:
|
||||
for item in annotated:
|
||||
item['update_available'] = True
|
||||
return annotated
|
||||
|
||||
if self.update_service is None:
|
||||
for item in annotated:
|
||||
item['update_available'] = False
|
||||
|
||||
@@ -105,7 +105,8 @@
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.control-group button.favorite-filter {
|
||||
.control-group button.favorite-filter,
|
||||
.control-group button.update-filter {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -120,6 +121,20 @@
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.control-group button.update-filter i {
|
||||
margin-right: 4px;
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.control-group button.update-filter.active {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.control-group button.update-filter.active i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Active state for buttons that can be toggled */
|
||||
.control-group button.active {
|
||||
background: var(--lora-accent);
|
||||
|
||||
@@ -749,7 +749,11 @@ export class BaseModelApiClient {
|
||||
if (pageState.showFavoritesOnly) {
|
||||
params.append('favorites_only', 'true');
|
||||
}
|
||||
|
||||
|
||||
if (pageState.showUpdateAvailableOnly) {
|
||||
params.append('update_available_only', 'true');
|
||||
}
|
||||
|
||||
if (this.apiConfig.config.supportsLetterFilter && pageState.activeLetterFilter) {
|
||||
params.append('first_letter', pageState.activeLetterFilter);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ export class PageControls {
|
||||
// Initialize event listeners
|
||||
this.initEventListeners();
|
||||
|
||||
// Initialize update availability filter button state
|
||||
this.initUpdateAvailableFilter();
|
||||
|
||||
// Initialize favorites filter button state
|
||||
this.initFavoritesFilter();
|
||||
|
||||
@@ -189,12 +192,17 @@ export class PageControls {
|
||||
if (bulkButton) {
|
||||
bulkButton.addEventListener('click', () => this.toggleBulkMode());
|
||||
}
|
||||
|
||||
|
||||
// Favorites filter button handler
|
||||
const favoriteFilterBtn = document.getElementById('favoriteFilterBtn');
|
||||
if (favoriteFilterBtn) {
|
||||
favoriteFilterBtn.addEventListener('click', () => this.toggleFavoritesOnly());
|
||||
}
|
||||
|
||||
const updateFilterBtn = document.getElementById('updateFilterBtn');
|
||||
if (updateFilterBtn) {
|
||||
updateFilterBtn.addEventListener('click', () => this.toggleUpdateAvailableOnly());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,17 +386,33 @@ export class PageControls {
|
||||
// Get current state from session storage with page-specific key
|
||||
const storageKey = `show_favorites_only_${this.pageType}`;
|
||||
const showFavoritesOnly = getSessionItem(storageKey, false);
|
||||
|
||||
|
||||
// Update button state
|
||||
if (showFavoritesOnly) {
|
||||
favoriteFilterBtn.classList.add('active');
|
||||
}
|
||||
|
||||
|
||||
// Update app state
|
||||
this.pageState.showFavoritesOnly = showFavoritesOnly;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize update availability filter button state
|
||||
*/
|
||||
initUpdateAvailableFilter() {
|
||||
const storageKey = `show_update_available_only_${this.pageType}`;
|
||||
const storedValue = getSessionItem(storageKey, false);
|
||||
const showUpdatesOnly = storedValue === true || storedValue === 'true';
|
||||
|
||||
this.pageState.showUpdateAvailableOnly = showUpdatesOnly;
|
||||
|
||||
const updateFilterBtn = document.getElementById('updateFilterBtn');
|
||||
if (updateFilterBtn) {
|
||||
updateFilterBtn.classList.toggle('active', showUpdatesOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle favorites-only filter and reload models
|
||||
*/
|
||||
@@ -410,10 +434,29 @@ export class PageControls {
|
||||
if (favoriteFilterBtn) {
|
||||
favoriteFilterBtn.classList.toggle('active', newState);
|
||||
}
|
||||
|
||||
|
||||
// Reload models with new filter
|
||||
await this.resetAndReload(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle update-available-only filter and reload models
|
||||
*/
|
||||
async toggleUpdateAvailableOnly() {
|
||||
const updateFilterBtn = document.getElementById('updateFilterBtn');
|
||||
const storageKey = `show_update_available_only_${this.pageType}`;
|
||||
const newState = !this.pageState.showUpdateAvailableOnly;
|
||||
|
||||
setSessionItem(storageKey, newState);
|
||||
|
||||
this.pageState.showUpdateAvailableOnly = newState;
|
||||
|
||||
if (updateFilterBtn) {
|
||||
updateFilterBtn.classList.toggle('active', newState);
|
||||
}
|
||||
|
||||
await this.resetAndReload(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find duplicate models
|
||||
@@ -437,4 +480,4 @@ export class PageControls {
|
||||
this.sidebarManager.cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,11 @@ export class FilterManager {
|
||||
removeStorageItem(storageKey);
|
||||
|
||||
// Update UI
|
||||
this.filterButton.classList.remove('active');
|
||||
if (this.hasActiveFilters()) {
|
||||
this.filterButton.classList.add('active');
|
||||
} else {
|
||||
this.filterButton.classList.remove('active');
|
||||
}
|
||||
|
||||
// Reload data using the appropriate method for the current page
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
|
||||
@@ -81,6 +81,7 @@ export const state = {
|
||||
selectedLoras: new Set(),
|
||||
loraMetadataCache: new Map(),
|
||||
showFavoritesOnly: false,
|
||||
showUpdateAvailableOnly: false,
|
||||
duplicatesMode: false,
|
||||
},
|
||||
|
||||
@@ -131,6 +132,7 @@ export const state = {
|
||||
selectedModels: new Set(),
|
||||
metadataCache: new Map(),
|
||||
showFavoritesOnly: false,
|
||||
showUpdateAvailableOnly: false,
|
||||
duplicatesMode: false,
|
||||
},
|
||||
|
||||
@@ -158,6 +160,7 @@ export const state = {
|
||||
selectedModels: new Set(),
|
||||
metadataCache: new Map(),
|
||||
showFavoritesOnly: false,
|
||||
showUpdateAvailableOnly: false,
|
||||
duplicatesMode: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -56,6 +56,11 @@
|
||||
<i class="fas fa-star"></i> <span>{{ t('loras.controls.favorites.action') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="updateFilterBtn" data-action="toggle-updates" class="update-filter" title="{{ t('loras.controls.updates.title') }}">
|
||||
<i class="fas fa-sync-alt"></i> <span>{{ t('loras.controls.updates.action') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="customFilterIndicator" class="control-group hidden">
|
||||
<div class="filter-active">
|
||||
<i class="fas fa-filter"></i> <span class="customFilterText" title=""></span>
|
||||
|
||||
@@ -135,6 +135,9 @@ function renderControlsDom(pageKey) {
|
||||
<div class="control-group">
|
||||
<button id="favoriteFilterBtn" class="favorite-filter"></button>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="updateFilterBtn" class="update-filter"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,6 +312,84 @@ describe('PageControls favorites, sorting, and duplicates scenarios', () => {
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('persists update-available toggle for LoRAs and triggers reload', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('loras');
|
||||
const { LorasControls } = await import('../../../static/js/components/controls/LorasControls.js');
|
||||
|
||||
const controls = new LorasControls();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_loras')).toBe('true');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(true);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(true);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
|
||||
resetAndReloadMock.mockClear();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_loras')).toBe('false');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(false);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(false);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('does not change filter badge or button when toggling update availability', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('loras');
|
||||
const { LorasControls } = await import('../../../static/js/components/controls/LorasControls.js');
|
||||
|
||||
const controls = new LorasControls();
|
||||
|
||||
const filterBadge = document.getElementById('activeFiltersCount');
|
||||
const filterButton = document.getElementById('filterButton');
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
});
|
||||
|
||||
it('persists update-available toggle for checkpoints and triggers reload', async () => {
|
||||
renderControlsDom('checkpoints');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('checkpoints');
|
||||
const { CheckpointsControls } = await import('../../../static/js/components/controls/CheckpointsControls.js');
|
||||
|
||||
const controls = new CheckpointsControls();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_checkpoints')).toBe('true');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(true);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(true);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
|
||||
resetAndReloadMock.mockClear();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_checkpoints')).toBe('false');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(false);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(false);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('saves sort selection and reloads models', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
|
||||
@@ -341,7 +341,7 @@ async def test_get_paginated_data_filters_by_update_status():
|
||||
page=1,
|
||||
page_size=5,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [1, 2, 3])]
|
||||
@@ -379,7 +379,7 @@ async def test_get_paginated_data_has_update_without_service_returns_empty():
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert response["items"] == []
|
||||
@@ -414,7 +414,7 @@ async def test_get_paginated_data_skips_items_when_update_check_fails():
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [1, 2])]
|
||||
@@ -460,6 +460,79 @@ async def test_get_paginated_data_annotates_update_flags_with_bulk_dedup():
|
||||
assert response["total_pages"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paginated_data_filters_update_available_only():
|
||||
items = [
|
||||
{"model_name": "Alpha", "civitai": {"modelId": 101}},
|
||||
{"model_name": "Beta", "civitai": {"modelId": 102}},
|
||||
{"model_name": "Gamma", "civitai": {"modelId": 103}},
|
||||
]
|
||||
repository = StubRepository(items)
|
||||
filter_set = PassThroughFilterSet()
|
||||
search_strategy = NoSearchStrategy()
|
||||
update_service = StubUpdateService({101: True, 102: False, 103: True})
|
||||
settings = StubSettings({})
|
||||
|
||||
service = DummyService(
|
||||
model_type="stub",
|
||||
scanner=object(),
|
||||
metadata_class=BaseModelMetadata,
|
||||
cache_repository=repository,
|
||||
filter_set=filter_set,
|
||||
search_strategy=search_strategy,
|
||||
settings_provider=settings,
|
||||
update_service=update_service,
|
||||
)
|
||||
|
||||
response = await service.get_paginated_data(
|
||||
page=1,
|
||||
page_size=5,
|
||||
sort_by="name:asc",
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [101, 102, 103])]
|
||||
assert update_service.calls == []
|
||||
assert [item["model_name"] for item in response["items"]] == ["Alpha", "Gamma"]
|
||||
assert all(item["update_available"] is True for item in response["items"])
|
||||
assert response["total"] == 2
|
||||
assert response["total_pages"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paginated_data_update_available_only_without_update_service():
|
||||
items = [
|
||||
{"model_name": "Alpha", "civitai": {"modelId": 201}},
|
||||
{"model_name": "Beta", "civitai": {"modelId": 202}},
|
||||
]
|
||||
repository = StubRepository(items)
|
||||
filter_set = PassThroughFilterSet()
|
||||
search_strategy = NoSearchStrategy()
|
||||
settings = StubSettings({})
|
||||
|
||||
service = DummyService(
|
||||
model_type="stub",
|
||||
scanner=object(),
|
||||
metadata_class=BaseModelMetadata,
|
||||
cache_repository=repository,
|
||||
filter_set=filter_set,
|
||||
search_strategy=search_strategy,
|
||||
settings_provider=settings,
|
||||
update_service=None,
|
||||
)
|
||||
|
||||
response = await service.get_paginated_data(
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert response["items"] == []
|
||||
assert response["total"] == 0
|
||||
assert response["total_pages"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"service_cls, extra_fields",
|
||||
|
||||
Reference in New Issue
Block a user