mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-08-01 16:29:07 -04:00
* Add new default light and dark themes to match AdventureLog styles * Allow different theme for site and maps Fixes #1130 * Update settings page to use translation keys for currency and map style descriptions * Add global flag to CollectionItineraryItemSerializer and implement map view persistence in frontend * Update appVersion to v0.12.0-main-052126 * Enhance permissions for shared collections and restrict publicity changes to owners only * Update appVersion to v0.12.1-beta-052126 * Add new localization strings and descriptions for various languages - Updated Spanish (es.json) with new map style and preferred currency descriptions. - Enhanced French (fr.json) with additional details for Strava activities and currency descriptions. - Added new strings in Hungarian (hu.json) for map style and preferred currency. - Included additional details in Italian (it.json) for Strava activities and currency descriptions. - Updated Japanese (ja.json) with new strings for map style and preferred currency. - Enhanced Korean (ko.json) with additional details for Strava activities and currency descriptions. - Added new strings in Dutch (nl.json) for map style and preferred currency. - Updated Norwegian (no.json) with new strings for map style and preferred currency. - Enhanced Polish (pl.json) with additional details for Strava activities and currency descriptions. - Updated Brazilian Portuguese (pt-br.json) with new map style and preferred currency descriptions. - Added new strings in Romanian (ro.json) for additional details and currency descriptions. - Enhanced Russian (ru.json) with new strings for map style and preferred currency. - Updated Slovak (sk.json) with additional details for map style and preferred currency. - Enhanced Swedish (sv.json) with new strings for map style and preferred currency. - Added new strings in Turkish (tr.json) for map style and preferred currency. - Updated Ukrainian (uk.json) with new strings for map style and preferred currency. - Enhanced Chinese (zh.json) with additional details for map style and preferred currency. * feat: Implement cloud billing system with subscription management - Added billing app with models, serializers, views, and admin configuration for Subscription. - Integrated Stripe for payment processing and subscription management. - Created middleware for cloud access control based on user subscriptions. - Developed frontend components for subscription status and billing management. - Updated settings to include cloud mode and Stripe configuration. - Added routes for billing and subscription management in the API. - Implemented user subscription creation and management on user registration and checkout. * feat: Implement media storage limit and usage tracking - Added media storage limit configuration in settings. - Introduced utility functions for managing media storage limits and usage. - Updated models to delete media files using storage backend. - Enhanced serializers to build media URLs dynamically. - Implemented media usage tracking in user views and added endpoint for fetching media usage. - Updated frontend to display media storage usage and limits. - Added translations for media storage related strings in multiple languages. * feat: Implement conditional throttling for various external services and update settings for rate limits
451 lines
18 KiB
Python
451 lines
18 KiB
Python
"""
|
|
AdventureLog Server settings
|
|
|
|
Reference:
|
|
- Django settings: https://docs.djangoproject.com/en/stable/ref/settings/
|
|
"""
|
|
|
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
import os
|
|
from dotenv import load_dotenv
|
|
from os import getenv
|
|
from pathlib import Path
|
|
from urllib.parse import urlparse
|
|
from publicsuffix2 import get_sld
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Environment & Paths
|
|
# ---------------------------------------------------------------------------
|
|
# Load environment variables from .env file early so getenv works everywhere.
|
|
load_dotenv()
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
|
|
|
# Quick-start development settings - unsuitable for production
|
|
# See Django deployment checklist for production hardening.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core Security & Debug
|
|
# ---------------------------------------------------------------------------
|
|
# SECURITY WARNING: keep the secret key used in production secret!
|
|
SECRET_KEY = getenv('SECRET_KEY')
|
|
|
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
DEBUG = getenv('DEBUG', 'true').lower() == 'true'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cloud Mode & Billing
|
|
# ---------------------------------------------------------------------------
|
|
CLOUD_MODE = getenv('CLOUD_MODE', 'false').lower() == 'true'
|
|
CLOUD_TRIAL_DAYS = int(getenv('CLOUD_TRIAL_DAYS', '30'))
|
|
STRIPE_SECRET_KEY = getenv('STRIPE_SECRET_KEY', '')
|
|
STRIPE_WEBHOOK_SECRET = getenv('STRIPE_WEBHOOK_SECRET', '')
|
|
STRIPE_PRICE_ID = getenv('STRIPE_PRICE_ID', '')
|
|
STRIPE_PUBLISHABLE_KEY = getenv('STRIPE_PUBLISHABLE_KEY', '')
|
|
|
|
# ALLOWED_HOSTS = [
|
|
# 'localhost',
|
|
# '127.0.0.1',
|
|
# 'server'
|
|
# ]
|
|
ALLOWED_HOSTS = ['*'] # In production, restrict to known hosts.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Installed Apps
|
|
# ---------------------------------------------------------------------------
|
|
INSTALLED_APPS = (
|
|
"allauth_ui",
|
|
'django.contrib.admin',
|
|
'django.contrib.auth',
|
|
'django.contrib.contenttypes',
|
|
'django.contrib.sessions',
|
|
'django.contrib.messages',
|
|
'django.contrib.staticfiles',
|
|
'storages',
|
|
'django.contrib.sites',
|
|
'rest_framework',
|
|
'rest_framework.authtoken',
|
|
'allauth',
|
|
'allauth.account',
|
|
'allauth.mfa',
|
|
'allauth.headless',
|
|
'allauth.socialaccount',
|
|
'allauth.socialaccount.providers.github',
|
|
'allauth.socialaccount.providers.openid_connect',
|
|
'invitations',
|
|
'drf_yasg',
|
|
'djmoney',
|
|
'corsheaders',
|
|
'adventures',
|
|
'worldtravel',
|
|
'users',
|
|
'billing.apps.BillingConfig',
|
|
'cloud.apps.CloudConfig',
|
|
'integrations',
|
|
'django.contrib.gis',
|
|
# 'achievements', # Not done yet, will be added later in a future update
|
|
'widget_tweaks',
|
|
'slippers',
|
|
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Middleware
|
|
# ---------------------------------------------------------------------------
|
|
MIDDLEWARE = (
|
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
|
'adventures.middleware.XSessionTokenMiddleware',
|
|
'adventures.middleware.DisableCSRFForSessionTokenMiddleware',
|
|
'adventures.middleware.DisableCSRFForAPIKeyMiddleware',
|
|
'adventures.middleware.DisableCSRFForMobileLoginSignup',
|
|
'corsheaders.middleware.CorsMiddleware',
|
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
'django.middleware.common.CommonMiddleware',
|
|
'adventures.middleware.OverrideHostMiddleware',
|
|
'django.middleware.csrf.CsrfViewMiddleware',
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
'cloud.middleware.CloudAccessMiddleware',
|
|
'django.contrib.messages.middleware.MessageMiddleware',
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
'allauth.account.middleware.AccountMiddleware',
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Caching
|
|
# ---------------------------------------------------------------------------
|
|
CACHES = {
|
|
'default': {
|
|
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
|
|
'LOCATION': '127.0.0.1:11211',
|
|
'TIMEOUT': 60 * 60 * 24, # Optional: 1 day cache
|
|
}
|
|
}
|
|
|
|
# For backwards compatibility for Django 1.8
|
|
MIDDLEWARE_CLASSES = MIDDLEWARE
|
|
|
|
ROOT_URLCONF = 'main.urls'
|
|
|
|
# WSGI_APPLICATION = 'demo.wsgi.application'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Database
|
|
# ---------------------------------------------------------------------------
|
|
# Using legacy PG environment variables for compatibility with existing setups
|
|
|
|
def env(*keys, default=None):
|
|
"""Return the first non-empty environment variable from a list of keys."""
|
|
for key in keys:
|
|
value = os.getenv(key)
|
|
if value:
|
|
return value
|
|
return default
|
|
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
|
'NAME': env('PGDATABASE', 'POSTGRES_DB'),
|
|
'USER': env('PGUSER', 'POSTGRES_USER'),
|
|
'PASSWORD': env('PGPASSWORD', 'POSTGRES_PASSWORD'),
|
|
'HOST': env('PGHOST', default='localhost'),
|
|
'PORT': int(env('PGPORT', default='5432')),
|
|
'OPTIONS': {
|
|
'sslmode': 'prefer', # Prefer SSL, but allow non-SSL connections
|
|
},
|
|
}
|
|
}
|
|
|
|
# Internationalization
|
|
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Internationalization
|
|
# ---------------------------------------------------------------------------
|
|
LANGUAGE_CODE = 'en-us'
|
|
TIME_ZONE = 'UTC'
|
|
USE_I18N = True
|
|
USE_L10N = True
|
|
USE_TZ = True
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Frontend URL & Cookies
|
|
# ---------------------------------------------------------------------------
|
|
# Derive frontend URL from environment and configure cookie behavior.
|
|
unParsedFrontenedUrl = getenv('FRONTEND_URL', 'http://localhost:3000')
|
|
FRONTEND_URL = unParsedFrontenedUrl.translate(str.maketrans('', '', '\'"'))
|
|
|
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
SESSION_COOKIE_NAME = 'sessionid'
|
|
|
|
# Secure cookies if frontend is served over HTTPS
|
|
SESSION_COOKIE_SECURE = FRONTEND_URL.startswith('https')
|
|
CSRF_COOKIE_SECURE = FRONTEND_URL.startswith('https')
|
|
|
|
# Dynamically determine cookie domain to support subdomains while avoiding IPs
|
|
hostname = urlparse(FRONTEND_URL).hostname
|
|
is_ip_address = hostname.replace('.', '').isdigit()
|
|
is_single_label = '.' not in hostname # single-label hostnames (e.g., "localhost")
|
|
|
|
if is_ip_address or is_single_label:
|
|
SESSION_COOKIE_DOMAIN = None
|
|
else:
|
|
cookie_domain = get_sld(hostname)
|
|
SESSION_COOKIE_DOMAIN = f".{cookie_domain}" if cookie_domain else hostname
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Static & Media Files
|
|
# ---------------------------------------------------------------------------
|
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
|
STATIC_URL = '/static/'
|
|
|
|
MEDIA_URL = '/media/'
|
|
MEDIA_ROOT = BASE_DIR / 'media' # Must match NGINX root for media serving
|
|
STATICFILES_DIRS = [BASE_DIR / 'static']
|
|
|
|
MEDIA_STORAGE_LIMIT_BYTES = int(getenv('MEDIA_STORAGE_LIMIT_BYTES', '0') or 0)
|
|
MEDIA_STORAGE_LIMIT_MB = int(getenv('MEDIA_STORAGE_LIMIT_MB', '0') or 0)
|
|
if MEDIA_STORAGE_LIMIT_BYTES <= 0 and MEDIA_STORAGE_LIMIT_MB > 0:
|
|
MEDIA_STORAGE_LIMIT_BYTES = MEDIA_STORAGE_LIMIT_MB * 1024 * 1024
|
|
|
|
MEDIA_STORAGE = getenv('MEDIA_STORAGE', 'local').lower()
|
|
USE_S3_MEDIA = MEDIA_STORAGE == 's3'
|
|
|
|
STORAGES = {
|
|
"staticfiles": {
|
|
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
|
},
|
|
"default": {
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
}
|
|
}
|
|
|
|
if USE_S3_MEDIA:
|
|
STORAGES["default"] = {
|
|
"BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
|
|
}
|
|
AWS_ACCESS_KEY_ID = getenv('AWS_ACCESS_KEY_ID')
|
|
AWS_SECRET_ACCESS_KEY = getenv('AWS_SECRET_ACCESS_KEY')
|
|
AWS_STORAGE_BUCKET_NAME = getenv('AWS_STORAGE_BUCKET_NAME')
|
|
AWS_S3_ENDPOINT_URL = getenv('AWS_S3_ENDPOINT_URL') or None
|
|
AWS_S3_REGION_NAME = getenv('AWS_S3_REGION_NAME') or None
|
|
AWS_S3_ADDRESSING_STYLE = getenv('AWS_S3_ADDRESSING_STYLE', 'path')
|
|
AWS_S3_SIGNATURE_VERSION = getenv('AWS_S3_SIGNATURE_VERSION', 's3v4')
|
|
AWS_S3_CUSTOM_DOMAIN = getenv('AWS_S3_CUSTOM_DOMAIN') or None
|
|
AWS_QUERYSTRING_AUTH = getenv('AWS_QUERYSTRING_AUTH', 'true').lower() == 'true'
|
|
AWS_QUERYSTRING_EXPIRE = int(getenv('AWS_QUERYSTRING_EXPIRE', '3600'))
|
|
AWS_S3_FILE_OVERWRITE = getenv('AWS_S3_FILE_OVERWRITE', 'true').lower() == 'true'
|
|
AWS_DEFAULT_ACL = None
|
|
|
|
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Templates
|
|
# ---------------------------------------------------------------------------
|
|
TEMPLATES = [
|
|
{
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
'DIRS': [os.path.join(BASE_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',
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
ALLAUTH_UI_THEME = "dim"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Authentication & Accounts
|
|
# ---------------------------------------------------------------------------
|
|
DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'false').lower() == 'true'
|
|
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')
|
|
|
|
SOCIALACCOUNT_ALLOW_SIGNUP = getenv('SOCIALACCOUNT_ALLOW_SIGNUP', 'false').lower() == 'true'
|
|
|
|
AUTH_USER_MODEL = 'users.CustomUser'
|
|
ACCOUNT_ADAPTER = 'users.adapters.CustomAccountAdapter'
|
|
INVITATIONS_ADAPTER = ACCOUNT_ADAPTER
|
|
INVITATIONS_ACCEPT_INVITE_AFTER_SIGNUP = True
|
|
INVITATIONS_EMAIL_SUBJECT_PREFIX = 'AdventureLog: '
|
|
SOCIALACCOUNT_ADAPTER = 'users.adapters.CustomSocialAccountAdapter'
|
|
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
|
|
|
|
SESSION_SAVE_EVERY_REQUEST = True
|
|
LOGIN_REDIRECT_URL = FRONTEND_URL # Redirect to frontend after login
|
|
|
|
SOCIALACCOUNT_LOGIN_ON_GET = True
|
|
INVITATIONS_INVITE_FORM = 'users.form_overrides.UseAdminInviteForm'
|
|
INVITATIONS_SIGNUP_REDIRECT_URL = f"{FRONTEND_URL}/signup"
|
|
|
|
HEADLESS_FRONTEND_URLS = {
|
|
"account_confirm_email": f"{FRONTEND_URL}/user/verify-email/{{key}}",
|
|
"account_reset_password": f"{FRONTEND_URL}/user/reset-password",
|
|
"account_reset_password_from_key": f"{FRONTEND_URL}/user/reset-password/{{key}}",
|
|
"account_signup": f"{FRONTEND_URL}/signup",
|
|
# Fallback if handshake with provider fails and `next` URL is lost.
|
|
"socialaccount_login_error": f"{FRONTEND_URL}/account/provider/callback",
|
|
}
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
'users.backends.NoPasswordAuthBackend',
|
|
# 'allauth.account.auth_backends.AuthenticationBackend',
|
|
# 'django.contrib.auth.backends.ModelBackend',
|
|
]
|
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
SITE_ID = 1
|
|
ACCOUNT_EMAIL_REQUIRED = True
|
|
ACCOUNT_UNIQUE_EMAIL = True
|
|
ACCOUNT_EMAIL_VERIFICATION = getenv('ACCOUNT_EMAIL_VERIFICATION', 'none') # 'none', 'optional', 'mandatory'
|
|
|
|
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
|
|
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True # Auto-link by email
|
|
SOCIALACCOUNT_AUTO_SIGNUP = True # Allow auto-signup post adapter checks
|
|
|
|
# Enable or disable app-level rate limiting/throttling globally.
|
|
# Defaults to disabled for local/dev convenience.
|
|
ENABLE_RATE_LIMITS = getenv('ENABLE_RATE_LIMITS', 'false').lower() == 'true'
|
|
|
|
# Centralized API rate limit defaults (override via env vars as needed).
|
|
RATE_LIMIT_RATES = {
|
|
'user': getenv('RATE_LIMIT_USER', '10000/hour'),
|
|
'image_proxy': getenv('RATE_LIMIT_IMAGE_PROXY', '60/minute'),
|
|
'image_import': getenv('RATE_LIMIT_IMAGE_IMPORT', '12/minute'),
|
|
'external_geocode': getenv('RATE_LIMIT_EXTERNAL_GEOCODE', '120/minute'),
|
|
'external_recommendations': getenv('RATE_LIMIT_EXTERNAL_RECOMMENDATIONS', '30/minute'),
|
|
'external_wikipedia': getenv('RATE_LIMIT_EXTERNAL_WIKIPEDIA', '60/minute'),
|
|
'external_sun_times': getenv('RATE_LIMIT_EXTERNAL_SUN_TIMES', '30/minute'),
|
|
}
|
|
|
|
FORCE_SOCIALACCOUNT_LOGIN = getenv('FORCE_SOCIALACCOUNT_LOGIN', 'false').lower() == 'true' # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured.
|
|
|
|
if getenv('EMAIL_BACKEND', 'console') == 'console':
|
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
else:
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = getenv('EMAIL_HOST')
|
|
EMAIL_USE_TLS = getenv('EMAIL_USE_TLS', 'true').lower() == 'true'
|
|
EMAIL_PORT = getenv('EMAIL_PORT', 587)
|
|
EMAIL_USE_SSL = getenv('EMAIL_USE_SSL', 'false').lower() == 'true'
|
|
EMAIL_HOST_USER = getenv('EMAIL_HOST_USER')
|
|
EMAIL_HOST_PASSWORD = getenv('EMAIL_HOST_PASSWORD')
|
|
DEFAULT_FROM_EMAIL = getenv('DEFAULT_FROM_EMAIL')
|
|
|
|
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
# EMAIL_HOST = 'smtp.resend.com'
|
|
# EMAIL_USE_TLS = False
|
|
# EMAIL_PORT = 2465
|
|
# EMAIL_USE_SSL = True
|
|
# EMAIL_HOST_USER = 'resend'
|
|
# EMAIL_HOST_PASSWORD = ''
|
|
# DEFAULT_FROM_EMAIL = 'mail@mail.user.com'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Account Rate Limits
|
|
# ---------------------------------------------------------------------------
|
|
# Configure rate limits for allauth authentication actions to prevent abuse
|
|
# Format: "action": "count/period/scope"
|
|
# Examples: "5/m/user" = 5 per minute per user, "20/m/ip" = 20 per minute per IP
|
|
ACCOUNT_RATE_LIMITS = {
|
|
"change_password": "5/m/user", # 5 password changes per minute per user
|
|
"change_phone": "1/m/user", # 1 phone change per minute per user
|
|
"manage_email": "10/m/user", # 10 email management actions per minute per user
|
|
"reset_password": "20/m/ip,5/m/key", # 20 per minute per IP, 5 per minute per email
|
|
"reauthenticate": "10/m/user", # 10 reauthentication attempts per minute per user
|
|
"reset_password_from_key": "20/m/ip", # 20 password resets per minute per IP
|
|
"signup": "20/m/ip", # 20 signups per minute per IP (prevents mass registration)
|
|
"login": "30/m/ip", # 30 login attempts per minute per IP
|
|
"login_failed": "10/m/ip,5/5m/key", # 10 failed logins per minute per IP, 5 per 5 min per user
|
|
"confirm_email": "1/3m/key", # 1 email confirmation per 3 minutes per email
|
|
} if ENABLE_RATE_LIMITS else {}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Django REST Framework
|
|
# ---------------------------------------------------------------------------
|
|
REST_FRAMEWORK = {
|
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
'users.authentication.APIKeyAuthentication',
|
|
'rest_framework.authentication.SessionAuthentication',
|
|
),
|
|
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',
|
|
'DEFAULT_THROTTLE_CLASSES': [
|
|
'rest_framework.throttling.UserRateThrottle',
|
|
] if ENABLE_RATE_LIMITS else [],
|
|
'DEFAULT_THROTTLE_RATES': RATE_LIMIT_RATES if ENABLE_RATE_LIMITS else {},
|
|
}
|
|
|
|
if DEBUG:
|
|
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = (
|
|
'rest_framework.renderers.JSONRenderer',
|
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
|
)
|
|
else:
|
|
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = (
|
|
'rest_framework.renderers.JSONRenderer',
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CORS & CSRF
|
|
# ---------------------------------------------------------------------------
|
|
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
|
|
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
|
|
CORS_ALLOW_CREDENTIALS = True
|
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Logging
|
|
# ---------------------------------------------------------------------------
|
|
LOGGING = {
|
|
'version': 1,
|
|
'disable_existing_loggers': False,
|
|
'handlers': {
|
|
'console': {
|
|
'class': 'logging.StreamHandler',
|
|
},
|
|
'file': {
|
|
'class': 'logging.FileHandler',
|
|
'filename': 'scheduler.log',
|
|
},
|
|
},
|
|
'root': {
|
|
'handlers': ['console', 'file'],
|
|
'level': 'INFO',
|
|
},
|
|
'loggers': {
|
|
'django': {
|
|
'handlers': ['console', 'file'],
|
|
'level': 'INFO',
|
|
'propagate': False,
|
|
},
|
|
},
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public URLs & Third-Party Integrations
|
|
# ---------------------------------------------------------------------------
|
|
PUBLIC_URL = getenv('PUBLIC_URL', 'http://localhost:8000')
|
|
|
|
# ADVENTURELOG_CDN_URL = getenv('ADVENTURELOG_CDN_URL', 'https://cdn.adventurelog.app')
|
|
|
|
# Major release version of AdventureLog, not including the patch version date.
|
|
ADVENTURELOG_RELEASE_VERSION = 'v0.12.1'
|
|
|
|
# https://github.com/dr5hn/countries-states-cities-database/tags
|
|
COUNTRY_REGION_JSON_VERSION = 'v3.1'
|
|
|
|
# External service keys (do not hardcode secrets)
|
|
GOOGLE_MAPS_API_KEY = getenv('GOOGLE_MAPS_API_KEY', '')
|
|
STRAVA_CLIENT_ID = getenv('STRAVA_CLIENT_ID', '')
|
|
STRAVA_CLIENT_SECRET = getenv('STRAVA_CLIENT_SECRET', '')
|