initial commit
This commit is contained in:
3
config/__init__.py
Normal file
3
config/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .services.celery import app as celery_app
|
||||
|
||||
__all__ = ["celery_app"]
|
||||
16
config/asgi.py
Normal file
16
config/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for config project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
1
config/services/__init__.py
Normal file
1
config/services/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Service configuration modules."""
|
||||
1
config/services/auditlog.py
Normal file
1
config/services/auditlog.py
Normal file
@@ -0,0 +1 @@
|
||||
AUDITLOG_INCLUDE_ALL_MODELS = True
|
||||
9
config/services/celery.py
Normal file
9
config/services/celery.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||
|
||||
app = Celery("qlockify")
|
||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
app.autodiscover_tasks()
|
||||
244
config/services/logging.py
Normal file
244
config/services/logging.py
Normal file
@@ -0,0 +1,244 @@
|
||||
import logging
|
||||
import logging.config
|
||||
import time
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
from pythonjsonlogger import jsonlogger
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parents[2]
|
||||
LOG_DIR = BASE_DIR / "logs"
|
||||
LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
# Custom formatter for console output (ERROR logs)
|
||||
class CustomConsoleFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
if isinstance(record.msg, dict):
|
||||
msg_dict = record.msg
|
||||
return (
|
||||
f"{record.levelname} {self.formatTime(record, self.datefmt)} "
|
||||
f"{msg_dict.get('request_method', '')} "
|
||||
f"{msg_dict.get('request_url', '')} "
|
||||
f"{msg_dict.get('status_code', '')} "
|
||||
f"{msg_dict.get('remote_addr', '')} "
|
||||
f"{msg_dict.get('duration_ms', 0)}ms "
|
||||
f"{msg_dict.get('message', record.getMessage())}"
|
||||
)
|
||||
|
||||
return super().format(record)
|
||||
|
||||
|
||||
# Custom formatter for INFO console output (simpler format)
|
||||
class CustomConsoleInfoFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
if isinstance(record.msg, dict):
|
||||
msg_dict = record.msg
|
||||
return (
|
||||
f"{record.levelname} {self.formatTime(record, self.datefmt)} "
|
||||
f"{msg_dict.get('request_method', '')} "
|
||||
f"{msg_dict.get('request_url', '')} "
|
||||
f"{msg_dict.get('status_code', '')} "
|
||||
f"{msg_dict.get('remote_addr', '')} "
|
||||
f"{msg_dict.get('duration_ms', 0)}ms"
|
||||
)
|
||||
|
||||
return super().format(record)
|
||||
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": True,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "{levelname} {asctime} {name} {module} {funcName} {lineno} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"json": {
|
||||
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
|
||||
"format": "%(levelname)s %(asctime)s %(name)s %(module)s %(funcName)s %(lineno)d %(message)s %(pathname)s",
|
||||
},
|
||||
"request_formatter": {
|
||||
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
|
||||
"format": "%(levelname)s %(asctime)s %(name)s %(message)s %(pathname)s %(lineno)d %(request_method)s %(request_url)s %(status_code)d %(remote_addr)s %(user_agent)s %(duration_ms)d", # noqa: E501
|
||||
},
|
||||
# Custom formatters for console
|
||||
"error_console": {
|
||||
"()": CustomConsoleFormatter,
|
||||
"format": "%(levelname)s %(asctime)s %(name)s %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
"info_console": {
|
||||
"()": CustomConsoleInfoFormatter,
|
||||
"format": "%(levelname)s %(asctime)s %(name)s %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"error_console": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "error_console",
|
||||
},
|
||||
"info_console": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "info_console",
|
||||
},
|
||||
"file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": str(LOG_DIR / "django.log"),
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "json",
|
||||
},
|
||||
"error_file": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": str(LOG_DIR / "errors.log"),
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 5,
|
||||
"formatter": "json",
|
||||
},
|
||||
"debug_file": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": str(LOG_DIR / "debug.log"),
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 5,
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"request_info_file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": str(LOG_DIR / "info.log"),
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "request_formatter",
|
||||
},
|
||||
"request_error_file": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": str(LOG_DIR / "errors.log"),
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "request_formatter",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"django.request": {
|
||||
"handlers": ["error_console", "error_file"],
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
"django.server": {
|
||||
"handlers": ["error_console"],
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
"myapp": {
|
||||
"handlers": ["console", "file", "debug_file", "error_file"],
|
||||
"level": "DEBUG",
|
||||
"propagate": False,
|
||||
},
|
||||
"project.requests.info": {
|
||||
"handlers": [
|
||||
"request_info_file",
|
||||
"info_console",
|
||||
], # Use info_console for INFO
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"project.requests.error": {
|
||||
"handlers": [
|
||||
"request_error_file",
|
||||
"error_console",
|
||||
], # Use error_console for ERROR
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
"common.exceptions": {
|
||||
"handlers": ["error_console", "error_file", "debug_file"],
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
"common.middleware": {
|
||||
"handlers": ["error_console", "error_file", "debug_file"],
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class RequestLoggingMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
self.info_logger = logging.getLogger("project.requests.info")
|
||||
self.error_logger = logging.getLogger("project.requests.error")
|
||||
|
||||
def __call__(self, request):
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
response = self.get_response(request)
|
||||
except Exception:
|
||||
duration_ms = int((time.perf_counter() - start) * 1000)
|
||||
message = f"HTTP {request.method} request to {request.get_full_path()}"
|
||||
log_data = {
|
||||
"request_method": request.method,
|
||||
"request_url": request.get_full_path(),
|
||||
"status_code": 500,
|
||||
"remote_addr": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
|
||||
"duration_ms": duration_ms,
|
||||
"message": message,
|
||||
}
|
||||
self.error_logger.exception(log_data)
|
||||
raise
|
||||
|
||||
duration_ms = int((time.perf_counter() - start) * 1000)
|
||||
message = f"HTTP {request.method} request to {request.get_full_path()}"
|
||||
log_data = {
|
||||
"request_method": request.method,
|
||||
"request_url": request.get_full_path(),
|
||||
"status_code": response.status_code,
|
||||
"remote_addr": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
|
||||
"duration_ms": duration_ms,
|
||||
"message": message,
|
||||
}
|
||||
|
||||
if response.status_code == 404:
|
||||
log_data["message"] = f"Not Found: {request.get_full_path()}"
|
||||
elif response.status_code == 401:
|
||||
log_data["message"] = f"Unauthorized: {request.get_full_path()}"
|
||||
|
||||
if response.status_code >= 400:
|
||||
self.error_logger.error(log_data)
|
||||
else:
|
||||
self.info_logger.info(log_data)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_custom_logger(name):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
custom_handler = RotatingFileHandler(
|
||||
filename=str(LOG_DIR / f"{name}.log"),
|
||||
maxBytes=1024 * 1024 * 15, # 15MB
|
||||
backupCount=10,
|
||||
)
|
||||
custom_handler.setFormatter(jsonlogger.JsonFormatter())
|
||||
if not logger.handlers:
|
||||
logger.addHandler(custom_handler)
|
||||
return logger
|
||||
|
||||
|
||||
logging.config.dictConfig(LOGGING)
|
||||
15
config/services/storage.py
Normal file
15
config/services/storage.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
|
||||
|
||||
class UploadStorage(FileSystemStorage):
|
||||
"""
|
||||
Storage for uploaded files (images, etc.)
|
||||
Saves files under MEDIA_ROOT/uploads/
|
||||
"""
|
||||
|
||||
location = os.path.join(settings.MEDIA_ROOT, "uploads")
|
||||
base_url = urljoin(settings.MEDIA_URL, "uploads/")
|
||||
46
config/services/unfold.py
Normal file
46
config/services/unfold.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.conf import settings
|
||||
|
||||
UNFOLD = {
|
||||
"SITE_TITLE": "Qlockify Admin",
|
||||
"SITE_HEADER": "Qlockify Admin Panel",
|
||||
"SITE_BRANDING": "Qlockify",
|
||||
"SITE_URL": "/api/docs/",
|
||||
"SITE_SYMBOL": "speed",
|
||||
"SHOW_HISTORY": True,
|
||||
"SHOW_VIEW_ON_SITE": True,
|
||||
"ENVIRONMENT": "config.services.unfold.environment_callback",
|
||||
"COLORS": {
|
||||
"primary": {
|
||||
"50": "#f5f7ff",
|
||||
"100": "#e8edff",
|
||||
"200": "#c7d2ff",
|
||||
"300": "#a3b4ff",
|
||||
"400": "#7a8dff",
|
||||
"500": "#4f6bff",
|
||||
"600": "#3f55d6",
|
||||
"700": "#3243ab",
|
||||
"800": "#263281",
|
||||
"900": "#1b245b",
|
||||
},
|
||||
"gray": {
|
||||
"50": "#f8fafc",
|
||||
"100": "#f1f5f9",
|
||||
"200": "#e2e8f0",
|
||||
"300": "#cbd5e1",
|
||||
"400": "#94a3b8",
|
||||
"500": "#64748b",
|
||||
"600": "#475569",
|
||||
"700": "#334155",
|
||||
"800": "#1f2937",
|
||||
"900": "#0f172a",
|
||||
},
|
||||
},
|
||||
"SIDEBAR": {
|
||||
"show_search": True,
|
||||
"show_all_applications": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def environment_callback(request):
|
||||
return ["Development", "warning"] if settings.DEBUG else ["Production", "success"]
|
||||
17
config/settings/__init__.py
Normal file
17
config/settings/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
from .base import *
|
||||
|
||||
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
|
||||
if ENVIRONMENT == "production":
|
||||
from .production import *
|
||||
else:
|
||||
from .development import *
|
||||
|
||||
from config.services.auditlog import *
|
||||
from config.services.logging import *
|
||||
from config.services.unfold import *
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from .local import *
|
||||
215
config/settings/base.py
Normal file
215
config/settings/base.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
|
||||
|
||||
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "yes")
|
||||
|
||||
DJANGO_APPS = [
|
||||
"unfold",
|
||||
"unfold.contrib.filters",
|
||||
"unfold.contrib.forms",
|
||||
"unfold.contrib.import_export",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
]
|
||||
|
||||
THIRD_PARTY_APPS = [
|
||||
"rest_framework",
|
||||
"rest_framework_simplejwt",
|
||||
"rest_framework.authtoken",
|
||||
"rest_framework_simplejwt.token_blacklist",
|
||||
"drf_spectacular",
|
||||
"drf_spectacular_sidecar",
|
||||
"django_filters",
|
||||
"import_export",
|
||||
"corsheaders",
|
||||
"auditlog",
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
"apps.users",
|
||||
"apps.workspaces",
|
||||
]
|
||||
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"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",
|
||||
"core.middlewares.current_user.CurrentUserMiddleware",
|
||||
"core.middlewares.exception_logging.ExceptionLoggingMiddleware",
|
||||
"config.services.logging.RequestLoggingMiddleware",
|
||||
"auditlog.middleware.AuditlogMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "config.urls"
|
||||
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [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",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
ASGI_APPLICATION = "config.asgi.application"
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.getenv("POSTGRES_DB"),
|
||||
"USER": os.getenv("POSTGRES_USER"),
|
||||
"PASSWORD": os.getenv("POSTGRES_PASSWORD"),
|
||||
"HOST": os.getenv("POSTGRES_HOST"),
|
||||
"PORT": os.getenv("POSTGRES_PORT"),
|
||||
"CONN_MAX_AGE": 0,
|
||||
"DISABLE_SERVER_SIDE_CURSORS": True,
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
],
|
||||
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.AllowAny"],
|
||||
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
|
||||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M",
|
||||
"DATE_FORMAT": "%Y-%m-%d",
|
||||
"DEFAULT_THROTTLE_CLASSES": [
|
||||
"rest_framework.throttling.AnonRateThrottle",
|
||||
"rest_framework.throttling.UserRateThrottle",
|
||||
],
|
||||
"EXCEPTION_HANDLER": "core.exceptions.handlers.exception_handler",
|
||||
}
|
||||
|
||||
LANGUAGE_CODE = os.getenv("LANGUAGE_CODE", "en-us")
|
||||
TIME_ZONE = os.getenv("TIME_ZONE", "Asia/Tehran")
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = BASE_DIR / "static"
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": "Qlockify.ir API Documentation",
|
||||
"DESCRIPTION": "API documentation for Qlockify.ir",
|
||||
"VERSION": "1.0.0",
|
||||
"SERVE_INCLUDE_SCHEMA": True,
|
||||
"SWAGGER_UI_SETTINGS": {
|
||||
"deepLinking": True,
|
||||
},
|
||||
"COMPONENT_SPLIT_REQUEST": True,
|
||||
"ENUM_NAME_OVERRIDES": {},
|
||||
"TAG_SORTING": "alpha",
|
||||
"SWAGGER_UI_DIST": "SIDECAR",
|
||||
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
|
||||
"REDOC_DIST": "SIDECAR",
|
||||
}
|
||||
|
||||
JWT_ACCESS_TOKEN_LIFETIME = int(os.getenv("JWT_ACCESS_TOKEN_LIFETIME_MINUTES", 60))
|
||||
JWT_REFRESH_TOKEN_LIFETIME = int(os.getenv("JWT_REFRESH_TOKEN_LIFETIME_DAYS", 7))
|
||||
JWT_ROTATE_REFRESH_TOKENS = os.getenv("JWT_ROTATE_REFRESH_TOKENS", "True") == "True"
|
||||
JWT_BLACKLIST_AFTER_ROTATION = os.getenv("JWT_BLACKLIST_AFTER_ROTATION", "True") == "True"
|
||||
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=JWT_ACCESS_TOKEN_LIFETIME),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=JWT_REFRESH_TOKEN_LIFETIME),
|
||||
"ROTATE_REFRESH_TOKENS": JWT_ROTATE_REFRESH_TOKENS,
|
||||
"BLACKLIST_AFTER_ROTATION": JWT_BLACKLIST_AFTER_ROTATION,
|
||||
"ALGORITHM": JWT_ALGORITHM,
|
||||
"SIGNING_KEY": SECRET_KEY,
|
||||
"AUTH_HEADER_TYPES": ("Bearer",),
|
||||
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
||||
"USER_ID_FIELD": "id",
|
||||
"USER_ID_CLAIM": "user_id",
|
||||
}
|
||||
|
||||
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = os.getenv("REDIS_PORT", "6379")
|
||||
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
|
||||
|
||||
if REDIS_PASSWORD:
|
||||
REDIS_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0"
|
||||
else:
|
||||
REDIS_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1")
|
||||
CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://127.0.0.1:6379/1")
|
||||
CELERY_ACCEPT_CONTENT = ["json"]
|
||||
CELERY_TASK_SERIALIZER = "json"
|
||||
CELERY_RESULT_SERIALIZER = "json"
|
||||
CELERY_TASK_ALWAYS_EAGER = False
|
||||
CELERY_IMPORTS = ("apps.users.tasks",)
|
||||
CELERY_TIMEZONE = os.getenv("TIME_ZONE")
|
||||
CELERY_TASK_TRACK_STARTED = True
|
||||
|
||||
|
||||
STORAGES = {
|
||||
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
||||
"images": {"BACKEND": "config.services.storage.UploadStorage"},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
SMS_APIKEY = os.getenv("SMS_APIKEY", "")
|
||||
BASE_URL = os.getenv("BASE_URL", "")
|
||||
23
config/settings/development.py
Normal file
23
config/settings/development.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
DEBUG = True
|
||||
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
CSRF_COOKIE_SAMESITE = "Lax"
|
||||
SESSION_COOKIE_SAMESITE = "Lax"
|
||||
|
||||
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED_ORIGINS", "http://localhost,http://127.0.0.1").split(",")
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_ALLOWED_ORIGINS = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:5173,http://127.0.0.1:5173").split(",")
|
||||
|
||||
# Django Debug Toolbar
|
||||
INSTALLED_APPS = settings.INSTALLED_APPS + ["debug_toolbar"]
|
||||
MIDDLEWARE = settings.MIDDLEWARE + ["debug_toolbar.middleware.DebugToolbarMiddleware"]
|
||||
INTERNAL_IPS = ["127.0.0.1"]
|
||||
8
config/settings/local.py
Normal file
8
config/settings/local.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Local developer overrides.
|
||||
|
||||
Copy this file and set only the settings you want to override locally.
|
||||
"""
|
||||
|
||||
# Example:
|
||||
# DEBUG = True
|
||||
16
config/settings/production.py
Normal file
16
config/settings/production.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
|
||||
DEBUG = False
|
||||
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
|
||||
CSRF_COOKIE_SAMESITE = "None"
|
||||
SESSION_COOKIE_SAMESITE = "None"
|
||||
|
||||
ALLOWED_HOSTS = [x for x in os.getenv("DJANGO_ALLOWED_HOSTS", "").split(",") if x]
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [x for x in os.getenv("DJANGO_CSRF_TRUSTED_ORIGINS", "").split(",") if x]
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = False
|
||||
CORS_ALLOWED_ORIGINS = [x for x in os.getenv("DJANGO_CORS_ALLOWED_ORIGINS", "").split(",") if x]
|
||||
27
config/urls.py
Normal file
27
config/urls.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularRedocView,
|
||||
SpectacularSwaggerView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
# API Documentations
|
||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
|
||||
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
||||
# Apps
|
||||
path("api/users/", include("apps.users.api.urls"), name="users"),
|
||||
path('api/', include('apps.workspaces.api.urls')),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
16
config/wsgi.py
Normal file
16
config/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for config project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
Reference in New Issue
Block a user