""" Django settings for core project. Generated by 'django-admin startproject' using Django 6.0.7. """ from pathlib import Path import os from django.core.exceptions import ImproperlyConfigured from django.templatetags.static import static from django.urls import reverse, reverse_lazy # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent def env_bool(name, default=False): return os.getenv(name, str(default)).strip().lower() in {"1", "true", "yes", "on"} def env_list(name, default=""): return [value.strip() for value in os.getenv(name, default).split(",") if value.strip()] def load_local_env(path): """Load a simple KEY=VALUE .env file without overriding server variables.""" if not path.exists(): return for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'")) load_local_env(BASE_DIR / ".env") # Quick-start development settings - unsuitable for production DEVELOPMENT_SECRET_KEY = 'django-insecure-development-only-change-me' SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', DEVELOPMENT_SECRET_KEY) DEBUG = env_bool('DJANGO_DEBUG', True) ALLOWED_HOSTS = env_list('DJANGO_ALLOWED_HOSTS', '127.0.0.1,localhost') if not DEBUG and SECRET_KEY == DEVELOPMENT_SECRET_KEY: raise ImproperlyConfigured("DJANGO_SECRET_KEY must be set to a secure value in production.") # Application definition INSTALLED_APPS = [ 'unfold', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'allauth', 'allauth.account', 'allauth.socialaccount', 'allauth.socialaccount.providers.google', 'exams', 'accounts.apps.AccountsConfig', ] # Keep the back office clean and close to Unfold's maintained defaults. UNFOLD = { "SITE_TITLE": "Testpoint Admin", "SITE_HEADER": "Testpoint", "SITE_ICON": lambda request: static("images/favicon.svg"), "SITE_SYMBOL": "menu_book", "SHOW_HISTORY": True, "SHOW_VIEW_ON_SITE": True, "DASHBOARD_CALLBACK": "core.admin_dashboard.dashboard_callback", "STYLES": [lambda request: "/static/css/unfold-dashboard.css"], "SIDEBAR": { "show_search": True, "show_all_applications": False, "navigation": [ { "title": "Navigation", "separator": True, "items": [ {"title": "Dashboard", "icon": "dashboard", "link": reverse_lazy("admin:index")}, {"title": "Tests", "icon": "description", "link": reverse_lazy("admin:exams_examset_changelist")}, {"title": "Questions", "icon": "quiz", "link": reverse_lazy("admin:exams_question_changelist")}, {"title": "Results", "icon": "monitoring", "link": reverse_lazy("admin:exams_studentattempt_changelist")}, ], }, { "title": "Create a test", "collapsible": True, "items": [ {"title": "Reading test", "icon": "menu_book", "link": lambda request: reverse("admin:exams_examset_add") + "?category=reading"}, {"title": "Listening test", "icon": "headphones", "link": lambda request: reverse("admin:exams_examset_add") + "?category=listening"}, {"title": "Writing test", "icon": "edit_note", "link": lambda request: reverse("admin:exams_examset_add") + "?category=writing"}, {"title": "Speaking test", "icon": "mic", "link": lambda request: reverse("admin:exams_examset_add") + "?category=speaking"}, {"title": "Full Mock Test", "icon": "assignment", "link": lambda request: reverse("admin:exams_examset_add") + "?category=full"}, ], }, { "title": "Import content", "collapsible": True, "items": [ { "title": "Import Excel workbook", "icon": "upload_file", "link": reverse_lazy("admin:exams_examset_import_excel"), }, ], }, { "title": "Users & settings", "separator": True, "items": [ {"title": "Users", "icon": "group", "link": reverse_lazy("admin:auth_user_changelist")}, {"title": "Student profiles", "icon": "school", "link": reverse_lazy("admin:accounts_studentprofile_changelist")}, {"title": "Site settings", "icon": "settings", "link": reverse_lazy("admin:sites_site_changelist")}, ], }, ], }, "COLORS": {"primary": { "50": "oklch(97.2% .016 251)", "100": "oklch(93.8% .043 251)", "200": "oklch(88.8% .086 251)", "300": "oklch(80.7% .14 251)", "400": "oklch(70.4% .177 251)", "500": "oklch(60.3% .195 251)", "600": "oklch(52.7% .19 251)", "700": "oklch(45.2% .16 251)", "800": "oklch(38.2% .127 251)", "900": "oklch(32.5% .091 251)", "950": "oklch(23.8% .057 251)" }}, } AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', ] SITE_ID = 1 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'allauth.account.middleware.AccountMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] if not DEBUG: MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') ROOT_URLCONF = 'core.urls' TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "core.context_processors.site_settings", ], }, }, ] # Database DATABASE_URL = os.getenv('DATABASE_URL') if DATABASE_URL: import dj_database_url database_ssl_default = not DEBUG and DATABASE_URL.startswith( ("postgres://", "postgresql://") ) DATABASES = { 'default': dj_database_url.parse( DATABASE_URL, conn_max_age=600, conn_health_checks=True, ssl_require=env_bool('DJANGO_DB_SSL_REQUIRE', database_ssl_default), ) } if DATABASES['default']['ENGINE'] == 'django.db.backends.mysql': mysql_options = DATABASES['default'].setdefault('OPTIONS', {}) mysql_options.setdefault('charset', 'utf8mb4') mysql_options.setdefault('init_command', "SET sql_mode='STRICT_TRANS_TABLES'") else: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation 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 LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Tashkent' USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) STATIC_URL = '/static/' # Where collectstatic will put files STATIC_ROOT = BASE_DIR / 'staticfiles' # Extra places Django will look for static files (your app-level static folders) STATICFILES_DIRS = [ BASE_DIR / "static", ] STORAGES = { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, "staticfiles": { "BACKEND": ( "django.contrib.staticfiles.storage.StaticFilesStorage" if DEBUG else "whitenoise.storage.CompressedManifestStaticFilesStorage" ), }, } # Media files MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' DATA_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024 FILE_UPLOAD_MAX_MEMORY_SIZE = 2_621_440 # Authentication LOGIN_REDIRECT_URL = '/accounts/dashboard/' ACCOUNT_LOGOUT_REDIRECT_URL = '/' ACCOUNT_EMAIL_VERIFICATION = os.getenv( 'DJANGO_ACCOUNT_EMAIL_VERIFICATION', 'mandatory' if not DEBUG else 'none', ) ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_LOGIN_METHODS = {'email'} ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*'] EMAIL_BACKEND = os.getenv( 'DJANGO_EMAIL_BACKEND', ( 'django.core.mail.backends.console.EmailBackend' if DEBUG else 'django.core.mail.backends.smtp.EmailBackend' ), ) EMAIL_HOST = os.getenv('DJANGO_EMAIL_HOST', 'localhost') EMAIL_PORT = int(os.getenv('DJANGO_EMAIL_PORT', '587')) EMAIL_HOST_USER = os.getenv('DJANGO_EMAIL_HOST_USER', '') EMAIL_HOST_PASSWORD = os.getenv('DJANGO_EMAIL_HOST_PASSWORD', '') EMAIL_USE_TLS = env_bool('DJANGO_EMAIL_USE_TLS', True) DEFAULT_FROM_EMAIL = os.getenv('DJANGO_DEFAULT_FROM_EMAIL', 'Testpoint ') SERVER_EMAIL = os.getenv('DJANGO_SERVER_EMAIL', DEFAULT_FROM_EMAIL) SUPPORT_EMAIL = os.getenv('DJANGO_SUPPORT_EMAIL', 'support@ieltsmock.com') SOCIAL_FACEBOOK_URL = os.getenv('SOCIAL_FACEBOOK_URL', '') SOCIAL_INSTAGRAM_URL = os.getenv('SOCIAL_INSTAGRAM_URL', '') SOCIAL_YOUTUBE_URL = os.getenv('SOCIAL_YOUTUBE_URL', '') SOCIAL_TELEGRAM_URL = os.getenv('SOCIAL_TELEGRAM_URL', '') SOCIALACCOUNT_PROVIDERS = { 'google': { 'SCOPE': ['profile', 'email'], 'AUTH_PARAMS': {'access_type': 'online'}, } } # Production security. These remain development-friendly while DEBUG=True. CSRF_TRUSTED_ORIGINS = env_list('DJANGO_CSRF_TRUSTED_ORIGINS') SECURE_SSL_REDIRECT = env_bool('DJANGO_SECURE_SSL_REDIRECT', not DEBUG) SESSION_COOKIE_SECURE = not DEBUG CSRF_COOKIE_SECURE = not DEBUG SECURE_HSTS_SECONDS = int(os.getenv('DJANGO_SECURE_HSTS_SECONDS', '3600' if not DEBUG else '0')) SECURE_HSTS_INCLUDE_SUBDOMAINS = env_bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', False) SECURE_HSTS_PRELOAD = env_bool('DJANGO_SECURE_HSTS_PRELOAD', False) SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = 'DENY' if env_bool('DJANGO_TRUST_PROXY_HEADERS', False): SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') LOG_LEVEL = os.getenv('DJANGO_LOG_LEVEL', 'INFO') LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '{asctime} {levelname} {name}: {message}', 'style': '{', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'standard', }, }, 'root': {'handlers': ['console'], 'level': LOG_LEVEL}, 'loggers': { 'django.request': { 'handlers': ['console'], 'level': 'WARNING', 'propagate': False, }, }, }