В пакете
Создайте функцию
Класс
-
-
Далее создайте четыре переменные:
-
-
-
-
Далее создайте цикл, в котором будем итерироваться по списку интервалов и проверять, входит ли текущее время в этот список.
Полный код:
Проверка входящих сообщений.
При получении входящего сообщения необходимо проверить актуальный режим работы и либо передать сообщение дальше в обработчик, либо "сбросить" его, тем самым никак не реагируя.
Для этого будем использовать миддлвари (middleware) - это так называзываемые "посредники", срабатывающие до передачи сообщения в обработчик и в зависимости от логики выполняющие различные действия, например, запись в БД, проверку аутентификации и многое другое.
В пакете
В этом файле создайте класс
В нём нам нужно переопределить
Далее нам необходимо получить из текущего чата объект класса
Лирическое отступление.
В актуальной на момент написания поста версии
В этом посте мы применим небольшой "костыль", для решения этой проблемы.
Разработчикам
app, создайте новый пакет utils. В этом пакете создайте файл opening_hours.py.Создайте функцию
check_opening_hours, принимающую opening_hours - объект класса BusinessOpeningHours.Класс
BusinessOpeningHours содержит два поля:-
time_zone_name - Название временной зоны. Определяется в профиле Telegram при заполнении графика работы.-
opening_hours - Упомянутый выше список с объектами класса BusinessOpeningHoursInterval.Далее создайте четыре переменные:
-
tz - В ней при помощи библиотеки pytz получаем информацию об указанной временной зоне.-
now - В ней получаем текущее время с учётом временной зоны.-
monday_start - В ней высчитываем время до начала понедельника.-
minutes_since_monday - В ней высчитываем сколько прошло минут с начала недели.tz = pytz.timezone(opening_hours.time_zone_name)
now = datetime.datetime.now(tz)
monday_start = now - datetime.timedelta(
days=now.weekday(),
hours=now.hour,
minutes=now.minute,
seconds=now.second,
microseconds=now.microsecond,
)
minutes_since_monday = (now - monday_start).total_seconds() / 60
Далее создайте цикл, в котором будем итерироваться по списку интервалов и проверять, входит ли текущее время в этот список.
for day in opening_hours.opening_hours:
if day.opening_minute <= minutes_since_monday <= day.closing_minute:
return False
return True
Полный код:
import datetime
import pytz
from aiogram.types import BusinessOpeningHours
def check_opening_hours(opening_hours: BusinessOpeningHours):
tz = pytz.timezone(opening_hours.time_zone_name)
now = datetime.datetime.now(tz)
monday_start = now - datetime.timedelta(
days=now.weekday(),
hours=now.hour,
minutes=now.minute,
seconds=now.second,
microseconds=now.microsecond,
)
minutes_since_monday = (now - monday_start).total_seconds() / 60
for day in opening_hours.opening_hours:
if day.opening_minute <= minutes_since_monday <= day.closing_minute:
return False
return True
Проверка входящих сообщений.
При получении входящего сообщения необходимо проверить актуальный режим работы и либо передать сообщение дальше в обработчик, либо "сбросить" его, тем самым никак не реагируя.
Для этого будем использовать миддлвари (middleware) - это так называзываемые "посредники", срабатывающие до передачи сообщения в обработчик и в зависимости от логики выполняющие различные действия, например, запись в БД, проверку аутентификации и многое другое.
В пакете
app создайте пакет middlewares. В нём создайте файл business_middleware.py.В этом файле создайте класс
BusinessMiddleware, унаследованный от BaseMiddleware.В нём нам нужно переопределить
dunder-метод __call__, принимающий self, handler, event, data.Далее нам необходимо получить из текущего чата объект класса
BusinessOpeningHours.Лирическое отступление.
В актуальной на момент написания поста версии
aiogram 3.6.0, заявлена полная поддержка Bot API 7.3. Если обратиться к объекту чата, то там будет параметр business_opening_hours, однако вместо желаемого объекта BusinessOpeningHours там находится None.В этом посте мы применим небольшой "костыль", для решения этой проблемы.
Разработчикам
aiogram был отправлен баг-репорт. Если в будущих версиях ситуация будет исправлена, пост будет обновлён.🔥4👍2❤1
Конец лирического отступления.
Для получения актуального графика работы мы обратимся к API Telegram.
Используя асинхронный менеджер контекста и библиотеку
В переменную
В переменной
Затем в переменной
Далее в блоке
Если возвращается
Внутри условия создаём переменную
Дальше ещё одно условие
Если условия соблюдаются, передаём сообщение дальше в обработчик.
Полный код файла:
Подключение ChatGPT.
В этой функции будем отправлять запрос к ChatGPT и возвращать полученный ответ.
В пакете
Создайте асинхронную функцию
В переменной
В переменной
Для получения актуального графика работы мы обратимся к API Telegram.
Используя асинхронный менеджер контекста и библиотеку
httpx, откройте асинхронный клиент для работы.В переменную
response получаем результат GET-запроса на сервер Telegram.В переменной
chat получаем JSON-объект из переменной response.Затем в переменной
full_chat создаём экземпляр класса ChatFullInfo, распаковав в него содержимое chat по ключу result. Таким образом мы преобразуем чистые JSON-данные в Python-объекты.async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.telegram.org/bot{secrets.token}/getChat?chat_id={secrets.admin_id}"
)
chat = response.json()
full_chat = ChatFullInfo(**chat["result"])
Далее в блоке
if вызываем ранее написанную функцию check_opening_hours, передав в неё full_chat.business_opening_hours.Если возвращается
True, мы продолжаем.Внутри условия создаём переменную
context, в которую присваиваем значение ключа event_context из переменной data.Дальше ещё одно условие
if, в котором проверяем, что сообщение содержит business_connection_id, т.е. является личным и что отправитель сообщения не админ, иначе бот будет реагировать и на ваши сообщения тоже.Если условия соблюдаются, передаём сообщение дальше в обработчик.
if check_opening_hours(full_chat.business_opening_hours):
context: EventContext = data.get("event_context")
if (
context.user.id != secrets.admin_id
and context.business_connection_id
):
return await handler(event, data)
Полный код файла:
from typing import Callable, Dict, Any, Awaitable
import httpx
from aiogram import BaseMiddleware
from aiogram.dispatcher.middlewares.user_context import EventContext
from aiogram.types import TelegramObject, ChatFullInfo
from app.settings import secrets
from app.utils.opening_hours import check_opening_hours
class BusinessMiddleware(BaseMiddleware):
async def __call__(
self,
handler: CallableTelegramObject, Dict[str, Any, Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.telegram.org/bot{secrets.token}/getChat?chat_id={secrets.admin_id}"
)
chat = response.json()
full_chat = ChatFullInfo(**chat["result"])
if check_opening_hours(full_chat.business_opening_hours):
context: EventContext = data.get("event_context")
if (
context.user.id != secrets.admin_id
and context.business_connection_id
):
return await handler(event, data)
Подключение ChatGPT.
В этой функции будем отправлять запрос к ChatGPT и возвращать полученный ответ.
В пакете
utils, создайте файл openai_actions.py.Создайте асинхронную функцию
get_chat_completion, принимающую message - объект класса Message.В переменной
http_client определите объект класса httpx.AsyncClient. Это объект HTTP-клиента, используя который будет произведён запрос.В переменной
client определите объект класса AsyncOpenAI, передав в него аргументы: api_key, http_client и base_url. Это объект клиента для OpenAI.🔥2👍1
http_client = httpx.AsyncClient(
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20)
)
client = AsyncOpenAI(
api_key=secrets.openai_key,
http_client=http_client,
base_url=secrets.openai_base_url,
)
Далее в переменной
messages создайте список словарей, где первый словарь – это системный промт, а второй – сообщение от пользователя:messages = [
{"role": "system", "content": system_prompt()},
{"role": "user", "content": message.text},
]
В переменную
response создайте запрос, передав в него:-
model - Выбранная модель ChatGPT, например, gpt-3.5-turbo, gpt-4-turbo, gpt-4o или любую другую поддерживаемую OpenAI.-
messages - Список словарей с сообщениями.-
max_tokens - Ограничение на максимальное количество токенов в ответе.-
temperature - Температура в диапазоне от 0 до 1. Определяет уровень "фантазии" бота. Чем ближе число к нулю, тем более предсказуемы будут ответы и наоборот, чем ближе к единице, тем более случайными будут ответы.И возвращаем результат запроса в обработчик:
response = await client.chat.completions.create(
model="gpt-3.5-turbo", messages=messages, max_tokens=1000, temperature=0.8
)
return response.choices[0].message.content
Полный код:
import httpx
from aiogram.types import Message
from openai import AsyncOpenAI
from app.settings import secrets
from app.views import system_prompt
async def get_chat_completion(message: Message):
http_client = httpx.AsyncClient(
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20)
)
client = AsyncOpenAI(
api_key=secrets.openai_key,
http_client=http_client,
base_url=secrets.openai_base_url,
)
messages = [
{"role": "system", "content": system_prompt()},
{"role": "user", "content": message.text},
]
response = await client.chat.completions.create(
model="gpt-3.5-turbo", messages=messages, max_tokens=1000, temperature=0.8
)
return response.choices[0].message.content
Задержка обработки сообщений.
Для того, чтобы пользователи не спамили и не использовали личные сообщения как "бесплатный GPT", добавим задержку в обработке сообщений.
В вашей реализации логики она может быть не нужна.
В пакете
utils создайте файл check_delay.py, а в нём асинхронную функцию check_user_delay, принимающую user_id.Тут-то нам и понадобится
Redis для хранения пользовательских ID и времени последнего сообщения. Вы можете использовать для этого другую БД или вовсе словарь в коде, это не принципиально.В переменную
last_message_time получаем из Redis по user_id время последнего сообщения, если оно есть. Если его нет - вернётся None.В блоке
if проверяем, что last_message_time True (проще говоря, не None).Внутри блока в переменную
time_since_last_message получаем разницу между текущим временем и полученным из хранилища.Ниже проверяем, если оно меньше указанной в
.env допустимой задержки, то возвращаем False.Во всех остальных случаях возвращаем
True.Полный код:
🔥4👍1
import asyncio
from app.settings import redis_conn, secrets
async def check_user_delay(user_id: int):
last_message_time = await redis_conn.get(f"users:{user_id}")
if last_message_time:
time_since_last_message = asyncio.get_event_loop().time() - float(
last_message_time
)
if time_since_last_message < secrets.delay * 60:
return False
return True
Обработчик бизнес сообщений.
Осталось написать обработчик, в который middleware будет передавать сообщение.
В пакете
app создайте пакет handlers, а в нём файл business_handler.py.В этом файле создайте асинхронную функцию
handle_business_message, принимающую message - объект класса Message.В самом начале создайте блок
if, проверяющий задержку и наличие текста в сообщении (отправить могут картинку или видео, а это другая логика работы с ChatGPT).Если условие не выполняется, то сообщение просто игнорируется.
Если условие выполнено, переходим к обработке.
В переменной
answer вызываем функцию get_chat_completion, передав в неё message.Затем отвечаем пользователю полученным сообщением.
Сохраняем в
Redis время текущего сообщения.Полный код:
import asyncio
from aiogram.types import Message
from app.settings import redis_conn
from app.utils.check_delay import check_user_delay
from app.utils.openai_actions import get_chat_completion
async def handle_business_message(message: Message):
if await check_user_delay(message.from_user.id) and message.text:
answer = await get_chat_completion(message)
await message.reply(answer)
await redis_conn.set(
f"users:{message.from_user.id}", asyncio.get_event_loop().time()
)
Обработка уведомлений о запуске/остановке бота.
Небольшое, но удобное дополнение.
В пакете
handlers создайте файл events.py.В нём создайте две асинхронные функции:
start_bot и stop_bot.В функциях отправляем сообщение администратору.
from app.settings import bot, secrets
from app import views
async def start_bot():
await bot.send_message(secrets.admin_id, views.start_bot_message())
async def stop_bot():
await bot.send_message(secrets.admin_id, views.stop_bot_message())
Основной файл.
Логику написали. Теперь осталось соединить всё вместе.
Откройте созданный ранее файл
main.py. Он должен находиться в корне проекта рядом с файлом .env.В нём создайте асинхронную функцию
start.В переменной
dp объявите экземпляр класса Dispatcher.Далее в несколько строк зарегистрируйте middleware и обработчики:
dp = Dispatcher()
dp.update.middleware(BusinessMiddleware())
dp.startup.register(start_bot)
dp.shutdown.register(stop_bot)
dp.business_message.register(handle_business_message)
Обратите внимание на
dp.business_message.register. Регистрируется обработка business_message, а не обычного message.Далее в блоке
try вызывается очистка сообщений, отправленных, когда бот был офлайн, и запуск пуллинга, а в блоке finally выполняется остановка бота.Вне функции в блоке
if __name__ "__main__" запускаем функцию старт.Полный код:
🔥2
import asyncio
from aiogram import Dispatcher
from aiogram.methods import DeleteWebhook
from app.handlers.business_handler import handle_business_message
from app.handlers.events import start_bot, stop_bot
from app.middlewares.business_middleware import BusinessMiddleware
from app.settings import bot
async def start():
dp = Dispatcher()
dp.update.middleware(BusinessMiddleware())
dp.startup.register(start_bot)
dp.shutdown.register(stop_bot)
dp.business_message.register(handle_business_message)
try:
await bot(DeleteWebhook(drop_pending_updates=True))
await dp.start_polling(bot)
finally:
await bot.session.close()
if __name__ "__main__":
asyncio.run(start())
Запуск бота.
Для запуска бота и Redis будем использовать Docker compose.
Сперва необходимо создать образ с ботом, для этого создайте файл
Dockerfile со следующим содержимым:FROM python:3.11-slim
WORKDIR /code
COPY requirements.txt /code
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY . /code
CMD [ "python", "./main.py" ]
В нём создаётся Docker-образ, в котором устанавливаются все зависимости из файла
requirements.txt. Затем копируются файлы проекта и выполняется команда запуска бота.Затем создайте файл
docker-compose.yaml со следующим содержимым:services:
bot:
build: .
restart: always
env_file:
- .env
volumes:
- .:/code
redis:
image: redis
restart: always
volumes:
- ./redis_data:/data
В нём описываются два сервиса:
Первый
bot. Указываем, что необходимо создать образ из Dockerfile, передать в него .env-файл и подключить текущую папку внутри контейнера.Второй
redis. Указываем, что будет использоваться официальный образ redis последней версии, и подключаем папку redis_data внутри контейнера, чтобы не потерять данные.Готово.
Запустить бота можно командой:
docker compose up -d
Пост на сайте
Поддержать проект
#aiogram #redis #telegram #Telegram_для_Бизнеса #telegram_бот #Telegram_business_mode #openai #python #chatgpt #docker
❤2🔥2
Docker. Запуск бота-автоответчика по готовому образу
Автор: Иван Ашихмин
После публикации поста "Бот-автоответчик с ChatGPT для Бизнес-аккаунта в Telegram на Aiogram 3", появился запрос на готовый Docker-образ.
Не все захотят собирать бота по гайду. Некоторым нужна возможность "взять и запустить". Для этого был собран готовый образ бота.
Получить образ можно, выполнив следующую команду:
Однако для бота всё равно необходим
Запуск бота.
Автор: Иван Ашихмин
После публикации поста "Бот-автоответчик с ChatGPT для Бизнес-аккаунта в Telegram на Aiogram 3", появился запрос на готовый Docker-образ.
Не все захотят собирать бота по гайду. Некоторым нужна возможность "взять и запустить". Для этого был собран готовый образ бота.
Получить образ можно, выполнив следующую команду:
docker pull git.pressanybutton.ru/prodream/manager_bot:latest
Однако для бота всё равно необходим
Redis и набор переменных окружения.Запуск бота.
🔥5👍1
Для запуска бота и
В отличие от композ-файла из поста в этом мы не собираем образ сами, а используем готовый.
Далее создадим файл
Пропишем в нём все необходимые данные:
Затем выполняем команду для поднятия
Бот запустится и уведомит об этом.
Данный способ позволит запустить бота без необходимости написания кода. Это поможет новичкам или тем, кому достаточно указанного в посте функционала, воспользоваться ботом-автоответчиком для своих нужд.
Если же вам нужен исходный код к проекту, то он доступен подписчикам на Boosty
Пост на сайте
Поддержать проект
#aiogram #redis #telegram #Telegram_для_Бизнеса #telegram_бот #Telegram_business_mode #openai #python #chatgpt #docker #docker_compose #docker_image
Redis, как и описано в посте, лучше использовать Docker compose. Создадим файл docker-compose.yaml со следующим содержимым:services:
bot:
image: git.pressanybutton.ru/prodream/manager_bot:latest
restart: always
env_file:
- .env
volumes:
- .:/code
redis:
image: redis
restart: always
volumes:
- ./redis_data:/data
В отличие от композ-файла из поста в этом мы не собираем образ сами, а используем готовый.
Далее создадим файл
.env в той же директории. Переменные окружения можно прописать и в композ-файле, но это может быть не очень удобно.Пропишем в нём все необходимые данные:
token=adasfasfas # токен бота
admin_id=1234567 # id администратора
openai_key=sk-... # токен OpenAI или neuroapi
openai_base_url=https://neuroapi.host/v1 # Оставляем neuroapi, либо прописываем API OpenAI
redis_host=redis # оставляем без изменений, либо прописываем свой Redis хост
delay=10 # указываем необходимую задержку на ответ
system_prompt=Ты бот помощник\nТвоя задача помогать людям # прописываем нужны системный промт используя \n для переноса строки
Затем выполняем команду для поднятия
Docker compose сервиса:docker compose up -d
Бот запустится и уведомит об этом.
Данный способ позволит запустить бота без необходимости написания кода. Это поможет новичкам или тем, кому достаточно указанного в посте функционала, воспользоваться ботом-автоответчиком для своих нужд.
Если же вам нужен исходный код к проекту, то он доступен подписчикам на Boosty
Пост на сайте
Поддержать проект
#aiogram #redis #telegram #Telegram_для_Бизнеса #telegram_бот #Telegram_business_mode #openai #python #chatgpt #docker #docker_compose #docker_image
🔥5
Привет!
Добро пожаловать в вечер пятницы, который идеально подходит для просмотра увлекательного фильма! Надеюсь, что вы уже подготовили уютное место, закуску и напитки, чтобы насладиться прекрасным вечером. Погрузитесь в мир кино и позвольте себе расслабиться после долгой недели.
Фильм: Достать ножи
Год: 2019
Когда сразу после празднования 85-летия известного автора криминальных романов Харлана Тромби виновника торжества находят мёртвым, за расследование берётся обаятельный и дотошный частный детектив Бенуа Блан. Ему предстоит распутать тугую сеть уловок и корыстной лжи, которой его опутывают члены неблагополучной семьи Харлана и преданный ему персонал.
https://www.kinopoisk.ru/film/1188529/
https://www.sspoisk.ru/film/1188529/
Приятного просмотра и отличного отдыха!
Добро пожаловать в вечер пятницы, который идеально подходит для просмотра увлекательного фильма! Надеюсь, что вы уже подготовили уютное место, закуску и напитки, чтобы насладиться прекрасным вечером. Погрузитесь в мир кино и позвольте себе расслабиться после долгой недели.
Фильм: Достать ножи
Год: 2019
Когда сразу после празднования 85-летия известного автора криминальных романов Харлана Тромби виновника торжества находят мёртвым, за расследование берётся обаятельный и дотошный частный детектив Бенуа Блан. Ему предстоит распутать тугую сеть уловок и корыстной лжи, которой его опутывают члены неблагополучной семьи Харлана и преданный ему персонал.
https://www.kinopoisk.ru/film/1188529/
https://www.sspoisk.ru/film/1188529/
Приятного просмотра и отличного отдыха!
🔥3🤮2🥴1
Что выведет код с изображения ниже? №23
Anonymous Quiz
23%
True True True False
20%
True True True True
17%
False True True False
23%
False True False True
17%
False False False False
🔥3🌭1
Судя по ответам, вчерашняя задача разделила голосующих по вариантам, если не поровну, то хотя бы близко к этому. Давайте разберём верный ответ.
Код задачи:
Разбор задачи.
Задача похожа на некоторые, которые мы задавали ранее, но в этой есть хитрость (в прочем, как и в других).
Разборы прошлых похожих задач:
№15 - https://t.iss.one/press_any_button/591
№17 - https://t.iss.one/press_any_button/613
Вся суть в значении переменных
Казалось бы, значение
Всё дело в работе Python с
Для питона 0.00...001 - погрешность, которую он предпочитает игнорировать, в следствии чего, значение
Именно по этому
Что касается последнего
Всё это можно проверить, выведя значение функции
Код задачи:
x = 0.1
y = 0.10000000000000001
z = round(y, 1)
print(x is y, x == z, z == y, z is x)
Разбор задачи.
Задача похожа на некоторые, которые мы задавали ранее, но в этой есть хитрость (в прочем, как и в других).
Разборы прошлых похожих задач:
№15 - https://t.iss.one/press_any_button/591
№17 - https://t.iss.one/press_any_button/613
Вся суть в значении переменных
x и y. Казалось бы, значение
x = 0.1 и y = 0.10000000000000001 - разные числа, следовательно и храниться должны в разных ячейках памяти, но нет. Всё дело в работе Python с
float-числами (числа с плавающей запятой, вещественные числа).Для питона 0.00...001 - погрешность, которую он предпочитает игнорировать, в следствии чего, значение
y считает как 0.1 и даёт ссылку на тот же участок памяти, что и x.Именно по этому
x is y = True, как и z == y = True .Что касается последнего
z is x, то тут именно то, что и должно быть. После применения функции round(), число помещается в новую ячейку памяти.Всё это можно проверить, выведя значение функции
id() по отношению к переменной:x = 0.1
y = 0.10000000000000001
z = round(y, 1)
print(x is y, x == z, z == y, z is x)
print(x, y, z)
print(id(x), id(y), id(z))
>>> True True True False
>>> 0.1 0.1 0.1
>>> 2078346127888 2078346127888 2078346131408
🔥4👍1
Всем привет!
Близится лето и с каждым днём всё больше становится конференций, митапов и прочих мероприятий IT-мира.
Об одном из них я хотел бы рассказать вам - Yandex Pytup.
Где: Нижний Новгород и Онлайн
Когда: 01.06.2024
Страница регистрации: https://yandex.ru/pytup
Конференция по Python от Яндекс. На мероприятии будет несколько интересных докладов, например, создание RAG-приложений, про GIL и другие.
Я уже записался в онлайн, будет очень интересно посмотреть и послушать.
Хотел бы рассказывать о подобных мероприятиях, если вам будет интересно - пишите в комментарии!
P.S. Это не реклама ( нам не заплатили🙁 )
Близится лето и с каждым днём всё больше становится конференций, митапов и прочих мероприятий IT-мира.
Об одном из них я хотел бы рассказать вам - Yandex Pytup.
Где: Нижний Новгород и Онлайн
Когда: 01.06.2024
Страница регистрации: https://yandex.ru/pytup
Конференция по Python от Яндекс. На мероприятии будет несколько интересных докладов, например, создание RAG-приложений, про GIL и другие.
Я уже записался в онлайн, будет очень интересно посмотреть и послушать.
Хотел бы рассказывать о подобных мероприятиях, если вам будет интересно - пишите в комментарии!
P.S. Это не реклама ( нам не заплатили🙁 )
🔥8
Приветствую!
В длинных постах можно запутаться поэтому, собираю воедино всё, что есть на данный момент.
Оглавления:
Для удобства навигации есть посты с оглавлениями по темам:
"Сайт на Django"
"Telegram-бот на AIOgram3"
"Применение Docker"
"Полезные инструменты"
"Путь в IT."
"Код в мешке"
"Boosty эксклюзив"
Ресурсы канала:
Уютный и немного безумный чат канала.
Бот с материалами к постам
Сайт со всеми постами
Канал в Dzen
Сообщество в VK
Поддержка.
Если вам нравится канал и выходящий материал, поделитесь ссылкой с людьми, кому это тоже может быть интересно.
Также поддержать канал можно:
Подпиской или донатом на Boosty.
Донатом в нашем Telegram-боте.
Или внеся сайт в исключения вашего блокировщика рекламы.
В длинных постах можно запутаться поэтому, собираю воедино всё, что есть на данный момент.
Оглавления:
Для удобства навигации есть посты с оглавлениями по темам:
"Сайт на Django"
"Telegram-бот на AIOgram3"
"Применение Docker"
"Полезные инструменты"
"Путь в IT."
"Код в мешке"
"Boosty эксклюзив"
Ресурсы канала:
Уютный и немного безумный чат канала.
Бот с материалами к постам
Сайт со всеми постами
Канал в Dzen
Сообщество в VK
Поддержка.
Если вам нравится канал и выходящий материал, поделитесь ссылкой с людьми, кому это тоже может быть интересно.
Также поддержать канал можно:
Подпиской или донатом на Boosty.
Донатом в нашем Telegram-боте.
Или внеся сайт в исключения вашего блокировщика рекламы.
🔥3❤2👍2
Первый фриланс проект - школа паралимпийского резерва
Автор: Иван Ашихмин
Всем привет!
Вчера закончилась работа по проекту для Хабаровской краевой спортивно-адаптивной школы паралимпийского и сурдлимпийского резерва. Проект длился почти год - с июня 2023 г. по конец мая 2024 г.
Задача заключалась в создании новостного сайта школы для замены устаревшего.
О том, почему так долго и как всё проходило расскажу в этом посте.
Сайт школы: https://kski.ru
Старый сайт школы: https://old.kski.ru
С чего всё началось?
Автор: Иван Ашихмин
Всем привет!
Вчера закончилась работа по проекту для Хабаровской краевой спортивно-адаптивной школы паралимпийского и сурдлимпийского резерва. Проект длился почти год - с июня 2023 г. по конец мая 2024 г.
Задача заключалась в создании новостного сайта школы для замены устаревшего.
О том, почему так долго и как всё проходило расскажу в этом посте.
Сайт школы: https://kski.ru
Старый сайт школы: https://old.kski.ru
С чего всё началось?
🔥5
Шёл 2023-й год, я ещё учился в GeekBrains, только начал думать о канале в Telegram. В один из дней, в разговоре с одногруппником Юрием, он сообщает, что, возможно, будет заказ на новый сайт для школы. Начали обсуждать на чём его можно сделать и дошли до разработки с нуля на Django. К слову, Django я в то время знал весьма посредственно, хотя мой дипломный проект уже был готов, а у Юры другой стек, он больше по ботам в Telegram, автоматизации, парсерам и сайтам на CMS-движках.
Несколько дней спустя он мне написал с предложением заняться проектом вместе. Честно сказать, я сперва подумал, что это шутка, т.к. знал и умел тогда не-то, чтобы много. Ещё учиться и учиться, а тут, бац, "давай делать проект!". Да ещё и денег заплатят! Не то, что бы много, конечно, но "дарёному коню в зубы не смотрят". Это была возможность. Та самая возможность получить практический опыт, а не все те лабораторные задачки в GB. Я согласился.
На следующее утро я всё ещё думая, что это он просто ошибся, переспросил у него, на что получил положительный ответ. И началась работа.
Начало работы.
Было составлено первоначальное ТЗ:
- Фреймворк - Django.
- БД - MySQL.
- Менеджер БД - PHPMyAdmin.
- Веб-сервер - Nginx.
- Фронтенд должен работать на CSS-фреймворке - Bootstrap.
- В админке должен быть визуальный редактор для удобного добавления статей с возможностью добавления медиафайлов.
- На сайте должен быть поиск по материалам.
- Необходимы группы пользователей с разными уровнями прав.
- Необходимо логирование действий пользователей в админке.
- На сайте должны быть категории и материалы.
- Должна быть пагинация.
- Должна быть форма обратной связи с отправкой сообщения пользователя администратору по email.
- Должен быть раздел "Фотогалерея" с удобным управлением в админке.
Задача поставлена, также были обозначены первые сроки - до конца года, но хотят пораньше. Понял, принял и взялся за работу.
Создал первоначальный проект в Django и сделал приватный репозиторий для всего этого дела.
Было решено сразу всё делать используя Docker-контейнеры и Docker Compose, поэтому были созданы необходимые Dockerfile и docker-compose.yml. Прописаны сервисы БД, PHPMyAdmin, Nginx. Юра предоставил для разработки технический домен и VPS-сервер.
При прописывании БД, порты MySQL были прописаны наружу сервера, для того, чтобы можно было вести разработку используя БД сервера, без необходимости потом делать миграции и перенос содержимого.
Довольно быстро был выполнен начальный пласт работ - созданы необходимые модели, добавлен визуальный редактор CKeditor 4, подключен Bootstrap, созданы технические шаблоны с примерами вывода информации из БД.
В это же время Юрий занялся шаблоном сайта, разбираясь с шаблонизатором Django.
Позже были добавлены "хлебные крошки" на страницы сайта, превьюшки для статей, дополнительные кастомизации админки, теги к статьям и поиск.
Архитектура, а скорее структура менялась в процессе. Сперва было две модели - для категории статей и для статей. Затем понадобились контентные страницы (для разного рода документов школы), а затем и страницы с видео. Дабы не перегружать две модели вешая на них с указанием разных типов, для каждого были написаны свои модели. Это позволило разделить функционал и более удобно настраивать необходимые разделы.
Первые сложности.
Первая сложность возникла с реализацией галереи. Я тогда не представлял, как можно реализовать галерею с альбомами, да и сейчас бы, наверное, задумался о реализации. Выбор пал на готовую библиотеку django-photologue. Удобная библиотека, позволяющая загружать изображения не только по одному, но и в zip-архиве, создавать альбомы и управлять ими.
Пришлось немного править шаблоны библиотеки под макет сайта, но в целом трудность решена. Хотя, с этой библиотекой позже возникла ещё одна проблема, но об этом в конце поста.
Вторая большая сложность возникла при использовании библиотеки django-mptt. Она позволяет создавать древовидные категории. Сложностей было несколько:
Несколько дней спустя он мне написал с предложением заняться проектом вместе. Честно сказать, я сперва подумал, что это шутка, т.к. знал и умел тогда не-то, чтобы много. Ещё учиться и учиться, а тут, бац, "давай делать проект!". Да ещё и денег заплатят! Не то, что бы много, конечно, но "дарёному коню в зубы не смотрят". Это была возможность. Та самая возможность получить практический опыт, а не все те лабораторные задачки в GB. Я согласился.
На следующее утро я всё ещё думая, что это он просто ошибся, переспросил у него, на что получил положительный ответ. И началась работа.
Начало работы.
Было составлено первоначальное ТЗ:
- Фреймворк - Django.
- БД - MySQL.
- Менеджер БД - PHPMyAdmin.
- Веб-сервер - Nginx.
- Фронтенд должен работать на CSS-фреймворке - Bootstrap.
- В админке должен быть визуальный редактор для удобного добавления статей с возможностью добавления медиафайлов.
- На сайте должен быть поиск по материалам.
- Необходимы группы пользователей с разными уровнями прав.
- Необходимо логирование действий пользователей в админке.
- На сайте должны быть категории и материалы.
- Должна быть пагинация.
- Должна быть форма обратной связи с отправкой сообщения пользователя администратору по email.
- Должен быть раздел "Фотогалерея" с удобным управлением в админке.
Задача поставлена, также были обозначены первые сроки - до конца года, но хотят пораньше. Понял, принял и взялся за работу.
Создал первоначальный проект в Django и сделал приватный репозиторий для всего этого дела.
Было решено сразу всё делать используя Docker-контейнеры и Docker Compose, поэтому были созданы необходимые Dockerfile и docker-compose.yml. Прописаны сервисы БД, PHPMyAdmin, Nginx. Юра предоставил для разработки технический домен и VPS-сервер.
При прописывании БД, порты MySQL были прописаны наружу сервера, для того, чтобы можно было вести разработку используя БД сервера, без необходимости потом делать миграции и перенос содержимого.
Довольно быстро был выполнен начальный пласт работ - созданы необходимые модели, добавлен визуальный редактор CKeditor 4, подключен Bootstrap, созданы технические шаблоны с примерами вывода информации из БД.
В это же время Юрий занялся шаблоном сайта, разбираясь с шаблонизатором Django.
Позже были добавлены "хлебные крошки" на страницы сайта, превьюшки для статей, дополнительные кастомизации админки, теги к статьям и поиск.
Архитектура, а скорее структура менялась в процессе. Сперва было две модели - для категории статей и для статей. Затем понадобились контентные страницы (для разного рода документов школы), а затем и страницы с видео. Дабы не перегружать две модели вешая на них с указанием разных типов, для каждого были написаны свои модели. Это позволило разделить функционал и более удобно настраивать необходимые разделы.
Первые сложности.
Первая сложность возникла с реализацией галереи. Я тогда не представлял, как можно реализовать галерею с альбомами, да и сейчас бы, наверное, задумался о реализации. Выбор пал на готовую библиотеку django-photologue. Удобная библиотека, позволяющая загружать изображения не только по одному, но и в zip-архиве, создавать альбомы и управлять ими.
Пришлось немного править шаблоны библиотеки под макет сайта, но в целом трудность решена. Хотя, с этой библиотекой позже возникла ещё одна проблема, но об этом в конце поста.
Вторая большая сложность возникла при использовании библиотеки django-mptt. Она позволяет создавать древовидные категории. Сложностей было несколько:
🔥4
- Первая была связана с отображением древовидных категорий в админке. Эта сложность возникла не сразу, а после того, как мы сменили шаблон админки со стандартного на django-jazzmin. Пришлось немного править файлы шаблона.
- Вторая была связана с отображением выпадающего списка категорий на страницах сайта. Изначально была мысль делать выпадающий список, в котором будут свои выпадашки следующего уровня и это доставило много проблем, но какая-никакая, первая реализация была сделана.
В процессе Юра то и дело подкидывал задачи, например, нужно было связать фотогалерею со статьёй. Задача оказалась не трудной, достаточно было в модели статьи прописать внешний ключ на модель альбома. Это позволило при написании статьи выбирать связанную с ней галерею.
Ещё одной задачей было реализовать видеоархив. Необходимо было вставлять только ссылку на YouTube, при этом на странице выводить встраиваемый плеер, а в качестве обложки страницы брать превьюшку видео. Поскольку получать каждый раз превьюшку видео с YouTube чревато баном, для получения превьюшки была написана функция, получающая изображение и сохраняющая её на диске. Вызывается функция при сохранении новой страницы с видео в админке, там же и происходит формирование плеера.
На всё это ушёл месяц. Проект в большей степени был выполнен. И началось затишье...
Провал на пол года.
Юрий показал текущий на тот момент вариант руководству и мы начали ждать следующих распоряжений по сайту. Они пришли в феврале вместе с многостраничным документом от министерства образования. В документе описывались требования, которым должен соответствовать сайт бюджетной организации.
Изучив документ, в целом практически не нашли ничего нового. Небольшие правки шаблона, но был и один пункт - версия для слабовидящих. Работу по её внедрению взял на себя Юра. В интернете есть несколько сайтов предоставляющих скрипт для этого и почти все платно. Был найден бесплатный вариант, доработан и установлен на сайт.
Юрий занялся переносом данных со старого сайта на новый.
Legacy наше всё.
Руководство школы решило сохранить старый сайт как архив. И раз у нас куплен сервер под новый сайт, зачем платить за хостинг для старого? Давайте перенесём старый сайт на сервер.
Что может пойти не так? А вот, что - старый сайт был написан в 2011-м году. С тех пор он не обновлялся, а следовательно, базируется на очень старых технологиях:
- Apache 2.2
- PHP 5.2
- MySQL 5.7
Отойдя от ностальгических воспоминаний, я принялся за работу. Первой идеей было установить LAMP, но в нём самая старая версия PHP - 7.2. Далее были эксперименты с панелями Vesta и Hestia, но и там PHP был относительно свежий. Но, видимо не мне одному понадобился PHP 5.2, поскольку был найден docker-образ с установленным PHP и Apache. Установить MySQL версии 5.7 не составило труда, он доступен в репозитории Docker Hub.
Однако, сайт не заработал. Жаловался на базу данных, мол не может подключиться.
После ряда попыток "подружить" современные технологии со старыми, перезагрузок, правок файлов - в общем действий "методом тыка", старый сайт удалось завести.
Чтобы не мешать старый сайт и новый, для старого сайта был создан второй Docker compose файл, содержащий php, mysql и phpmyadmin.
Последний месяц работы.
Когда работа по переносу данных была закончена и и сайт был почти готов, начали доводить его до ума.
Реализовали форму обратной связи с применением celery для отправки почты администратору в фоновом режиме. Поскольку в форме присутствует возможность к сообщению прикрепить файл, был написан валидатор для прикрепляемых файлов.
Были сделаны мелкие правки для передачи нужных данных в шаблоны.
Я писал выше о проблеме реализации выпадающих списков в меню на страницах сайта, для её решения было решено сделать отдельную модель для меню сайта. Также с использованием mptt, но без дополнительных выпадающих списков. Теперь в меню указаны только нужные разделы, а не все существующие.
- Вторая была связана с отображением выпадающего списка категорий на страницах сайта. Изначально была мысль делать выпадающий список, в котором будут свои выпадашки следующего уровня и это доставило много проблем, но какая-никакая, первая реализация была сделана.
В процессе Юра то и дело подкидывал задачи, например, нужно было связать фотогалерею со статьёй. Задача оказалась не трудной, достаточно было в модели статьи прописать внешний ключ на модель альбома. Это позволило при написании статьи выбирать связанную с ней галерею.
Ещё одной задачей было реализовать видеоархив. Необходимо было вставлять только ссылку на YouTube, при этом на странице выводить встраиваемый плеер, а в качестве обложки страницы брать превьюшку видео. Поскольку получать каждый раз превьюшку видео с YouTube чревато баном, для получения превьюшки была написана функция, получающая изображение и сохраняющая её на диске. Вызывается функция при сохранении новой страницы с видео в админке, там же и происходит формирование плеера.
На всё это ушёл месяц. Проект в большей степени был выполнен. И началось затишье...
Провал на пол года.
Юрий показал текущий на тот момент вариант руководству и мы начали ждать следующих распоряжений по сайту. Они пришли в феврале вместе с многостраничным документом от министерства образования. В документе описывались требования, которым должен соответствовать сайт бюджетной организации.
Изучив документ, в целом практически не нашли ничего нового. Небольшие правки шаблона, но был и один пункт - версия для слабовидящих. Работу по её внедрению взял на себя Юра. В интернете есть несколько сайтов предоставляющих скрипт для этого и почти все платно. Был найден бесплатный вариант, доработан и установлен на сайт.
Юрий занялся переносом данных со старого сайта на новый.
Legacy наше всё.
Руководство школы решило сохранить старый сайт как архив. И раз у нас куплен сервер под новый сайт, зачем платить за хостинг для старого? Давайте перенесём старый сайт на сервер.
Что может пойти не так? А вот, что - старый сайт был написан в 2011-м году. С тех пор он не обновлялся, а следовательно, базируется на очень старых технологиях:
- Apache 2.2
- PHP 5.2
- MySQL 5.7
Отойдя от ностальгических воспоминаний, я принялся за работу. Первой идеей было установить LAMP, но в нём самая старая версия PHP - 7.2. Далее были эксперименты с панелями Vesta и Hestia, но и там PHP был относительно свежий. Но, видимо не мне одному понадобился PHP 5.2, поскольку был найден docker-образ с установленным PHP и Apache. Установить MySQL версии 5.7 не составило труда, он доступен в репозитории Docker Hub.
Однако, сайт не заработал. Жаловался на базу данных, мол не может подключиться.
После ряда попыток "подружить" современные технологии со старыми, перезагрузок, правок файлов - в общем действий "методом тыка", старый сайт удалось завести.
Чтобы не мешать старый сайт и новый, для старого сайта был создан второй Docker compose файл, содержащий php, mysql и phpmyadmin.
Последний месяц работы.
Когда работа по переносу данных была закончена и и сайт был почти готов, начали доводить его до ума.
Реализовали форму обратной связи с применением celery для отправки почты администратору в фоновом режиме. Поскольку в форме присутствует возможность к сообщению прикрепить файл, был написан валидатор для прикрепляемых файлов.
Были сделаны мелкие правки для передачи нужных данных в шаблоны.
Я писал выше о проблеме реализации выпадающих списков в меню на страницах сайта, для её решения было решено сделать отдельную модель для меню сайта. Также с использованием mptt, но без дополнительных выпадающих списков. Теперь в меню указаны только нужные разделы, а не все существующие.
🔥3
У нас возникла проблема с библиотекой фотогалереи, а именно - при загрузке архива с изображениями должен создаваться новый альбом со своим slug'ом на основе указанного названия альбома, но он не создавался. Причина была в том, что при создании альбома непосредственно в админке, поле slug заполняется одновременно с названием альбома силами JavaScript, а при создании альбома путём загрузки архива оно создаётся используя функцию slugify из Django. С этой функцией я уже сталкивался ранее и даже писал об этом в одном из постов по Django.
Проблема в том, что стандартная функция не преобразует кириллицу. Взять и изменить код в файлах библиотеки не так то просто, но это же Django, а значит можно подключить библиотеку как приложение. Это и было сделано. Удалил библиотеку из зависимостей и поместил директорию с файлами приложения в проект, поправил импорты и всё заработало. Далее оставалось дело за малым, в одном файлике изменить используемую библиотеку для слагификации названия альбома.
Оставался последний шаг - сменить технический домен на основной с поддержкой https.
С бесплатными сертификатами есть одна проблема, их нужно обновлять каждые три месяца. Это можно обойти написав скрипт, но мало ли, что пойдёт не так у владельцев сайта. Было решено заменить nginx на caddy. Caddy это современный вебсервер, напоминающий nginx структурой файла конфигурации, но в нём есть один важный плюс - он автоматически получает сертификат для сайта и сам его продлевает по истечении срока действия.
После того, как убедились, что всё работает как надо перенесли домен со старого хостинга на сервер. С переносом тоже были проблемы - регистратор R01 никак не хотел менять NS-сервера у домена. Очень странное поведение, но после обращения в поддержку NS-ы сменили.
Оставалось только заменить домен в конфигурационных файлах. Всё!
У нас получилось в итоге два Docker Compose файла - для нового и старого сайта. По сути у нас два сайта, две версии MySQL и два связанных с ними PHPMyAdmin. Благодаря докеру это не вызывает никаких проблем с версионностью и позволяет управлять контейнерами без вреда для остального.
Заключение.
На всё про всё ушёл год. Хотя работы по сути было на 2-3 месяца. Однако, работа над этим проектом позволила заняться реальным, а не PET-проектом. Дала толчок к существованию канала.
Не бойтесь браться за то, что кажется сложным. Пусть это проект для кого-то или личный. Всё это опыт, навыки и повод гордиться собой.
Пост на сайте
Поддержать проект
#Django #Проект #Celery #Фриланс #КСКИ #Заказ
Проблема в том, что стандартная функция не преобразует кириллицу. Взять и изменить код в файлах библиотеки не так то просто, но это же Django, а значит можно подключить библиотеку как приложение. Это и было сделано. Удалил библиотеку из зависимостей и поместил директорию с файлами приложения в проект, поправил импорты и всё заработало. Далее оставалось дело за малым, в одном файлике изменить используемую библиотеку для слагификации названия альбома.
Оставался последний шаг - сменить технический домен на основной с поддержкой https.
С бесплатными сертификатами есть одна проблема, их нужно обновлять каждые три месяца. Это можно обойти написав скрипт, но мало ли, что пойдёт не так у владельцев сайта. Было решено заменить nginx на caddy. Caddy это современный вебсервер, напоминающий nginx структурой файла конфигурации, но в нём есть один важный плюс - он автоматически получает сертификат для сайта и сам его продлевает по истечении срока действия.
После того, как убедились, что всё работает как надо перенесли домен со старого хостинга на сервер. С переносом тоже были проблемы - регистратор R01 никак не хотел менять NS-сервера у домена. Очень странное поведение, но после обращения в поддержку NS-ы сменили.
Оставалось только заменить домен в конфигурационных файлах. Всё!
У нас получилось в итоге два Docker Compose файла - для нового и старого сайта. По сути у нас два сайта, две версии MySQL и два связанных с ними PHPMyAdmin. Благодаря докеру это не вызывает никаких проблем с версионностью и позволяет управлять контейнерами без вреда для остального.
Заключение.
На всё про всё ушёл год. Хотя работы по сути было на 2-3 месяца. Однако, работа над этим проектом позволила заняться реальным, а не PET-проектом. Дала толчок к существованию канала.
Не бойтесь браться за то, что кажется сложным. Пусть это проект для кого-то или личный. Всё это опыт, навыки и повод гордиться собой.
Пост на сайте
Поддержать проект
#Django #Проект #Celery #Фриланс #КСКИ #Заказ
🔥8👍1
Docker ушёл из РФ! Как это исправить?
Автор: Иван Ашихмин
Сегодня утром многие были удивлены новостью о том, что Docker заблокировал свой главный репозиторий Docker Hub для ряда стран, включая и Россию. Новость, конечно, неприятная, но вполне ожидаемая. Давайте разберёмся как это исправить.
Docker Hub - это основной источник образов. При сборке контейнера, Docker в первую очередь обращается туда, однако, путь туда нам закрыт. Однако, Docker не ограничивается только основным репозиторием. Docker позволяет делать собственные репозитории. Помимо этого, есть и сторонние "зеркала". Ими мы и воспользуемся.
В данный момент под запрет попадают пользователи из России использующие Docker Desktop, но, вероятно, позже будет затронут и Docker Engine, работающий на VPS и серверах.
Решение проблемы для Docker Desktop.
Для решения проблемы необходимо прописать дополнительные зеркала в конфигурационный файл.
Автор: Иван Ашихмин
Сегодня утром многие были удивлены новостью о том, что Docker заблокировал свой главный репозиторий Docker Hub для ряда стран, включая и Россию. Новость, конечно, неприятная, но вполне ожидаемая. Давайте разберёмся как это исправить.
Docker Hub - это основной источник образов. При сборке контейнера, Docker в первую очередь обращается туда, однако, путь туда нам закрыт. Однако, Docker не ограничивается только основным репозиторием. Docker позволяет делать собственные репозитории. Помимо этого, есть и сторонние "зеркала". Ими мы и воспользуемся.
В данный момент под запрет попадают пользователи из России использующие Docker Desktop, но, вероятно, позже будет затронут и Docker Engine, работающий на VPS и серверах.
Решение проблемы для Docker Desktop.
Для решения проблемы необходимо прописать дополнительные зеркала в конфигурационный файл.
🔥7🤔1
Откройте приложение Docker Desktop и перейдите в настройки. В нём выбираем Docker Engine.
В окне будет редактор с предзаписанной конфигурацией в виде JSON. Необходимо добавить новый ключ со списком зеркал:
Мой конфиг выглядит так:
После чего нажимаем кнопку "Apply & restart". После перезагрузки всё будет работать.
Решение проблемы на VPS.
В данный момент на VPS Docker работает без проблем, однако подготовиться не помешает.
Файл конфигурации Docker находится по пути:
Откроем файл, выполнив команду
Если у вас открылся пустой редактор, значит файла у вас не было и после сохранения, он появится.
Точно так же как и в предыдущем пункте, необходимо в JSON добавить ключ со списком. Если у вас как у меня файла не было, то вставляем следующее:
Если у вас файл конфигурации был, то добавьте новый блок.
Сохраняем файл сочетанием клавиш
После этого необходимо перезапустить службу Docker, выполнив следующую команду:
Заключение.
Получать такие новости очень неприятно. Одно дело когда уходят (блокируют) какие-то их местные компании, которыми у нас никто не пользовался. Совсем по другому ощущается блокировка инструмента, которым пользуешься буквально каждый день.
Пост на сайте
Поддержать проект
#Docker #санкции #Docker_hub #обход
В окне будет редактор с предзаписанной конфигурацией в виде JSON. Необходимо добавить новый ключ со списком зеркал:
"registry-mirrors": [
"https://dockerhub.timeweb.cloud",
"https://mirror.gcr.io",
"https://huecker.io"
]
Мой конфиг выглядит так:
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"registry-mirrors": [
"https://dockerhub.timeweb.cloud",
"https://mirror.gcr.io",
"https://huecker.io"
]
}
После чего нажимаем кнопку "Apply & restart". После перезагрузки всё будет работать.
Решение проблемы на VPS.
В данный момент на VPS Docker работает без проблем, однако подготовиться не помешает.
Файл конфигурации Docker находится по пути:
/etc/docker/daemon.json, но его там может и не быть.Откроем файл, выполнив команду
sudo nano /etc/docker/daemon.json.Если у вас открылся пустой редактор, значит файла у вас не было и после сохранения, он появится.
Точно так же как и в предыдущем пункте, необходимо в JSON добавить ключ со списком. Если у вас как у меня файла не было, то вставляем следующее:
{
"registry-mirrors": [
"https://dockerhub.timeweb.cloud",
"https://mirror.gcr.io",
"https://huecker.io"
]
}
Если у вас файл конфигурации был, то добавьте новый блок.
Сохраняем файл сочетанием клавиш
CTRL+S и закрываем CTRL+X.После этого необходимо перезапустить службу Docker, выполнив следующую команду:
sudo systemctl restart docker.Заключение.
Получать такие новости очень неприятно. Одно дело когда уходят (блокируют) какие-то их местные компании, которыми у нас никто не пользовался. Совсем по другому ощущается блокировка инструмента, которым пользуешься буквально каждый день.
Пост на сайте
Поддержать проект
#Docker #санкции #Docker_hub #обход
🔥10👍3🤮1
Вместе следим за событиями!
Автор: Иван Ашихмин
Приветствую всех!
В суете повседневной работы и дел нередко бывает сложно следить за всеми происходящими событиями, новостями и обновлениями. Легко можно пропустить что-то важное, как, например, утренний пост о Docker.
Недавнюю новость о Docker, которую скинули в наш чат "Кот на салфетке", заметил благодаря Юрию. Спасибо ему за это! Однако, далеко не вся интересная и важная информация доходит до нас через привычные каналы.
Поэтому хочу обратиться к вам. Если у вас есть интересные новости или важные обновления, которые вы считаете стоящими нашего внимания, пожалуйста, делитесь ими в чате канала или пишите мне в личные сообщения (ссылка в описании канала).
Вместе мы сможем быть в курсе всех актуальных событий!
Пост на сайте
Поддержать проект
#код_на_салфетке #Новости #обновления #чат
Автор: Иван Ашихмин
Приветствую всех!
В суете повседневной работы и дел нередко бывает сложно следить за всеми происходящими событиями, новостями и обновлениями. Легко можно пропустить что-то важное, как, например, утренний пост о Docker.
Недавнюю новость о Docker, которую скинули в наш чат "Кот на салфетке", заметил благодаря Юрию. Спасибо ему за это! Однако, далеко не вся интересная и важная информация доходит до нас через привычные каналы.
Поэтому хочу обратиться к вам. Если у вас есть интересные новости или важные обновления, которые вы считаете стоящими нашего внимания, пожалуйста, делитесь ими в чате канала или пишите мне в личные сообщения (ссылка в описании канала).
Вместе мы сможем быть в курсе всех актуальных событий!
Пост на сайте
Поддержать проект
#код_на_салфетке #Новости #обновления #чат
🔥5👍1