Initial commit

This commit is contained in:
kddd21a
2026-07-29 10:47:40 +00:00
commit 402c3ec6ed
171 changed files with 10536 additions and 0 deletions
+322
View File
@@ -0,0 +1,322 @@
"""
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 <noreply@localhost>')
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,
},
},
}