""" Base settings for Django project. Generated by 'django-admin startproject' using Django 3.2.25. """ from pathlib import Path from decouple import Config, RepositoryEnv # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Application version APP_VERSION = "1.0.0" # Load environment variables ENV_FILE = BASE_DIR / ".env" if ENV_FILE.exists(): config = Config(RepositoryEnv(str(ENV_FILE))) else: from decouple import AutoConfig config = AutoConfig(search_path=BASE_DIR) # Helper function for getting config values def get_env(key, default=None): return config(key, default=default) # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = get_env( "SECRET_KEY", "django-insecure-development-key-change-in-production" ) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = get_env("DEBUG", True) if isinstance(DEBUG, str): DEBUG = DEBUG.lower() in ("true", "1", "yes") ALLOWED_HOSTS = get_env("ALLOWED_HOSTS", "localhost,127.0.0.1") if isinstance(ALLOWED_HOSTS, str): ALLOWED_HOSTS = ALLOWED_HOSTS.split(",") # 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", "django_celery_beat", "django_celery_results", "drf_yasg", # Local apps "apps.core", "apps.user", "apps.parsers", ] # 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", "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", "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", "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 = "config.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 = "config.wsgi.application" # Database DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": get_env("POSTGRES_DB", "project_db"), "USER": get_env("POSTGRES_USER", "project_user"), "PASSWORD": get_env("POSTGRES_PASSWORD", "project_password"), "HOST": get_env("POSTGRES_HOST", "db"), "PORT": int(get_env("POSTGRES_PORT", "5432")), "OPTIONS": { "charset": "utf8mb4", }, }, } # Cache configuration CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": get_env("REDIS_URL", "redis://localhost:6379/0"), "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, }, } # ============================================================================= # PARSERS SETTINGS # ============================================================================= # Zakupki.gov.ru API Token (получить через Госуслуги) ZAKUPKI_TOKEN = get_env("ZAKUPKI_TOKEN", "") # Proxy list for parsers (comma-separated) PARSER_PROXIES = get_env("PARSER_PROXIES", "") if isinstance(PARSER_PROXIES, str) and PARSER_PROXIES: PARSER_PROXIES = [p.strip() for p in PARSER_PROXIES.split(",") if p.strip()] else: PARSER_PROXIES = [] # 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 = BASE_DIR / "staticfiles" STATICFILES_DIRS = [BASE_DIR / "static"] # Media files MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" # Default primary key field type DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # Custom user model AUTH_USER_MODEL = "user.User" # REST Framework settings REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework_simplejwt.authentication.JWTAuthentication", "rest_framework.authentication.SessionAuthentication", ], "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 from datetime import timedelta 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", "SIGNING_KEY": SECRET_KEY, "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 = get_env( "CORS_ALLOWED_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000" ) if isinstance(CORS_ALLOWED_ORIGINS, str): CORS_ALLOWED_ORIGINS = CORS_ALLOWED_ORIGINS.split(",") 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": BASE_DIR / "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 = BASE_DIR / "input" / "fns" # Directory for processed files (moved after successful processing) FNS_PROCESSED_DIRECTORY = BASE_DIR / "input" / "fns" / "processed" # Directory for failed files (moved after failed processing) FNS_FAILED_DIRECTORY = BASE_DIR / "input" / "fns" / "failed"