Развёртывание Django 1.9 на IIS 7+

Предисловие


Всем привет!


Не так давно ко мне обратились с просьбой о создании сайта. Интересный крупный проект с множеством "хочу вот это". Среди пожеланий были два главных, определивших web framework для написания, это интернационализация и панель администратора. Как уже понятно из заголовка статьи, таким framework'ом стала Django.


На старте, и почти всё время разработки, у нас не было одной вещи — сервера. Было доменное имя, бодрым темпом разрабатывался сайт, к проекту присоединился дизайнер, но сервер нам так выделить не могли. Все показы сайта проходили на моём ноутбуке, не давая возможности заказчику сесть вечером с кружкой кофе, расслабиться и насладиться тем, что мы уже для него сделали. А также, отсутствие возможности показать нашу работу людям лишало нас получения обратной связи.


И вот, можно сказать на днях, свершилось чудо — у нас появился сервер. И как следствие этого — эта статья.



Проблема


Радость моя была не долгой. Сайт на Django, а сервер на Windows Server 2012. Первая мысль:


python manage.py runserver 80

Открываю сайт в браузер на сервере — всё работает. Отлично. Открываю сайт на домашней машине и вижу, что сайт не доступен. Грусть.


Следующая идея, это использовать IIS. Но как? "Ok, Google" выдал мне кучу статей на разных языках с кучей сложностей. Так же я не обошел вниманием cтатью с Хабра от Microsoft и с официального сайта Django, но все эти решения были довольно громоздкие, а мне хотелось некой простоты и элегантности.


Что мы имеем


  • Python 3.5
  • Django 1.9.7
  • Windows Server 2012 R2

Структура сайта (часть дерева):


project/
    manage.py
    project/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Приступим к решению проблемы развёртывания Django на IIS.


Решение проблемы


Python

Тут всё просто, устанавливаем wfastcgi и готово.


pip install wfastcgi

Я бы предложил установить в глобальное окружение, дабы один раз настроить IIS для всех Python framewok'ов.


IIS

Теперь настраиваем IIS. В Windows Server этот компонент уже установлен по умолчанию, но не в полном объёме. Я имею в ввиду важный для нашего сайта компонент — CGI.


Компоненты Windows Службы IISСлужбы ИнтернетаКомпоненты разработки приложенийCGI.


Устанавливаем его. Теперь в Диспетчере IIS у нашего сервера появился пункт Настройки FastCGI.


iis_manager_scrin


Далее заходим в него. Возможно, если у вас уже был установлен CGI до того как вы установите wfastcgi, у вас уже будет такая строка.


fastcgi_settings


В противном случае нажимаем на Добавить Приложение... и заполняем поля:


  • Полный путь — С:\Python\python.exe
  • Аргументы — C:\Python\Lib\site-packages\wfastcgi.py

Всё OK. Основная часть сделана. Далее создаём новый сайт. И у сайта выбираем Сопоставления обработчиков.


handler_mappings


Сопоставления обработчиковДобавление сопоставления модуля


  • Путь запроса — *
  • Модуль — FastCgiModule
  • Исполняемый файл — C:\Python\python.exe|C:\Python\Lib\site-packages\wfastcgi.py
  • Имя — Python FastCGI
  • Ограничения запроса — нажать и убрать галочку

Далее ОК. IIS настроен.


Django App

Копируем проект в папку сайта. Создаём в ней виртуальное окружение. В этой же папке создаём файл web.config (или открываем для редактирования, если он уже создан).


web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appSettings>
        <add key="WSGI_ALT_VIRTUALENV_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
        <add key="WSGI_ALT_VIRTUALENV_ACTIVATE_THIS" value="venv\Scripts\activate_this.py" />
        <add key="WSGI_HANDLER" value="ptvs_virtualenv_proxy.get_virtualenv_handler()" />
        <add key="PYTHONPATH" value="%APPL_PHYSICAL_PATH%" />
        <add key="DJANGO_SETTINGS_MODULE" value="project.settings" />
    </appSettings>
    <system.webServer>
        <handlers>
            <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\Python\python.exe|C:\Python\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" />
        </handlers>
    </system.webServer>
</configuration>

