""" Base settings for Django project. """ import os import warnings from datetime import timedelta from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # Repository root (one level above src/) PROJECT_ROOT = BASE_DIR.parent APP_VERSION = "1.0.0" OPENAPI_USE_ENGLISH_TAGS = False STARTUP_CHECKS_ENABLED = True STARTUP_DB_TIMEOUT_SECONDS = 3 STARTUP_REDIS_TIMEOUT_SECONDS = 3 # Suppress noisy warning from legacy coreapi used by drf-yasg. warnings.filterwarnings( "ignore", message="pkg_resources is deprecated as an API.*", category=UserWarning, module="coreapi.utils", ) # SECRET_KEY, DEBUG, ALLOWED_HOSTS определяются в dev.py / production.py # Application definition INSTALLED_APPS = [ "jazzmin", # Django Jazzmin - modern admin theme (must be before admin) "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Third-party apps "rest_framework", "django_filters", "corsheaders", "rest_framework_simplejwt.token_blacklist", "django_celery_beat", "django_celery_results", "drf_yasg", # Local apps "apps.core", "apps.user", "apps.parsers", "apps.registers", "apps.exchange", "apps.backups", ] # Jazzmin Admin Configuration JAZZMIN_SETTINGS = { # Title "site_title": "Mostovik Admin", "site_header": "Mostovik", "site_brand": "Mostovik", "site_logo": None, "login_logo": None, "login_logo_dark": None, "site_logo_classes": "img-circle", "site_icon": None, "welcome_sign": "Добро пожаловать в панель управления", "copyright": "Mostovik Backend", # Search "search_model": ["user.User", "parsers.IndustrialCertificateRecord"], # User menu "topmenu_links": [ {"name": "Главная", "url": "admin:index", "permissions": ["auth.view_user"]}, {"name": "API Docs", "url": "/api/docs/", "new_window": True}, {"model": "user.User"}, ], # Side menu "show_sidebar": True, "navigation_expanded": True, "hide_apps": ["django_celery_results"], "hide_models": [], "order_with_respect_to": [ "user", "parsers", "registers", "exchange", "backups", "core", "django_celery_beat", ], # Icons (Font Awesome) "icons": { "auth": "fas fa-users-cog", "auth.Group": "fas fa-users", "user.User": "fas fa-user", "user.Profile": "fas fa-id-card", "parsers.Proxy": "fas fa-shield-alt", "parsers.ParserLoadLog": "fas fa-history", "parsers.IndustrialCertificateRecord": "fas fa-certificate", "parsers.ManufacturerRecord": "fas fa-industry", "registers.Register": "fas fa-book", "registers.Organization": "fas fa-building", "exchange.ExchangeConnection": "fas fa-database", "core.BackgroundJob": "fas fa-tasks", "django_celery_beat.PeriodicTask": "fas fa-clock", "django_celery_beat.CrontabSchedule": "fas fa-calendar-alt", "django_celery_beat.IntervalSchedule": "fas fa-stopwatch", "django_celery_results.TaskResult": "fas fa-clipboard-check", }, "default_icon_parents": "fas fa-chevron-circle-right", "default_icon_children": "fas fa-circle", # Related modal "related_modal_active": True, # UI Tweaks "custom_css": None, "custom_js": None, "use_google_fonts_cdn": True, "show_ui_builder": False, # Change view "changeform_format": "horizontal_tabs", "changeform_format_overrides": { "user.User": "collapsible", "parsers.IndustrialCertificateRecord": "vertical_tabs", }, } JAZZMIN_UI_TWEAKS = { "navbar_small_text": False, "footer_small_text": False, "body_small_text": False, "brand_small_text": False, "brand_colour": "navbar-primary", "accent": "accent-primary", "navbar": "navbar-dark", "no_navbar_border": False, "navbar_fixed": True, "layout_boxed": False, "footer_fixed": False, "sidebar_fixed": True, "sidebar": "sidebar-dark-primary", "sidebar_nav_small_text": False, "sidebar_disable_expand": False, "sidebar_nav_child_indent": False, "sidebar_nav_compact_style": False, "sidebar_nav_legacy_style": False, "sidebar_nav_flat_style": False, "theme": "default", "dark_mode_theme": "darkly", "button_classes": { "primary": "btn-primary", "secondary": "btn-secondary", "info": "btn-info", "warning": "btn-warning", "danger": "btn-danger", "success": "btn-success", }, } MIDDLEWARE = [ "apps.core.middleware.RequestIDMiddleware", "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "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", ] 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.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "core.wsgi.application" # Database и Cache определяются в dev.py / production.py # ============================================================================= # PARSERS SETTINGS # ============================================================================= ZAKUPKI_TOKEN = os.getenv("ZAKUPKI_TOKEN", "") FNS_LOCK_TTL_SECONDS = 3600 PARSER_PROXIES = [ item.strip() for item in os.getenv("PARSER_PROXIES", "").split(",") if item.strip() ] BACKUP_ENCRYPTION_KEY = os.getenv("BACKUP_ENCRYPTION_KEY", "") BACKUP_KEY_ID = os.getenv("BACKUP_KEY_ID", "default") BACKUP_EXPORT_DIRECTORY = os.getenv( "BACKUP_EXPORT_DIRECTORY", str(PROJECT_ROOT / "media" / "backups"), ) # Celery: сохраняем ретраи подключения на старте и для 6.x совместимости. CELERY_BROKER_CONNECTION_RETRY = True CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True # Celery: автообновление источников при старте worker. CELERY_STARTUP_REFRESH_ENABLED = True CELERY_STARTUP_REFRESH_DELAY_SECONDS = 30 CELERY_STARTUP_REFRESH_LOCK_KEY = "celery:startup:parse_all_sources:lock" CELERY_STARTUP_REFRESH_LOCK_TTL_SECONDS = 3600 # 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 = "ru-RU" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" STATIC_ROOT = PROJECT_ROOT / "staticfiles" STATICFILES_DIRS = [BASE_DIR / "static"] # Media files MEDIA_URL = "/media/" MEDIA_ROOT = PROJECT_ROOT / "media" # Default primary key field type DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # Custom user model AUTH_USER_MODEL = "user.User" # Login URL for drf-yasg and DRF browsable API LOGIN_URL = "/auth/login/" # REST Framework settings REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework_simplejwt.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], "DEFAULT_FILTER_BACKENDS": [ "django_filters.rest_framework.DjangoFilterBackend", "rest_framework.filters.SearchFilter", "rest_framework.filters.OrderingFilter", ], "DEFAULT_PAGINATION_CLASS": "apps.core.pagination.StandardPagination", "PAGE_SIZE": 20, "DEFAULT_RENDERER_CLASSES": [ "rest_framework.renderers.JSONRenderer", "rest_framework.renderers.BrowsableAPIRenderer", ], "EXCEPTION_HANDLER": "apps.core.exception_handler.custom_exception_handler", # Rate limiting "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/hour", "user": "1000/hour", }, } # JWT settings (SIGNING_KEY определяется в dev.py / production.py) SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), "REFRESH_TOKEN_LIFETIME": timedelta(days=7), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "UPDATE_LAST_LOGIN": True, "ALGORITHM": "HS256", "VERIFYING_KEY": None, "AUDIENCE": None, "ISSUER": None, "JWK_URL": None, "LEEWAY": 0, "AUTH_HEADER_TYPES": ("Bearer",), "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", "USER_ID_FIELD": "id", "USER_ID_CLAIM": "user_id", "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), "TOKEN_TYPE_CLAIM": "token_type", "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", "JTI_CLAIM": "jti", "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1), } # CORS settings CORS_ALLOWED_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:3000"] CORS_ALLOW_CREDENTIALS = True # ============================================================================= # SWAGGER SETTINGS (drf-yasg) # ============================================================================= SWAGGER_SETTINGS = { "SECURITY_DEFINITIONS": { "Bearer": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "JWT авторизация. Формат: Bearer ", } }, "USE_SESSION_AUTH": True, "PERSIST_AUTH": True, "REFETCH_SCHEMA_WITH_AUTH": True, "REFETCH_SCHEMA_ON_LOGOUT": True, } # Logging configuration LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "verbose": { "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}", "style": "{", }, "simple": { "format": "{levelname} {message}", "style": "{", }, }, "handlers": { "file": { "level": "INFO", "class": "logging.FileHandler", "filename": PROJECT_ROOT / "logs/django.log", "formatter": "verbose", }, "console": { "level": "INFO", "class": "logging.StreamHandler", "formatter": "simple", }, }, "root": { "handlers": ["console", "file"], "level": "INFO", }, "loggers": { "django": { "handlers": ["console", "file"], "level": "INFO", "propagate": False, }, }, } # ============================================================================= # FNS Parser Settings # ============================================================================= # Directory for watching incoming FNS files FNS_WATCH_DIRECTORY = PROJECT_ROOT / "input" / "fns" # Directory for processed files (moved after successful processing) FNS_PROCESSED_DIRECTORY = PROJECT_ROOT / "input" / "fns" / "processed" # Directory for failed files (moved after failed processing) FNS_FAILED_DIRECTORY = PROJECT_ROOT / "input" / "fns" / "failed" # ============================================================================= # Checko API Settings (checko.ru) # ============================================================================= CHECKO_API_KEY = os.getenv("CHECKO_API_KEY", "")