""" Django settings for mysite project. Generated by 'django-admin startproject' using Django 5.2.7. For more information on this file, see https://docs.djangoproject.com/en/5.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.2/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os import socket try: import certifi except Exception: certifi = None PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(PROJECT_DIR) # Ensure Python SSL always has a CA bundle unless caller explicitly sets one. if not os.environ.get("SSL_CERT_FILE") and certifi is not None: os.environ["SSL_CERT_FILE"] = certifi.where() def env_list(name, default): """ Return a list from a comma-separated env var; fall back to provided default list. """ value = os.environ.get(name) if value: return [item.strip() for item in value.split(",") if item.strip()] return default def env_bool(name, default=False): value = os.environ.get(name) if value is None: return default return value.strip().lower() in {"1", "true", "yes", "on"} def env_optional(name, default=None): value = os.environ.get(name) if value is None: return default normalized = value.strip() if normalized == "" or normalized.lower() in {"none", "null"}: return None return normalized def build_media_storage_options(): options = { "access_key": os.environ.get("AWS_ACCESS_KEY_ID"), "secret_key": os.environ.get("AWS_SECRET_ACCESS_KEY"), "bucket_name": os.environ.get("AWS_STORAGE_BUCKET_NAME"), "region_name": os.environ.get("AWS_S3_REGION_NAME", default="us-east-1"), "default_acl": env_optional("AWS_S3_DEFAULT_ACL"), "querystring_auth": env_bool("AWS_S3_QUERYSTRING_AUTH", default=True), "custom_domain": env_optional("AWS_S3_CUSTOM_DOMAIN"), "url_protocol": env_optional("AWS_S3_URL_PROTOCOL", default="https:"), } endpoint_url = env_optional("AWS_S3_ENDPOINT_URL") if endpoint_url: options["endpoint_url"] = endpoint_url addressing_style = env_optional("AWS_S3_ADDRESSING_STYLE") if addressing_style: options["addressing_style"] = addressing_style return options def detect_private_ip(): """ Return the primary private IPv4 address for this container when available. """ configured_ip = os.environ.get("PRIVATE_IP", "").strip() if configured_ip: return configured_ip sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # No packets are sent; this asks the OS which interface would be used. sock.connect(("10.255.255.255", 1)) return sock.getsockname()[0] except OSError: return "" finally: sock.close() def build_allowed_hosts(): hosts = env_list("ALLOWED_HOSTS", default=[]) private_ip = detect_private_ip() for host in ("localhost", "127.0.0.1", private_ip): if host and host not in hosts: hosts.append(host) return hosts GA4_MEASUREMENT_ID = os.environ.get("GA4_MEASUREMENT_ID", "").strip() SECRET_KEY = os.environ.get("SECRET_KEY", "").strip() # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # Application definition INSTALLED_APPS = [ "home", "search", "wagtail.contrib.forms", "wagtail.contrib.redirects", "wagtail.contrib.sitemaps", "wagtail.contrib.settings", "wagtail.embeds", "wagtail.sites", "wagtail.users", "wagtail.snippets", "wagtail.documents", "wagtail.images", "wagtail.search", "wagtail.admin", "wagtail", "modelcluster", "taggit", "django_filters", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.sitemaps", "base", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] ROOT_URLCONF = "mysite.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [ os.path.join(PROJECT_DIR, "templates"), ], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "wagtail.contrib.settings.context_processors.settings", "home.context_processors.navigation_pages", "mysite.context_processors.ga4", "mysite.context_processors.default_og_image", ], }, }, ] WSGI_APPLICATION = "mysite.wsgi.application" # Database # https://docs.djangoproject.com/en/5.2/ref/settings/#databases import dj_database_url DATABASES = { 'default': dj_database_url.config() } # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ LANGUAGE_CODE = "zh-hant" TIME_ZONE = "Asia/Taipei" USE_I18N = True USE_TZ = True LOCALE_PATHS = [ os.path.join(BASE_DIR, "locale"), ] # --- Wagtail embeds (Instagram/FB via Graph oEmbed) --- # Reads a token from env and adds an extra oEmbed finder for IG/FB. # Keeps the default oEmbed finder first for YouTube/Vimeo/etc. WAGTAIL_EMBED_FINDERS = [ {"class": "wagtail.embeds.finders.oembed"}, { "class": "wagtail.embeds.finders.oembed", "options": { "providers": [ {"endpoint": "https://graph.facebook.com/v11.0/instagram_oembed", "urls": ["https://www.instagram.com/*"]}, {"endpoint": "https://graph.facebook.com/v11.0/oembed_post", "urls": ["https://www.facebook.com/*"]}, {"endpoint": "https://graph.facebook.com/v11.0/oembed_page", "urls": ["https://www.facebook.com/*"]}, {"endpoint": "https://graph.facebook.com/v11.0/oembed_video", "urls": ["https://www.facebook.com/*"]}, ], "params": { "access_token": os.environ.get("IG_OEMBED_ACCESS_TOKEN", ""), "omitscript": True, }, }, }, ] from django.utils.translation import gettext_lazy as _ LANGUAGES = [ ('en', _('English')), ('zh-hant', _('Traditional Chinese')) ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.2/howto/static-files/ STATICFILES_FINDERS = [ "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", ] STATICFILES_DIRS = [ os.path.join(PROJECT_DIR, "static"), ] STATIC_ROOT = os.path.join(BASE_DIR, "static") STATIC_URL = "/static/" MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_URL = ( (os.environ.get("MEDIA_URL", "").strip() if os.environ.get("MEDIA_URL") else "") or f'{os.environ.get("AWS_S3_ENDPOINT_URL")}/{os.environ.get("AWS_STORAGE_BUCKET_NAME")}/' ) # Default storage settings # See https://docs.djangoproject.com/en/5.2/ref/settings/#std-setting-STORAGES STORAGES = { "default": { "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", "OPTIONS": build_media_storage_options(), }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, } # Avoid overwriting user uploads when using S3 storage unless explicitly enabled via env AWS_S3_FILE_OVERWRITE = os.environ.get("AWS_S3_FILE_OVERWRITE", "False").lower() == "true" # Django sets a maximum of 1000 fields per form by default, but particularly complex page models # can exceed this limit within Wagtail's page editor. DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000 # Wagtail settings WAGTAIL_SITE_NAME = "mysite" # Search # https://docs.wagtail.org/en/stable/topics/search/backends.html WAGTAILSEARCH_BACKENDS = { "default": { "BACKEND": "wagtail.search.backends.database", } } # Base URL to use when referring to full URLs within the Wagtail admin backend - # e.g. in notification emails. Don't include '/admin' or a trailing slash WAGTAILADMIN_BASE_URL = "http://example.com" # Allowed file extensions for documents in the document library. # This can be omitted to allow all files, but note that this may present a security risk # if untrusted users are allowed to upload files - # see https://docs.wagtail.org/en/stable/advanced_topics/deploying.html#user-uploaded-files WAGTAILDOCS_EXTENSIONS = ['csv', 'docx', 'key', 'odt', 'pdf', 'pptx', 'rtf', 'txt', 'xlsx', 'zip'] CSRF_TRUSTED_ORIGINS = env_list( "CSRF_TRUSTED_ORIGINS", default=[] ) ALLOWED_HOSTS = build_allowed_hosts() LOGGING = { "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", }, }, "root": { "handlers": ["console"], "level": "INFO", }, "loggers": { "django": { "handlers": ["console"], "level": "INFO", "propagate": False, }, "django.request": { "handlers": ["console"], "level": "CRITICAL", "propagate": False, }, "wagtail": { "handlers": ["console"], "level": "INFO", "propagate": True, }, }, }