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

http://blog.damonjablons.com/2009/10/15/the-perfect-django-settings-file/
  • Перевод
За то время, пока я занимаюсь внедрением проектов, написанных на 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

Применяю приемы на своем опыте — помогает. Приятного деплоя!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 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 дважды.

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