Шаг 3. Обслуживание статических файлов, добавление страниц и использование наследования шаблонов с приложением Flask
Область применения:
Visual Studio Visual Studio для Mac
Visual Studio Code ![]()
В предыдущих шагах этого руководства рассматривалось создание минимального приложения Flask с одной страницей автономного HTML. Современные веб-приложения обычно состоят из многих страниц и используют общие ресурсы, такие как файлы CSS и JavaScript, для обеспечения согласованного стиля и реакции на события.
На этом шаге вы научитесь делать следующее:
- с помощью шаблонов элементов Visual Studio быстро добавлять новые файлы различных типов с удобным стереотипным кодом (шаг 3.1);
- обрабатывать статические файлы из кода (шаг 3.2, дополнительный);
- добавлять дополнительные страницы в приложение (шаг 3–3);
- использовать наследование шаблона для создания заголовка и панели навигации, которая используется на разных страницах (шаг 3–4).
Шаг 3–1. Знакомство с шаблонами элементов
По мере разработки приложения Flask обычно добавляется гораздо больше дополнительных файлов Python, HTML, CSS и JavaScript. Для каждого типа файла (и других файлов, таких как web.config, которые могут понадобиться при развертывании) Visual Studio предоставляет удобные шаблоны элементов для начала работы.
Чтобы просмотреть доступные шаблоны, перейдите к обозревателю решений, щелкните правой кнопкой мыши папку, в которой необходимо создать элемент, выберите Добавить>Новый элемент:

Чтобы использовать шаблон, выберите нужный шаблон, укажите имя файла и нажмите кнопку ОК. При добавлении элемента таким образом автоматически добавляется файл в проект Visual Studio и отмечает изменения для системы управления версиями.
Вопрос. Как Visual Studio определяет, какой шаблон элемента предложить?
Ответ. Файл проекта Visual Studio (PYPROJ) содержит идентификатор типа проекта, который помечает его как проект Python. Visual Studio использует этот тип идентификатора для отображения шаблонов элементов, подходящих для типа проекта. Таким образом Visual Studio может предоставлять богатый набор шаблонов элементов для многих типов проектов, который не требуется отсортировывать каждый раз.
Шаг 3–2. Обработка статических файлов из приложения
В веб-приложении, созданном с помощью Python (с помощью любой платформы), файлы Python всегда выполняются на сервере веб-узла и никогда не передаются на компьютер пользователя. Другие файлы, такие как каскадные таблицы стилей и JavaScript, используются исключительно браузером, поэтому сервер узла просто доставляет их без изменений всякий раз, когда они запрашиваются. Такие файлы называются статическими файлами, Flask может доставлять их автоматически без необходимости написания кода. В HTML-файлах, например, можно ссылаться на статические файлы, используя относительный путь в проекте. Первый раздел в этом шаге служит для добавления файла CSS в существующий шаблон страницы.
Если статический файл требуется предоставлять из кода, например через реализацию конечной точки API, Flask предоставляет удобный метод, который позволяет ссылаться на файлы, используя относительные пути в папке с именем static (в корневом каталоге проекта). Во втором разделе этого шага демонстрируется работа этого метода с помощью простого статического файла данных.
В любом случае можно упорядочить файлы в static так, как вам удобно.
Использование статического файла в шаблоне

