21 сентября 2010 в 17:28

Красивые конфиги Django перевод

За то время, пока я занимаюсь внедрением проектов, написанных на Django, накопились простые приемы упрощающие деплой. Разберем settings.py, который был использован в одном из последних проектов. Полностью файл доступен на http://gist.github.com/214361
Начнем.

Локальные настройки:

Локальные настройки позволяют иметь разные конфигурации одного проекта на разных машинах. В конце каждого моего файла settings.py есть подключение local_settings.py. Это позволяет переопределить любые значения переменных из settings.py. Например, на продакшн сервере я использую mysql, а на локальной машине — sqllite.
try:
    from local_settings import *
except ImportError:
    pass


Флаг отладки:


Вместо ручного изменения DEBUG=True/False, определяем сервер на котором запущена Django и меняем флаг автоматом. Если сайтов несколько ( тест сервер, QA сервер) — просто добавляем домены в проверку.
if socket.gethostname() == 'your.domain.com':
    DEBUG = False
else:
    DEBUG = True


Путь до медиа файлов:

PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')

Первая строчка создаст переменную, хранящую путь до файла settings.py, который я храню в корне каталога проекта, там же расположена папка media. Таким образом можно автоматически построить полный путь до media, не зависимо от того где развернут проект. PROJECT_PATH удобно использовать и для настроек сторонних приложений.

Используем только то, что нужно:

if DEBUG:
    TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.debug',)
if USE_I18N:
    TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.i18n',)
if DEBUG:
    MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)

Возможно, я немного дотошный, но я люблю когда в проект включено только то, что реально используется. Поэтому отбросим лишнее используя флаги.

Пути до шаблонов:

# Dir Structure
# + Application
# + templates
# + Application
# - someTemplate.html
# - models.py
# - views.py
# - otherAppSpecificFiles.py
# + OtherApplication
# + Templates
# - base.html
# - settings.py
# - urls.py
# - otherfiles.py
TEMPLATE_DIRS = ()
for root, dirs, files in os.walk(PROJECT_PATH)
    if 'templates' in dirs: TEMPLATE_DIRS += (os.path.join(root, 'templates'),)


Я храню папку с шаблонами(templates) в корень приложения, поэтому можно использовать простой обход для получения полных путей до шаблонов.

Настройки подключения к базе:

# The database settings are left blank so to force the use of local_settings.py below
DATABASE_ENGINE = ''
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''


Я всегда оставляю настройки базы пустыми, это заставляет включать их в локальные настройки «local_settings.py». Я использую sqllite для разработки и mysql(или postgresql) для продакшена, но благодаря тому, что настройки хранятся в local_settings.py можно спокойно коммитить и апдейтить settings.py

