import logging import os from urllib.parse import urljoin from django.conf import settings from django.core.files.uploadedfile import UploadedFile from django.db import ProgrammingError from django.db.models.signals import post_migrate from django.dispatch import receiver from django.utils.cache import caches cache = caches['config'] logger = logging.getLogger('palanaeum.admin') CONFIG_ENTRIES = { # key: (type, default value) 'page_title': ('str', 'Palanaeum'), 'default_page_length': ('int', 20), 'approval_message': ('str', 'Reviewed'), 'review_pending_explanation': ('str', 'This event is waiting for review. The information here might not be correct.'), 'review_reviewed_explanation': ('str', 'This event has been reviewed. Information presented here is correct.'), 'index_hello': ('str', 'Welcome to Palanaeum, the free transcription and archiving platform!'), 'google_analytics': ('str', ''), 'audio_keep_original_file': ('bool', True), 'audio_quality': ('str', '128k'), 'cloud_backend': ('str', ''), 'cloud_login': ('str', ''), 'cloud_passwd': ('str', ''), 'cloud_b2_bucket_id': ('str', ''), 'audio_staff_size_limit': ('int', 1000), 'audio_user_size_limit': ('int', 100), 'image_size_limit': ('int', 10), 'logo_file': ('file', ''), 'favicon16': ('file', ''), 'favicon32': ('file', ''), 'favicon96': ('file', ''), 'favicon120': ('file', ''), 'favicon152': ('file', ''), 'favicon167': ('file', ''), 'favicon180': ('file', ''), 'favicon200': ('file', ''), } @receiver(post_migrate) def config_update(*args, **kwargs): from palanaeum.models import ConfigEntry for key, val in CONFIG_ENTRIES.items(): ConfigEntry.objects.get_or_create(key=key, defaults={'value': _serialize_value(key, val[1])}) def _deserialize_value(key, value): value_type = CONFIG_ENTRIES[key][0] if value_type == 'str': return str(value) elif value_type == 'int': return int(value) elif value_type == 'bool': return value == '1' elif value_type == 'file': return urljoin(settings.MEDIA_URL, value) if value else None else: raise NotImplementedError def _serialize_value(key, value): value_type = CONFIG_ENTRIES[key][0] if value_type in ('str', 'file', 'int'): return str(value) elif value_type == 'bool': return '1' if value else '0' else: raise NotImplementedError def get_config(key: str): from palanaeum.models import ConfigEntry value = cache.get(key) if value is not None: return _deserialize_value(key, value) try: value = ConfigEntry.objects.get(key=key).value cache.set(key, value) except ConfigEntry.DoesNotExist: value = CONFIG_ENTRIES[key][1] except ProgrammingError: logger.exception("Can't get config entry for %s.", key) return None return _deserialize_value(key, value) def set_config(key: str, value): from palanaeum.models import ConfigEntry value = _serialize_value(key, value) ConfigEntry.objects.update_or_create(key=key, defaults={'value': value}) cache.set(key, value) return value def set_config_file(key: str, file: UploadedFile): from palanaeum.models import ConfigEntry try: entry = ConfigEntry.objects.get(key=key) if entry.value: os.unlink(os.path.join(settings.MEDIA_ROOT, entry.value)) except ConfigEntry.DoesNotExist: entry = ConfigEntry(key=key) except FileNotFoundError: pass file_path = os.path.join(settings.CONFIG_UPLOADS, file.name) os.makedirs(settings.CONFIG_UPLOADS, exist_ok=True) entry.value = os.path.relpath(file_path, settings.MEDIA_ROOT) with open(file_path, mode='wb') as write_file: for chunk in file.chunks(): write_file.write(chunk) entry.save() cache.set(key, entry.value) return def get_config_dict(): from palanaeum.models import ConfigEntry return { entry.key: _deserialize_value(entry.key, entry.value) for entry in ConfigEntry.objects.all() }