angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask настройки приложения изменяются администратором на лету

Конфигурация приложения должна быть статической, настройки приложения должны быть динамическими.

26 июля 2019
В Flask
post main image
Original photo unsplash.com/@ssvveennn

У Flask нас есть конфигурационный объект, с помощью которого можно указать параметры базы данных, параметры электронной почты и т.д. Когда мы запускаем Flask программу, она сначала создает приложение. После создания приложения последующие запросы пропускают процесс создания приложения и перенаправляются на blueprint представления.

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

Я также считаю, что мы должны различать переменные конфигурации, такие как база данных, электронная почта, переменные настройки сессии и параметры, которые мы хотим изменить на лету, как текст названия программы, отображаемый в шаблонах или логика разрешения/блокировки регистрации. Мы должны уметь управлять переменными параметров с помощью администратора и запускать другие запущенные экземпляры без перезагрузки этих других экземпляров.

Короче говоря, для реализации настроек приложения мы должны сделать следующее:

  • Определите классы настроек приложения
  • Загрузка текущей версии настроек приложения при Flask его создании
  • Сделайте функцию администрирования для редактирования настроек приложения и создания новой версии настроек приложения.
  • Сделайте функцию администрирования, чтобы активировать новую версию настроек приложения.
  • Запуск в других случаях новой версии настроек приложения.
  • Сделайте настройки приложения доступными в наших Jinja шаблонах

Определение классов настройки приложения

Требование заключается в том, что должна быть возможность создания различных версий настроек приложения. Это больше работы, но позволяет без проблем переключиться на новую версию и вернуться к предыдущей. Я использую атрибут класса/поле seqno таблицы базы данных для идентификации версии.

Настройки приложения, Settings, сгруппированы в секции SettingSections и имеют тип value_type, который показывает, целое число, строка или булева. При каждом сохранении настроек приложения создается новая версия настроек приложения. Эта версия может быть проверена, и если все в порядке, она может быть активирована. Я также добавил переменную from_seqno, которая указывает на версию, которую мы изменили, я использую ее для отображения изменений между версиями.

Конечно, есть настройки приложения, секунды или версии, SettingSeqno, которая содержит номер версии активной версии настроек приложения. Обратите внимание, что я пропустил все данные о населении спины, я не думаю, что они что-то добавляют, см. также предыдущий пост.

class SettingSeqno(Base):

    __tablename__ = 'setting_seqno'

    id = Column(Integer, primary_key=True)
    ...
    # only one can be active at a time
    active_seqno = Column(Integer, server_default='0', index=True)


class Setting(Base):

    __tablename__ = 'setting'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)
    value = Column(String(60), server_default='')
    value_type = Column(Integer, server_default='0')
    setting_section_id = Column(Integer, ForeignKey('setting_section.id'))


class SettingSection(Base):

    __tablename__ = 'setting_section'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)

Загрузка текущей версии настроек приложения при Flask его создании

Я сделал это, создав объект AppSettings, подобно тому, как вы создаете Flask расширение. В функции __init__ выполняется загрузка настроек приложения из БД. В функции create_app я только что добавил строку:

app.app_settings = AppSettings(app)

Обратите внимание, что я добавляю префикс app_settings через 'app.', чтобы сделать app_settings глобальными. В функции create_app я могу называть ее app.app_settings, а в blueprint функциях - .app_settings. До сих пор у нас есть настройки приложения, которые мы можем использовать в нашем приложении, например, позвонив по телефону:

allow_registration = current_app.app_settings.get_value('SECTION_AUTH', 'ALLOW_REGISTRATION')

Сделайте функцию администрирования для редактирования настроек приложения и создания новой версии настроек приложения.

Речь идет о разработке шаблонов и кодирования. У меня есть страница списка, которая содержит список всех версий и страницу редактирования, которая используется для редактирования настроек приложения. При каждом сохранении изменений они сохраняются в новой версии настроек приложения. Эта новая версия не активируется автоматически. Страница с версиями также показывает различия между настройками приложения с помощью Python's difflib.

Сделайте функцию администрирования, чтобы активировать новую версию настроек приложения.

Страница списка с версиями также имеет группу переключателей с переключателями для каждой версии, активированная переключатель отмечена галочкой. Для перехода на новую версию достаточно выбрать версию и в коде обновить значение SettingSeqno.active_seqno и перезагрузить настройки приложения из базы данных. Это прекрасно работает, но только в случае с администратором, другие пользователи (посетители сайта) не знают об этом.

Запуск в других случаях новой версии настроек приложения.

Поскольку мы не хотим перезагружать Flask приложение для посетителей сайта, мы должны искать другой способ. Я использовал обработчик запросов Flask before_request. Я использую переменную сеанса 'app_settings_current_seqno' и значение базы данных 'active_seqno' для проверки необходимости загрузки новой версии настроек приложения.

К сожалению, чтобы получить текущую версию настроек приложения, нам нужно добавить запрос к базе данных в обработчик Flask's before_request. Этого можно было бы избежать, используя общий файл и т.д., но так же, как и в будущем, я могу захотеть поместить некоторые другие значения здесь, я решил добавить дополнительный объект/таблицу перед запросом данных, которая содержит копию переменной active_seqno. Короче говоря, если переменная сеанса отличается от значения БД, настройки приложения перезагружаются из БД. После этого переменная сеанса обновляется до значения active_seqno, предотвращая это при последующих запросах.

    @app.before_request
    def before_request():
        with app.app_context():
            ...
            # reload application settings if session var not equal to database var
            if session_app_settings_current_seqno != database_app_settings_current_seqno:
                app.app_settings.load_from_database(app)
                session['app_settings_current_seqno'] = database_app_settings_current_seqno

Сделайте настройки приложения доступными в наших Jinja шаблонах

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

Я также добавил текущую версию настроек приложения, чтобы легко было увидеть на веб-странице, какая версия настроек приложения используется:

    @app.context_processor
    def inject_base_template_parameters():

        base_template_parameters = {}

        # app_settings
        base_template_parameters['app_settings'] = app.app_settings.get_setting_section_name2setting_name2settings()

        # app_settings seqno
        app_settings_current_seqno = ''
        if session.get('app_settings_current_seqno'):
            app_settings_current_seqno = str( session.get('app_settings_current_seqno') )
        base_template_parameters['app_settings_current_seqno'] = app_settings_current_seqno
        ...
        return base_template_parameters

И в базовом шаблоне:

	<p class="copyright text-muted small">
		{{ app_settings['SECTION_GENERAL']['WEBSITE_COPYRIGHT']['value'] }} [{{ app_settings_current_seqno }}] [72ms]
	</p>

Резюме

Это была нелегкая задача, но она дала больше понимания того, что создание Flask приложения отличается от выполнения последующих запросов. Сбивает с толку, что при инициализации мы должны использовать объект Flask приложения, а после этого и сам Flask current_app объект. Я где-то читал об этом, мне придется читать об этом снова и снова, и снова, и снова.

Ссылки / кредиты

Configuration Handling
https://flask.palletsprojects.com/en/1.1.x/config/

Context Processors
https://flask.palletsprojects.com/en/1.1.x/templating/#context-processors

Dynaconf - Easy and Powerful Settings Configuration for Python
https://dynaconf.readthedocs.io/en/latest/index.html

How to reload a configuration file on each request for Flask?
https://stackoverflow.com/questions/39456672/how-to-reload-a-configuration-file-on-each-request-for-flask

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.