import os import importlib.util from pathlib import Path try: from dotenv import load_dotenv except ImportError: # pragma: no cover - optional in stripped test envs def load_dotenv(): return False load_dotenv() BASE_DIR = Path(__file__).resolve().parent.parent LOG_DIR = Path(os.environ.get("LOG_DIR", BASE_DIR / "logs")) LOG_DIR.mkdir(parents=True, exist_ok=True) SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-dev-only") DEBUG = os.environ.get("DEBUG", "0") == "1" ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",") INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "dashboard_data", "rag", "location_data", "farm_data.apps.FarmDataConfig", "weather", "plant", "irrigation", "fertilization", "crop_simulation.apps.CropSimulationConfig", ] for optional_app in [ "rest_framework", "corsheaders", "drf_spectacular", "drf_spectacular_sidecar", ]: if importlib.util.find_spec(optional_app): INSTALLED_APPS.insert(6, optional_app) MIDDLEWARE = [ "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", ] if importlib.util.find_spec("corsheaders"): MIDDLEWARE.insert(1, "corsheaders.middleware.CorsMiddleware") ROOT_URLCONF = "config.urls" WSGI_APPLICATION = "config.wsgi.application" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "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", ], }, }, ] DATABASES = { "default": { "ENGINE": os.environ.get("DB_ENGINE", "django.db.backends.mysql"), "NAME": os.environ.get("DB_NAME", "ai"), "USER": os.environ.get("DB_USER", "ai"), "PASSWORD": os.environ.get("DB_PASSWORD", ""), "HOST": os.environ.get("DB_HOST", "127.0.0.1"), "PORT": os.environ.get("DB_PORT", "3306"), } } if DATABASES["default"]["ENGINE"].endswith("mysql"): DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} 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"}, ] LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True STATIC_URL = "static/" STATIC_ROOT = BASE_DIR / "staticfiles" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "croplogic-auth-otp", } } REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.AllowAny", ], "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } SPECTACULAR_SETTINGS = { "TITLE": "CropLogic AI API", "DESCRIPTION": "API‌های هوش مصنوعی CropLogic — داده خاک، سنسور، هواشناسی و چت RAG", "VERSION": "1.0.0", "SERVE_INCLUDE_SCHEMA": False, "SWAGGER_UI_DIST": "SIDECAR", "SWAGGER_UI_FAVICON_HREF": "SIDECAR", "REDOC_DIST": "SIDECAR", "COMPONENT_SPLIT_REQUEST": True, "TAGS": [ {"name": "Dashboard Data", "description": "تجمیع داده‌های داشبورد مزرعه"}, {"name": "RAG Chat", "description": "چت هوشمند RAG"}, {"name": "RAG Recommendations", "description": "توصیه‌های آبیاری و کودهی مبتنی بر RAG"}, {"name": "Soil Data", "description": "داده‌های خاک (SoilGrids)"}, {"name": "Farm Data", "description": "داده‌های مزرعه و سنسورها"}, {"name": "Farm Parameters", "description": "پارامترهای سنسورهای مزرعه"}, {"name": "Plant", "description": "مدیریت گیاهان و دریافت اطلاعات گیاه"}, {"name": "Irrigation", "description": "مدیریت روش‌های آبیاری"}, {"name": "Irrigation Recommendation", "description": "درخواست و پیگیری توصیه آبیاری"}, {"name": "Fertilization Recommendation", "description": "درخواست و پیگیری توصیه کودهی"}, ], } CORS_ALLOW_ALL_ORIGINS = DEBUG # Celery CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0") CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379/0") CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" # Celery Beat — embed دیتای کاربران هر ۶ ساعت CELERY_BEAT_SCHEDULE = { "rag-ingest-periodic": { "task": "rag.tasks.rag_ingest_task", "schedule": 6 * 60 * 60, # ۶ ساعت }, "weather-fetch-periodic": { "task": "weather.tasks.fetch_weather_all_locations_task", "schedule": 6 * 60 * 60, # ۶ ساعت }, } # Weather API WEATHER_API_BASE_URL = os.environ.get( "WEATHER_API_BASE_URL", "https://api.open-meteo.com/v1/forecast" ) WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY", "") LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "standard": { "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s", }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "standard", }, "file": { "class": "logging.handlers.TimedRotatingFileHandler", "filename": str(LOG_DIR / "app.log"), "when": "midnight", "backupCount": 14, "encoding": "utf-8", "formatter": "standard", }, }, "loggers": { "django": { "handlers": ["console", "file"], "level": os.environ.get("DJANGO_LOG_LEVEL", "INFO"), "propagate": False, }, "rag": { "handlers": ["console", "file"], "level": os.environ.get("RAG_LOG_LEVEL", "INFO"), "propagate": False, }, }, "root": { "handlers": ["console", "file"], "level": os.environ.get("ROOT_LOG_LEVEL", "INFO"), }, }