Создание резервных копий VPS на Яндекс Диск
Автор: Иван Ашихмин
Бэкапы – больная тема. Разработчики скажут "какие бэкапы? У меня есть git!" и будут правы. Частично. Git, безусловно, позволяет откатиться на прошлую, "рабочую", версию. Но что будет, если откажет накопитель с проектом, который был размещен локально? Или случатся проблемы с сервером, где, помимо файлов проекта, лежит ещё и БД?
Где бэкапы? Где резервные копии? Их нет... Давайте исправим это.
Почему я задумался о бэкапах? Всё просто. У меня на сервере сейчас много всего:
- Сайт https://pressanybutton.ru/
- Telegram-бот для канала и чата https://news.1rj.ru/str/press_any_button_bot
- Собственный git-сервер на базе Gitea.
Автор: Иван Ашихмин
Бэкапы – больная тема. Разработчики скажут "какие бэкапы? У меня есть git!" и будут правы. Частично. Git, безусловно, позволяет откатиться на прошлую, "рабочую", версию. Но что будет, если откажет накопитель с проектом, который был размещен локально? Или случатся проблемы с сервером, где, помимо файлов проекта, лежит ещё и БД?
Где бэкапы? Где резервные копии? Их нет... Давайте исправим это.
Почему я задумался о бэкапах? Всё просто. У меня на сервере сейчас много всего:
- Сайт https://pressanybutton.ru/
- Telegram-бот для канала и чата https://news.1rj.ru/str/press_any_button_bot
- Собственный git-сервер на базе Gitea.
🔥2
- Собственный почтовый сервер на базе Docker Mailserver.
- Собственная Wiki на базе WikiJS.
- И БД PostgreSQL для всего этого.
Потерять это будет крайне обидно.
Мой VPS приобретён у Timeweb. У них хорошие сервера и поддержка, но я не застрахован от внештатных ситуаций. Они, конечно, предоставляют услугу по созданию резервных копий, но она стоит 180 рублей в месяц при одном бэкапе в день/неделю/месяц. Если нужно хранить 7 копий на всю неделю, это 1260 рублей в месяц, что уже "кусается". Делать же всего одну копию вчерашнего дня или раз в неделю – не так уж эффективно.
С другой стороны, у меня есть подписка на "Яндекс 360", предоставляющая 1ТБ за 2290 рублей в год. Почему бы не воспользоваться этим хранилищем?
В итоге формируется следующая задача: необходимо организовать резервное копирование данных на сервере и отправку их на Яндекс Диск.
В качестве программы для создания резервных копий мы будем использовать Restic.Документация доступна по ссылке: https://restic.readthedocs.io/en/stable/
Яндекс Диск подключим, как удалённое хранилище при помощи rclone.Документация доступна по ссылке: https://rclone.org/
Конфигурация используемого VPS:
- CPU: 2 x 3.3ГГц
- RAM: 4 Гб
- NVMe: 30 Гб
- OS: Debian 12
Все действия производятся в терминале подключенном к VPS по SSH.
Установка и настройка rclone.
Начнём мы с подключения Яндекс Диска в качестве доступного в системе раздела.
Для установки rclone, выполним в терминале команду:
В результате должно быть сообщение как на скриншоте:
После установки нам необходимо установить подключение к Яндекс Диску, создав новое удалённое подключение в rclone.
Настройка выполняется в интерактивном режиме. Для начала настройки необходимо выполнить команду:
Первое сообщение оповещает о том, что у нас нет файла настроек и будет создан новый. Далее предлагается три варианта действий:
-
-
-
Нам необходим первый вариант. Нажимаем клавишу
Следующим действием нас просят указать имя подключения. Вписываем.
Далее отобразится большой список поддерживаемых сервисов, в котором необходимо найти нужный нам Yandex Disk и ввести его номер или краткое название, указанное в скобках. В моём случае это номер 52 и имя
Далее будет три вопроса о вводе client_id, client_secret и расширенные настройки. Первые два оставляем пустыми, а в третьем выбираем
Затем нас попросят выбрать, открыть браузер для авторизации на сайте или нет. Поскольку мы подключились по SSH к VPS, никакого браузера у нас нет, выбираем
И тут возникает сложность. Нам необходимо авторизоваться в Яндекс, а для этого необходимо запустить rclone на локальной машине, где есть возможность открыть браузер.Скачиваем бинарный файл для своей системы на сайте: https://rclone.org/downloads/Пользователи Linux могут воспользоваться командой описанной выше.Локальная машина у меня на Windows, поэтому все действия буду выполнять на ней.
Не разрываем соединение с VPS сервером! Все действия проводим во втором открытом окне терминала!
Скачиваем и распаковываем архив с программой: https://downloads.rclone.org/v1.66.0/rclone-v1.66.0-windows-amd64.zip
Открываем в этой директории терминал.
Копируем команду, указанную в сообщении на VPS, заменив в начале
В браузере откроется страница с авторизацией в Яндекс. Вводим логин и пароль. Затем разрешаем доступ к Диску.
- Собственная Wiki на базе WikiJS.
- И БД PostgreSQL для всего этого.
Потерять это будет крайне обидно.
Мой VPS приобретён у Timeweb. У них хорошие сервера и поддержка, но я не застрахован от внештатных ситуаций. Они, конечно, предоставляют услугу по созданию резервных копий, но она стоит 180 рублей в месяц при одном бэкапе в день/неделю/месяц. Если нужно хранить 7 копий на всю неделю, это 1260 рублей в месяц, что уже "кусается". Делать же всего одну копию вчерашнего дня или раз в неделю – не так уж эффективно.
С другой стороны, у меня есть подписка на "Яндекс 360", предоставляющая 1ТБ за 2290 рублей в год. Почему бы не воспользоваться этим хранилищем?
В итоге формируется следующая задача: необходимо организовать резервное копирование данных на сервере и отправку их на Яндекс Диск.
В качестве программы для создания резервных копий мы будем использовать Restic.Документация доступна по ссылке: https://restic.readthedocs.io/en/stable/
Яндекс Диск подключим, как удалённое хранилище при помощи rclone.Документация доступна по ссылке: https://rclone.org/
Конфигурация используемого VPS:
- CPU: 2 x 3.3ГГц
- RAM: 4 Гб
- NVMe: 30 Гб
- OS: Debian 12
Все действия производятся в терминале подключенном к VPS по SSH.
Установка и настройка rclone.
Начнём мы с подключения Яндекс Диска в качестве доступного в системе раздела.
Для установки rclone, выполним в терминале команду:
sudo -v ; curl https://rclone.org/install.sh | sudo bash
В результате должно быть сообщение как на скриншоте:
После установки нам необходимо установить подключение к Яндекс Диску, создав новое удалённое подключение в rclone.
Настройка выполняется в интерактивном режиме. Для начала настройки необходимо выполнить команду:
rclone config
Первое сообщение оповещает о том, что у нас нет файла настроек и будет создан новый. Далее предлагается три варианта действий:
-
n - Новое подключение-
s - Установить пароль-
q - Выйти из настроек.Нам необходим первый вариант. Нажимаем клавишу
n и отправляем.Следующим действием нас просят указать имя подключения. Вписываем.
Далее отобразится большой список поддерживаемых сервисов, в котором необходимо найти нужный нам Yandex Disk и ввести его номер или краткое название, указанное в скобках. В моём случае это номер 52 и имя
(yandex).Далее будет три вопроса о вводе client_id, client_secret и расширенные настройки. Первые два оставляем пустыми, а в третьем выбираем
n.Затем нас попросят выбрать, открыть браузер для авторизации на сайте или нет. Поскольку мы подключились по SSH к VPS, никакого браузера у нас нет, выбираем
n.И тут возникает сложность. Нам необходимо авторизоваться в Яндекс, а для этого необходимо запустить rclone на локальной машине, где есть возможность открыть браузер.Скачиваем бинарный файл для своей системы на сайте: https://rclone.org/downloads/Пользователи Linux могут воспользоваться командой описанной выше.Локальная машина у меня на Windows, поэтому все действия буду выполнять на ней.
Не разрываем соединение с VPS сервером! Все действия проводим во втором открытом окне терминала!
Скачиваем и распаковываем архив с программой: https://downloads.rclone.org/v1.66.0/rclone-v1.66.0-windows-amd64.zip
Открываем в этой директории терминал.
Копируем команду, указанную в сообщении на VPS, заменив в начале
rclone на .\rclone.exe. Выполняем.В браузере откроется страница с авторизацией в Яндекс. Вводим логин и пароль. Затем разрешаем доступ к Диску.
🔥4
После того, как увидели надпись
Нас попросят подтвердить, что всё верно. Печатаем и отправляем
На этом добавление подключения закончено.
Не отходя от rclone, создадим директорию для хранения бэкапов на диске, выполнив команду:
Где
Установка и настройка Restic.
После того, как мы подключили rclone, можно устанавливать Restic.
Для установки выполним команду:
Далее инициализируем созданное подключение, выполнив команду:
Нас сразу попросят ввести пароль от репозитория, так называются хранилища в Restic. Вводим.
Сохраните пароль! Без него не будет доступа к репозиторию!
Для создания бэкапа используем команду:
Где
Начнётся процесс создания резервной копии. В моём случае на 7 Гб данных из 38 тысяч файлов, заняло чуть меньше 9-ти минут. Дольше всего добавлялись файлы git-сервера. Следующие бэкапы будут производиться быстрее, поскольку тут система похожа с git - создаётся изначальный индекс репозитория, а затем добавляются или удаляются файлы как "коммиты", называемые "снимками".
Всё предельно просто.
Автоматический запуск создания резервной копии.
Автоматизировать создание бэкапов нам поможет cron, но сперва мы напишем простейший скрипт для вызова команды Restic.
Создадим новый файл
Затем откроем его в nano и пропишем следующее содержимое:
В первой строке прописываем так называемый "шебанг". Это строка начинающаяся с
Нажимаем CTRL+S для сохранения и CTRL+X ля выхода.
Далее файлу необходимо дать права на исполнение. Выполним команду:
Затем запустим crontab для редактирования, выполнив команду:
И впишем туда следующую строку:
Стока выше определяет, что скрипт указанный по пути будет запускаться каждый день в 00:30.
Проверить работу и посмотреть список снимков можно командой:
Восстановление резервной копии.
Резервную копию сделали, а как восстанавливать? Всё достаточно просто.
Для того, чтобы восстановить снимок, достаточно выполнить две команды.
Первая команда описана выше, сперва необходимо выбрать идентификатор снимка для восстановления:
Вторая команда непосредственно восстанавливает снимок:
Вместо
Заключение.
Мы подключили к VPS Яндекс Диск и настроили автоматическое создание резервных копий. Тема была интересна, и я надеюсь, что бэкапы так и останутся невосстановленными.
Success!, возвращаемся в терминал и видим несколько строк с токеном. Копируем его и вставляем в терминал VPS.Нас попросят подтвердить, что всё верно. Печатаем и отправляем
y.На этом добавление подключения закончено.
Не отходя от rclone, создадим директорию для хранения бэкапов на диске, выполнив команду:
rclone mkdir yandex_disk:vps_backup
Где
yandex_disk - имя подключения, а vps_backup - имя директории.Установка и настройка Restic.
После того, как мы подключили rclone, можно устанавливать Restic.
Для установки выполним команду:
apt-get install restic
Далее инициализируем созданное подключение, выполнив команду:
restic -r rclone:yandex_disk:vps_backup init
Нас сразу попросят ввести пароль от репозитория, так называются хранилища в Restic. Вводим.
Сохраните пароль! Без него не будет доступа к репозиторию!
Для создания бэкапа используем команду:
restic -r rclone:yandex_disk:vps_backup backup /root
Где
/root - директория, которая будет сохранена в резервной копии.Начнётся процесс создания резервной копии. В моём случае на 7 Гб данных из 38 тысяч файлов, заняло чуть меньше 9-ти минут. Дольше всего добавлялись файлы git-сервера. Следующие бэкапы будут производиться быстрее, поскольку тут система похожа с git - создаётся изначальный индекс репозитория, а затем добавляются или удаляются файлы как "коммиты", называемые "снимками".
Всё предельно просто.
Автоматический запуск создания резервной копии.
Автоматизировать создание бэкапов нам поможет cron, но сперва мы напишем простейший скрипт для вызова команды Restic.
Создадим новый файл
backup_noscript.sh, вызвав команду:touch backup_noscript.sh
Затем откроем его в nano и пропишем следующее содержимое:
#!/bin/bash
export RESTIC_PASSWORD="password"
restic -r rclone:yandex_disk:vps_backup backup /root
В первой строке прописываем так называемый "шебанг". Это строка начинающаяся с
#! и указывающая путь к интерпретатору или программе, в которой следует выполнить описанный в файле код.В третьей строке мы передаём пароль от репозитория в переменные окружения.В пятой строке вызываем команду создания бэкапа.Нажимаем CTRL+S для сохранения и CTRL+X ля выхода.
Далее файлу необходимо дать права на исполнение. Выполним команду:
chmod +x backup_noscript.sh
Затем запустим crontab для редактирования, выполнив команду:
crontab -e
И впишем туда следующую строку:
30 0 * * * /root/backup_noscript.sh
Стока выше определяет, что скрипт указанный по пути будет запускаться каждый день в 00:30.
Проверить работу и посмотреть список снимков можно командой:
restic -r rclone:yandex_disk:vps_backup snapshots
Восстановление резервной копии.
Резервную копию сделали, а как восстанавливать? Всё достаточно просто.
Для того, чтобы восстановить снимок, достаточно выполнить две команды.
Первая команда описана выше, сперва необходимо выбрать идентификатор снимка для восстановления:
restic -r rclone:yandex_disk:vps_backup snapshots
Вторая команда непосредственно восстанавливает снимок:
restic -r rclone:yandex_disk:vps_backup restore <ID> --target /root
Вместо
<ID> вписываем идентификатор снимка.Заключение.
Мы подключили к VPS Яндекс Диск и настроили автоматическое создание резервных копий. Тема была интересна, и я надеюсь, что бэкапы так и останутся невосстановленными.
🔥9👍1
Приветствую!
Каналу исполнилось 9 месяцев!
За это время:
- Вышло свыше 120 постов.
- На канал подписались 473 человек!
- На сайте примерно 80-100 ежедневных посещений.
- Стримы и другие активности.
Спасибо всем тем, кто читает наши посты. Тем, кто участвует в обсуждении. И тем, кто поддерживает нас.
Впереди много планов, которые по чуть-чуть да воплощаются.
Каналу исполнилось 9 месяцев!
За это время:
- Вышло свыше 120 постов.
- На канал подписались 473 человек!
- На сайте примерно 80-100 ежедневных посещений.
- Стримы и другие активности.
Спасибо всем тем, кто читает наши посты. Тем, кто участвует в обсуждении. И тем, кто поддерживает нас.
Впереди много планов, которые по чуть-чуть да воплощаются.
🔥13❤🔥2☃2❤2🆒1
Отправка поста с Django в AIOgram - обновление
Автор: Иван Ашихмин
В посте "Django + AIOgram3 + Redis - Отправка поста с Django в AIOgram" я рассказывал как отправлять текст поста в AIOgram, обрабатывать и публиковать в Telegram-канале.
Это была первая версия обработчика, которая работала, но была не совсем удобна в использовании.
После нескольких месяцев использования прошлой системы, появилась необходимость "апгрейда" для более удобной публикации постов с сайта в канале.
Обновление основано на файлах из поста: "Django + AIOgram3 + Redis - Отправка поста с Django в AIOgram".
Продолжение в посте на Boosty (платный контент)
Пост на сайте
Поддержать проект
#Python #Django #Гайды #Telegram #AIOgram #Telegram_бот #AIOgram3 #бот #Boosty #автоматический_постинг
Автор: Иван Ашихмин
В посте "Django + AIOgram3 + Redis - Отправка поста с Django в AIOgram" я рассказывал как отправлять текст поста в AIOgram, обрабатывать и публиковать в Telegram-канале.
Это была первая версия обработчика, которая работала, но была не совсем удобна в использовании.
После нескольких месяцев использования прошлой системы, появилась необходимость "апгрейда" для более удобной публикации постов с сайта в канале.
Обновление основано на файлах из поста: "Django + AIOgram3 + Redis - Отправка поста с Django в AIOgram".
Продолжение в посте на Boosty (платный контент)
Пост на сайте
Поддержать проект
#Python #Django #Гайды #Telegram #AIOgram #Telegram_бот #AIOgram3 #бот #Boosty #автоматический_постинг
🔥3👍1
Добрый вечер! Пятница наступила!
Время отключиться от повседневных забот, погрузиться в атмосферу кино и позволить себе почувствовать всю гамму эмоций, которые передают герои на экране. Устройтесь поудобнее и готовьтесь к погружению в мир кинематографа, который позволит вам окунуться в историю и пережить каждый момент вместе с героями.
Фильм: Путь домой
Год: 2010
1940 год, Сибирь. Самый страшный лагерь для политзаключенных. Небольшая группа осужденных смогла вырваться из этого страшного места. Теперь их ждёт долгий и трудный путь домой: выживание в пустыне, переход через горы, столкновение с дикими животными и встречи с незнакомыми людьми. Они идут домой из последних сил, надеясь на встречу со своими близкими.
https://www.sspoisk.ru/film/430638/
Приятного просмотра!
Время отключиться от повседневных забот, погрузиться в атмосферу кино и позволить себе почувствовать всю гамму эмоций, которые передают герои на экране. Устройтесь поудобнее и готовьтесь к погружению в мир кинематографа, который позволит вам окунуться в историю и пережить каждый момент вместе с героями.
Фильм: Путь домой
Год: 2010
1940 год, Сибирь. Самый страшный лагерь для политзаключенных. Небольшая группа осужденных смогла вырваться из этого страшного места. Теперь их ждёт долгий и трудный путь домой: выживание в пустыне, переход через горы, столкновение с дикими животными и встречи с незнакомыми людьми. Они идут домой из последних сил, надеясь на встречу со своими близкими.
https://www.sspoisk.ru/film/430638/
Приятного просмотра!
🔥4
Что выведет код с изображения ниже? №16
Anonymous Quiz
6%
['Желает', 'Знать', 'Охотник', 'Где', 'Сидит', 'Каждый', 'Фазан']
12%
['Каждый', 'Охотник', 'Знать', 'Сидит', 'Желает', 'Где', 'Фазан']
14%
['Каждый', 'Охотник', 'Желает', 'Знать', 'Где', 'Сидит', 'Фазан']
69%
['Где', 'Желает', 'Знать', 'Каждый', 'Охотник', 'Сидит', 'Фазан']
🔥5👍1🤓1
Вчера мы опубликовали задачу на знание основ работы словарей в Python. Задачу верно решило большее число ответивших, а именно 66%.
Код задачи:
Разбор задачи:
Задача состоит из двух шагов:
1. Создаём переменную
2. Выводим на печать отсортированный список значений словаря.
Что происходит в коде?
В переменной
Далее мы хотим его отсортировать, но не указываем по какому параметру, а только лишь передаём в "сортировщик" список (list) со значениями. Возможно, кого-то этот ход обманул: мы действительно просто сортируем список значений словаря, вне всякой зависимости от ключей, которые оказываются просто отброшенными.
В Python сортировка строк осуществляется побуквенно, т.е. строка символов преобразуется в список чисел, где каждый элемент – это число из unicode-таблицы символов. При этом строка из четырёх символов будет всегда считаться меньшей, чем строка из пяти, если четыре первые символа в обеих строках совпадают.
Например,
В нашем же примере слова просто отсортируются по алфавиту:
Где
Желает
Знать
Каждый
Охотник
Сидит
Фазан
Если же мы хотим получить "правильный" порядок, основанный на ключах, то можно воспользоваться сортировкой по ключу:
В примере выше в функцию
Код задачи:
dct = {
"10": "Желает",
"2": "Знать",
"1": "Охотник",
"22": "Где",
"3": "Сидит",
"0": "Каждый",
"30": "Фазан"
}
print(sorted(dct.values()))Разбор задачи:
Задача состоит из двух шагов:
1. Создаём переменную
dct, в которой прописываем словарь.2. Выводим на печать отсортированный список значений словаря.
Что происходит в коде?
В переменной
dct мы прописали словарь, в котором и ключи, и значения – строки. Далее мы хотим его отсортировать, но не указываем по какому параметру, а только лишь передаём в "сортировщик" список (list) со значениями. Возможно, кого-то этот ход обманул: мы действительно просто сортируем список значений словаря, вне всякой зависимости от ключей, которые оказываются просто отброшенными.
В Python сортировка строк осуществляется побуквенно, т.е. строка символов преобразуется в список чисел, где каждый элемент – это число из unicode-таблицы символов. При этом строка из четырёх символов будет всегда считаться меньшей, чем строка из пяти, если четыре первые символа в обеих строках совпадают.
Например,
"абвг" будет идти после "абв" или "Я" будет считаться меньше чем "а".В нашем же примере слова просто отсортируются по алфавиту:
Где
Желает
Знать
Каждый
Охотник
Сидит
Фазан
Если же мы хотим получить "правильный" порядок, основанный на ключах, то можно воспользоваться сортировкой по ключу:
print([v for _, v in sorted(dct.items())])
В примере выше в функцию
sorted() попадёт кортеж (ключ: значение), и сортировка будет по первому элементу – т.е. по ключу. Затем в цикле выводим значение на экран и получаем:['Каждый', 'Охотник', 'Желает', 'Знать', 'Где', 'Сидит', 'Фазан']
🔥6
Django 41. Комментарии к постам
Автор: Иван Ашихмин
В нашем проекте не хватает одного важного элемента – блока комментариев на странице поста. Давайте исправим это.
Что мы сделаем:
- Добавим на страницу поста отображение комментариев.
- Добавим возможность авторизованным пользователям оставлять комментарии.
- Также у автора комментария или администратора должна быть возможность удалить или отредактировать комментарий.
Модель комментария.
Для хранения комментариев нам нужна модель.
Откроем файл
Создадим класс
-
-
Автор: Иван Ашихмин
В нашем проекте не хватает одного важного элемента – блока комментариев на странице поста. Давайте исправим это.
Что мы сделаем:
- Добавим на страницу поста отображение комментариев.
- Добавим возможность авторизованным пользователям оставлять комментарии.
- Также у автора комментария или администратора должна быть возможность удалить или отредактировать комментарий.
Модель комментария.
Для хранения комментариев нам нужна модель.
Откроем файл
models.py в директории приложения blog.Создадим класс
CommentModel, унаследованный от models.Model, и пропишем четыре поля:-
user – Внешний ключ на модель пользователя.-
post – Внешний ключ на модель поста. Для доступа к модели комментария из объекта модели поста указываем аргумент related_name="comments".🔥1
-
-
Также пропишем внутренний класс
Код модели:
Регистрация в панели администратора.
Откроем файл
Создадим класс
Внутри класса поля можно прописать по желанию, например:
-
-
Дополнительно добавим столбец с названием поста при клике на которую будет открываться страница поста.
Для этого создадим метод
Внутри метода, используя функцию
Затем ниже вызываем метод
Не забываем прописать новое поле в
Код класса:
Форма добавления комментария.
Для того чтобы можно было добавлять новый комментарий, необходима форма модели.
Откроем файл
Форма будет весьма классическая:
- Определяем поле
- Прописываем класс
Код формы:
Представления.
Теперь займёмся представлениями. Нам для комментариев нужно три представления:
- Добавление комментария.
- Редактирование.
- Удаление.
Также необходимо изменить представление страницы поста для передачи в него формы комментария.
Представление страницы поста.
Откроем файл
Он тоже весьма стандартный. Внутри метода в контекст добавим новый ключ
Код метода:
comment – Текстовое поле для комментария.-
created_at – Поле с датой написания комментария.Также пропишем внутренний класс
Meta с названием модели и dunder-метод __str__ с текстовым представлением объекта модели.Код модели:
class CommentModel(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, verbose_name="Пользователь"
)
post = models.ForeignKey(
PostModel,
on_delete=models.CASCADE,
verbose_name="Пост",
related_name="comments",
)
comment = models.TextField(verbose_name="Комментарий")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Комментарий"
verbose_name_plural = "Комментарии"
def __str__(self):
return f"Комментарий от {self.user} к посту {self.post}"
Регистрация в панели администратора.
Откроем файл
admin.py.Создадим класс
CommentAdmin, унаследованный от admin.ModelAdmin. Обернём его в декоратор @admin.register(), передав в качестве аргумента модель комментария.Внутри класса поля можно прописать по желанию, например:
-
list_display - отображаемые столбцы.-
list_filter - выбор по каким полям проводить фильтрацию.Дополнительно добавим столбец с названием поста при клике на которую будет открываться страница поста.
Для этого создадим метод
post_link, в аргументах он принимает self и объект модели obj.Внутри метода, используя функцию
mark_safe, возвращаем строку со ссылкой.Затем ниже вызываем метод
allow_tags и устанавливаем его в True. Это позволит выводить HTML на странице панели администратора.Не забываем прописать новое поле в
list_display.Код класса:
@admin.register(models.CommentModel)
class CommentAdmin(admin.ModelAdmin):
list_display = ("user", "post_link", "created_at", "comment")
list_filter = ("user", "post")
def post_link(self, obj):
return mark_safe(
f'<a href="{obj.post.get_absolute_url()}">{obj.post.noscript}</a>'
)
post_link.allow_tags = True
Форма добавления комментария.
Для того чтобы можно было добавлять новый комментарий, необходима форма модели.
Откроем файл
forms.py и создадим класс CommentForm, унаследованный от forms.ModelForm.Форма будет весьма классическая:
- Определяем поле
comment.- Прописываем класс
Meta, указав модель и всего одно поле - comment.Код формы:
class CommentForm(forms.ModelForm):
comment = forms.CharField(
widget=forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "Введите текст комментария",
"rows": 5
}
),
)
class Meta:
model = CommentModel
fields = ("comment",)
Представления.
Теперь займёмся представлениями. Нам для комментариев нужно три представления:
- Добавление комментария.
- Редактирование.
- Удаление.
Также необходимо изменить представление страницы поста для передачи в него формы комментария.
Представление страницы поста.
Откроем файл
views.py. Найдём класс PostPageView и добавим метод get_context_data.Он тоже весьма стандартный. Внутри метода в контекст добавим новый ключ
comment_form, в который определим объект формы CommentForm.Код метода:
🔥1
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["comment_form"] = CommentForm()
return context
Базовое представление.
Нам понадобится три представления, и по сути они идентичны. Различия только в наследуемых классах, поэтому, чтобы избежать дублирования кода, создадим базовый класс
BaseCommentView.В классе пропишем поле
model, в котором определим класс модели комментария.И переопределим метод
get_success_url, принимающий только self.В нём мы вернём путь до страницы поста.
Код класса:
class BaseCommentView:
model = CommentModel
def get_success_url(self):
post = models.PostModel.objects.get(pk=self.object.post.pk)
return reverse(
"blog:post_page",
kwargs={"category_slug": post.category.slug, "slug": post.slug},
)
Представление добавления комментария.
Создадим класс
AddCommentView, унаследованный от BaseCommentView и CreateView, который будет отвечать за добавление комментария.В нём мы добавим поле
form_class, определив в нём класс формы.И переопределим метод
form_valid, принимающий self и объект формы form.Поскольку в модель класса требуется передавать пользователя и связанный с комментарием пост, а в форме мы не можем этого сделать, определим их тут.
Внутри метода определяем поля
user и post, затем вызываем super-метод для сохранения объекта.Код представления:
class AddCommentView(BaseCommentView, CreateView):
form_class = CommentForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post = models.PostModel.objects.get(pk=self.kwargs.get("pk"))
return super().form_valid(form)
Представления редактирования и удаления комментария.
Создадим два класса:
-
EditCommentView, унаследованный от BaseCommentView и UpdateView.-
DeleteCommentView, унаследованный от BaseCommentView и DeleteView.В обоих классах пропишем поле
template_name, в котором пропишем шаблон для страницы редактирования и страницы удаления комментария.В классе
EditCommentView дополнительно пропишем поле form_class, как в классе добавления комментария.Код классов:
class EditCommentView(BaseCommentView, UpdateView):
form_class = CommentForm
template_name = "blog/comment_edit.html"
class DeleteCommentView(BaseCommentView, DeleteView):
template_name = "blog/comment_delete.html"
Маршруты представлений.
Для всех трёх представлений осталось создать маршруты. Для этого откроем файл
urls.py и в список urlpatterns добавим следующие строки:path("comment/add/<int:pk>/", views.AddCommentView.as_view(), name="add_comment"),
path(
"comment/edit/<int:pk>/", views.EditCommentView.as_view(), name="edit_comment"
),
path(
"comment/delete/<int:pk>/",
views.DeleteCommentView.as_view(),
name="delete_comment",
),
Шаблоны комментариев.
Начнём с основного шаблона вывода комментариев.
Шаблон добавления и вывода комментариев.
В директории с шаблонами создадим новый файл
comments.html.В нём пропишем шаблон для отображения комментариев:
🔥1
<div class="row">
<h2 class="mt-3 mb-3">Добавить комментарий:</h2>
{% if user.is_authenticated %}
<form method="post" enctype="multipart/form-data" action="{% url "blog:add_comment" pk=post.pk %}">
{% csrf_token %}
{{ comment_form.comment }}
<button class="btn btn-outline-warning mt-3" type="submit">Отправить</button>
</form>
{% else %}
<p><a href="{% url 'user_app:login' %}">Войдите</a> чтобы добавить комментарий.</p>
{% endif %}
</div>
<div class="row">
<h2 class="mt-3 mb-3">Комментарии пользователей:</h2>
{% if comments %}
{% for comment in comments %}
<p><a href="{% url 'user_app:user_profile' username=post.author %}">{{ comment.user }}</a>
| {{ comment.created_at }} | {% if comment.user user or user.is_superuser %}
<a href="{% url 'blog:edit_comment' comment.pk %}">Редактировать</a> |
<a href="{% url 'blog:delete_comment' comment.pk %}">Удалить</a>
{% endif %}</p>
<p>{{ comment.comment }}</p>
<hr>
{% endfor %}
{% else %}
<p>Нет комментариев</p>
{% endif %}
</div>
В первом блоке выводим форму для добавления комментария. Обратите внимание, что в форме передаём аргумент
action с маршрутом на представление добавления комментария.Во втором блоке в цикле выводим комментарии. Также проверяем, что автор комментария – это текущий пользователь или администратор, чтобы отобразить кнопки для редактирования и удаления комментария.
Далее откроем шаблон страницы поста и в удобное место подключим файл шаблона комментариев, передав в него объект поста и список комментариев:
{% include "blog/modules/comments.html" with post=post comments=post.comments.all%}
Шаблон редактирования комментария.
Создадим файл
comment_edit.html и пропишем следующий код:{% extends 'blog/base.html' %}
{% block noscript %}Редактирование комментария{% endblock %}
{% block content %}
<div class="form-section container mt-3">
<h2>Редактирование комментария</h2>
<form method="post">
{% csrf_token %}
<p>{{ form.comment }}</p>
<button class="btn btn-primary" type="submit">Обновить</button>
<a href="{% url 'blog:post_page' slug=object.post.slug category_slug=object.post.category.slug %}"
class="btn btn-secondary">Отмена</a>
</form>
</div>
{% endblock %}
Шаблон удаления комментария.
Создадим файл
comment_delete.html и пропишем следующий код:{% extends 'blog/base.html' %}
{% block noscript %}Удаление комментария{% endblock %}
{% block content %}
<div class="form-section container mt-3">
<h2>Удаление комментария</h2>
<p>Вы действительно хотите удалить этот комментарий?</p>
<pre>{{ object.comment }}</pre>
<form method="post">
{% csrf_token %}
<button class="btn btn-danger me-2" type="submit">Да, удалить</button>
<a href="{% url 'blog:post_page' slug=object.post.slug category_slug=object.post.category.slug %}"
class="btn btn-secondary">Отмена</a>
</form>
</div>
{% endblock %}
Заключение.
🔥3👍1
Добавить комментарии несложно. Куда сложнее будет далее за ними следить. Хоть у нас и есть "защита" в виде ограничения на зарегистрированных пользователей, спамеры "не дремлют", и, возможно, в будущем придётся добавлять защиту в виде капчи или что-то другое.
Файлы к посту, можно получить в боте по коду: 279348
Пост на сайте
Поддержать проект
#Python #Django #Гайды #Модели #Представления #Формы #Комментарии
Файлы к посту, можно получить в боте по коду: 279348
Пост на сайте
Поддержать проект
#Python #Django #Гайды #Модели #Представления #Формы #Комментарии
🔥3
Forwarded from Заметки на салфетке
Всем привет!
За несколько дней произошло несколько событий.
Начнём с главного.
19-го января у моей группы в GB прошла итоговая аттестация (защита диплома). Нам обещали в течении месяца выслать диплом и номер его отслеживания... Видимо в GB месяц длится иначе, т.к. диплом я и мои одногруппники начали получать только с этой недели... Получив оповещение от Почты России о прибытии письма. 2.5 месяца на изготовление, отправки, но прикол не в этом! Теперь они предоставляют дипломы в виде двух листочков, без корочки. Ну да ладно, я уже на WB выбрал обложку)
И вот казалось бы, диплом, наконец-то эпопея длительностью в 1.5 года + 2.5 месяца ожидания диплома подошли к концу, надо радоваться... А радости то нет. И ощущения "дипломированного специалиста" тоже. Такие дела.
Второе "событие", опишу позже. там тоже есть приколы)
За несколько дней произошло несколько событий.
Начнём с главного.
19-го января у моей группы в GB прошла итоговая аттестация (защита диплома). Нам обещали в течении месяца выслать диплом и номер его отслеживания... Видимо в GB месяц длится иначе, т.к. диплом я и мои одногруппники начали получать только с этой недели... Получив оповещение от Почты России о прибытии письма. 2.5 месяца на изготовление, отправки, но прикол не в этом! Теперь они предоставляют дипломы в виде двух листочков, без корочки. Ну да ладно, я уже на WB выбрал обложку)
И вот казалось бы, диплом, наконец-то эпопея длительностью в 1.5 года + 2.5 месяца ожидания диплома подошли к концу, надо радоваться... А радости то нет. И ощущения "дипломированного специалиста" тоже. Такие дела.
Второе "событие", опишу позже. там тоже есть приколы)
🔥7👏2❤1
Добрый вечер, друзья!
Пятница - время для отдыха и развлечений. Подготовьте уютное место, закуски и напитки, чтобы создать атмосферу настоящего кинотеатра прямо у себя дома. Готовьтесь к запредельным приключениям и не забудьте приглушить свет - ведь именно в темноте ужасы кажутся еще более реальными. Наслаждайтесь вечером и пусть этот фильм подарит вам заряд адреналина на целую неделю!
Фильм: Зеркала
Год: 2008
Бывший нью-йоркский полицейский устраивается на работу ночным сторожем выгоревших руин крупного универмага. Обходя обугленные развалины, он начинает замечать что-то зловещее в декоративных зеркалах, украшающих стены: в огромных мерцающих стёклах отражается что-то жуткое.
https://www.sspoisk.ru/film/263447/
Приятного просмотра!
Пятница - время для отдыха и развлечений. Подготовьте уютное место, закуски и напитки, чтобы создать атмосферу настоящего кинотеатра прямо у себя дома. Готовьтесь к запредельным приключениям и не забудьте приглушить свет - ведь именно в темноте ужасы кажутся еще более реальными. Наслаждайтесь вечером и пусть этот фильм подарит вам заряд адреналина на целую неделю!
Фильм: Зеркала
Год: 2008
Бывший нью-йоркский полицейский устраивается на работу ночным сторожем выгоревших руин крупного универмага. Обходя обугленные развалины, он начинает замечать что-то зловещее в декоративных зеркалах, украшающих стены: в огромных мерцающих стёклах отражается что-то жуткое.
https://www.sspoisk.ru/film/263447/
Приятного просмотра!
🔥2😱1
Что выведет код с изображения ниже? №17
Anonymous Quiz
8%
True, True, True,
28%
True, True, False
20%
True, False, False
13%
False, False, True
33%
Ничего из перечисленного выше
Позавчера мы опубликовали задачу, основанную на сравнении элементов и списках. В викторине приняло участие не так много человек, а правильных ответов было всего 7.
Код задачи:
Разбор задачи:
Создаём три переменные:
-
-
-
Затем в функции
- Сравнение нулевого элемента в списках
- Проверку на то, что нулевой элемент в списке
- Сравнение нулевого элемента в списках
Где подвох?
В общем и целом задача подходит под разбор, написанный для 15-й задачи, с одним лишь исключением.
При создании переменных мы использовали умножение списка, и если при умножении на единицу список останется неизменным, то при умножении на ноль он просто "очистится".
Подвох именно в этом. После того, как мы умножили список
Код задачи:
lst = [1]
lst1 = lst * 0
lst2 = lst * 1
print(lst[0] == lst2[0], lst[0] is lst2[0], lst[0] == lst1[0])
Разбор задачи:
Создаём три переменные:
-
lst - список с одним элементом.-
lst1 - умноженный на ноль список lst.-
lst2 - умноженный на один список lst.Затем в функции
print() выводим три проверки:- Сравнение нулевого элемента в списках
lst и lst2.- Проверку на то, что нулевой элемент в списке
lst идентичен нулевому элементу из списка lst2.- Сравнение нулевого элемента в списках
lst и lst1.Где подвох?
В общем и целом задача подходит под разбор, написанный для 15-й задачи, с одним лишь исключением.
При создании переменных мы использовали умножение списка, и если при умножении на единицу список останется неизменным, то при умножении на ноль он просто "очистится".
Подвох именно в этом. После того, как мы умножили список
lst = [1] на ноль, мы получили пустой список []. Следовательно, все попытки получить какой-либо элемент списка будут вызывать исключение IndexError.🔥7
Рад снова приветствовать вас, друзья!
Пятница уже здесь, и это означает, что рабочая неделя подходит к концу. Давайте насладимся этим прекрасным вечером и проведём его за просмотром увлекательного фильма. Пусть эта пятница станет особенной и запомнится нам надолго!
Фильм: Кон-Тики
Год: 2012
Это известная история о том, как норвежский путешественник Тур Хейердал в 1947 году пересёк Тихий океан от Перу до Полинезии на бальсовом плоту. Таким способом он хотел доказать свою теорию о заселении Полинезии из Южной Америки, а не из Азии, как тогда считалось.
https://www.kinopoisk.ru/film/506432/
https://www.sspoisk.ru/film/506432/
Приятного просмотра и отдыха!
Пятница уже здесь, и это означает, что рабочая неделя подходит к концу. Давайте насладимся этим прекрасным вечером и проведём его за просмотром увлекательного фильма. Пусть эта пятница станет особенной и запомнится нам надолго!
Фильм: Кон-Тики
Год: 2012
Это известная история о том, как норвежский путешественник Тур Хейердал в 1947 году пересёк Тихий океан от Перу до Полинезии на бальсовом плоту. Таким способом он хотел доказать свою теорию о заселении Полинезии из Южной Америки, а не из Азии, как тогда считалось.
https://www.kinopoisk.ru/film/506432/
https://www.sspoisk.ru/film/506432/
Приятного просмотра и отдыха!
🔥4