یه مشکل که بیشتر توسعهدهندههای تازهکار جنگو، از جمله خودم، دارند اینه که میخواهیم برخی از تنظیمها فقط در محیط توسعه اعمالیده شوند (اعمال شوند) و برخی دیگر فقط در محیط استقرار؛ و نیز مقدار/ارزشهای حساس (مانند توکنهای دسترسی، کلیدهای امنیتی و ...) بشکل متن خام در فایل تنظیم نوشته نشوند تا هنگام استفیدن (استفاده) از گیت دچار مشکل امنیتی نشویم.
حالا یه راهحل تمیز یافتم که میخواهم با دیگران هم باشتراکم (اشتراک بگذارم) اش. [البته با یه زبان فارسی عجیب! چون بنظرم به چنین چیزی برای بیانیدن (بیان کردن) دقیقتر، آسانتر و کوتاهتر اندیشهها نیاز داریم. امیدوارم خوشایندت باشد!]
توجه. فرضیدهام (فرض کردهام) که پروژهامان داکرسازیده شده (داکرسازی شده).
نخست، یه پوشه به نام settings میایجادیم (ایجاد میکنیم)؛ سپس، فایل settings.py را، پس از دگرنامیدن به base.py، به درونش میانتقالیم (انتقال میدهیم). و نیز دو فایل development.py و production.py بترتیب برای نگهداری تنظیمهای «محیط توسعه» و «محیط استقرار» درونش میایجادیم.
درنهایت، فایل base.py را در فایلهای development.py و production.py میصدوریم (صادر میکنیم).
# ./settings/development.py from .base import * # ... ----------------------------------- # ./settings/production.py from .base import * # ...
حالا ازین پس تنظیمهای مشترک را در فایل base.py، و تنظیمهای هر محیط را هم در فایل خودش میآوریم! برای مثال، در زیر تنظیمهای بسته django-debug-toolbar که تنها برای محیط توسعه است را در فایل development.py آوردهام.
# ./settings/development.py from .base import * # django-debug-toolbar INSTALLED_APPS += [ 'debug_toolbar', ] MIDDLEWARE = [ 'debug_toolbar.middleware.DebugToolbarMiddleware', *MIDDLEWARE, ] INTERNAL_IPS = [ '172.31.0.1', 'localhost', ]
تا اینجا، تنظیمهای هر محیط را ازهم جداییدیم (جدا کردیم). درادامه، روش تعریفیدن و استفیدن از متغیرهای محیطی را در یه پروژهی جنگویی داکرسازیدهشده (داکرسازیشده) آوردهام. از این روش میتوان برای پروژههای داکرسازیدهنشده (داکرسازی نشده) هم الهام گرفت و با کمی تغییر در آنها استفید.
برای پرهیز از نوشتن مقدار/ارزش تنظیمهای حساس بشکل متن خام، یا متغیرهای وابسته به یه محیط خاص (توسعه یا استقرار) از «متغیرهای محیطی» (Environment Variable) میاستفیم.
نخست، یه فایل به نام .env در ریشهی پروژه (در پوشه فایل docker-compose.yml) میایجادیم و بشکل کلید-مقدار متغیرها و مقدارشان را درونش مینویسیم.
# .env DEBUG=true SECRET_KEY='django-insecure-)9+gi&bc*a&9$fs782@wqo8@3$hw*wd8%v*t^vdt7u-4fo4kvu' ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
سپس، بشکل زیر در فایل docker-compose.yml میاعلانیم اشان (اعلانشان میکنیم):
# docker-compose.yml version: "3.9" services: app: build: context: . environment: DEBUG: ${DEBUG} SECRET_KEY: ${SECRET_KEY} ALLOWED_HOSTS: ${ALLOWED_HOSTS}
تا اینجا، متغیرهای محیطی اعلانیده شدند؛ ولی، حالا چگونه باستفیم اشان؟
حالا، با استفیدن از پودمان os.environ میتوان به مقدار متغیرهای محیطی بدسترسیم (دسترسی داشته باشم).
# ./settings/base.py import os # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get('DEBUG').lower() in ('true', 'yse', 'on', '1' 't', 'y') ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS').split(' ')
توجه. بنابر مهارت و سلیقهی خودتان میتوانید یه تابع/کلاس برای دسترسیدن به متغیرهای محلی بنویسید یا از بستههای سوم-شخص موجود باستفید. برای نمونه من کلاس زیر را برای خودم تعریفیدهام:
# utils.py import os from typing import Optional from django.core.exceptions import ImproperlyConfigured class env: """convert env values to currect types""" @staticmethod def _get_key(key: str, default=None): try: return str(os.environ.get(key)) except KeyError: if default is not None: return default else: raise ImproperlyConfigured(f'Set the {key} environment variable') @staticmethod def bool(key: str, default: Optional[bool] = None) -> bool: truethy = ('true', 'yse', 'on', '1' 't', 'y') return bool(env._get_key(key, default).lower() in truethy) @staticmethod def int(key: str, default: Optional[int] = None) -> int: return int(env._get_key(key, default)) @staticmethod def list(key: str, default: Optional[list[str]] = None, sep: str = ' ') -> list[str]: return env._get_key(key, default).split(sep) @staticmethod def str(key: str, default: Optional[str] = None) -> str: return env._get_key(key, default)
و بشکل زیر ازش میاستفم:
# ./settings/base.py from app.utils import env # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = env.str('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool('DEBUG') ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', [])
بسیاری از دستورهای دستیار جنگو، که بشکل python manage.py command ازشان میاستفیم، نیازمند فایل تنظیمها (settings.py)هستند؛ که بشکل پیشفرض در مسیر پروژه قرار دارد. درواقع، در کدهای این دستورها از متغیر محیطی DJANGO_SETTINGS_MODULE برای دسترسیدن به مسیر فایل تنظیم استفیده شده؛ که این متغیر محیطی در فایل manage.py اعلانیده میشود:
# manage.py # ... def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') # ...
پس، چون متد os.environ.setdefault تنها درصورت تهی/تعریفیدهنشده بودن متغیر DJANGO_SETTINGS_MODULE مقدار پیشفرض app.settings را بهش میانتسابد (منتسب میکند)؛ برای انتخابیدن فایل تنظیم مناسب کافیه خودمان بنابر هر محیط این متغیر محیطی را، همانند متغیرهای محیطی دیگر، باعلانیم. برای نمونه:
# .env # ... DJANGO_SETTINGS_MODULE=app.settings.development ------------ # docker-compose.yml version: "3.9" services: app: # ... environment: # ... DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE}
توجه. چون وبسرورهای ASGI و WSGI هم از فایل تنظیمها میاستفند، و رویکرد در آنها نیز همانند manage.py است، تنظیمهای مناسب بر آنها هم اعمال میشود.
# wsgi.py # ... os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') # ... ------------ # asgi.py # ... os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') # ...
تمام! حالا ازین پس کافیه تنظیمهای هر محیط را در فایل خودش بیاوریم؛ و اطلاعهای حساس/محیطی را، بشکل بیانیده شده، باعلانیم؛ و فایل تنظیم مناسب محیط را هم بتعیینم (تعیین کنیم).