- В обозревателе решений в проекте Visual Studio щелкните папку HelloFlask правой кнопкой мыши, выберите Добавить>Новая папка и назовите папку static .
- Щелкните папку static правой кнопкой мыши и выберите Добавить>Новый элемент. В открывшемся диалоговом окне выберите шаблон Таблица стилей, назовите файл site.css и нажмите кнопку ОК. Файл site.css появится в проекте и откроется в редакторе. Структура папки должна выглядеть похоже на структуру на следующем рисунке:
- Замените все содержимое site.css следующим кодом и сохраните файл:
.message
Обслуживание статического файла из кода
Flask предоставляет функцию с именем serve_static_file , которую можно вызвать из кода для ссылки на любой файл в папке static проекта. В рамках следующей процедуры создается простая конечная точка API, которая возвращает статический файл данных.
- Создайте папку static, если это еще не сделано: в обозревателе решений в проекте Visual Studio щелкните папку HelloFlask правой кнопкой мыши, выберите Добавить>Новая папка и назовите папку static .
- В папке static создайте статический файл данных JSON с именем data.json со следующим содержимым (бессмысленный пример данных):
@app.route('/api/data') def get_data(): return app.send_static_file('data.json')
Вопрос. Существуют ли соглашения для организации статических файлов?
Ответ. Вы можете добавить другие файлы CSS, JavaScript и HTML в свою папку static любым способом. Типичный способ организации статических файлов — это создание вложенных папок fonts, scripts и content (для таблиц стилей и других файлов).
Вопрос. Как обрабатывать переменные URL-адреса и параметры запроса в API?
Шаг 3–3. Добавление страницы в приложение
Добавление еще одной страницы в приложение означает:
- добавление функции Python, которая определяет представление;
- добавление шаблона для исправления страницы;
- добавление необходимой маршрутизации в файл urls.py проекта Flask.
Следующие действия добавляются на странице «Дополнительные сведения» проекта HelloFlask и связывают эту страницу с домашней страницей:
-
В обозревателе решений щелкните правой кнопкой мыши папку templates, выберите Добавить>Новый элемент, щелкните шаблон элемента HTML-страница, назовите файл about.html и нажмите кнопку ОК.
Совет Если команда Новый элемент не появляется в меню Добавить, убедитесь, что вы остановили приложение, чтобы среда Visual Studio вышла из режима отладки.
> Home >
@app.route('/about') def about(): return render_template( "about.html", title = "About HelloFlask", content = "Example app page for Flask.")
Вопрос. Имеет ли имя функции страницы значение в Flask?
Ответ. Нет, так как URL-адреса, для которых Flask вызывает функцию для получения ответа, определяет декоратор @app.route . Разработчики обычно подбирают имя функции в соответствии с маршрутом, но такое сопоставление необязательно.
Шаг 3–4. Использование наследования шаблона для создания заголовка и навигационной панели
Вместо создания явных навигационных ссылок на каждой странице современные веб-приложения обычно используют заголовок фирменной символики и панель навигации, которая предоставляет самые важные ссылки на страницы, всплывающие меню и т. д. Чтобы обеспечить согласованность приложения, на всех страницах необходимо использовать одинаковые заголовок и панель навигации, однако при этом в каждом шаблоне страницы не должен повторяться один и тот же код. Вместо этого необходимо определить общие компоненты всех страниц в одном месте.
Система шаблонов Flask (по умолчанию Jinja) предоставляет два способа повторного использования определенных элементов в нескольких шаблонах: включение и наследование.
- Включение. Это другие шаблоны страниц, которые вставляются в определенное место в ссылочном шаблоне, используя синтаксис %> . Кроме того, можно использовать переменную, если необходимо динамически изменять путь в коде. Включение обычно используется в тексте страницы для извлечения общего шаблона в определенном расположении на странице.
- Наследование. В начале шаблона страницы используется %> для определения общего базового шаблона, на основе которого затем создается ссылочный шаблон. Наследование обычно используется для определения общих макета, панели навигации и других структур для страниц приложения, так что ссылочным шаблонам необходимо только добавлять или изменять определенные области базового шаблона, называемые блоками.
В обоих случаях относится к папке templates приложения ( ../ или ./ также разрешены).
Базовый шаблон отделяет блоки с помощью тегов %> и . Если ссылающийся на него шаблон использует теги с тем же именем блока, содержимое его блока переопределяет содержимое базового шаблона.
Далее демонстрируется наследование:
-
В папке templates приложения создайте HTML-файл (с помощью пункта контекстного меню Добавить>Новый элемент или Добавить>HTML-страница) с именем layout.html и замените его содержимое приведенным ниже кодом. Вы увидите, что этот шаблон содержит блок под названием content, который должен быть заменен для всех ссылающихся страниц:
> "
.navbar < background-color: lightslategray; font-size: 1em; font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; color: white; padding: 8px 5px 8px 5px; >.navbar a < text-decoration: none; color: inherit; >.navbar-brand < font-size: 1.2em; font-weight: 600; >.navbar-item < font-variant: small-caps; margin-left: 30px; >.body-content

