25 октября 2021 года суданские военные взяли под свой контроль правительство в результате военного переворота. До того, как это произошло, многие суданцы погибли в ходе протестов, часами сидели при 50-градусной жаре, надеясь на лучшее правительство. К сожалению, переворот свел на нет большую часть этой работы.
Я хотел чем-то помочь, поэтому создал веб-приложение для архива революционного суданского искусства https://sudan-art.com. Код с открытым исходным кодом, и это еще рано, поэтому вклад приветствуется 🔥!
В прошлом году я много работал над движением сопротивления перевороту в Мьянме. Я заметил, как быстро протестное движение выпадало из новостей и как трудно архивировать революционное искусство и материалы. Затем я нашел фантастический сайт под названием https://threefingers.org, на котором собраны массы блестящего бирманского искусства, поэтому я решил сделать что-то подобное.
Я думаю, что это важные проблемы, которые необходимо решить. Сохранение присутствия в новостях поддерживает международное давление на нелегитимное правительство. Архивирование искусства, на мой взгляд, помогает напомнить людям, что революция действительно произошла. Многие фрески в Судане, изображающие революционеров, были стерты. Многие бирманские революционеры были арестованы или того хуже.
По дороге я узнал много интересного. Я собираюсь разделить их на три разных поста в блоге, организованных по домену программирования и стеку:
Python и Django — часть I
- Как правильно предварительно обработать изображения для загрузки, уменьшив размер их файлов и удалив метаданные;
- Как смириться с тем, что сделать загрузку изображений на 100 % безопасной невозможно, и управлять сопутствующим риском;
- Как написать тесты для представлений Django, которые обрабатывают загрузку изображений;
- Как избежать смещения макета и показывать страницу, полную изображений, только после ее полной загрузки;
- В качестве альтернативы, как показать заполнители для ваших изображений, чтобы они выглядели хорошо до загрузки;
- Как разработать аккуратный компонент тегов изображений;
DevOps/Цифровой океан — часть III
Во-первых, если вы хотите 100 долларов на 60 дней, чтобы попробовать Digital Ocean, нажмите на ссылку эта.
- Как выполнить развертывание в Digital Ocean с помощью docker compose для MVP;
- Как обслуживать Django/React с NGINX в безопасном производственном развертывании с обратным прокси-сервером;
- Как настроить простой рабочий процесс действий Github для автоматизации конвейера CI/CD для анализа кода, тестирования и развертывания.
Часть I
Как правильно предварительно обработать изображения для загрузки, уменьшив размер их файлов и удалив метаданные
При создании этого приложения я быстро понял, что очень важно правильно предварительно обрабатывать изображения на бэкэнде. С точки зрения фронтенд-разработчика, изображения занимают очень много места.
Одно изображение может стоить вам столько же времени на загрузку, сколько и целая библиотека кода, а то и больше. Поэтому я думаю, что это хорошая идея, чтобы максимально уменьшить размер файла.
Однако изображения выглядят не очень хорошо, когда они сильно сжаты. Поэтому я также решил, что было бы неплохо сохранить версию изображения с более высоким разрешением в базе данных моего сайта, которую пользователи могли бы загрузить.
Последней проблемой, с которой я столкнулся, была очистка загрузок. Изображение с современной цифровой зеркальной камеры с настройками по умолчанию содержит ужасающее количество метаданных. Я чувствовал, что это особенно важно, поскольку большинство изображений, размещенных на моем сайте, носят политический и критический характер.
Итак, как я решал эти проблемы:
Здесь происходит довольно много вещей! Если вы посмотрите на функцию сохранения в строке 34, она переопределяет метод save
, который был частью класса models.Model
, от которого унаследован мой класс Artwork
.
Это удобно, потому что это означает, что этот код будет выполняться всякий раз, когда я сохраняю некоторые данные в базу данных в представлении. Следовательно, здесь я выполняю некоторую предварительную обработку, которая гарантирует, что каждое изображение проходит через функцию reduce_image_size
, определенную в строке 45.
Эта функция создает миниатюру и удаляет все метаданные изображения, читая изображение в поток байтов, а затем снова сохраняя его в файл. Он делает это, создавая новый объект File
из основной библиотеки django, поэтому он отлично сочетается с ImageField
, который я определил в классе Artwork
.
На следующем…
Как смириться с тем, что сделать загрузку изображений на 100 % безопасной невозможно, и справиться с сопутствующим риском
Очень сложно идеально защитить изображение. Поскольку ваш пользователь загружает большой файл разных типов, существует просто огромная поверхность для атаки. Поэтому очень сложно правильно проверить изображение с помощью собственного набора тестов.
Я знаю, что некоторые люди делают то, что запускают контейнер, сканируют файл с помощью антивирусного продукта, а затем уничтожают контейнер. Я думаю, что это хорошая идея, но это требует больших инженерных затрат… Возможно, вы захотите, чтобы это было асинхронным, и вам нужно будет придумать способ сделать все это в задаче celery или подобной задаче.
Я не говорю, что это плохая идея, но это было слишком много работы для этого небольшого личного проекта, который был скорее хобби. Итак, я решил просто положиться на встроенную проверку Django ImageField
, а затем хранить изображения в Digital Ocean Space отдельно от остальной части моего приложения. Это важно, поскольку это означает, что злоумышленник не может переключиться с загрузки изображения на среду, в которой запущено мое веб-приложение, — вместо этого он застрянет в пространстве Digital Ocean Space 😜
Итак, это то, что вы можете видеть в строках 21–30 выше. Если я запускаю приложение локально, то settings.DEBUG
будет истинным, поэтому мне не нужно этого делать. Но если я запускаю его в производственной среде, я настроил все для использования корзины S3 в Digital Ocean.
Для этого используются хранилища Django, в которых есть некоторые подробности о конфигурации с Digital Ocean здесь. Я использовал это для создания здесь класса хранения, на который ссылается PublicMediaStorage
в приведенном выше определении.
Есть несколько других вещей, которые вам нужно настроить в settings.py
вашего приложения, чтобы это работало, но они подробно описаны в документации Digital Ocean здесь.
Поскольку я использовал Django Rest Framework для создания внутреннего API для этой службы, есть несколько вещей, которые вам нужно сделать в ваших представлениях, чтобы убедиться, что ваша загрузка изображения действительно использует ImageField
Django. Это очень хорошо работает с остальной структурой.
Если у вас есть views.py
, как это:
Затем для вашей модели будет использоваться сериализатор, который вы можете определить следующим образом в serializers.py
:
Как написать тесты для представлений Django, которые обрабатывают загрузку изображений
И последнее, но не менее важное: после того, как все сделано, пришло время написать несколько тестов! Но как это сделать для представлений Django, которые включают загрузку изображений?
Вот так ☺️:
Первое, что я здесь сделал, — это создал базовый класс, от которого я могу наследоваться при написании других тестов. Это просто добавляет объект в базу данных, используя образец изображения, который у меня есть в тестовой папке, а затем удаляет все из значения settings.MEDIA_ROOT
, которое является папкой, в которой хранятся все изображения.
Как только это будет сделано, довольно просто загрузить изображение и протестировать его добавление в базу данных, так как Django фактически использует базу данных для тестов, поэтому нет необходимости все имитировать.
Одна вещь, которую я люблю делать, чтобы упростить запуск тестов, — это создать отдельный settings.py
для тестов, чтобы они запускались в базе данных sql lite в памяти. Это может не сработать, если вы используете в своем приложении много синтаксиса, подобного postgres, но если это так, то это удобно, поскольку это означает, что вам не нужно настраивать какую-либо базу данных для запуска стандартного набора тестов Django.
Вот как это выглядит:
Затем вы должны вызывать тесты с помощью python manage.py test --settings=<path to test_settings.py
.
И вот оно!