А также создаём ptvs_virtualenv_proxy.py и вставляем в него код ниже


ptvs_virtualenv_proxy.py
# ############################################################################
#
# Copyright (c) Microsoft Corporation.
#
# This source code is subject to terms and conditions of the Apache License, Version 2.0. A
# copy of the license can be found in the License.html file at the root of this distribution. If
# you cannot locate the Apache License, Version 2.0, please send an email to
# vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
# by the terms of the Apache License, Version 2.0.
#
# You must not remove this notice, or any other, from this software.
#
# ###########################################################################

import datetime
import os
import sys

if sys.version_info[0] == 3:
    def to_str(value):
        return value.decode(sys.getfilesystemencoding())

    def execfile(path, global_dict):
        """Execute a file"""
        with open(path, 'r') as f:
            code = f.read()
        code = code.replace('\r\n', '\n') + '\n'
        exec(code, global_dict)
else:
    def to_str(value):
        return value.encode(sys.getfilesystemencoding())

def log(txt):
    """Logs fatal errors to a log file if WSGI_LOG env var is defined"""
    log_file = os.environ.get('WSGI_LOG')
    if log_file:
        f = open(log_file, 'a+')
        try:
            f.write('%s: %s' % (datetime.datetime.now(), txt))
        finally:
            f.close()

ptvsd_secret = os.getenv('WSGI_PTVSD_SECRET')
if ptvsd_secret:
    log('Enabling ptvsd ...\n')
    try:
        import ptvsd
        try:
            ptvsd.enable_attach(ptvsd_secret)
            log('ptvsd enabled.\n')
        except:
            log('ptvsd.enable_attach failed\n')
    except ImportError:
        log('error importing ptvsd.\n')

def get_wsgi_handler(handler_name):
    if not handler_name:
        raise Exception('WSGI_HANDLER env var must be set')

    if not isinstance(handler_name, str):
        handler_name = to_str(handler_name)

    module_name, _, callable_name = handler_name.rpartition('.')
    should_call = callable_name.endswith('()')
    callable_name = callable_name[:-2] if should_call else callable_name
    name_list = [(callable_name, should_call)]
    handler = None

    while module_name:
        try:
            handler = __import__(module_name, fromlist=[name_list[0][0]])
            for name, should_call in name_list:
                handler = getattr(handler, name)
                if should_call:
                    handler = handler()
            break
        except ImportError:
            module_name, _, callable_name = module_name.rpartition('.')
            should_call = callable_name.endswith('()')
            callable_name = callable_name[:-2] if should_call else callable_name
            name_list.insert(0, (callable_name, should_call))
            handler = None

    if handler is None:
        raise ValueError('"%s" could not be imported' % handler_name)

    return handler

activate_this = os.getenv('WSGI_ALT_VIRTUALENV_ACTIVATE_THIS')
if not activate_this:
    raise Exception('WSGI_ALT_VIRTUALENV_ACTIVATE_THIS is not set')

