From 21fa9a55ccf599e703e6d40c69bf0d7250939205 Mon Sep 17 00:00:00 2001 From: ITQ Date: Tue, 18 Feb 2025 23:56:28 +0300 Subject: [PATCH] feat: added boilerplate code --- solution/services/backend/api/__init__.py | 0 solution/services/backend/api/urls.py | 10 + solution/services/backend/api/v1/__init__.py | 0 solution/services/backend/api/v1/handlers.py | 121 ++++ solution/services/backend/api/v1/router.py | 58 ++ solution/services/backend/api/v1/schemas.py | 24 + solution/services/backend/apps/__init__.py | 0 solution/services/backend/config/__init__.py | 3 + solution/services/backend/config/asgi.py | 9 + solution/services/backend/config/celery.py | 10 + solution/services/backend/config/errors.py | 13 + solution/services/backend/config/handlers.py | 43 ++ solution/services/backend/config/settings.py | 593 +++++++++++++++++++ solution/services/backend/config/urls.py | 34 ++ solution/services/backend/config/wsgi.py | 9 + 15 files changed, 927 insertions(+) create mode 100644 solution/services/backend/api/__init__.py create mode 100644 solution/services/backend/api/urls.py create mode 100644 solution/services/backend/api/v1/__init__.py create mode 100644 solution/services/backend/api/v1/handlers.py create mode 100644 solution/services/backend/api/v1/router.py create mode 100644 solution/services/backend/api/v1/schemas.py create mode 100644 solution/services/backend/apps/__init__.py create mode 100644 solution/services/backend/config/__init__.py create mode 100644 solution/services/backend/config/asgi.py create mode 100644 solution/services/backend/config/celery.py create mode 100644 solution/services/backend/config/errors.py create mode 100644 solution/services/backend/config/handlers.py create mode 100644 solution/services/backend/config/settings.py create mode 100644 solution/services/backend/config/urls.py create mode 100644 solution/services/backend/config/wsgi.py diff --git a/solution/services/backend/api/__init__.py b/solution/services/backend/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/api/urls.py b/solution/services/backend/api/urls.py new file mode 100644 index 0000000..ffeb361 --- /dev/null +++ b/solution/services/backend/api/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from health_check.views import MainView + +from api.v1.router import router as api_v1_router + +urlpatterns = [ + path("", api_v1_router.urls), + # Health endpoint + path("health", MainView.as_view(), name="health_check_home"), +] diff --git a/solution/services/backend/api/v1/__init__.py b/solution/services/backend/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/api/v1/handlers.py b/solution/services/backend/api/v1/handlers.py new file mode 100644 index 0000000..d09fe0a --- /dev/null +++ b/solution/services/backend/api/v1/handlers.py @@ -0,0 +1,121 @@ +import logging +from collections.abc import Callable +from http import HTTPStatus as status +from typing import Any + +import django.core.exceptions +import django.http +import ninja.errors +from django.http import HttpRequest, HttpResponse +from ninja import NinjaAPI + +from config.errors import ConflictError, ForbiddenError + +logger = logging.getLogger("django") + + +def handle_validation_error( + request: HttpRequest, + exc: ninja.errors.ValidationError, + router: NinjaAPI, +) -> HttpResponse: + return router.create_response( + request, + {"detail": exc.errors}, + status=status.BAD_REQUEST, + ) + + +def handle_django_validation_error( + request: HttpRequest, + exc: django.core.exceptions.ValidationError, + router: NinjaAPI, +) -> HttpResponse: + detail = list(exc) + + if hasattr(exc, "error_dict"): + detail = dict(exc) + + return router.create_response( + request, + {"detail": detail}, + status=status.BAD_REQUEST, + ) + + +def handle_authentication_error( + request: HttpRequest, + exc: ninja.errors.AuthenticationError, + router: NinjaAPI, +) -> HttpResponse: + return router.create_response( + request, + {"detail": status.UNAUTHORIZED.phrase}, + status=status.UNAUTHORIZED, + ) + + +def handle_forbidden_error( + request: HttpRequest, + exc: ForbiddenError, + router: NinjaAPI, +) -> HttpResponse: + return router.create_response( + request, + {"detail": exc.message}, + status=status.FORBIDDEN, + ) + + +def handle_not_found_error( + request: HttpRequest, + exc: Exception, + router: NinjaAPI, +) -> HttpResponse: + return router.create_response( + request, + {"detail": status.NOT_FOUND.phrase}, + status=status.NOT_FOUND, + ) + + +def handle_conflict_error( + request: HttpRequest, + exc: ConflictError, + router: NinjaAPI, +) -> HttpResponse: + detail = list(exc.validation_error) + + if hasattr(exc, "error_dict"): + detail = dict(exc.validation_error) + + return router.create_response( + request, + {"detail": detail}, + status=status.CONFLICT, + ) + + +def handle_unknown_exception( + request: HttpRequest, + exc: Exception, + router: NinjaAPI, +) -> HttpResponse: + logger.exception(exc) + + return router.create_response( + request, + {"detail": status.INTERNAL_SERVER_ERROR.phrase}, + status=status.INTERNAL_SERVER_ERROR, + ) + + +exception_handlers: list[tuple[Any, Callable]] = [ + (ninja.errors.ValidationError, handle_validation_error), + (django.core.exceptions.ValidationError, handle_django_validation_error), + (ninja.errors.AuthenticationError, handle_authentication_error), + (ForbiddenError, handle_forbidden_error), + (django.http.Http404, handle_not_found_error), + (ConflictError, handle_conflict_error), + (Exception, handle_unknown_exception), +] diff --git a/solution/services/backend/api/v1/router.py b/solution/services/backend/api/v1/router.py new file mode 100644 index 0000000..3257de1 --- /dev/null +++ b/solution/services/backend/api/v1/router.py @@ -0,0 +1,58 @@ +from functools import partial + +from ninja import NinjaAPI + +from api.v1 import handlers +from api.v1.ads.views import router as ads_router +from api.v1.advertisers.views import router as advertisers_router +from api.v1.campaigns.views import router as compaigns_router +from api.v1.clients.views import router as clients_router +from api.v1.generate.views import router as generate_router +from api.v1.report.views import router as report_router +from api.v1.stats.views import router as stats_router +from api.v1.time.views import router as time_router + +router = NinjaAPI( + title="AdNova API", + version="1", + description="API docs for AdNova", + openapi_url="/docs/openapi.json", +) + + +router.add_router( + "clients", + clients_router, +) +router.add_router( + "advertisers", + advertisers_router, +) +router.add_router( + "advertisers", + compaigns_router, +) +router.add_router( + "ads", + ads_router, +) +router.add_router( + "stats", + stats_router, +) +router.add_router( + "generate", + generate_router, +) +router.add_router( + "report", + report_router, +) +router.add_router( + "time", + time_router, +) + + +for exception, handler in handlers.exception_handlers: + router.add_exception_handler(exception, partial(handler, router=router)) diff --git a/solution/services/backend/api/v1/schemas.py b/solution/services/backend/api/v1/schemas.py new file mode 100644 index 0000000..c58b291 --- /dev/null +++ b/solution/services/backend/api/v1/schemas.py @@ -0,0 +1,24 @@ +from http import HTTPStatus as status +from typing import Any + +from ninja import Schema + + +class BadRequestError(Schema): + detail: Any + + +class UnauthorizedError(Schema): + detail: str = status.UNAUTHORIZED.phrase + + +class ForbiddenError(Schema): + detail: str = status.FORBIDDEN.phrase + + +class NotFoundError(Schema): + detail: str = status.NOT_FOUND.phrase + + +class ConflictError(Schema): + detail: Any diff --git a/solution/services/backend/apps/__init__.py b/solution/services/backend/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/config/__init__.py b/solution/services/backend/config/__init__.py new file mode 100644 index 0000000..3eb91a6 --- /dev/null +++ b/solution/services/backend/config/__init__.py @@ -0,0 +1,3 @@ +from config.celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/solution/services/backend/config/asgi.py b/solution/services/backend/config/asgi.py new file mode 100644 index 0000000..da42751 --- /dev/null +++ b/solution/services/backend/config/asgi.py @@ -0,0 +1,9 @@ +"""ASGI config for AdNova.""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_asgi_application() diff --git a/solution/services/backend/config/celery.py b/solution/services/backend/config/celery.py new file mode 100644 index 0000000..cd1f95a --- /dev/null +++ b/solution/services/backend/config/celery.py @@ -0,0 +1,10 @@ +import os + +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +app = Celery("adnova") + +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() diff --git a/solution/services/backend/config/errors.py b/solution/services/backend/config/errors.py new file mode 100644 index 0000000..9729b26 --- /dev/null +++ b/solution/services/backend/config/errors.py @@ -0,0 +1,13 @@ +from http import HTTPStatus as status + +from django.core.exceptions import ValidationError + + +class ConflictError(Exception): + def __init__(self, validation_error: ValidationError) -> None: + self.validation_error = validation_error + + +class ForbiddenError(Exception): + def __init__(self, message: str = status.FORBIDDEN.phrase) -> None: + self.message = message diff --git a/solution/services/backend/config/handlers.py b/solution/services/backend/config/handlers.py new file mode 100644 index 0000000..7adeead --- /dev/null +++ b/solution/services/backend/config/handlers.py @@ -0,0 +1,43 @@ +from http import HTTPStatus as status + +from django.http import HttpRequest, JsonResponse + + +def handler400( + request: HttpRequest, + exception: Exception | None = None, +) -> JsonResponse: + return JsonResponse( + status=status.BAD_REQUEST, + data={"detail": status.BAD_REQUEST.phrase}, + ) + + +def handler403( + request: HttpRequest, + exception: Exception | None = None, +) -> JsonResponse: + return JsonResponse( + status=status.FORBIDDEN, + data={"detail": status.FORBIDDEN.phrase}, + ) + + +def handler404( + request: HttpRequest, + exception: Exception | None = None, +) -> JsonResponse: + return JsonResponse( + status=status.NOT_FOUND, + data={"detail": status.NOT_FOUND.phrase}, + ) + + +def handler500( + request: HttpRequest, + exception: Exception | None = None, +) -> JsonResponse: + return JsonResponse( + status=status.INTERNAL_SERVER_ERROR, + data={"detail": status.INTERNAL_SERVER_ERROR.phrase}, + ) diff --git a/solution/services/backend/config/settings.py b/solution/services/backend/config/settings.py new file mode 100644 index 0000000..0bc500d --- /dev/null +++ b/solution/services/backend/config/settings.py @@ -0,0 +1,593 @@ +"""Django settings for AdNova.""" + +import contextlib +import logging +from collections.abc import Callable +from pathlib import Path + +import django_stubs_ext +import environ +from django.utils.translation import gettext_lazy as _ +from health_check.plugins import plugin_dir + +from integrations.yandexai.healthcheck import YandexAIHealthCheck + +BASE_DIR = Path(__file__).resolve().parent.parent + +env = environ.Env() +environ.Env.read_env(BASE_DIR / ".env") + +django_stubs_ext.monkeypatch() + + +# Common settings + +DEBUG = env("DJANGO_DEBUG", default=False) + +ALLOWED_HOSTS = env( + "DJANGO_ALLOWED_HOSTS", + list, + default=["localhost", "127.0.0.1"], +) + + +# Integrations + +YANDEX_CLOUD_FOLDER_ID = env("YANDEX_CLOUD_FOLDER_ID", default=None) + +YANDEX_CLOUD_API_KEY = env("YANDEX_CLOUD_API_KEY", default=None) + +YANDEX_CLOUD_INTEGRATION_ENABLED = ( + YANDEX_CLOUD_FOLDER_ID and YANDEX_CLOUD_API_KEY +) + +# Register healthcheck + +plugin_dir.register(YandexAIHealthCheck) + + +# Caching + +REDIS_URI = env("REDIS_URI", default="redis://localhost:6379") + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": REDIS_URI, + "TIMEOUT": None, + "KEY_PREFIX": "backend", + "VERSION": 1, + }, +} + + +# Celery + +CELERY_BROKER_URL = REDIS_URI + +CELERY_RESULT_BACKEND = REDIS_URI + +CELERY_TIMEZONE = "UTC" + +CELERY_WORKER_SEND_TASK_EVENTS = True + +CELERY_TASK_SEND_SENT_EVENT = True + +CELERY_TASK_TRACK_STARTED = True + + +# Database + +DB_URI = env.db_url("DJANGO_DB_URI", default="sqlite:///db.sqlite3") + +DATABASES = {"default": {**DB_URI, "CONN_MAX_AGE": 50}} + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +# 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" + ), + }, +] + + +# Static files (CSS, JavaScript, Images) + +STATIC_ROOT = BASE_DIR / "static" + +STATIC_URL = env("DJANGO_STATIC_URL", default="static/") + +STATICFILES_DIRS: list[str] = [] + +STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" + +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + + +# Files + +FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 + +# Minio + +MINIO_STORAGE_ENDPOINT = env("MINIO_ENDPOINT", default=None) + +MINIO_STORAGE_ACCESS_KEY = env("MINIO_ACCESS_KEY", default=None) + +MINIO_STORAGE_SECRET_KEY = env("MINIO_SECRET_KEY", default=None) + +MINIO_STORAGE_USE_HTTPS = env("MINIO_USE_HTTPS", default=False) + +MINIO_STORAGE_MEDIA_BUCKET_NAME = env( + "MINIO_MEDIA_BUCKET_NAME", default="adnova-media" +) + +MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True + +MINIO_STORAGE_AUTO_CREATE_MEDIA_POLICY = "GET_ONLY" + +MINIO_DEFAULT_CUSTOM_ENDPOINT_URL = ( + "https://" + if MINIO_STORAGE_USE_HTTPS + else "http://" + str(MINIO_STORAGE_ENDPOINT) +) + +MINIO_STORAGE_MEDIA_URL = ( + env("MINIO_CUSTOM_ENDPOINT_URL", default=MINIO_DEFAULT_CUSTOM_ENDPOINT_URL) + + "/" + f"{MINIO_STORAGE_MEDIA_BUCKET_NAME}" +) + +MINIO_STORAGE_DEFAULT_ACL = "public-read" + +STORAGES = { + "default": { + "BACKEND": "minio_storage.storage.MinioMediaStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + + +# Cors + +CORS_ALLOWED_ORIGINS_FROM_ENV = env("DJANGO_CORS_ALLOWED_ORIGINS", list, ["*"]) + +if CORS_ALLOWED_ORIGINS_FROM_ENV == ["*"]: + CORS_ALLOW_ALL_ORIGINS = True +else: + CORS_ALLOWED_ORIGINS = CORS_ALLOWED_ORIGINS_FROM_ENV + + +# Forms + +FORM_RENDERER = "django.forms.renderers.DjangoTemplates" + +FORMS_URLFIELD_ASSUME_HTTPS = False + + +# Internationalization + +DATE_FORMAT = "N j, Y" + +DATE_INPUT_FORMATS = [ + "%Y-%m-%d", # '2006-10-25' + "%m/%d/%Y", # '10/25/2006' + "%m/%d/%y", # '10/25/06' + "%b %d %Y", # 'Oct 25 2006' + "%b %d, %Y", # 'Oct 25, 2006' + "%d %b %Y", # '25 Oct 2006' + "%d %b, %Y", # '25 Oct, 2006' + "%B %d %Y", # 'October 25 2006' + "%B %d, %Y", # 'October 25, 2006' + "%d %B %Y", # '25 October 2006' + "%d %B, %Y", # '25 October, 2006' +] + +DATETIME_FORMAT = "N j, Y, H:i:s" + +DATETIME_INPUT_FORMATS = [ + "%Y-%m-%d %H:%M:%S", # '2006-10-25 14:30:59' + "%Y-%m-%d %H:%M:%S.%f", # '2006-10-25 14:30:59.000200' + "%Y-%m-%d %H:%M", # '2006-10-25 14:30' + "%m/%d/%Y %H:%M:%S", # '10/25/2006 14:30:59' + "%m/%d/%Y %H:%M:%S.%f", # '10/25/2006 14:30:59.000200' + "%m/%d/%Y %H:%M", # '10/25/2006 14:30' + "%m/%d/%y %H:%M:%S", # '10/25/06 14:30:59' + "%m/%d/%y %H:%M:%S.%f", # '10/25/06 14:30:59.000200' + "%m/%d/%y %H:%M", # '10/25/06 14:30' +] + +DECIMAL_SEPARATOR = "." + +FIRST_DAY_OF_WEEK = 1 + +FORMAT_MODULE_PATH = None + +LANGUAGE_CODE = env("DJANGO_LANGUAGE_CODE", default="en-us") + +LANGUAGES = [("en", _("English")), ("ru", _("Russian"))] + +LOCALE_PATHS: list[str] = [] + +MONTH_DAY_FORMAT = "F j" + +NUMBER_GROUPING = 0 + +SHORT_DATE_FORMAT = "m/d/Y" + +SHORT_DATETIME_FORMAT = "m/d/Y H:i:s" + +THOUSAND_SEPARATOR = "," + +TIME_FORMAT = "H:i:s" + +TIME_INPUT_FORMATS = [ + "%H:%M:%S", # '14:30:59' + "%H:%M:%S.%f", # '14:30:59.000200' + "%H:%M", # '14:30' +] + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_THOUSAND_SEPARATOR = True + +USE_TZ = True + +YEAR_MONTH_FORMAT = "F Y" + + +# HTTP + +DATA_UPLOAD_MAX_MEMORY_SIZE = None + +DATA_UPLOAD_MAX_NUMBER_FIELDS = None + +DATA_UPLOAD_MAX_NUMBER_FILES = None + +DEFAULT_CHARSET = "utf-8" + +FORCE_SCRIPT_NAME = None + +INTERNAL_IPS = env( + "DJANGO_INTERNAL_IPS", + list, + default=["127.0.0.1"], +) + +MIDDLEWARE = [ + "django_guid.middleware.guid_middleware", + "corsheaders.middleware.CorsMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", +] + +SIGNING_BACKEND = "django.core.signing.TimestampSigner" + +USE_X_FORWARDED_HOST = False + +USE_X_FORWARDED_PORT = False + +WSGI_APPLICATION = "config.wsgi.application" + + +# Logging + +LOGGER_NAME = "adnova" + +LOGGER = logging.getLogger(LOGGER_NAME) + +LOGGING_FILTERS = { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue", + }, + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", + }, + "correlation_id": { + "()": "django_guid.log_filters.CorrelationId", + }, +} + +LOGGING_FORMATTERS = { + "json": { + "()": "pythonjsonlogger.jsonlogger.JsonFormatter", + "format": ( + "{levelname}{correlation_id}{asctime}" + "{name}{pathname}{lineno}{message}" + ), + "style": "{", + }, + "text": { + "()": "colorlog.ColoredFormatter", + "format": ( + "{log_color}[{levelname}]{reset} " + "{light_black}{asctime} {name} | {pathname}:{lineno}{reset}\n" + "{bold_black}{message}{reset}" + ), + "log_colors": { + "DEBUG": "bold_green", + "INFO": "bold_cyan", + "WARNING": "bold_yellow", + "ERROR": "bold_red", + "CRITICAL": "bold_purple", + }, + "style": "{", + }, +} + +LOGGING_HANDLERS = { + "console_debug": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "filters": ["require_debug_true"], + "formatter": "text", + }, + "console_prod": { + "class": "logging.StreamHandler", + "level": "INFO", + "filters": ["require_debug_false", "correlation_id"], + "formatter": "json", + }, +} + +LOGGING_LOGGERS = { + "django": { + "handlers": ["console_debug", "console_prod"], + "level": "INFO" if DEBUG else "ERROR", + "propagate": False, + }, + "django.request": { + "handlers": ["console_debug", "console_prod"], + "level": "INFO" if DEBUG else "ERROR", + "propagate": False, + }, + "django.server": { + "handlers": ["console_debug"], + "level": "INFO", + "filters": ["require_debug_true"], + "propagate": False, + }, + "django.template": {"handlers": []}, + "django.db.backends.schema": {"handlers": []}, + "django.security": {"handlers": [], "propagate": True}, + "django.db.backends": { + "handlers": ["console_debug"], + "filters": ["require_debug_true"], + "level": "DEBUG", + "propagate": False, + }, + "health-check": { + "handlers": ["console_debug", "console_prod"], + "level": "INFO" if DEBUG else "ERROR", + "propagate": False, + }, + LOGGER_NAME: { + "handlers": ["console_debug", "console_prod"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": False, + }, + "root": { + "handlers": ["console_debug", "console_prod"], + "level": "INFO" if DEBUG else "ERROR", + "propagate": False, + }, +} + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "filters": LOGGING_FILTERS, + "formatters": LOGGING_FORMATTERS, + "handlers": LOGGING_HANDLERS, + "loggers": LOGGING_LOGGERS, +} + +LOGGING_CONFIG = "logging.config.dictConfig" + + +# Models + +ABSOLUTE_URL_OVERRIDES: dict[str, Callable] = {} + +FIXTURE_DIRS: list[str] = [] + +INSTALLED_APPS = [ + # Build-in apps + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + # Healthcheck + "health_check", + "health_check.db", + "health_check.cache", + "health_check.storage", + "health_check.contrib.migrations", + # Third-party apps + "corsheaders", + "django_extensions", + "django_guid", + "ninja", + "minio_storage", + # Internal apps + "apps.core", + "apps.advertiser", + "apps.client", + "apps.mlscore", + "apps.campaign", + # API v1 apps + "api.v1.clients", +] + +# GUID + +DJANGO_GUID = { + "GUID_HEADER_NAME": "Correlation-ID", + "VALIDATE_GUID": True, + "RETURN_HEADER": True, + "EXPOSE_HEADER": True, + "INTEGRATIONS": [], + "IGNORE_URLS": [], + "UUID_LENGTH": 32, +} + + +# Security + +LANGUAGE_COOKIE_AGE = 31449600 + +LANGUAGE_COOKIE_DOMAIN = None + +LANGUAGE_COOKIE_HTTPONLY = False + +LANGUAGE_COOKIE_NAME = "django_language" + +LANGUAGE_COOKIE_PATH = "/" + +LANGUAGE_COOKIE_SAMESITE = "Lax" + +LANGUAGE_COOKIE_SECURE = False + +SECURE_PROXY_SSL_HEADER = None + +CSRF_COOKIE_AGE = 31449600 + +CSRF_COOKIE_DOMAIN = None + +CSRF_COOKIE_HTTPONLY = False + +CSRF_COOKIE_NAME = "djangocsrftoken" + +CSRF_COOKIE_PATH = "/" + +CSRF_COOKIE_SAMESITE = "Lax" + +CSRF_COOKIE_SECURE = False + +CSRF_FAILURE_VIEW = "django.views.csrf.csrf_failure" + +CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN" + +CSRF_TRUSTED_ORIGINS = env( + "DJANGO_CSRF_TRUSTED_ORIGINS", + list, + default=["http://localhost", "http://127.0.0.1"], +) + +CSRF_USE_SESSIONS = False + +SECRET_KEY = env("DJANGO_SECRET_KEY", default="very_insecure_key") + +SECRET_KEY_FALLBACKS: list[str] = [] + + +# Sessions + +SESSION_CACHE_ALIAS = "default" + +SESSION_COOKIE_AGE = 1209600 + +SESSION_COOKIE_DOMAIN = None + +SESSION_COOKIE_HTTPONLY = True + +SESSION_COOKIE_NAME = "djangosessionid" + +SESSION_COOKIE_PATH = "/" + +SESSION_COOKIE_SAMESITE = "Lax" + +SESSION_COOKIE_SECURE = False + +SESSION_ENGINE = "django.contrib.sessions.backends.db" + +SESSION_EXPIRE_AT_BROWSER_CLOSE = False + +SESSION_FILE_PATH = None + +SESSION_SAVE_EVERY_REQUEST = False + +SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer" + + +# Templates + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "autoescape": True, + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "debug": DEBUG, + "string_if_invalid": "", + "file_charset": "utf-8", + }, + }, +] + + +# Testing + +TEST_NON_SERIALIZED_APPS: list[str] = [] + +TEST_RUNNER = "django.test.runner.DiscoverRunner" + + +# URLs + +ROOT_URLCONF = "config.urls" + + +# debug-toolbar + +DEBUG_TOOLBAR_ENABLED = False + +with contextlib.suppress(Exception): + import debug_toolbar # noqa: F401 + + DEBUG_TOOLBAR_ENABLED = True + +DEBUG_TOOLBAR_CONFIG = {"SHOW_COLLAPSED": True, "UPDATE_ON_FETCH": True} + +if DEBUG and DEBUG_TOOLBAR_ENABLED: + INSTALLED_APPS.append("debug_toolbar") + MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware") diff --git a/solution/services/backend/config/urls.py b/solution/services/backend/config/urls.py new file mode 100644 index 0000000..f553970 --- /dev/null +++ b/solution/services/backend/config/urls.py @@ -0,0 +1,34 @@ +"""URL configuration for AdNova.""" + +from django.conf import settings +from django.contrib import admin +from django.urls import include, path + +from config import handlers + +admin.site.site_title = "AdNova" +admin.site.site_header = "AdNova" +admin.site.index_title = "AdNova" + + +urlpatterns = [ + # Admin urls + path("admin/", admin.site.urls), + # API urls + path("", include("api.urls")), +] + + +if settings.DEBUG and settings.DEBUG_TOOLBAR_ENABLED: + from debug_toolbar.toolbar import debug_toolbar_urls + + urlpatterns += debug_toolbar_urls() + + +handler400 = handlers.handler400 + +handler403 = handlers.handler403 + +handler404 = handlers.handler404 + +handler500 = handlers.handler500 diff --git a/solution/services/backend/config/wsgi.py b/solution/services/backend/config/wsgi.py new file mode 100644 index 0000000..e771e1e --- /dev/null +++ b/solution/services/backend/config/wsgi.py @@ -0,0 +1,9 @@ +"""WSGI config for AdNova.""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_wsgi_application()