Применяю приемы на своем опыте — помогает. Приятного деплоя!
Автор оригинала: Damon Jablons
Barman @Barman
карма
56,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (38)

  • +6
    Все сравнительно стандартно, в работе использую почти такой же. Но для начинающих, возможно, пригодится — сразу будут делать правильно.
  • 0
    Единственное, что мне не понравилось – это использование разных баз при разработке (отладке) и в продакшне. Не смотря на абстракции, всё равно могут быть инциденты. Да и не настроишь индексы…

    В остальном – очень полезная статья.
    • 0
      Настройки БД должны быть в локальном конфиге еще хотя бы и потому, что пароль от базы нехорошо держать в VCS.
      Да и базы (dev/production) должны быть разными. А какие «инциденты» могут быть, не понимаю?
      • 0
        Я имею ввиду sqllite vs mysql. При разных базах система может работать по разному. В частности, использовать EXPLAIN невозможно для улучшения производительности.
        • 0
          А, понял. Согласен.
        • 0
          вы правы, статья — перевод и автор любит разные БД. Сами используем одинаковые SQL.
  • 0
    за django-command-extensions отдельное спасибо, буду копать ))
  • +2
    Почему не написать «DEBUG = True» в локальном конфиге dev-сервера и не хардкодить имя домена?

    Кстати, как бы попроще включать режим отладки для определенных ip?
    Знаю, что есть решения через middleware, но они мне показались странными…
    • 0
      Подому как автор подключает локальный конфиг в конце. Мне кажется подключение локального конфига в начале — практичнее, так как позволяет в основном конфиге использовать разичные настройки из локального.
      • 0
        Ах… Ну я тоже за подключение локального конфига в начале. И в try не оборачивать.
      • 0
        Разве практичнее? Ведь в этом случае глобальные настройки будут перекрывать глобальные. По-моему, должно быть с точностью, да наоборот: в глобальном конфиге только то, что не зависит от платформы, а в локальном уже делать переопределения.
        • 0
          Ведь в этом случае глобальные настройки будут перекрывать глобальные локальные

          Предыдущий комментарий надо читать вот так
          • 0
            А зачем вы одни и те же настройки пишите и как локальные, и как глобальные? Для того, чтобы локально можно было переопределить любую глобальную настройку? В тех случаях, с которыми я сталкивался — всюду можно было определить, какие настройки будут общими для всех, а какие — будут заданы локально.

            Вместо того, чтобы оставлять в общем конфиге невразумительное
            DATABASE_ENGINE = ''
            DATABASE_NAME = ''
            DATABASE_USER = ''
            DATABASE_PASSWORD = ''
            DATABASE_HOST = ''
            DATABASE_PORT = ''
            не лучше ли написать что-то вроде
            import env

            DATABASE_ENGINE = 'django.db.backends.postgresql_psycopg2'
            DATABASE_NAME = env.DATABASE_NAME
            DATABASE_USER = env.DATABASE_USER
            DATABASE_PASSWORD = env.DATABASE_PASSWORD
            DATABASE_HOST = env.DATABASE_HOST
            DATABASE_PORT = env.DATABASE_PORT
            (здесь evn — импортированный вначале файл с конфигурацией окружения). В такой форме сразу видно, что даная опция вовсе не оставлена пустой, а задана локально.
            • 0
              А зачем вы одни и те же настройки пишите и как локальные, и как глобальные? Для того, чтобы локально можно было переопределить любую глобальную настройку?

              Почти. Для того, чтобы в случае надобности можно было переопределить любую глобальную настройку. А когда надобности нет, не писать ни одной лишней строчки :-)

              Попробую проиллюстрировать на примере. Я обычно прописываю в settings что-то такое:

              DEBUG=False
              TEMPLATE_LOADERS = (
                  ('django.template.loaders.cached.Loader',
                      (
                          'django.template.loaders.filesystem.Loader',
                          'django.template.loaders.app_directories.Loader',
                      )
                  ),
              )
              # ...etc
              

              То есть по умолчанию отладочный режим выключен, а кеширование шаблонов включено. Для боевого сервера это годится, для девелоперской машины — не очень. Поэтому в local_settings девелоперского окружения я просто переопределяю значения так, как мне нужно:

              DEBUG=True
              TEMPLATE_LOADERS = (
                  'django.template.loaders.filesystem.Loader',
                  'django.template.loaders.app_directories.Loader',
              )
              В локальных настройках продакшн-сервера я эти атрибуты не переопределяю.

              не лучше ли написать что-то вроде...

              По-моему, не просто не лучше, а намного хуже. Хотя бы потому что эти атрибуты встречаются во всех окружениях: и в глобальном (хорошо хоть, что их менять не надо), и в каждом из локальных. Короче, не мой выбор :-)
              • 0
                Проблемы с вашим вариантом:
                1. По основному конфигу не очевидно, что используется как есть, что будет обязательно переопределено, а что может быть переопределено.
                2. В основном конфиге нет возможность делать зависимости от настроек, заданных в локальном конфиге.
                3. Зная лишь глобальный конфиг нет возможности определить, как сформировать локальный конфиг. Да и вообще определить, что там может находиться.

                Да, ваш вариант гибче, но он в целом менее читабелен (что противоречит правилу «Readability counts» :) и боюсь он быстро становится трудноподдерживаемым. «Короче, не мой выбор» :D
                • 0
                  Всё, что вы описали — не проблемы, а решения, к которым долго и сознательно шли :)

                  1. По основному конфигу не очевидно, что используется как есть, что будет обязательно переопределено, а что может быть переопределено.
                  У нас действует соглашение, по которому глобальный конфиг является полностью рабочим. Единственное исключение — атрибут DATABASES, который обязательно всё-таки указать вручную.

                  В репозиторий мы помещаем шаблон локального конфига — settings_local.py.template, в котором указан шаблон обязательного атрибута DATABASES. В том же файле содержатся дополнительные атрибуты, которые желательно (но не обязательно) изменить для лучшего функционирования рабочей копии.

                  2. В основном конфиге нет возможность делать зависимости от настроек, заданных в локальном конфиге.
                  Это не проблема, а умышленное поведение. Я почему всюду этот settings и обзываю глобальным: он ни разу не должен зависеть от окружения и везде одинаков. И я, если честно, не могу придумать адекватного случая, когда такая зависимость нужна.

                  Поможете? :-)

                  3. Зная лишь глобальный конфиг нет возможности определить, как сформировать локальный конфиг. Да и вообще определить, что там может находиться.
                  Чем-то этот пункт напоминает п.1. Поэтому ответ тот же: настройки, которые желательно изменить, перечисляются в local_settings.py.template, на основе которого уже и лепится local_settings.

                  Или я его неправильно понял?
  • 0
    TEMPLATE_DIRS = ()
    for root, dirs, files in os.walk(PROJECT_PATH)
    if 'templates' in dirs: TEMPLATE_DIRS += (os.path.join(root, 'templates'),)


    При такой схеме сложно будет объяснить вашей любимой IDE, где именно искать шаблоны.
    Я делаю так:

    TEMPLATE_DIRS = (
    os.path.abspath(PROJECT_DIR),
    os.path.abspath(PROJECT_DIR) + '/templates', # тут общие шаблоны -- base, 404, 500...
    )

    и пишу имена шаблонов более развернуто:

    return render_to_response('geo/templates/index.html', content, RequestContext(request))
    • +2
      return render_to_response('geo/templates/index.html', content, RequestContext(request))
      Посмотрите django-annoying. Авось пригодится.
      • +1
        посмотрите direct_to_template(geo/index.html', content) — и со стандартной джангой можно писать проще
    • +2
      а зачем такая жуть: 'geo/templates/index.html'? Почему не просто 'geo/index.html'? Папку geo можно создать в PROJECT_ROOT/templates или в PROJECT_ROOT/geo/templates/
      • 0
        Я бы хотел, чтобы шаблоны приложения были внутри его директории. Бывает, что приложение достаточно независимо может работать.

        или в PROJECT_ROOT/geo/templates/
        Это не понял. У меня же так и есть? Или предлагается создать внутри "/geo/templates/" еще папку geo только для того, чтобы сократить путь с 'geo/templates/index.html' до 'geo/index.html'? А IDE как поймет, где эти шаблоны искать?
        • +2
          Обратите внимание на эту штуку:

          TEMPLATE_LOADERS = (
              #...
              'django.template.loaders.app_directories.load_template_source',
          )

          она делает ровно то, что Вы хотите. Строго говоря, чтобы шаблоны из директории приложения подхватывались, TEMPLATE_DIRS можно и не заполнять.
        • +1
          Да, в geo/templates/ делаем папку geo. Это полезный паттерн, и так делает большинство сторонних приложений. Чем он полезен:

          а) папку с шаблонами можно безболезненно перемещать между geo/templates и PROJECT_ROOT/templates
          б) в одном приложении можно задать несколько «неймспейсов» для шаблонов
          в) не требуется никаких хаков (вроде вашего) для того, чтобы подключать шаблоны
          г) шаблоны гарантированно (ну почти, с точностью до названия приложения) не «мусорят» и не перекрывают шаблоны других приложений. У Вас, если что, перекрывают, и это может привести (и когда-нибудь обязательно приведет) к странным багам.

          Про IDE вообще не понял, если честно. Зачем IDE «искать шаблоны», что под этим подразумевается?
  • +4
    Наверное, каждый месяц кто-нибудь у себя в блоге напишет о том, как настраивать settings.py) Думаю, число статей с советами за несколько сотен должно перевалить было уже давно.

    for root, dirs, files in os.walk(PROJECT_PATH)
        if 'templates' in dirs: TEMPLATE_DIRS += (os.path.join(root, 'templates'),)
    


    А это зачем? Можно просто добавть os.path.join(PROJECT_ROOT, 'templates') в TEMPLATE_DIRS. Шаблоны в папках templates у приложений 'django.template.loaders.app_directories.Loader' ведь сам находит.
    • 0
      Шаблоны приложений храню в app_dir/templates/_app_name_/
      Все шаблоны разных приложений собираю (копирую) в одном templates проекта.
      Если обновляются шаблоны стороннего приложения, редко когда удается туда не полезть своими грязными ручонками.
      С таким подходом, код выше не нужен.
  • +5
    Кошмар, код не оформлен, что критично для Python.
    После такого вообще пропадает желание читать:

    if socket.gethostname() == 'your.domain.com':
    DEBUG = False
    else:
    DEBUG = True
  • +2
    Комментарии — зло:
    # Set DEBUG = True if on the production server

    Вот за это я и ненавижу комментарии.
    'your.domain.com' должна быть константой с именем PRODUCTION_SERVER

    И весь кусок должен быть написан так:

    DEBUG = socket.gethostname() == PRODUCTION_SERVER
    • +1
      DEBUG = socket.gethostname() != PRODUCTION_SERVER
      • 0
        Точняк.
        Меня все еще комментарий сбивает с толку.
    • 0
      Это смотря где эти комментарии :)
      Вот, если бы не ваш комментарий к статье, я бы не узнал удобую фичу. :)
      Спасибо за комментарий.
  • 0
    Большое спасибо за статью!
    Некоторые хитрости знал, но остальные возьму на вооружение.

    Пишите еще, делитесь практическим опытом.

    Хотелось бы про использование Django и MongoDB почитать.

    Может кто-нибудь из знающих людей здесь напишет?!
  • 0
    try:
        from local_settings import *
    except ImportError:
        pass


    несколько раз натыкался, когда в local_settings опечатка, или ошибка, продакшн сервер запускается с настройками по умолчанию. ИМХО, лучше как-то так сделать:

    if os.path.isfile(os.path.join(PROJECT_PATH, "local_settings.py")):
        from local_settings import *
    


    в этом случае при ошибках в local_settings, вывалится исключение. И как по мне — удобнее в local_settings перекрывать настройки по умолчанию — подключаю в конце settings.py
    • +2
      А как по мне, лучше джанге вообще не позволять запускаться без local_settings:

      try:
          from local_settings import *
      except Exception, e:
          import os, warnings
          warnings.warn("Unable import local settings [%s]: %s" % (type(e),  e))
          sys.exit(1)
      

      Маловероятно ведь, что локальные настройки не понадобятся в принципе, согласитесь? Окружения — то, в котором ведётся разработка и боевое — чаще всего существенно различаются. С другой стороны, если в настройках есть какие-то проблемы, то мы о них узнаем ещё на этапе запуска сервера и вовремя всё исправим.
  • 0
    По поводу локальных настроек — мне лично неудобно, что их приходится хранить локально — бывает систему переустановлю или еще что.

    Я просто использую socket.gethostname() для того чтобы определить на какой машине запущен проект и, в зависимости от этого, разные настройки применяю :)
  • +1
    На мой взгляд гораздо проще и удобней пути в settings.py проставлять следующим образом:
    import os

    def rel(*x):
        return os.path.join(os.path.abspath(os.path.dirname(__file__)), *x)
        
    #указываем путь так:

    MEDIA_ROOT = rel('media')
  • 0
    Всё хорошо, только вот не понятно, зачем таким образом странным шаблоны подключать.
  • +2
    Только вот теперь константы
    DATABASE_ENGINE = ''
    DATABASE_NAME = ''
    DATABASE_USER = ''
    DATABASE_PASSWORD = ''
    DATABASE_HOST = ''
    DATABASE_PORT = ''
    вроде как deprecated.

    Лучше использовать DATABASES = {
    'engine':…

    }.
  • 0
    Я все настройки по умолчанию помещаю в файл settings_default.py, а в settings.py у меня следующее:

    from settings_default import *
    
    DATABASES['default']['NAME'] = os.path.abspath(os.path.join(PROJECT_PATH, '../database.db'))
    

    Также у меня есть файл settings_production.py который выглядит похожим образом и при деплое переименовывается в settings.py. Ещё можно добавить файл settings_template.py со списком типичных параметров которые необходимо задавать индивидуально для каждой машины и использовать его в качестве основы для settings_production_xx.py

    Это позволяет не просто переопределять значения переменных, но и модифицировать их. Ещё не забывайте что Джанго по какой-то причине импортирует файл settings.py дважды.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.