Refactor MongoDB client imports and implement lazy loading for main items in UI

This commit is contained in:
2026-04-12 17:36:49 +02:00
parent c078de0076
commit 0f372f5056
7 changed files with 151 additions and 25 deletions
+1 -1
View File
@@ -33,7 +33,6 @@ import ausleihung as au
import audit_log as al import audit_log as al
import datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from pymongo import MongoClient
from bson.objectid import ObjectId from bson.objectid import ObjectId
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
import requests import requests
@@ -59,6 +58,7 @@ import subprocess
# Set base directory and centralized settings # Set base directory and centralized settings
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import settings as cfg import settings as cfg
from settings import MongoClient
app = Flask(__name__, static_folder='static') # Correctly set static folder app = Flask(__name__, static_folder='static') # Correctly set static folder
+1 -1
View File
@@ -25,7 +25,6 @@ Sammlungsstruktur:
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited. Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev For commercial licensing inquiries: https://github.com/AIIrondev
''' '''
from pymongo import MongoClient
from bson.objectid import ObjectId from bson.objectid import ObjectId
import datetime import datetime
import pytz import pytz
@@ -34,6 +33,7 @@ import os
import json import json
import shutil import shutil
import settings as cfg import settings as cfg
from settings import MongoClient
# Add this helper function after imports # Add this helper function after imports
def ensure_timezone_aware(dt): def ensure_timezone_aware(dt):
+1 -1
View File
@@ -26,10 +26,10 @@ Collection Structure:
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited. Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev For commercial licensing inquiries: https://github.com/AIIrondev
''' '''
from pymongo import MongoClient
from bson.objectid import ObjectId from bson.objectid import ObjectId
import datetime import datetime
import settings as cfg import settings as cfg
from settings import MongoClient
LIBRARY_ITEM_TYPES = ('book', 'cd', 'dvd', 'media') LIBRARY_ITEM_TYPES = ('book', 'cd', 'dvd', 'media')
+21
View File
@@ -12,6 +12,9 @@ defaults for the web application and helper modules.
""" """
import os import os
import json import json
from functools import lru_cache
from pymongo import MongoClient as _PyMongoClient
# Base directory of this Web package # Base directory of this Web package
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -173,3 +176,21 @@ if not os.path.isabs(BACKUP_FOLDER):
BACKUP_FOLDER = os.path.join(PROJECT_ROOT, BACKUP_FOLDER) BACKUP_FOLDER = os.path.join(PROJECT_ROOT, BACKUP_FOLDER)
if not os.path.isabs(LOGS_FOLDER): if not os.path.isabs(LOGS_FOLDER):
LOGS_FOLDER = os.path.join(PROJECT_ROOT, LOGS_FOLDER) LOGS_FOLDER = os.path.join(PROJECT_ROOT, LOGS_FOLDER)
@lru_cache(maxsize=1)
def _shared_mongo_client():
return _PyMongoClient(
MONGODB_HOST,
MONGODB_PORT,
maxPoolSize=10,
minPoolSize=0,
connectTimeoutMS=5000,
serverSelectionTimeoutMS=5000,
retryWrites=True,
)
def MongoClient(*args, **kwargs):
"""Return a shared MongoDB client configured from this settings module."""
return _shared_mongo_client()
+66 -11
View File
@@ -725,9 +725,14 @@
let allItems = []; let allItems = [];
const MAIN_ITEMS_PAGE_SIZE = 120; const MAIN_ITEMS_PAGE_SIZE = 120;
let mainItemsNextOffset = 0;
let mainItemsHasMore = false;
let mainItemsLoadingMore = false;
let mainItemsObserver = null;
let mainItemsSentinel = null;
function loadItems(offset = 0, append = false) { function loadItems(offset = 0, append = false) {
fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}`) return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
@@ -1009,7 +1014,7 @@
populateFilterOptions('filter2-options', [...filter2Values].sort(), 2); populateFilterOptions('filter2-options', [...filter2Values].sort(), 2);
populateFilterOptions('filter3-options', [...filter3Values].sort(), 3); populateFilterOptions('filter3-options', [...filter3Values].sort(), 3);
// Start display from leftmost position (first card, which is now Z) // Start display from leftmost position on full reload only.
if (!append) { if (!append) {
itemsContainer.scrollLeft = 0; itemsContainer.scrollLeft = 0;
} }
@@ -1028,15 +1033,9 @@
rebuildFilter3Options(); rebuildFilter3Options();
} }
if (data.has_more) { mainItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length);
const nextOffset = (data.offset || offset) + (data.count || pageItems.length); mainItemsHasMore = Boolean(data.has_more);
const continueLoad = () => loadItems(nextOffset, true); setupMainItemsLazyLoading();
if ('requestIdleCallback' in window) {
requestIdleCallback(continueLoad, { timeout: 250 });
} else {
setTimeout(continueLoad, 0);
}
}
}) })
.catch(error => { .catch(error => {
console.error('Error fetching items:', error); console.error('Error fetching items:', error);
@@ -1049,6 +1048,62 @@
}); });
} }
function ensureMainItemsSentinel() {
const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null;
if (!mainItemsSentinel) {
mainItemsSentinel = document.createElement('div');
mainItemsSentinel.id = 'main-items-sentinel';
mainItemsSentinel.style.flex = '0 0 1px';
mainItemsSentinel.style.width = '1px';
mainItemsSentinel.style.minWidth = '1px';
mainItemsSentinel.style.height = '1px';
mainItemsSentinel.style.pointerEvents = 'none';
}
if (!mainItemsSentinel.parentNode) {
itemsContainer.appendChild(mainItemsSentinel);
} else {
itemsContainer.appendChild(mainItemsSentinel);
}
return itemsContainer;
}
function setupMainItemsLazyLoading() {
const itemsContainer = ensureMainItemsSentinel();
if (!itemsContainer) return;
if (mainItemsObserver) {
mainItemsObserver.disconnect();
mainItemsObserver = null;
}
if (!mainItemsHasMore) return;
mainItemsObserver = new IntersectionObserver(entries => {
if (!entries.some(entry => entry.isIntersecting)) {
return;
}
if (mainItemsLoadingMore || !mainItemsHasMore) {
return;
}
mainItemsLoadingMore = true;
loadItems(mainItemsNextOffset, true).finally(() => {
mainItemsLoadingMore = false;
});
}, {
root: itemsContainer,
threshold: 0.15,
rootMargin: '0px 220px 0px 0px'
});
mainItemsObserver.observe(mainItemsSentinel);
}
function searchByCode() { function searchByCode() {
const searchInput = document.getElementById('code-search'); const searchInput = document.getElementById('code-search');
const rawSearchTerm = searchInput.value; const rawSearchTerm = searchInput.value;
+60 -10
View File
@@ -3256,9 +3256,14 @@ document.addEventListener('DOMContentLoaded', ()=>{
// Function to load items from server // Function to load items from server
const MAIN_ADMIN_ITEMS_PAGE_SIZE = 120; const MAIN_ADMIN_ITEMS_PAGE_SIZE = 120;
let mainAdminItemsNextOffset = 0;
let mainAdminItemsHasMore = false;
let mainAdminItemsLoadingMore = false;
let mainAdminItemsObserver = null;
let mainAdminItemsSentinel = null;
function loadItems(offset = 0, append = false) { function loadItems(offset = 0, append = false) {
fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}`) return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
@@ -3556,15 +3561,9 @@ document.addEventListener('DOMContentLoaded', ()=>{
} }
/* favorites render count removed */ /* favorites render count removed */
if (data.has_more) { mainAdminItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length);
const nextOffset = (data.offset || offset) + (data.count || pageItems.length); mainAdminItemsHasMore = Boolean(data.has_more);
const continueLoad = () => loadItems(nextOffset, true); setupMainAdminItemsLazyLoading();
if ('requestIdleCallback' in window) {
requestIdleCallback(continueLoad, { timeout: 250 });
} else {
setTimeout(continueLoad, 0);
}
}
}) })
.catch(error => { .catch(error => {
console.error('Error fetching items:', error); console.error('Error fetching items:', error);
@@ -3581,6 +3580,57 @@ document.addEventListener('DOMContentLoaded', ()=>{
}); });
} }
function ensureMainAdminItemsSentinel() {
const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null;
if (!mainAdminItemsSentinel) {
mainAdminItemsSentinel = document.createElement('div');
mainAdminItemsSentinel.id = 'main-admin-items-sentinel';
mainAdminItemsSentinel.style.flex = '0 0 1px';
mainAdminItemsSentinel.style.width = '1px';
mainAdminItemsSentinel.style.minWidth = '1px';
mainAdminItemsSentinel.style.height = '1px';
mainAdminItemsSentinel.style.pointerEvents = 'none';
}
itemsContainer.appendChild(mainAdminItemsSentinel);
return itemsContainer;
}
function setupMainAdminItemsLazyLoading() {
const itemsContainer = ensureMainAdminItemsSentinel();
if (!itemsContainer) return;
if (mainAdminItemsObserver) {
mainAdminItemsObserver.disconnect();
mainAdminItemsObserver = null;
}
if (!mainAdminItemsHasMore) return;
mainAdminItemsObserver = new IntersectionObserver(entries => {
if (!entries.some(entry => entry.isIntersecting)) {
return;
}
if (mainAdminItemsLoadingMore || !mainAdminItemsHasMore) {
return;
}
mainAdminItemsLoadingMore = true;
loadItems(mainAdminItemsNextOffset, true).finally(() => {
mainAdminItemsLoadingMore = false;
});
}, {
root: itemsContainer,
threshold: 0.15,
rootMargin: '0px 220px 0px 0px'
});
mainAdminItemsObserver.observe(mainAdminItemsSentinel);
}
function searchByCode() { function searchByCode() {
const searchInput = document.getElementById('code-search'); const searchInput = document.getElementById('code-search');
const rawSearchTerm = searchInput.value; const rawSearchTerm = searchInput.value;
+1 -1
View File
@@ -10,10 +10,10 @@ Provides methods for creating, validating, and retrieving user information.
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited. Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev For commercial licensing inquiries: https://github.com/AIIrondev
''' '''
from pymongo import MongoClient
import hashlib import hashlib
from bson.objectid import ObjectId from bson.objectid import ObjectId
import settings as cfg import settings as cfg
from settings import MongoClient
def normalize_student_card_id(card_id): def normalize_student_card_id(card_id):