implementation of the right time zones and blocked user by mahnung as a blocker for the library ausleih process
Release Inventarsystem / release-docker (push) Successful in 2m14s

This commit is contained in:
2026-08-23 12:26:36 +02:00
parent 0f0f0ebefd
commit df9367a19d
7 changed files with 206 additions and 141 deletions
+94 -118
View File
@@ -228,96 +228,6 @@ def rollover_student_card_classes(dry_run=False, *, max_class=None, graduate_lab
client.close() client.close()
@app.route('/api/library_return_by_code', methods=['POST'])
def api_library_return_by_code():
"""
Return a library item by scanning its code only (no student card required).
This marks active ausleihungen for the item as completed and updates item status.
"""
if 'username' not in session:
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {}
item_code_raw = str(payload.get('item_code') or payload.get('code') or '').strip()
if not item_code_raw:
return jsonify({'ok': False, 'message': 'Mediencode fehlt.'}), 400
normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper()
client = None
try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = client[MONGODB_DB]
items_col = db['items']
ausleihungen_col = db['ausleihungen']
query_or = [
{'Code_4': item_code_raw},
{'Code_4': normalized_code},
]
if normalized_isbn:
query_or.append({'ISBN': normalized_isbn})
item_doc = items_col.find_one({
'ItemType': {'$in': LIBRARY_ITEM_TYPES},
'$or': query_or
})
if not item_doc:
return jsonify({'ok': False, 'message': 'Kein Bibliotheksmedium für diesen Code gefunden.'}), 404
item_id = str(item_doc['_id'])
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
# If item already available -> nothing to return
if item_doc.get('Verfuegbar', True):
return jsonify({'ok': False, 'message': 'Dieses Medium ist nicht als ausgeliehen markiert.'}), 409
# Mark active ausleihungen as completed
update_result = ausleihungen_col.update_many(
{'Item': item_id, 'Status': 'active'},
{'$set': {
'Status': 'completed',
'End': now,
'LastUpdated': now
}}
)
# Update item status to available
borrower_name = str(item_doc.get('User') or '').strip() or ''
it.update_item_status(item_id, True, borrower_name)
_append_audit_event_standalone(
event_type='ausleihung_returned_by_code',
payload={
'channel': 'library_return_code',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'performed_by': session.get('username')
}
)
return jsonify({
'ok': True,
'action': 'returned',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'message': f"{item_doc.get('Name', 'Medium')} wurde zurückgegeben."
}), 200
except Exception as e:
app.logger.error(f"Error in library return by code: {e}")
return jsonify({'ok': False, 'message': 'Fehler beim Verarbeiten der Rückgabe.'}), 500
finally:
if client:
client.close()
# Admin route to trigger rollover manually # Admin route to trigger rollover manually
@app.route('/admin/trigger_school_year_rollover', methods=['POST']) @app.route('/admin/trigger_school_year_rollover', methods=['POST'])
def admin_trigger_school_year_rollover(): def admin_trigger_school_year_rollover():
@@ -333,33 +243,6 @@ def admin_trigger_school_year_rollover():
return jsonify({'ok': True, 'summary': summary}), 200 return jsonify({'ok': True, 'summary': summary}), 200
# Schedule annual rollover job using APScheduler (configurable via env)
try:
if cfg.SCHEDULER_ENABLED:
_rollover_month = getattr(cfg, 'SCHOOL_ROLLOVER_MONTH', 9)
_rollover_day = getattr(cfg, 'SCHOOL_ROLLOVER_DAY', 1)
_rollover_hour = getattr(cfg, 'SCHOOL_ROLLOVER_HOUR', 3)
_rollover_minute = getattr(cfg, 'SCHOOL_ROLLOVER_MIN', 0)
_rollover_max_class = getattr(cfg, 'SCHOOL_ROLLOVER_MAX_CLASS', 13)
_rollover_grad_label = getattr(cfg, 'SCHOOL_ROLLOVER_GRADUATE_LABEL', '')
_scheduler = BackgroundScheduler()
# Use a cron-style yearly job on the configured month/day
_scheduler.add_job(
func=lambda: rollover_student_card_classes(dry_run=False, max_class=_rollover_max_class, graduate_label=_rollover_grad_label),
trigger='cron',
month=_rollover_month,
day=_rollover_day,
hour=_rollover_hour,
minute=_rollover_minute,
id='school_year_rollover',
replace_existing=True
)
_scheduler.start()
app.logger.info('Scheduled annual school year rollover: %s-%s %s:%s', _rollover_month, _rollover_day, _rollover_hour, _rollover_minute)
except Exception as e:
app.logger.warning('Failed to schedule school year rollover: %s', e)
# Thumbnail sizes # Thumbnail sizes
THUMBNAIL_SIZE = cfg.THUMBNAIL_SIZE THUMBNAIL_SIZE = cfg.THUMBNAIL_SIZE
PREVIEW_SIZE = cfg.PREVIEW_SIZE PREVIEW_SIZE = cfg.PREVIEW_SIZE
@@ -4291,6 +4174,93 @@ def api_library_group(series_group_id):
app.logger.error('Error loading library group %s: %s', series_group_id, exc) app.logger.error('Error loading library group %s: %s', series_group_id, exc)
return jsonify({'items': [], 'message': 'Gruppe konnte nicht geladen werden.'}), 500 return jsonify({'items': [], 'message': 'Gruppe konnte nicht geladen werden.'}), 500
@app.route('/api/library_return_by_code', methods=['POST'])
def api_library_return_by_code():
"""
Return a library item by scanning its code only (no student card required).
This marks active ausleihungen for the item as completed and updates item status.
"""
if 'username' not in session:
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
if not cfg.MODULES.is_enabled('library'):
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
payload = request.get_json(silent=True) or {}
item_code_raw = str(payload.get('item_code') or payload.get('code') or '').strip()
if not item_code_raw:
return jsonify({'ok': False, 'message': 'Mediencode fehlt.'}), 400
normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper()
client = None
try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT)
db = client[MONGODB_DB]
items_col = db['items']
ausleihungen_col = db['ausleihungen']
query_or = [
{'Code_4': item_code_raw},
{'Code_4': normalized_code},
]
if normalized_isbn:
query_or.append({'ISBN': normalized_isbn})
item_doc = items_col.find_one({
'ItemType': {'$in': LIBRARY_ITEM_TYPES},
'$or': query_or
})
if not item_doc:
return jsonify({'ok': False, 'message': 'Kein Bibliotheksmedium für diesen Code gefunden.'}), 404
item_id = str(item_doc['_id'])
now = datetime.datetime.now(ZoneInfo("Europe/Berlin"))
# If item already available -> nothing to return
if item_doc.get('Verfuegbar', True):
return jsonify({'ok': False, 'message': 'Dieses Medium ist nicht als ausgeliehen markiert.'}), 409
# Mark active ausleihungen as completed
update_result = ausleihungen_col.update_many(
{'Item': item_id, 'Status': 'active'},
{'$set': {
'Status': 'completed',
'End': now,
'LastUpdated': now
}}
)
# Update item status to available
borrower_name = str(item_doc.get('User') or '').strip() or ''
it.update_item_status(item_id, True, borrower_name)
_append_audit_event_standalone(
event_type='ausleihung_returned_by_code',
payload={
'channel': 'library_return_code',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'performed_by': session.get('username')
}
)
return jsonify({
'ok': True,
'action': 'returned',
'item_id': item_id,
'item_name': item_doc.get('Name', ''),
'completed_records': update_result.modified_count,
'message': f"{item_doc.get('Name', 'Medium')} wurde zurückgegeben."
}), 200
except Exception as e:
app.logger.error(f"Error in library return by code: {e}")
return jsonify({'ok': False, 'message': 'Fehler beim Verarbeiten der Rückgabe.'}), 500
finally:
if client:
client.close()
@app.route('/api/library_scan_action', methods=['POST']) @app.route('/api/library_scan_action', methods=['POST'])
def api_library_scan_action(): def api_library_scan_action():
@@ -4320,6 +4290,10 @@ def api_library_scan_action():
normalized_isbn = normalize_and_validate_isbn(item_code_raw) normalized_isbn = normalize_and_validate_isbn(item_code_raw)
normalized_code = item_code_raw.upper() normalized_code = item_code_raw.upper()
if us.student_is_blocked(student_card_id):
flash("User Blockiert!", "error")
return jsonify({'ok': False, 'message': 'User Blockiert.'}), 404
client = None client = None
try: try:
client = MongoClient(MONGODB_HOST, MONGODB_PORT) client = MongoClient(MONGODB_HOST, MONGODB_PORT)
@@ -4373,8 +4347,10 @@ def api_library_scan_action():
card_default = cfg.STUDENT_DEFAULT_BORROW_DAYS card_default = cfg.STUDENT_DEFAULT_BORROW_DAYS
borrow_duration_days = max(1, min(card_default, cfg.STUDENT_MAX_BORROW_DAYS)) borrow_duration_days = max(1, min(card_default, cfg.STUDENT_MAX_BORROW_DAYS))
due_date = now + datetime.timedelta(days=borrow_duration_days)
it.update_item_status(item_id, False, borrower_name) it.update_item_status(item_id, False, borrower_name)
au.add_ausleihung(item_id, borrower_name, now) au.add_ausleihung(item_id, borrower_name, now, due_date)
_append_audit_event_standalone( _append_audit_event_standalone(
event_type='ausleihung_borrowed', event_type='ausleihung_borrowed',
+5 -1
View File
@@ -34,6 +34,7 @@ import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
import Web.modules.inventarsystem.data_protection as dp import Web.modules.inventarsystem.data_protection as dp
import Web.modules.database.user as us import Web.modules.database.user as us
from zoneinfo import ZoneInfo
def _get_client(): def _get_client():
@@ -184,7 +185,7 @@ def create_backup_database():
# === AUSLEIHUNG MANAGEMENT === # === AUSLEIHUNG MANAGEMENT ===
def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="active", period=None, exemplar_data=None): def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="active", period=None, exemplar_data=None, due_date=None):
""" """
Add a new borrowing record for an item. Add a new borrowing record for an item.
@@ -225,6 +226,9 @@ def add_ausleihung(item_id, user, start_date, end_date=None, notes="", status="a
if exemplar_data: if exemplar_data:
ausleihung['ExemplarData'] = exemplar_data ausleihung['ExemplarData'] = exemplar_data
if due_date:
ausleihung['DueDate'] = due_date
result = ausleihungen.insert_one(ausleihung) result = ausleihungen.insert_one(ausleihung)
ausleihung_id = result.inserted_id ausleihung_id = result.inserted_id
+13 -12
View File
@@ -26,6 +26,7 @@ import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
import Web.modules.inventarsystem.data_protection as dp import Web.modules.inventarsystem.data_protection as dp
import logging import logging
from zoneinfo import ZoneInfo
def is_library_item(item): def is_library_item(item):
@@ -177,8 +178,8 @@ def add_item(name, ort, beschreibung, images=None, filter=None, filter2=None, fi
'SeriesPosition': series_position, 'SeriesPosition': series_position,
'IsGroupedSubItem': is_grouped_sub_item, 'IsGroupedSubItem': is_grouped_sub_item,
'ParentItemId': parent_item_id, 'ParentItemId': parent_item_id,
'Created': datetime.datetime.now(), 'Created': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
result = items.insert_one(item) result = items.insert_one(item)
item_id = result.inserted_id item_id = result.inserted_id
@@ -208,8 +209,8 @@ def remove_item(id):
{'_id': ObjectId(id), 'Deleted': {'$ne': True}}, {'_id': ObjectId(id), 'Deleted': {'$ne': True}},
{'$set': { {'$set': {
'Deleted': True, 'Deleted': True,
'DeletedAt': datetime.datetime.now(), 'DeletedAt': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'LastUpdated': datetime.datetime.now(), 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'Verfuegbar': False, 'Verfuegbar': False,
}} }}
) )
@@ -304,7 +305,7 @@ def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter
'is_library': is_lib, 'is_library': is_lib,
'library_category': library_category, 'library_category': library_category,
'Verfuegbar': bool(verfuegbar), 'Verfuegbar': bool(verfuegbar),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
specific_update = shared_update.copy() specific_update = shared_update.copy()
@@ -346,7 +347,7 @@ def update_item_status(id, verfuegbar, user=None):
update_data = { update_data = {
'Verfuegbar': verfuegbar, 'Verfuegbar': verfuegbar,
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
update_query = {'$set': update_data} update_query = {'$set': update_data}
@@ -387,7 +388,7 @@ def update_item_exemplare_status(id, exemplare_status):
update_data = { update_data = {
'ExemplareStatus': exemplare_status, 'ExemplareStatus': exemplare_status,
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
result = items.update_one( result = items.update_one(
@@ -734,7 +735,7 @@ def unstuck_item(id):
{'$set': { {'$set': {
'Status': 'cancelled', 'Status': 'cancelled',
'CancelledReason': 'unstuck_reset', 'CancelledReason': 'unstuck_reset',
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
}} }}
) )
@@ -745,7 +746,7 @@ def unstuck_item(id):
{ {
'$set': { '$set': {
'Verfuegbar': True, 'Verfuegbar': True,
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
}, },
'$unset': {'User': ""} '$unset': {'User': ""}
} }
@@ -1075,7 +1076,7 @@ def update_item_next_appointment(item_id, appointment_data):
if appointment_data is None: if appointment_data is None:
update_query = { update_query = {
'$unset': {'NextAppointment': ""}, '$unset': {'NextAppointment': ""},
'$set': {'LastUpdated': datetime.datetime.now()} '$set': {'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))}
} }
else: else:
# Create a copy so we don't mutate the original dictionary passed in # Create a copy so we don't mutate the original dictionary passed in
@@ -1088,7 +1089,7 @@ def update_item_next_appointment(item_id, appointment_data):
update_query = { update_query = {
'$set': { '$set': {
'NextAppointment': data_to_save, 'NextAppointment': data_to_save,
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
} }
@@ -1121,7 +1122,7 @@ def clear_item_next_appointment(item_id):
result = items.update_one( result = items.update_one(
{'_id': ObjectId(item_id)}, {'_id': ObjectId(item_id)},
{'$unset': {'NextAppointment': ""}, '$set': {'LastUpdated': datetime.datetime.now()}} {'$unset': {'NextAppointment': ""}, '$set': {'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))}}
) )
client.close() client.close()
+5 -4
View File
@@ -23,6 +23,7 @@ from Web.modules.database.settings import MongoClient
from bson.objectid import ObjectId from bson.objectid import ObjectId
import datetime import datetime
import ast import ast
from zoneinfo import ZoneInfo
def _get_tenant_db(client): def _get_tenant_db(client):
@@ -99,8 +100,8 @@ def add(date_start: str, date_end: str, time_span: list, slots: int, slot_lenght
'calendar_enabled': bool(calendar_enabled), 'calendar_enabled': bool(calendar_enabled),
'clients_per_slot': clients_p_slot, 'clients_per_slot': clients_p_slot,
'slots_booked': [], # -> [(start_time, (names),(custom1, custom2,...)), ...]the list gets there indexes as the slot 1-defined so is can be counted without an extra variable 'slots_booked': [], # -> [(start_time, (names),(custom1, custom2,...)), ...]the list gets there indexes as the slot 1-defined so is can be counted without an extra variable
'Created': datetime.datetime.now(), 'Created': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
result = items.insert_one(item) result = items.insert_one(item)
return result.inserted_id return result.inserted_id
@@ -138,7 +139,7 @@ def update(id, slots_used: list):
update_data = { update_data = {
'slots_booked': dp.encrypt_text(str(slots_used)), 'slots_booked': dp.encrypt_text(str(slots_used)),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
result = items.update_one( result = items.update_one(
@@ -200,7 +201,7 @@ def remove_slot(id, date_start_time, name):
{ {
'$set': { '$set': {
'slots_booked': dp.encrypt_text(str(updated_slots)), 'slots_booked': dp.encrypt_text(str(updated_slots)),
'LastUpdated': datetime.datetime.now() 'LastUpdated': datetime.datetime.now(ZoneInfo("Europe/Berlin"))
} }
} }
) )
+81
View File
@@ -740,6 +740,87 @@ def get_user_by_student_ident(student_ident):
return None return None
def get_user_by_student_name(student_name):
"""Return user dict by student name by decrypting all cards and matching."""
if not student_name:
return None
# Normalisiere den Suchbegriff, den wir finden wollen
normalized_target = str(student_name).strip()
if not normalized_target:
return None
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try:
db = _get_tenant_db(client)
all_student_cards = db['student_cards'].find()
for user_doc in all_student_cards:
encrypted_student_name = user_doc.get('SchülerName')
if encrypted_student_name:
try:
decrypted_ident = dp.decrypt_text(encrypted_student_name)
if decrypted_ident and str(decrypted_ident).strip() == normalized_target:
return user_doc
except Exception as e:
logger.error(f"Entschlüsselungsfehler bei ID {user_doc.get('_id')}: {e}")
continue
except Exception as exc:
logger.error(f"Datenbankfehler in get_user_by_student_name: {exc}")
finally:
client.close()
return None
def student_is_blocked(student_id):
"""
Überprüft, ob ein Schüler (Nutzer) im System gesperrt ist (z. B. aufgrund von überfälligen Ausleihen).
Akzeptiert entweder den Benutzernamen oder die Schülerausweis-ID.
Rückgabewert:
True, wenn der Nutzer gesperrt ist.
False, wenn der Nutzer nicht gesperrt ist oder nicht gefunden wurde.
"""
if not student_id:
return False
normalized_card_id = normalize_student_card_id(student_id)
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
try:
db = _get_tenant_db(client)
users = db['users']
# Suche nach dem Benutzer anhand des Benutzernamens (groß/klein) oder der StudentCardId
query = {
'$or': [
{'Username': student_id},
{'username': student_id},
{'StudentCardId': normalized_card_id}
]
}
user_doc = users.find_one(query)
if user_doc:
# Lese das Feld 'is_blocked' aus, standardmäßig False, falls es nicht existiert
return bool(user_doc.get('is_blocked', False))
# Wenn kein Nutzer gefunden wurde, gehen wir sicherheitshalber davon aus, dass keine Sperre vorliegt
return False
except Exception as e:
logger.error(f"Datenbankfehler in student_is_blocked: {e}")
# Im Fehlerfall False zurückgeben, um Systemblockaden durch Datenbankfehler zu vermeiden
return False
finally:
client.close()
def make_admin(username): def make_admin(username):
"""Grant administrator privileges to a user.""" """Grant administrator privileges to a user."""
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
+5 -4
View File
@@ -10,6 +10,7 @@ from bson import ObjectId
import requests import requests
import hashlib import hashlib
import logging import logging
from zoneinfo import ZoneInfo
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
@@ -153,7 +154,7 @@ def save_push_subscription(username, subscription_obj):
subs_col.update_one( subs_col.update_one(
{'_id': existing['_id']}, {'_id': existing['_id']},
{'$set': { {'$set': {
'LastUsed': datetime.datetime.now(), 'LastUsed': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'IsActive': True 'IsActive': True
}} }}
) )
@@ -172,8 +173,8 @@ def save_push_subscription(username, subscription_obj):
'Keys': encrypt_text(keys_str), 'Keys': encrypt_text(keys_str),
'SubscriptionHash': sub_hash, 'SubscriptionHash': sub_hash,
'IsActive': True, 'IsActive': True,
'CreatedAt': datetime.datetime.now(), 'CreatedAt': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'LastUsed': datetime.datetime.now(), 'LastUsed': datetime.datetime.now(ZoneInfo("Europe/Berlin")),
'UserAgent': subscription_obj.get('userAgent', ''), 'UserAgent': subscription_obj.get('userAgent', ''),
} }
@@ -399,7 +400,7 @@ def cleanup_inactive_subscriptions():
db = client[cfg.MONGODB_DB] db = client[cfg.MONGODB_DB]
subs_col = get_push_subscriptions_collection(db) subs_col = get_push_subscriptions_collection(db)
cutoff_date = datetime.datetime.now() - datetime.timedelta(days=30) cutoff_date = datetime.datetime.now(ZoneInfo("Europe/Berlin")) - datetime.timedelta(days=30)
result = subs_col.delete_many({ result = subs_col.delete_many({
'IsActive': False, 'IsActive': False,
+3 -2
View File
@@ -17,6 +17,7 @@ import re
import ipaddress import ipaddress
import Web.modules.database.settings as cfg import Web.modules.database.settings as cfg
from Web.modules.database.settings import MongoClient from Web.modules.database.settings import MongoClient
from zoneinfo import ZoneInfo
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -325,7 +326,7 @@ def get_tenant_trial_status(tenant_id=None, now=None):
"expires_after_days", "ttl_days", or "days". "expires_after_days", "ttl_days", or "days".
""" """
trial_config = get_tenant_trial_config(tenant_id) trial_config = get_tenant_trial_config(tenant_id)
now = now or datetime.datetime.now() now = now or datetime.datetime.now(ZoneInfo("Europe/Berlin"))
enabled = bool(trial_config.get('enabled') or trial_config.get('active')) enabled = bool(trial_config.get('enabled') or trial_config.get('active'))
if not enabled: if not enabled:
@@ -447,7 +448,7 @@ def delete_tenant(tenant_id, *, drop_database=True, remove_from_config=True):
def purge_expired_trial_tenants(now=None): def purge_expired_trial_tenants(now=None):
"""Delete expired trial tenants that opted into auto-delete.""" """Delete expired trial tenants that opted into auto-delete."""
now = now or datetime.datetime.now() now = now or datetime.datetime.now(ZoneInfo("Europe/Berlin"))
purged_tenants = [] purged_tenants = []
for tenant_id in list(TENANT_REGISTRY.keys()): for tenant_id in list(TENANT_REGISTRY.keys()):