Следующие шаги
Изучить тему подробнее можно с помощью следующих ресурсов:
- Сведения о дополнительных возможностях шаблонов Jinja, таких как поток управления, см. в документации по конструктору шаблонов Jinja (jinja.pocoo.org).
- Дополнительные сведения об использовании url_for см. в разделе url_for в документации по объекту приложения Flask (flask.pocoo.org).
- Исходный код, используемый в руководстве, на сайте GitHub: Microsoft/python-sample-vs-learning-flask
Чтение и запись файлов в Python
Перед тем как прочесть или записать что-либо в файл, его следует открыть с помощью функции open() . При вызове эта функция создает объект типа File , с которым в дальнейшем можно работать.
open(file, mode, buffering, encoding)
Режим доступа mode — строка, в которой указывается для чего открывается файл:
- ‘r’ — открытие на чтение (является значением по умолчанию)
- ‘w’ — открытие на запись, содержимое файла удаляется; если файла не существует, создается новый
- ‘x’ — если файла не существует — открытие на запись, иначе исключение
- ‘a’ — открытие на дозапись, информация добавляется в конец файла
- ‘b’ — открытие в двоичном режиме
- ‘t’ — открытие в текстовом режиме (является значением по умолчанию)
- ‘+’ — открытие на чтение и запись
Режимы могут быть объединены, то есть, к примеру, ‘rb’ — чтение в двоичном режиме. По умолчанию режим равен ‘rt’ .
Буферизация buffering (целое число): 0 — файл открывается без буферизации, 1 — с построчной буферизацией, больше одного — буферизация выполняется с указанным размером, отрицательное число — разер буфера будет равен системному.
И последний аргумент encoding задает кодировку, нужен только в текстовом режиме чтения файла.
Как только файл был открыт и появился файловый объект, можно получить следующую информацию:
- file.closed — возвращает True если файл был закрыт
- file.mode — возвращает режим доступа, с которым был открыт файл
- file.name — возвращает имя файла
- file.softspace — возвращает False , если при выводе содержимого файла следует отдельно добавлять пробел
>>> file = open('test.txt') >>> file.closed False >>> file.mode 'r' >>> file.name 'test.txt' >>> file.close()
Метод read() читает весь файл целиком, если был вызван без аргументов, и n символов, если был вызван с аргументом (целым числом n ). Запись в файл осуществляется с помощью метода write() . После окончания работы с файлом его обязательно нужно закрыть с помощью метода close() .
>>> helloFile = open('C:\\example\\hello.txt', 'w') >>> helloFile.write('Hello, world!\n') 14 >>> helloFile.close()
>>> helloFile = open('C:\\example\\hello.txt', 'r') >>> helloFile.read() 'Hello, world!\n' >>> helloFile.close()
Добавление информации в файл:
>>> helloFile = open('C:\\example\\hello.txt', 'a') >>> helloFile.write('It\'s nice to meet you.\n') 23 >>> helloFile.close()
Чтение всех строк из файла:
>>> helloFile.readlines() ['Hello, world!\n', "It's nice to meet you.\n"] >>> helloFile.close()
Построчное чтение из файла:
>>> helloFile = open('C:\\example\\hello.txt', 'r') >>> helloFile.readline() 'Hello, world!\n' >>> helloFile.readline() "It's nice to meet you.\n" >>> helloFile.close()
При считывании символ новой строки \n присутствует в конце каждой из строк. Его может не быть лишь в последней строке — это позволяет добиться однозначности: если метод возвращает пустую строку, значит достигнут конец файла; если строка содержит лишь символ \n значит это просто очередная строка.
Запись нескольких строк в файл (при использовании with закрывать файл не нужно):
>>> with open('test.txt', 'w') as file: . file.writelines(['First line\n', 'Second line'])
>>> with open('test.txt') as file: . file.readlines() ['First line\n', 'Second line']
Метод tell() возвращает позицию указателя, метод seek(offset, [from]) позволяет перейти на нужную позицию. Аргумент offset указывает на сколько байт перейти; аргумент from означает позицию, с которой начинается движение. 0 — означает начало файла, 1 — нынешняя позиция, 2 — конец файла.
Сохранение данных с помощью модуля shelve
>>> import shelve >>> shelfFile = shelve.open('data') >>> users = ['Иванов', 'Петров'] >>> mails = ['ivanov@mail.ru', 'petrov@mail.ru'] >>> shelfFile['users'] = users >>> shelfFile['mails'] = mails >>> shelfFile.close()
>>> os.listdir() ['data.bak', 'data.dat', 'data.dir', 'hello.txt']
>>> shelfFile = shelve.open('data') >>> shelfFile['users'] ['Иванов', 'Петров'] >>> shelfFile['mails'] ['ivanov@mail.ru', 'petrov@mail.ru'] >>> shelfFile.close()
- Работа с файлами в Python, модуль shutil
- Расширение «ESLint» для VS Code, часть 2 из 2
- Расширение «ESLint» для VS Code, часть 1 из 2
- Расширение «Prettier — Code formatter» для VS Code
- Git. Символы конца строки EOL
- Laravel. Файловое хранилище
- Пользователь и группа www-data
Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Категории блога
Облако тегов
- 1С:Предприятие (31)
- API (29)
- Bash (43)
- CLI (124)
- CMS (139)
- CSS (50)
- Frontend (75)
- HTML (66)
- JavaScript (150)
- Laravel (72)
- Linux (171)
- MySQL (76)
- PHP (125)
- React.js (66)
- SSH (27)
- Ubuntu (69)
- Web-разработка (509)
- WordPress (73)
- Yii2 (69)
- БазаДанных (95)
- Битрикс (66)
- Блог (29)
- Верстка (43)
- ИнтернетМагаз… (84)
- КаталогТоваров (87)
- Класс (30)
- Клиент (28)
- Ключ (28)
- Команда (88)
- Компонент (60)
- Конфигурация (66)
- Корзина (32)
- ЛокальнаяСеть (32)
- Модуль (34)
- Навигация (31)
- Настройка (143)
- ПанельУправле… (29)
- Плагин (33)
- Пользователь (26)
- Практика (101)
- Сервер (77)
- Событие (28)
- Теория (106)
- Установка (67)
- Файл (51)
- Форма (58)
- Фреймворк (192)
- Функция (36)
- ШаблонСайта (68)
Загрузка файлов¶
Ну да, старая добрая проблема загрузки файлов. Основная мысль загрузки файлов на самом деле очень проста. В общих чертах это работает так:
- Тег помечается атрибутом enctype=multipart/form-data , а в форму помещается тег .
- Приложение получает доступ к файлу через словарь files в объекте запроса.
- Воспользуйтесь методом save() для того, чтобы сохранить временный файл в файловой системе для последующего использования.
Введение¶
Начнём с простейшего приложения, которое загружает файл в определённый каталог и отображает его пользователю. Вот начало нашего приложения:
import os from flask import Flask, request, redirect, url_for from werkzeug.utils import secure_filename UPLOAD_FOLDER = '/path/to/the/uploads' ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) app = Flask(__name__) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
Сначала нужно выполнить серию импортов. Большая часть понятна, werkzeug.secure_filename() рассматривается чуть позже. UPLOAD_FOLDER — это путь, куда будут загружаться файлы, а ALLOWED_EXTENSIONS — это набор допустимых расширений. Теперь вручную добавим в приложение правило для URL. Обычно мы так не делаем, но почему мы делаем это сейчас? Причина в том, что мы хотим заставить веб-сервер (или наш сервер приложения) обслуживать эти файлы и поэтому нам нужно генерировать правила для связывания URL с этими файлами.
Почему мы ограничили список допустимых расширений? Наверное вам совсем не хочется, чтобы пользователи могли загружать что угодно, если сервер напрямую отправляет данные клиенту. В таком случае вам нужно быть уверенными в том, что пользователи не загрузят файлы HTML, которые могут вызвать проблему XSS (см. Cross-Site Scripting ( xss ) — межсайтовый скриптинг). Также убедитесь в том, что запрещены файлы с расширением .php , если сервер их выполняет. Правда, кому нужен PHP на сервере? 🙂
Следующая функция проверяет, что расширение файла допустимо, загружает файл и перенаправляет пользователя на URL с загруженным файлом:
def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS @app.route('/', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': file = request.files['file'] if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return redirect(url_for('uploaded_file', filename=filename)) return '''Upload new File Upload new File
'''
Что делает функция secure_filename() ? Мы исходим из принципа «никогда не доверяй тому, что ввёл пользователь». Это справедливо и для имени загружаемого файла. Все отправленные из формы данные могут быть поддельными и имя файла может представлять опасность. Сейчас главное запомнить: всегда используйте эту функцию для получения безопасного имени файла, если собираетесь поместить файл прямо в файловую систему.
Информация для профи
Может быть вам интересно, что делает функция secure_filename() и почему нельзя обойтись без её использования? Просто представьте, что кто-то хочет отправить следующую информацию в ваше приложение в качестве имени файла:
filename = "../../../../home/username/.bashrc"
Если считать, что ../ — это нормально, то при соединении этого имени с UPLOAD_FOLDER , пользователь может получить возможность изменять на файловой системе сервера те файлы, который он не должен изменять. Нужно немного разбираться в устройстве вашего приложения, но поверьте мне, хакеры настойчивы 🙂
Посмотрим, как отработает функция:
>>> secure_filename('../../../../home/username/.bashrc') 'home_username_.bashrc'
Осталась последняя вещь: обслуживание загруженных файлов. Начиная с Flask 0.5 для этого можно использовать соответствующую функцию:
from flask import send_from_directory @app.route('/uploads/') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
Другая возможность — зарегистрировать uploaded_file с помощью правила build_only и воспользоваться SharedDataMiddleware . Такой вариант будет работать и в более старых версиях Flask:
from werkzeug import SharedDataMiddleware app.add_url_rule('/uploads/', 'uploaded_file', build_only=True) app.wsgi_app = SharedDataMiddleware(app.wsgi_app, '/uploads': app.config['UPLOAD_FOLDER'] >)
Теперь, если запустить приложение, всё должно работать как положено.
Улучшение загрузки¶
Добавлено в версии 0.6.
Как на самом деле Flask обрабатывает загрузку? Если файл достаточно мал, он сохраняется в памяти веб-сервера. В противном случае он помещается во временное место (туда, куда укажет tempfile.gettempdir() ). Но как указать максимальный размер файла, после которого загрузка файла должна быть прервана? По умолчанию Flask не ограничивает размер файла, но вы можете задать лимит настройкой ключа конфигурации MAX_CONTENT_LENGTH :
from flask import Flask, Request app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Код выше ограничит максимальный размер файла 16 мегабайтами. Если передаваемый файл окажется больше, Flask сгенерирует исключение RequestEntityTooLarge .
Эта функциональность была добавлена во Flask 0.6, но может быть реализована и в более ранних версиях при помощи наследовании от класса request. За более подробной информацией обратитесь к документации Werkzeug об обработке файлов.
Индикаторы процесса загрузки¶
Многие разработчики придумывают считывать файл мелкими частями, сохранять процент загрузки в базу данных и давать возможность JavaScript считывать эти данные из клиента. Короче говоря, клиент спрашивает у сервера каждые 5 секунд, сколько уже было передано. Почувствовали иронию ситуации? Клиент спрашивает у сервера о том, что уже и так знает.
Сейчас существуют способы получше, которые работают быстрее и более надёжны. В последнее время в вебе многое изменилось и теперь можно использовать HTML5, Java, Silverlight или Flash, чтобы сделать загрузку удобнее со стороны клиента. Посмотрите на следующие библиотеки, предназначенные именно для этого:
- Plupload — HTML5, Java, Flash
- SWFUpload — Flash
- JumpLoader — Java
Простейшее решение¶
Поскольку общая процедура загрузки файлов остаётся неизменной для всех приложений, занимающихся загрузкой файлов, для Flask есть расширение под названием Flask-Uploads, которое реализует полностью самостоятельный механизм загрузки с белым и чёрным списком расширений и т.п.
Запись данных в файл после их получения на сервере Flask
Как записать полученные на сервере данные в файл. В моем варианте, при попытке записи в файл, я получаю ошибку 500 на сервере. Ошибка именно в этой строке, поскольку если я комментирую строку с записью файла, то все срабатывает.
from flask import Flask, jsonify, request app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" @app.route('/save', methods = ['POST']) def save_config(): request_data = request.get_json() custom_config = open("custom_config.json", "w+") custom_config.write(request_data) custom_config.close() return request_data if __name__ == "__main__": app.run(host='127.0.0.1', port=5002)
Отслеживать
задан 21 мар 2023 в 14:20
39 5 5 бронзовых знаков
Может у пользователя, от которого запущен Flask , нет прав на запись в этот каталог? Кстати, какой каталог для вашего скрипта текущий — это ещё нужно выяснять. Можете для начала попробовать указать полный путь к записываемому файлу. И используйте with , чтобы вручную файл не закрывать. А в более общем случае лучше сделать логирование хоть какое-то.
21 мар 2023 в 14:53
Если в файл записывать не данные получение в запросе, а например просто вписать «123» вместо request_data, то файл создается и записывает туда это. Ошибка 500 появляется, если именно request_data записывать туда