def get_virtualenv_handler():
    log('Activating virtualenv with %s\n' % activate_this)
    execfile(activate_this, dict(__file__=activate_this))

    log('Getting handler %s\n' % os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    handler = get_wsgi_handler(os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    log('Got handler: %r\n' % handler)
    return handler

def get_venv_handler():
    log('Activating venv with executable at %s\n' % activate_this)
    import site
    sys.executable = activate_this
    old_sys_path, sys.path = sys.path, []

    site.main()

    sys.path.insert(0, '')
    for item in old_sys_path:
        if item not in sys.path:
            sys.path.append(item)

    log('Getting handler %s\n' % os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    handler = get_wsgi_handler(os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    log('Got handler: %r\n' % handler)
    return handler

Теперь открываем браузер, вводим адрес нашего сайта и он работает.


Ещё немного


Если вы не планируете использовать виртуальное окружение в своём проекте, то ptvs_virtualenv_proxy.py добавлять не надо, а web.config будет иметь следующий вид:


web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appSettings>
        <add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
        <add key="PYTHONPATH" value="%APPL_PHYSICAL_PATH%" />
        <add key="DJANGO_SETTINGS_MODULE" value="project.settings" />
    </appSettings>
    <system.webServer>
        <handlers>
            <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\Python\python.exe|C:\Python\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" />
        </handlers>
    </system.webServer>
</configuration>

Так же, теперь на вашем IIS вы можете разворачивать проекты не только на Django, но и других Python framework'ах. Главное не забыть подредактировать web.config.


Послесловие


Теперь у нас есть рабочий сайт, небольшая группа людей, которая помогает нам с развитием и улучшением, за что им большое спасибо. А у меня появился некий опыт по дружбе Python проектов и IIS которым я поделился с вами. Надеюсь, что пригодится.

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 17

    0
    Вспомнил я свой деплой джанги на IIS несколько лет назад, это было ужасно. Да, там в итоге работало как-то, но потом всё время что-то вылезало, блочилось, текло, глючило понемногу. Но делал вроде не так, через какие-то полу-родные тулзы. Не знаю может сейчас лучше с этим. В итоге сейчас там на апаче+mod_wsgi всё это стоит, даже это лучше оказалось.
      0
      Два вопроса которые выползли из головы от начала статьи:
      Все показы сайта проходили на моём ноутбуке, не давая возможности заказчику сесть вечером с кружкой кофе, расслабиться и насладиться тем, что мы уже для него сделали.

      Почему не использовали heroku.com — dynos? Ведь для тестов и прототипов за глаза хватает бесплатного аккаунта. Ну или аналоги.

      Радость моя была не долгой. Сайт на Django, а сервер на Windows Server 2012.

      Почему не обговорили с клиентом тип сервера(особенно после выбора языка и фреймворка), а пустили всё на самотёк? Ведь клиент мог с тем же успехом и шаред хостинг взять за 30р в месяц, с поддержкой только статики.
        0
        Начну со второго. Всё было обговорено, кроме ОС. Я даже и не подумал, что может быть что-то кроме UNIX (я был в курсе о существовании Windows Server). К тому же, заказчик уверял, что через неделю — две он оформит нам сервер. И каждую неделю сроки отодвигались.
        Теперь первый. Сайт был развёрнут на тестовом сервере другого проекта и приходилось его (сайт) частенько останавливать. А обещание вот-вот получить собственный сервер — вселяло надежду))
        0
        python manage.py runserver 80
        

        Открываю сайт в браузер на сервере — всё работает. Отлично. Открываю сайт на домашней машине и вижу, что сайт не доступен. Грусть.

        Порою ошибки позволяют развить вполне себе интересные направления, как, например, Ваша статья.
        python manage.py runserver 0.0.0.0:8000
        

        Так было бы проще, но тогда Вы не освоили бы реализацию IIS+Django.
          0
          Спасибо, буду знать. Я перепробовал все комбинации, но не подумал о порте 8000.
            0

            Дело же не в порте, а в хосте.

              0
              Пробовал оба варианта:
              python manage.py runserver 80
              python manage.py runserver 0.0.0.0:80
              

              И оба раза провалился.
              А по поводу 8000 — предположил, вдруг магия в нём))
                0

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

                  0
                  И этот вариант был проверен. Третьим и четвёртым соответственно (первый и второй — запуски в обычной консоли).
                  А по поводу Apache/nginx — было желание воспользоваться ими, но желание «победить» IIS пересилило)))
                    +2
                    А еще, внезапно, скайп юзает 80 порт.
                    0
                    ну у вас же 80-й порт был IIS-ом занят
                      0
                      Отличная шутка!))
                +1
                Встроенный сервер только для разработки, использовать его в бою — очень плохая идея.
                  0
                  Попробую пока так, ибо нагрузка планируется не сильно высокой, но если что-то пойдёт не так — буду думать в сторону Apache/nginx
                0
                Сам не пробовал, но насколько мне известно, Apache + mod_wsgi нормально работают на Windows, а Django нормально работает под WSGI.
                  0
                  Спаибо за статью, интересный опыт!

                  Вариант с размещением виртуальной машины (и, например, бриджом интерфейсов) не рассматривался? Более гибкая и привычная архитектура, удобство миграций и бекапов в подарок :)
                    0
                    На третий час борьбы(а это было где-то в 00:00), я уже был готов на всё что угодно, но интерес взял своё.

                  Only users with full accounts can post comments. Log in, please.