HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"count": 5,
"next": null,
"previous": null,
"results": [
{
"title": "How to create GPT Telegram bot using CustomGPT",
"slug": "how-to-create-gpt-bot-using-customgpt",
"author": 1,
"created": "2024-03-18",
"text": "<p><i>**All code written below is for educational purposes.**</i></p>\r\n<p>\r\nWhile searching for GPT-based tools, I came across CustomGPT service. The service allows you to create your own custom bots using GPT 4 and your own database. Under its database - it means that you can upload any documents with information, such as doc PDF PPTX, as well as there is an opportunity to absorb entire forums and community (pre-prepared sitemap such resources). And this bot will be able to answer questions whose topics are revealed in these documents. If he does not find an answer in them, the bot will answer \"Sorry, but I can not answer this question\". Of course, if you want the bot to be able to answer even in such situations, you can configure it to work not only with the database, but also with the global GPT. But you have to be careful here, otherwise the correctness of answers and their accuracy drops rapidly. And such bots will give completely different answers to the same question.\r\n</p>\r\n<p>\r\nAs a result, for 100 dollars, we get:\r\n<ul>\r\n<li>up to 10 bots</li>\r\n<li>monetization support</li>\r\n<li>up to 60 million words in the database. And this for such a price is the most favourable that we could find</li>\r\n<li>work with GPT 4</li>\r\n<li>access to OpenAI API</li>\r\n</ul>\r\n<p>\r\nActually, further, we will talk about working with OpenAI API 😊. Working with a regular bot looks as follows. There is a link with public access (it must be created beforehand) to the bot. This link creates a new communication session where you can ask questions, etc. And here is an important point, your sessions will not be saved like in Chat GPT. Each click on this link will create a new chat session. But don't get frustrated, if you save a past link from the browser search bar, you can get into an existing session. Moreover, you can pass it to a friend, and he will also see your correspondence with the bot😈</p>\r\n<p>\r\nSo, if you put these links together somewhere, you can get a rough functionality with session history like in Chat GPT and a separate button to create a new session😈 Using Python, OpenAI API, and Telegram API it can be done.\r\nFor even more simplicity, we will show all of our created chats, but always chat in the last created one.\r\nTo work, we will need the following libraries:\r\n<ul>\r\n<li>Requests - for convenient work with OpenAI API commands</li>\r\n<li>peewee - ORM for convenient work with databases. In our case, it will be SQLite</li>\r\n<li>pyTelegramBotAPI - a convenient and simple library for working with Telegram API.</li>\r\n</ul>\r\n<p>\r\nBefore you start, you need to get the ID of the project in which you will work with the bot. This can be done using the <a href=\"https://docs.customgpt.ai/reference/get_api-v1-projects\" rel=\"noopener noreferrer\">API</a>.\r\nEnter your API token, run the command, and you will receive a response with a list of all projects and information on them, there you will find the ID of the project you need.\r\n<img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/custom_gpt_project_id.png\" alt=\"\" width=\"60%\" height=\"60%\" />\r\n</p>\r\n<p>Next, knowing this, you can start writing code for a Telegram bot.</p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #6ab825; font-weight: bold\">import</span> <span style=\"color: #447fcf; text-decoration: underline\">telebot</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">from</span> <span style=\"color: #447fcf; text-decoration: underline\">telebot</span> <span style=\"color: #6ab825; font-weight: bold\">import</span> <span style=\"color: #d0d0d0\">types</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">import</span> <span style=\"color: #447fcf; text-decoration: underline\">requests</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">import</span> <span style=\"color: #447fcf; text-decoration: underline\">peewee</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">from</span> <span style=\"color: #447fcf; text-decoration: underline\">uuid</span> <span style=\"color: #6ab825; font-weight: bold\">import</span> <span style=\"color: #d0d0d0\">uuid1</span>\r\n\r\n<span style=\"color: #d0d0d0\">DB</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.SqliteDatabase(</span><span style=\"color: #ed9d13\">'bot_database.db'</span><span style=\"color: #d0d0d0\">)</span>\r\n<span style=\"color: #d0d0d0\">TELEGRAM_BOT</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">telebot.TeleBot(</span><span style=\"color: #ed9d13\">'YOUR BOT API KEY'</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">parse_mode=</span><span style=\"color: #24909d\">None</span><span style=\"color: #d0d0d0\">)</span>\r\n<span style=\"color: #d0d0d0\">API_ENDPOINT</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #ed9d13\">'https://app.customgpt.ai/api/v1/'</span>\r\n<span style=\"color: #d0d0d0\">API_TOKEN</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #ed9d13\">'aaaaaaaaaa'</span>\r\n<span style=\"color: #d0d0d0\">PROJECT_ID</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #3677a9\">000000</span> <span style=\"color: #999999; font-style: italic\"># Your Custom GPT project ID</span>\r\n\r\n<span style=\"color: #d0d0d0\">headers</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">{</span>\r\n <span style=\"color: #ed9d13\">'accept'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'application/json'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">'Content-type'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'application/json'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">'Authorization'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'Bearer '</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">API_TOKEN</span>\r\n<span style=\"color: #d0d0d0\">}</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">class</span> <span style=\"color: #447fcf; text-decoration: underline\">Sessions</span><span style=\"color: #d0d0d0\">(peewee.Model):</span>\r\n <span style=\"color: #d0d0d0\">bot_id</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.IntegerField()</span>\r\n <span style=\"color: #d0d0d0\">session_id</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.CharField()</span>\r\n <span style=\"color: #d0d0d0\">name</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.CharField()</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold\">class</span> <span style=\"color: #447fcf; text-decoration: underline\">Meta</span><span style=\"color: #d0d0d0\">:</span>\r\n <span style=\"color: #d0d0d0\">database</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">DB</span>\r\n\r\n\r\n<span style=\"color: #999999; font-style: italic\"># Connect to the database and crete tables. Need to use only once.</span>\r\n<span style=\"color: #999999; font-style: italic\"># DB.connect()</span>\r\n<span style=\"color: #999999; font-style: italic\"># DB.create_tables([Sessions])</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">class</span> <span style=\"color: #447fcf; text-decoration: underline\">Project</span><span style=\"color: #d0d0d0\">:</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">__init__</span><span style=\"color: #d0d0d0\">(</span><span style=\"color: #24909d\">self</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #24909d\">id</span><span style=\"color: #d0d0d0\">):</span>\r\n <span style=\"color: #24909d\">self</span><span style=\"color: #d0d0d0\">.id</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #24909d\">id</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">conversation_constructor</span><span style=\"color: #d0d0d0\">(user_id,</span> <span style=\"color: #d0d0d0\">session_name):</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations'</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"name"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">session_name},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">return</span> <span style=\"color: #d0d0d0\">Sessions.create(name=session_name,</span> <span style=\"color: #d0d0d0\">session_id=resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'session_id'</span><span style=\"color: #d0d0d0\">],</span> <span style=\"color: #d0d0d0\">bot_id=user_id)</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_message</span><span style=\"color: #d0d0d0\">(text,</span> <span style=\"color: #d0d0d0\">session_id):</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(</span>\r\n <span style=\"color: #d0d0d0\">API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations/{session_id}/messages'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"response_source"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">"own_content"</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">"prompt"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">"{text}"</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">print</span><span style=\"color: #d0d0d0\">(f</span><span style=\"color: #ed9d13\">"[User query]: {resp['data']['user_query']}\\n[GPT response]: {resp['data']['openai_response']}"</span><span style=\"color: #d0d0d0\">)</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">return</span> <span style=\"color: #d0d0d0\">{</span><span style=\"color: #ed9d13\">'user_query'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'user_query'</span><span style=\"color: #d0d0d0\">],</span> <span style=\"color: #ed9d13\">'gpt_response'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'openai_response'</span><span style=\"color: #d0d0d0\">]}</span>\r\n\r\n\r\n<span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(commands=[</span><span style=\"color: #ed9d13\">'start'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_welcome</span><span style=\"color: #d0d0d0\">(message):</span>\r\n <span style=\"color: #d0d0d0\">chats</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">[]</span>\r\n <span style=\"color: #d0d0d0\">markup</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">types.ReplyKeyboardMarkup()</span>\r\n <span style=\"color: #d0d0d0\">chats.append(types.KeyboardButton(</span><span style=\"color: #ed9d13\">'/create_new_session'</span><span style=\"color: #d0d0d0\">))</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">for</span> <span style=\"color: #d0d0d0\">item</span> <span style=\"color: #6ab825; font-weight: bold\">in</span> <span style=\"color: #d0d0d0\">Sessions.select().where(Sessions.bot_id</span> <span style=\"color: #d0d0d0\">==</span> <span style=\"color: #d0d0d0\">message.from_user.id):</span>\r\n <span style=\"color: #d0d0d0\">chats.append(types.KeyboardButton(item.session_id))</span>\r\n <span style=\"color: #d0d0d0\">markup.add(*chats)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(message,</span>\r\n <span style=\"color: #ed9d13\">'Hello there! You have to create a new chat session using the command /create_new_session'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">reply_markup=markup)</span>\r\n\r\n\r\n<span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(commands=[</span><span style=\"color: #ed9d13\">'create_new_session'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">create_session</span><span style=\"color: #d0d0d0\">(message):</span>\r\n <span style=\"color: #d0d0d0\">name</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #24909d\">str</span><span style=\"color: #d0d0d0\">(uuid1())</span>\r\n <span style=\"color: #d0d0d0\">conversation_constructor(message.from_user.id,</span> <span style=\"color: #d0d0d0\">name)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(message,</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'New session {name}'</span><span style=\"color: #d0d0d0\">)</span>\r\n\r\n\r\n<span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(content_types=[</span><span style=\"color: #ed9d13\">'text'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_message</span><span style=\"color: #d0d0d0\">(msg):</span>\r\n <span style=\"color: #d0d0d0\">session</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">Sessions.select().where(Sessions.bot_id</span> <span style=\"color: #d0d0d0\">==</span> <span style=\"color: #d0d0d0\">msg.from_user.id).order_by(Sessions.id.desc()).get()</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(</span>\r\n <span style=\"color: #d0d0d0\">API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations/{session.session_id}/messages'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"response_source"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">"own_content"</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">"prompt"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">"{msg.text}"</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">print</span><span style=\"color: #d0d0d0\">(resp)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(msg,</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'Session: {session.name}\\n{resp['</span><span style=\"color: #d0d0d0\">data</span><span style=\"color: #ed9d13\">']['</span><span style=\"color: #d0d0d0\">openai_response</span><span style=\"color: #ed9d13\">']}'</span><span style=\"color: #d0d0d0\">)</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">if</span> <span style=\"color: #d0d0d0\">__name__</span> <span style=\"color: #d0d0d0\">==</span> <span style=\"color: #ed9d13\">'__main__'</span><span style=\"color: #d0d0d0\">:</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.infinity_polling()</span>\r\n</pre></div>\r\n\r\n<p>\r\nOf course, you need to remember that this bot does not yet give the ability to navigate through the sessions, only displays all you created through this Telegram. I think it won't be a problem to make such functionality if needed. As result:\r\n<img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/custom_gpt_telegram.png\" alt=\"\" width=\"60%\" height=\"60%\" />\r\n</p>\r\n\r\n<p>\r\nYou can just take the bot and copy it. Insert your data and work with it. Here will be a part for those who need a description of the code👌\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #d0d0d0\">headers</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">{</span>\r\n <span style=\"color: #ed9d13\">'accept'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'application/json'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">'Content-type'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'application/json'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">'Authorization'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">'Bearer '</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">API_TOKEN</span>\r\n<span style=\"color: #d0d0d0\">}</span>\r\n</pre></div>\r\nThe standard part of the CustomGPT API workflow. This is described in the documentation.\r\n</p>\r\n\r\n<p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #6ab825; font-weight: bold\">class</span> <span style=\"color: #447fcf; text-decoration: underline\">Sessions</span><span style=\"color: #d0d0d0\">(peewee.Model):</span>\r\n <span style=\"color: #d0d0d0\">bot_id</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.IntegerField()</span>\r\n <span style=\"color: #d0d0d0\">session_id</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.CharField()</span>\r\n <span style=\"color: #d0d0d0\">name</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">peewee.CharField()</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold\">class</span> <span style=\"color: #447fcf; text-decoration: underline\">Meta</span><span style=\"color: #d0d0d0\">:</span>\r\n <span style=\"color: #d0d0d0\">database</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">DB</span>\r\n\r\n\r\n<span style=\"color: #999999; font-style: italic\"># Connect to the database and crete tables. Need to use only once.</span>\r\n<span style=\"color: #999999; font-style: italic\"># DB.connect()</span>\r\n<span style=\"color: #999999; font-style: italic\"># DB.create_tables([Sessions])</span>\r\n</pre></div>\r\nCreating a database table using peewee. We will have three columns, and the table will be\r\n<img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/custom_gpt_database.png\" alt=\"\" width=\"60%\" />\r\n</p>\r\n\r\n<p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">conversation_constructor</span><span style=\"color: #d0d0d0\">(user_id,</span> <span style=\"color: #d0d0d0\">session_name):</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations'</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"name"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">session_name},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">return</span> <span style=\"color: #d0d0d0\">Sessions.create(name=session_name,</span> <span style=\"color: #d0d0d0\">session_id=resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'session_id'</span><span style=\"color: #d0d0d0\">],</span> <span style=\"color: #d0d0d0\">bot_id=user_id)</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_message</span><span style=\"color: #d0d0d0\">(text,</span> <span style=\"color: #d0d0d0\">session_id):</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(</span>\r\n <span style=\"color: #d0d0d0\">API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations/{session_id}/messages'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"response_source"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">"own_content"</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">"prompt"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">"{text}"</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">print</span><span style=\"color: #d0d0d0\">(f</span><span style=\"color: #ed9d13\">"[User query]: {resp['data']['user_query']}\\n[GPT response]: {resp['data']['openai_response']}"</span><span style=\"color: #d0d0d0\">)</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">return</span> <span style=\"color: #d0d0d0\">{</span><span style=\"color: #ed9d13\">'user_query'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'user_query'</span><span style=\"color: #d0d0d0\">],</span> <span style=\"color: #ed9d13\">'gpt_response'</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">resp[</span><span style=\"color: #ed9d13\">'data'</span><span style=\"color: #d0d0d0\">][</span><span style=\"color: #ed9d13\">'openai_response'</span><span style=\"color: #d0d0d0\">]}</span>\r\n</pre></div>\r\nA constructor function to create a new session with the bot and a function to send a message to the bot session that returns a response from CustomGPT.\r\n</p>\r\n\r\n<p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(commands=[</span><span style=\"color: #ed9d13\">'start'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_welcome</span><span style=\"color: #d0d0d0\">(message):</span>\r\n <span style=\"color: #d0d0d0\">chats</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">[]</span>\r\n <span style=\"color: #d0d0d0\">markup</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">types.ReplyKeyboardMarkup()</span>\r\n <span style=\"color: #d0d0d0\">chats.append(types.KeyboardButton(</span><span style=\"color: #ed9d13\">'/create_new_session'</span><span style=\"color: #d0d0d0\">))</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">for</span> <span style=\"color: #d0d0d0\">item</span> <span style=\"color: #6ab825; font-weight: bold\">in</span> <span style=\"color: #d0d0d0\">Sessions.select().where(Sessions.bot_id</span> <span style=\"color: #d0d0d0\">==</span> <span style=\"color: #d0d0d0\">message.from_user.id):</span>\r\n <span style=\"color: #d0d0d0\">chats.append(types.KeyboardButton(item.session_id))</span>\r\n <span style=\"color: #d0d0d0\">markup.add(*chats)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(message,</span>\r\n <span style=\"color: #ed9d13\">'Hello there! You have to create a new chat session using the command /create_new_session'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">reply_markup=markup)</span>\r\n</pre></div>\r\nHandler for the /start command to start communication with the bot and display the control buttons.\r\n</p>\r\n\r\n<p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(commands=[</span><span style=\"color: #ed9d13\">'create_new_session'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">create_session</span><span style=\"color: #d0d0d0\">(message):</span>\r\n <span style=\"color: #d0d0d0\">name</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #24909d\">str</span><span style=\"color: #d0d0d0\">(uuid1())</span>\r\n <span style=\"color: #d0d0d0\">conversation_constructor(message.from_user.id,</span> <span style=\"color: #d0d0d0\">name)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(message,</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'New session {name}'</span><span style=\"color: #d0d0d0\">)</span>\r\n</pre></div>\r\nCreate a new session on command from the Telegram bot.\r\n</p>\r\n\r\n<p>\r\n<div style=\"background: #202020; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;\"><pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #ffa500\">@TELEGRAM_BOT.message_handler</span><span style=\"color: #d0d0d0\">(content_types=[</span><span style=\"color: #ed9d13\">'text'</span><span style=\"color: #d0d0d0\">])</span>\r\n<span style=\"color: #6ab825; font-weight: bold\">def</span> <span style=\"color: #447fcf\">send_message</span><span style=\"color: #d0d0d0\">(msg):</span>\r\n <span style=\"color: #d0d0d0\">session</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">Sessions.select().where(Sessions.bot_id</span> <span style=\"color: #d0d0d0\">==</span> <span style=\"color: #d0d0d0\">msg.from_user.id).order_by(Sessions.id.desc()).get()</span>\r\n <span style=\"color: #d0d0d0\">resp</span> <span style=\"color: #d0d0d0\">=</span> <span style=\"color: #d0d0d0\">requests.post(</span>\r\n <span style=\"color: #d0d0d0\">API_ENDPOINT</span> <span style=\"color: #d0d0d0\">+</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'projects/{PROJECT_ID}/conversations/{session.session_id}/messages'</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #d0d0d0\">json={</span><span style=\"color: #ed9d13\">"response_source"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #ed9d13\">"own_content"</span><span style=\"color: #d0d0d0\">,</span>\r\n <span style=\"color: #ed9d13\">"prompt"</span><span style=\"color: #d0d0d0\">:</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">"{msg.text}"</span><span style=\"color: #d0d0d0\">,</span> <span style=\"color: #d0d0d0\">},</span>\r\n <span style=\"color: #d0d0d0\">headers=headers).json()</span>\r\n <span style=\"color: #6ab825; font-weight: bold\">print</span><span style=\"color: #d0d0d0\">(resp)</span>\r\n <span style=\"color: #d0d0d0\">TELEGRAM_BOT.reply_to(msg,</span> <span style=\"color: #d0d0d0\">f</span><span style=\"color: #ed9d13\">'Session: {session.name}\\n{resp['</span><span style=\"color: #d0d0d0\">data</span><span style=\"color: #ed9d13\">']['</span><span style=\"color: #d0d0d0\">openai_response</span><span style=\"color: #ed9d13\">']}'</span><span style=\"color: #d0d0d0\">)</span>\r\n</pre></div>\r\nHere we pull the latest user ID record from the database and send a message to it. After that we output the response.\r\n</p>",
"category": 4
},
{
"title": "Django-CMS #3 PART",
"slug": "django-cms-3-part",
"author": 1,
"created": "2023-12-12",
"text": "<p><span style=\"font-weight: 400;\">Well, we’ve already discussed how to create a custom template, how to add placeholders and how to add text to your page. Now, we are going to look through the process of integration of a new or already existing application to the Django-cms project.</span></p>\r\n<p><span style=\"font-weight: 400;\">Look at the picture below.</span></p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Django_cms_app_integration.png\" alt=\"\" width=\"60%\" height=\"60%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Here we have the </span><strong>DJANGO-CMS</strong><span style=\"font-weight: 400;\"> with </span><em><span style=\"font-weight: 400;\">backend</span></em><span style=\"font-weight: 400;\">. The application with the </span><em><span style=\"font-weight: 400;\">settings.py</span></em><span style=\"font-weight: 400;\"> and all the templates. Next, we have the </span><strong>DJANGO APPLICATIONS</strong><span style=\"font-weight: 400;\"> section, with all the applications we want to add to the project. It may be a main page, personal cabinets or any other external logic we need. What do we have to do to add applications to the project? How do we add applications to the project? In simple Django, it's enough to include the application in the APPLICATION_LIST in the settings.py. However, in Django-cms we have to use a plugin for each Django application, the </span><strong>DJANGO PLUGINS</strong><span style=\"font-weight: 400;\"> section.</span></p>\r\n<p><span style=\"font-weight: 400;\">For example, we want to add a blog page to our project. I want a full page with a full text and all the content.</span></p>\r\n<p><span style=\"font-weight: 400;\">Create a Django application by entering the command into the command line 'python manage.py createapp blog'. Add <em>blog </em>to the end of the <strong>APPLICATION_LIST</strong>. Add the <em>blog</em> url to the urls.py in the backend directory (before the <em>cms.ruls</em>). Create a model: Blog.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Blog</span><span style=\"color: #d0d0d0;\">(models.Model):</span>\r\n <span style=\"color: #d0d0d0;\">title</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.CharField(max_length=</span><span style=\"color: #3677a9;\">128</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">text</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.TextField()</span>\r\n <span style=\"color: #d0d0d0;\">categories</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ManyToManyField(Category)</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">__str__</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.title</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">In some sense, a plugin is similar to an application in Django. Just enter “<em>python manage.py createapp blog_cms_integration</em>” in the command line and let’s go. Add </span><em><span style=\"font-weight: 400;\">blog_cms_integration </span></em><span style=\"font-weight: 400;\">to the end of the <strong>APPLICATION_LIST</strong>, it should look like:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">INSTALLED_APPS</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #a61717; background-color: #e3d2d2;\">…</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">'blog_cms_integration'</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">'blog'</span><span style=\"color: #d0d0d0;\">,</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">In the models.py of </span><em><span style=\"font-weight: 400;\">blog_cms_integration </span></em><span style=\"font-weight: 400;\">add next:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">BlogPluginModel</span><span style=\"color: #d0d0d0;\">(CMSPlugin):</span>\r\n <span style=\"color: #d0d0d0;\">blog</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ForeignKey(Blog,</span> <span style=\"color: #d0d0d0;\">on_delete=models.CASCADE)</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">__str__</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.blog.title</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Next, create a new file “cms_pluigins.py” in the </span><em><span style=\"font-weight: 400;\">blog_cms_integration </span></em><span style=\"font-weight: 400;\">folder. The plugin class is responsible for providing Dango CMS with the necessary information to render your plugin.</span></p>\r\n<p><span style=\"font-weight: 400;\">For our plugin, we’re going to write the following plugin class:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #ffa500;\">@plugin_pool.register_plugin</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">BlogPluginPublisher</span><span style=\"color: #d0d0d0;\">(CMSPluginBase):</span>\r\n <span style=\"color: #d0d0d0;\">model</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">BlogPluginModel</span>\r\n <span style=\"color: #d0d0d0;\">module</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">_(</span><span style=\"color: #ed9d13;\">'Blogs'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">_(</span><span style=\"color: #ed9d13;\">'Blog Plugin'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">render_template</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'blog_cms_integration/blog_plugin.html'</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">render</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">context,</span> <span style=\"color: #d0d0d0;\">instance,</span> <span style=\"color: #d0d0d0;\">placeholder):</span>\r\n <span style=\"color: #d0d0d0;\">context.update({</span><span style=\"color: #ed9d13;\">'instance'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">instance})</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">context</span>\r\n</pre>\r\n</div>\r\n<p><strong>module </strong>— is the section in the plugin list in the admin page<br /><strong>name </strong>— is the name of the plugin in the plugin list<br /><strong>render_template </strong>— is the HTML template of the plugin</p>\r\n<p><span style=\"font-weight: 400;\">In the </span><em><span style=\"font-weight: 400;\">blog_cms_integration/templates/blog_cms_integration </span></em><span style=\"font-weight: 400;\">create a HTML file “</span><em><span style=\"font-weight: 400;\">blog_plugin.html</span></em><span style=\"font-weight: 400;\">”:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\"><h1>{{</span> <span style=\"color: #d0d0d0;\">instance.blog.title</span> <span style=\"color: #d0d0d0;\">}}</h1></span>\r\n\r\n<span style=\"color: #d0d0d0;\"><p>{{</span> <span style=\"color: #d0d0d0;\">instance.blog.text</span> <span style=\"color: #d0d0d0;\">}}</p></span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Add the Blog model to the admin site in the </span><em><span style=\"font-weight: 400;\">blog/admin.py. </span></em></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.contrib</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">admin</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Blog,</span> <span style=\"color: #d0d0d0;\">Category</span>\r\n\r\n\r\n<span style=\"color: #ffa500;\">@admin.register</span><span style=\"color: #d0d0d0;\">(Blog)</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">BlogAdmin</span><span style=\"color: #d0d0d0;\">(admin.ModelAdmin):</span>\r\n <span style=\"color: #d0d0d0;\">list_display</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span><span style=\"color: #ed9d13;\">'title'</span><span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p>Now you can restart the runserver (required because you added the new <em>cms_plugins.py</em> file), and visit <a href=\"http://localhost:8000/admin/\">http://localhost:8000/admin/</a>.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/site_admin_blog_entry.png\" alt=\"\" width=\"80%\" height=\"80%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Open “<strong>Blogs</strong>” and create a new blog entry.</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/site_admin_blog_entry_2.png\" alt=\"\" width=\"80%\" height=\"80%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">Navigate to the </span><a href=\"http://localhost:8000/\"><span style=\"font-weight: 400;\">http://localhost:8000</span></a><span style=\"font-weight: 400;\">/ and let’s add a <strong>Blog Plugin</strong>.</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/add_plugin_to_the_page.png\" alt=\"\" width=\"60%\" height=\"60%\" />You can now drop the <strong>Blog Plugin</strong> to any placeholder on any page, similar to adding any other plugin.</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/plugin_on_the_page.png\" alt=\"\" width=\"80%\" height=\"80%\" /></span></p>\r\n<p>Now you know how to create your own Django-CMS project, change templates of your website and integrate applications you need to the Django-CMS.</p>",
"category": 3
},
{
"title": "Django-CMS #2 PART",
"slug": "django-cms-2-part",
"author": 1,
"created": "2023-12-01",
"text": "<p><span style=\"font-weight: 400;\">Let’s create our first page. Press New Page and select Next.</span></p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_1.png\" alt=\"\" width=\"50%\" height=\"50%\" /></p>\r\n<p><span style=\"font-weight: 400;\">After selecting Next, you will add in your title and some basic text content for the new page (optional), click Create. </span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_2.png\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">The page we've just created is a draft and needs to be published once you finish. As an editor, only you can see and edit your drafts, other visitors to your site will only see your published pages. Press “Publish page now.”</span></p>\r\n<p><span style=\"font-weight: 400;\">You can check pages on the admin page. Navigate </span><a href=\"../../../../../\"><span style=\"font-weight: 400;\">http://127.0.0.1:8000</span></a><span style=\"font-weight: 400;\"> and open the “Pages” tab, there you will see:</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_3.png\" alt=\"\" width=\"75%\" height=\"75%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">Django-cms has pre-installed templates. The Minimal template is used by default. It’s a simple HTML page which represents all content. Django-cms also has two another template: the Bootstrap 5 Demo and the Static File Demo. You can switch template in Page -> Templates -> <em>choose a template</em></span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_4.png\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">We can add custom template to the system if we want. In the settings.py file we will find a CMS_TEMPLATES variable which contains a list of tuples (‘template_file_name.html’, ‘Menu item name’) of templates you can select for a page. All templates defined in CMS_TEMPLATES must contain at least the js and css sekizai namespaces (I will show you later). </span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">CMS_TEMPLATES</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #999999; font-style: italic;\"># a minimal template to get started with</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'minimal.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Minimal template'</span><span style=\"color: #d0d0d0;\">),</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># optional templates that extend base.html, to be used with Bootstrap 5</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'bootstrap5.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Bootstrap 5 Demo'</span><span style=\"color: #d0d0d0;\">),</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># serving static files with whitenoise demo</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'whitenoise-static-files-demo.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Static File Demo'</span><span style=\"color: #d0d0d0;\">),</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">It will be simpler to explain using “minimal.html”. </span><span style=\"font-weight: 400;\">Open backend->templates->menu->minimal.html</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">load</span> <span style=\"color: #d0d0d0;\">cms_tags</span> <span style=\"color: #d0d0d0;\">sekizai_tags</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"><html></span>\r\n <span style=\"color: #d0d0d0;\"><head></span>\r\n <span style=\"color: #d0d0d0;\"><title>{%</span> <span style=\"color: #d0d0d0;\">page_attribute</span> <span style=\"color: #ed9d13;\">\"page_title\"</span> <span style=\"color: #d0d0d0;\">%}</title></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">render_block</span> <span style=\"color: #ed9d13;\">\"css\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"></head></span>\r\n <span style=\"color: #d0d0d0;\"><body></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">cms_toolbar</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">placeholder</span> <span style=\"color: #ed9d13;\">\"Content\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">render_block</span> <span style=\"color: #ed9d13;\">\"js\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"></body></span>\r\n<span style=\"color: #d0d0d0;\"></html></span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Here you can see “sekizai_tags” and “js” namespaces.“minimal.html” contains single “Content” placeholder. Placeholders are an easy way to define sections in an HTML template that will be filled with content from the database when the page is rendered. This content is edited using django CMS’s frontend editing mechanism, using Django template tags.</span></p>\r\n<p><span style=\"font-weight: 400;\">Add a new placeholder to “minimal.html”, {% placeholder \"feature\" %} inside the {% block content %} section.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\"><body></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">cms_toolbar</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">placeholder</span> <span style=\"color: #ed9d13;\">\"Content\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">placeholder</span> <span style=\"color: #ed9d13;\">\"feature\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">render_block</span> <span style=\"color: #ed9d13;\">\"js\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"></body></span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">If you switch to Structure mode (button in the upper-right corner of the page), you’ll see the new placeholders available for use.</span></p>\r\n<p><span style=\"font-weight: 400;\">You also can add “statis_placeholder” if you want to add footer or header to your page's manually. For example, if you want to have a footer which you could change at any time, you must add:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\"><footer></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">static_placeholder</span> <span style=\"color: #ed9d13;\">'footer'</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"></footer></span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Save the template and return to your browser. Refresh any page in Structure mode, and you’ll see the new static placeholder.</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_5.png\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">Now, let’s return to our project. We will be working with “bootstrap5.html”, as it already have navigation bar, bootstrap styling, and it looks much better than “minimal.html” 😁. On the main page, open “Edit” mode, next Page->Templates->Bootstrap 5 Demo and publish page changes. Now we have the navigation bar with one link to the main page and the main page content. Looks nice, doesn’t it?😉</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_6.png\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">We are able to add placeholders to the “bootstrap5.html” too. Navigate to the file and let’s look what we have here:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">extends</span> <span style=\"color: #ed9d13;\">\"base.html\"</span> <span style=\"color: #d0d0d0;\">%}{%</span> <span style=\"color: #d0d0d0;\">load</span> <span style=\"color: #d0d0d0;\">cms_tags</span> <span style=\"color: #d0d0d0;\">menu_tags</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">base_css</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"><link</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css\"</span> <span style=\"color: #d0d0d0;\">rel=</span><span style=\"color: #ed9d13;\">\"stylesheet\"</span> <span style=\"color: #d0d0d0;\">integrity=</span><span style=\"color: #ed9d13;\">\"sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT\"</span> <span style=\"color: #d0d0d0;\">crossorigin=</span><span style=\"color: #ed9d13;\">\"anonymous\"</span><span style=\"color: #d0d0d0;\">/></span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">base_js</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"><script</span> <span style=\"color: #d0d0d0;\">src=</span><span style=\"color: #ed9d13;\">\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js\"</span> <span style=\"color: #d0d0d0;\">integrity=</span><span style=\"color: #ed9d13;\">\"sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8\"</span> <span style=\"color: #d0d0d0;\">crossorigin=</span><span style=\"color: #ed9d13;\">\"anonymous\"</span><span style=\"color: #d0d0d0;\">></script></span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">navbar</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"><nav</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"navbar {% block navbar_options %}navbar-expand-lg navbar-dark bg-dark{% endblock %}\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><div</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"container\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><a</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"navbar-brand\"</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"/\"</span><span style=\"color: #d0d0d0;\">>{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">brand</span> <span style=\"color: #d0d0d0;\">%}{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</a></span>\r\n <span style=\"color: #d0d0d0;\"><button</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"navbar-toggler\"</span> <span style=\"color: #24909d;\">type</span><span style=\"color: #d0d0d0;\">=</span><span style=\"color: #ed9d13;\">\"button\"</span> <span style=\"color: #d0d0d0;\">data-bs-toggle=</span><span style=\"color: #ed9d13;\">\"collapse\"</span> <span style=\"color: #d0d0d0;\">data-bs-target=</span><span style=\"color: #ed9d13;\">\"#navbarSupportedContent\"</span> <span style=\"color: #d0d0d0;\">aria-controls=</span><span style=\"color: #ed9d13;\">\"navbarSupportedContent\"</span> <span style=\"color: #d0d0d0;\">aria-expanded=</span><span style=\"color: #ed9d13;\">\"false\"</span> <span style=\"color: #d0d0d0;\">aria-label=</span><span style=\"color: #ed9d13;\">\"Toggle navigation\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><span</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"navbar-toggler-icon\"</span><span style=\"color: #d0d0d0;\">></span></span>\r\n <span style=\"color: #d0d0d0;\"></button></span>\r\n <span style=\"color: #d0d0d0;\"><div</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"collapse navbar-collapse\"</span> <span style=\"color: #24909d;\">id</span><span style=\"color: #d0d0d0;\">=</span><span style=\"color: #ed9d13;\">\"navbarSupportedContent\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><ul</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"navbar-nav me-auto mb-2 mb-lg-0\"</span><span style=\"color: #d0d0d0;\">>{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">menubar</span> <span style=\"color: #d0d0d0;\">%}{%</span> <span style=\"color: #d0d0d0;\">show_menu</span> <span style=\"color: #3677a9;\">0</span> <span style=\"color: #3677a9;\">100</span> <span style=\"color: #3677a9;\">0</span> <span style=\"color: #3677a9;\">100</span> <span style=\"color: #ed9d13;\">'menu/menu.html'</span> <span style=\"color: #d0d0d0;\">%}{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</ul></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">block</span> <span style=\"color: #d0d0d0;\">searchbar</span> <span style=\"color: #d0d0d0;\">%}{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n <span style=\"color: #d0d0d0;\"></div></span>\r\n <span style=\"color: #d0d0d0;\"></div></span>\r\n <span style=\"color: #d0d0d0;\"></nav></span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">endblock</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">There is extending to the “base.html” file in the same “menu” directory in the project. We add css styles and js scripts to the base.html and customized navigation bar. Base.html is the place where we can add placeholders, so let do this. Navigate to the base.html and add placeholder “feature” to block “{% block content %}”.</span></p>\r\n<p><span style=\"font-weight: 400;\">At last, let’s create our own template HTML file and add to the system and Edit panel. In the backend/templates/ create “my_test_template.html” file. Open the file and add next code:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">load</span> <span style=\"color: #d0d0d0;\">cms_tags</span> <span style=\"color: #d0d0d0;\">sekizai_tags</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n\r\n<span style=\"color: #d0d0d0;\"><</span><span style=\"color: #a61717; background-color: #e3d2d2;\">!</span><span style=\"color: #d0d0d0;\">DOCTYPE</span> <span style=\"color: #d0d0d0;\">html></span>\r\n<span style=\"color: #d0d0d0;\"><html</span> <span style=\"color: #d0d0d0;\">lang=</span><span style=\"color: #ed9d13;\">\"en\"</span><span style=\"color: #d0d0d0;\">></span>\r\n<span style=\"color: #d0d0d0;\"><head></span>\r\n <span style=\"color: #d0d0d0;\"><</span><span style=\"color: #a61717; background-color: #e3d2d2;\">!</span><span style=\"color: #d0d0d0;\">--</span> <span style=\"color: #d0d0d0;\">Custom</span> <span style=\"color: #d0d0d0;\">styles</span> <span style=\"color: #6ab825; font-weight: bold;\">for</span> <span style=\"color: #d0d0d0;\">this</span> <span style=\"color: #d0d0d0;\">template</span> <span style=\"color: #d0d0d0;\">--></span>\r\n <span style=\"color: #d0d0d0;\"><link</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"headers.css\"</span> <span style=\"color: #d0d0d0;\">rel=</span><span style=\"color: #ed9d13;\">\"stylesheet\"</span><span style=\"color: #d0d0d0;\">></span>\r\n\r\n <span style=\"color: #d0d0d0;\"><title>{%</span> <span style=\"color: #d0d0d0;\">page_attribute</span> <span style=\"color: #ed9d13;\">\"page_title\"</span> <span style=\"color: #d0d0d0;\">%}</title></span>\r\n <span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">render_block</span> <span style=\"color: #ed9d13;\">\"css\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"></head></span>\r\n<span style=\"color: #d0d0d0;\"><body></span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">cms_toolbar</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"><header</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"/backend/static\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><svg</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"bi me-2\"</span> <span style=\"color: #d0d0d0;\">width=</span><span style=\"color: #ed9d13;\">\"40\"</span> <span style=\"color: #d0d0d0;\">height=</span><span style=\"color: #ed9d13;\">\"32\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><use</span> <span style=\"color: #d0d0d0;\">xlink:href=</span><span style=\"color: #ed9d13;\">\"#bootstrap\"</span><span style=\"color: #d0d0d0;\">></use></span>\r\n <span style=\"color: #d0d0d0;\"></svg></span>\r\n <span style=\"color: #d0d0d0;\"><span</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"fs-4\"</span><span style=\"color: #d0d0d0;\">>Simple</span> <span style=\"color: #d0d0d0;\">header</span></span>\r\n <span style=\"color: #d0d0d0;\"></a></span>\r\n\r\n <span style=\"color: #d0d0d0;\"><ul</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav nav-pills\"</span><span style=\"color: #d0d0d0;\">></span>\r\n <span style=\"color: #d0d0d0;\"><li</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-item\"</span><span style=\"color: #d0d0d0;\">><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"#\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-link active\"</span> <span style=\"color: #d0d0d0;\">aria-current=</span><span style=\"color: #ed9d13;\">\"page\"</span><span style=\"color: #d0d0d0;\">>Home</a></li></span>\r\n <span style=\"color: #d0d0d0;\"><li</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-item\"</span><span style=\"color: #d0d0d0;\">><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"#\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-link\"</span><span style=\"color: #d0d0d0;\">>Features</a></li></span>\r\n <span style=\"color: #d0d0d0;\"><li</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-item\"</span><span style=\"color: #d0d0d0;\">><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"#\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-link\"</span><span style=\"color: #d0d0d0;\">>Pricing</a></li></span>\r\n <span style=\"color: #d0d0d0;\"><li</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-item\"</span><span style=\"color: #d0d0d0;\">><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"#\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-link\"</span><span style=\"color: #d0d0d0;\">>FAQs</a></li></span>\r\n <span style=\"color: #d0d0d0;\"><li</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-item\"</span><span style=\"color: #d0d0d0;\">><a</span> <span style=\"color: #d0d0d0;\">href=</span><span style=\"color: #ed9d13;\">\"#\"</span> <span style=\"color: #d0d0d0;\">class=</span><span style=\"color: #ed9d13;\">\"nav-link\"</span><span style=\"color: #d0d0d0;\">>About</a></li></span>\r\n <span style=\"color: #d0d0d0;\"></ul></span>\r\n<span style=\"color: #d0d0d0;\"></header></span>\r\n<span style=\"color: #d0d0d0;\">{%</span> <span style=\"color: #d0d0d0;\">render_block</span> <span style=\"color: #ed9d13;\">\"js\"</span> <span style=\"color: #d0d0d0;\">%}</span>\r\n<span style=\"color: #d0d0d0;\"></body></span>\r\n<span style=\"color: #d0d0d0;\"></html></span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Then we need to specify the template in the settings.py in the variable CMS_TEMPLATES, like that:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">CMS_TEMPLATES</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'my_test_template.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'My test template'</span><span style=\"color: #d0d0d0;\">),</span>\r\n <span style=\"color: #999999; font-style: italic;\"># a minimal template to get started with</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'minimal.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Minimal template'</span><span style=\"color: #d0d0d0;\">),</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># optional templates that extend base.html, to be used with Bootstrap 5</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'bootstrap5.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Bootstrap 5 Demo'</span><span style=\"color: #d0d0d0;\">),</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># serving static files with whitenoise demo</span>\r\n <span style=\"color: #d0d0d0;\">(</span><span style=\"color: #ed9d13;\">'whitenoise-static-files-demo.html'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'Static File Demo'</span><span style=\"color: #d0d0d0;\">),</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Return to browser, open page in Edit mode and check available templates. You will see our “My test template”. That’s all. </span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/django-cms-2_7.png\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/a61aa067-1c2e-4f8f-aad9-acc2deadc906-1690045939460.jpg\" alt=\"\" width=\"50%\" height=\"50%\" /></span></p>",
"category": 3
},
{
"title": "Django-CMS #1 PART",
"slug": "django-cms-1-part",
"author": 1,
"created": "2023-11-20",
"text": "<p><span style=\"font-weight: 400;\">Django-cms is a powerful instrument powered by Django. It’s become easy to create modern and flexible websites that can be edited at any time. Django-cms is a direct competitor to Wordpress, but at the same time has all the advantages of the Django framework.</span></p>\r\n<p><span style=\"font-weight: 400;\">The setup is incredibly simple. I will set up a project using docker demo image. First of all, install Docker from <a href=\"https://docs.docker.com/get-docker/\">here</a>.</span></p>\r\n<p><span style=\"font-weight: 400;\">The demo project is a minimal django project with some additional requirements in the requirements.txt.</span></p>\r\n<p><span style=\"font-weight: 400;\">Open the terminal application on your computer and go to a safe folder, then:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">git</span> <span style=\"color: #d0d0d0;\">clone</span> <span style=\"color: #d0d0d0;\">https://github.com/django-cms/django-cms-quickstart.git</span>\r\n<span style=\"color: #d0d0d0;\">cd</span> <span style=\"color: #d0d0d0;\">django-cms-quickstart</span>\r\n<span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">build</span> <span style=\"color: #d0d0d0;\">web</span>\r\n<span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">up</span> <span style=\"color: #d0d0d0;\">-d</span> <span style=\"color: #d0d0d0;\">database_default</span>\r\n<span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">run</span> <span style=\"color: #d0d0d0;\">web</span> <span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">migrate</span>\r\n<span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">run</span> <span style=\"color: #d0d0d0;\">web</span> <span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">createsuperuser</span>\r\n<span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">up</span> <span style=\"color: #d0d0d0;\">-d</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">During the installation process, you will be prompted to enter your email address and set a username and password. Open your browser and insert http://localhost:8000/admin there you should be invited to login. You will see the following page:</span></p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/create_page_with_django_cms2.webp\" alt=\"\" width=\"75%\" height=\"75%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Now our project works well, but Django CMS requires a relational database backend. Each django CMS installation should have its own database. Let’s do this first. Navigate to settings.py file in the backend directory and find a DATABASE_URL and DATABASES variables.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">DATABASE_URL</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">os.environ.get(</span><span style=\"color: #ed9d13;\">'DATABASE_URL'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'sqlite://:memory:'</span><span style=\"color: #d0d0d0;\">)</span>\r\n<span style=\"color: #d0d0d0;\">DATABASES</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">{</span><span style=\"color: #ed9d13;\">'default'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">dj_database_url.parse(DATABASE_URL)}</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\"><strong><em>DATABASE_URL</em></strong> contains a database url from the<em> .env-local </em>file. If the file doesn’t contain <strong><em>DATABASE_URL</em></strong> variable, it will be placed default value<em> 'sqlite://:memory:'</em>. So, navigate to </span><span style=\"font-weight: 400;\">the <em>.env-local</em> file and we will change <em><strong>DATABASE_URL</strong></em> variable.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">DATABASE_URL=postgres://postgres:password</span><span style=\"color: #ffa500;\">@database_default</span><span style=\"color: #d0d0d0;\">:</span><span style=\"color: #3677a9;\">5432</span><span style=\"color: #d0d0d0;\">/db</span>\r\n<span style=\"color: #d0d0d0;\">DEFAULT_STORAGE_DSN=</span><span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">:///data/media/</span><span style=\"color: #a61717; background-color: #e3d2d2;\">?</span><span style=\"color: #d0d0d0;\">url=%</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">Fmedia%</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">F</span>\r\n<span style=\"color: #d0d0d0;\">DEBUG=</span><span style=\"color: #24909d;\">True</span>\r\n<span style=\"color: #d0d0d0;\">DOMAIN_ALIASES=</span><span style=\"color: #ed9d13;\">\"localhost, 127.0.0.1\"</span>\r\n<span style=\"color: #d0d0d0;\">SECURE_SSL_REDIRECT=</span><span style=\"color: #24909d;\">False</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Look at <em><strong>DATABASE_URL</strong></em>. It says to use PostgreSQL database, with a username <em>“postgres”</em> and a password <em>“password”</em>, connect to a database <em>“db”</em> via <em>5432 </em>port. We will leave admin username and password (<strong>I STRONGLY RECOMMEND NOT ALLOWING SUCH PASSWORDS IN THE PRODUCTION OF YOUR PROJECT</strong>), but change database name to <em>‘django_cms_blog’</em>.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">DATABASE_URL=postgres://postgres:password</span><span style=\"color: #ffa500;\">@database_default</span><span style=\"color: #d0d0d0;\">:</span><span style=\"color: #3677a9;\">5432</span><span style=\"color: #d0d0d0;\">/django_cms_blog</span>\r\n<span style=\"color: #d0d0d0;\">DEFAULT_STORAGE_DSN=</span><span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">:///data/media/</span><span style=\"color: #a61717; background-color: #e3d2d2;\">?</span><span style=\"color: #d0d0d0;\">url=%</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">Fmedia%</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">F</span>\r\n<span style=\"color: #d0d0d0;\">DEBUG=</span><span style=\"color: #24909d;\">True</span>\r\n<span style=\"color: #d0d0d0;\">DOMAIN_ALIASES=</span><span style=\"color: #ed9d13;\">\"localhost, 127.0.0.1\"</span>\r\n<span style=\"color: #d0d0d0;\">SECURE_SSL_REDIRECT=</span><span style=\"color: #24909d;\">False</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Next, open the docker-compose.yml file, and change the <em><strong>POSTGRES_DB</strong></em> variable in <em>“database_default”</em> service to ‘django_cms_blog’:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"> <span style=\"color: #d0d0d0;\">database_default:</span>\r\n <span style=\"color: #999999; font-style: italic;\"># Select one of the following db configurations for the database</span>\r\n <span style=\"color: #d0d0d0;\">image:</span> <span style=\"color: #d0d0d0;\">postgres:</span><span style=\"color: #3677a9;\">13.5</span><span style=\"color: #d0d0d0;\">-alpine</span>\r\n <span style=\"color: #d0d0d0;\">ports:</span>\r\n <span style=\"color: #d0d0d0;\">-</span> <span style=\"color: #ed9d13;\">\"5432:5432/tcp\"</span> <span style=\"color: #999999; font-style: italic;\"># allow your local dev env to connect to the db</span>\r\n <span style=\"color: #d0d0d0;\">environment:</span>\r\n <span style=\"color: #d0d0d0;\">POSTGRES_DB:</span> <span style=\"color: #ed9d13;\">\"django_cms_blog\"</span>\r\n <span style=\"color: #d0d0d0;\">POSTGRES_PASSWORD:</span> <span style=\"color: #ed9d13;\">\"password\"</span>\r\n <span style=\"color: #d0d0d0;\">POSTGRES_HOST_AUTH_METHOD:</span> <span style=\"color: #ed9d13;\">\"trust\"</span>\r\n <span style=\"color: #d0d0d0;\">SERVICE_MANAGER:</span> <span style=\"color: #ed9d13;\">\"fsm-postgres\"</span>\r\n\r\n <span style=\"color: #d0d0d0;\">networks:</span>\r\n <span style=\"color: #d0d0d0;\">-</span> <span style=\"color: #d0d0d0;\">djangocmsnet</span>\r\n\r\n <span style=\"color: #d0d0d0;\">volumes:</span>\r\n <span style=\"color: #d0d0d0;\">-</span> <span style=\"color: #ed9d13;\">\".:/app:rw\"</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Now let’s rebuild our docker container.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">compose</span> <span style=\"color: #d0d0d0;\">up</span> <span style=\"color: #d0d0d0;\">-d</span> <span style=\"color: #d0d0d0;\">--build</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">After the containers were created successfully, we will check logs.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">ps</span>\r\n</pre>\r\n</div>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image7.png\" alt=\"\" width=\"75%\" height=\"75%\" /></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">logs</span> <span style=\"color: #d0d0d0;\">29a95</span>\r\n</pre>\r\n</div>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image3.png\" alt=\"\" width=\"75%\" height=\"75%\" /></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker</span> <span style=\"color: #d0d0d0;\">logs</span> <span style=\"color: #d0d0d0;\">496d22</span>\r\n</pre>\r\n</div>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image1.png\" alt=\"\" width=\"75%\" height=\"75%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Now we get errors that database ‘django_cms_blog’ does not exist and that’s normal. We haven’t created the database yet. Let’s do it:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker-compose</span> <span style=\"color: #6ab825; font-weight: bold;\">exec</span> <span style=\"color: #d0d0d0;\">database_default</span> <span style=\"color: #d0d0d0;\">createdb</span> <span style=\"color: #d0d0d0;\">-U</span> <span style=\"color: #d0d0d0;\">postgres</span> <span style=\"color: #d0d0d0;\">-W</span> <span style=\"color: #d0d0d0;\">django_cms_blog</span>\r\n</pre>\r\n</div>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image4.png\" alt=\"\" width=\"50%\" height=\"50%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Enter password of your PostgreSQL, make migrations and create new super user.</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">docker-compose</span> <span style=\"color: #d0d0d0;\">run</span> <span style=\"color: #d0d0d0;\">web</span> <span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">migrate</span>\r\n<span style=\"color: #d0d0d0;\">docker-compose</span> <span style=\"color: #d0d0d0;\">run</span> <span style=\"color: #d0d0d0;\">web</span> <span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">createsuperuser</span>\r\n</pre>\r\n</div>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image2.png\" alt=\"\" width=\"50%\" height=\"50%\" /></p>\r\n<p><span style=\"font-weight: 400;\">Now rebuild your containers again, open your browser and insert </span><a href=\"http://localhost:8000/admin\"><span style=\"font-weight: 400;\">http://localhost:8000/admin</span></a><span style=\"font-weight: 400;\">. After successful login you will see a welcome page.</span></p>\r\n<p><span style=\"font-weight: 400;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/image5.png\" alt=\"\" width=\"75%\" height=\"75%\" /></span></p>\r\n<p><span style=\"font-weight: 400;\">Now you can create and publish your first page. In the next chapter, we will add a custom HTML template and create the first application.</span></p>",
"category": 3
},
{
"title": "Django based help desk ticketing system",
"slug": "django-based-help-desk-ticketing-system",
"author": 1,
"created": "2023-10-31",
"text": "<p><strong><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-53-56 Dashboard.png\" alt=\"\" width=\"80%\" height=\"80%\" /></strong></p>\r\n<p><strong>WHAT IS “DJANGO-CHANNELS”</strong> <br /><br /></p>\r\n<p>Recently, I had a problem: \"To create a real-time platform for communication between customers and the support team based on Django.\" Luckily, Django has a perfect library, \"Django-channels.\" This library provides us with WebSocket connections between the client-side and server-side.</p>\r\n<p>As I mentioned, Django-channels is an extension to the Django web framework that enables the development of real-time web applications. While Django is excellent for building traditional request-response web applications, it is not inherently suited for handling WebSocket connections or long-polling requests. Django-channels addresses this limitation by providing a framework for handling real-time communication, making it possible to create chat applications, live notifications, collaborative editing tools, and more.</p>\r\n<p>I hope you already know what a traditional request-response web application is: REQUEST FROM CLIENT → SERVER → RESPONSE FROM SERVER → CLIENT → DONE.</p>\r\n<p>When we talk about \"real-time web applications and long-polling requests,\" a \"real-time web application\" is a type of web application that provides different live actions, immediate responses to client actions, live updates to users as events occur, without the need to refresh the page. Common use cases for real-time applications include online chat, online gaming, collaborative tools, financial trading platforms, and more. \"Long-polling\" is one of the techniques used to achieve real-time communication between a web server and a client. It works as follows:</p>\r\n<p>The client initiates a request: a simple HTTP request to the server.<br />The server holds the request. In the traditional Django case, the server responds immediately to the request. But using Django-channels, the server keeps the connection open and waits for new data from the client-side.<br />When there is new data or an event to be delivered to the client, the server responds to the request with the updated information.<br />The client processes the response. The client receives the response and processes the data or event.<br />This process is repeated.<br />Using \"high-level\" documentation slang: \"Channels change Django to weave asynchronous code underneath and through Django's synchronous core, allowing Django projects to handle not only HTTP but also protocols that require long-running connections, such as Web Sockets, MQTT, chatbots, amateur radio, and more. Our project will run under SGI, instead of WSGI. In simple words, we can write asynchronous code, use Django's auth system, session system, and more.</p>\r\n<p><strong>WHAT WAS MY GOAL</strong></p>\r\n<p>So, I needed a new and simple CRM system for my team. I know a lot of different CRM systems that basically cover all my requirements. But there are several things that these CRM systems can't handle:</p>\r\n<p>Cleanliness: All CRM systems that I've encountered were overloaded with different information, graphs, tables, windows with statistics, etc. Therefore, there is a lot of information noise on the screen, which makes it difficult for a new user to understand what information is important and what is secondary.</p>\r\n<p>Easy response to the client: Many CRM systems have integration with email, Telegram, Viber, WhatsApp, and so on. But all of these chats are mostly divided into different tabs or windows. You can make over 15 clicks between tabs before you answer the client, instead of \"open request → type answer → send → done → open the second one → repeat → open the third one → repeat → ….\"</p>\r\n<p>Simplicity: It must be simple and clear to understand how to work with the system, how to handle client requests, and respond to them.</p>\r\n<p>Easy integration into your department: Like \"Plug and Play.\"</p>\r\n<p>Of course, all these statements are valid if your company does not strive for all corporate standards but needs to organize the work of the customer relations department. So do I. Unfortunately, I haven't found such a CRM system, so I've decided to create it myself 😎.</p>\r\n<p><strong> LET’S START </strong></p>\r\n<p>First, I have to create a Django project. It’s a simple and common thing.</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">django-admin</span> <span style=\"color: #d0d0d0;\">startproject</span> <span style=\"color: #d0d0d0;\">ticket_server</span>\r\n</pre>\r\n</div>\r\n<p>Second, I have to create an application for my ticket dashboard. This will create a directory “dashboard”.</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">startapp</span> <span style=\"color: #d0d0d0;\">dashboard</span>\r\n</pre>\r\n</div>\r\n<p>And I got next directory structure:</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">ticket_server/</span>\r\n<span style=\"color: #d0d0d0;\"> manage.py</span>\r\n<span style=\"color: #d0d0d0;\"> ticket_server/</span>\r\n<span style=\"color: #d0d0d0;\"> __init__.py</span>\r\n<span style=\"color: #d0d0d0;\"> settings.py</span>\r\n<span style=\"color: #d0d0d0;\"> urls.py</span>\r\n<span style=\"color: #d0d0d0;\"> asgi.py</span>\r\n<span style=\"color: #d0d0d0;\"> wsgi.py</span>\r\n<span style=\"color: #d0d0d0;\"> dashboard/</span>\r\n<span style=\"color: #d0d0d0;\"> __init__.py</span>\r\n<span style=\"color: #d0d0d0;\"> admin.py</span>\r\n<span style=\"color: #d0d0d0;\"> apps.py</span>\r\n<span style=\"color: #d0d0d0;\"> migrations/</span>\r\n<span style=\"color: #d0d0d0;\"> __init__.py</span>\r\n<span style=\"color: #d0d0d0;\"> models.py</span>\r\n<span style=\"color: #d0d0d0;\"> tests.py</span>\r\n<span style=\"color: #d0d0d0;\"> urls.py</span>\r\n<span style=\"color: #d0d0d0;\"> views.py</span>\r\n</pre>\r\n</div>\r\n<p>Third, configure URLconf and add dashboard application to INSTALLED_APPS in settings.py file. Create a file called urls.py in dashboard directory. Modify urls.py in ticket_server directory.</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>ticket_server/urls.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.conf.urls.static</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">static</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.contrib</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">admin</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.urls</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">path,</span> <span style=\"color: #d0d0d0;\">include</span>\r\n\r\n<span style=\"color: #d0d0d0;\">urlpatterns</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #d0d0d0;\">path(</span><span style=\"color: #ed9d13;\">'admin/'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">admin.site.urls),</span>\r\n <span style=\"color: #d0d0d0;\">path(</span><span style=\"color: #ed9d13;\">''</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">include(</span><span style=\"color: #ed9d13;\">'dashboard.urls'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">namespace=</span><span style=\"color: #ed9d13;\">'dashboard'</span><span style=\"color: #d0d0d0;\">)),</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>ticket_server/settings.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">INSTALLED_APPS</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">(</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.auth\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.contenttypes\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.sessions\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.sites\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n\r\n <span style=\"color: #ed9d13;\">\"dashboard\"</span><span style=\"color: #d0d0d0;\">,\r\n)</span>\r\n</pre>\r\n</div>\r\n<p>Next, I will implement my models in dashboard/models.py.</p>\r\n<ol>\r\n<li>Message – this model will contain every message from client or team member.</li>\r\n<li>Attachments – this model will contain files from clients.</li>\r\n<li>Ticket – this model will contain all messages from clients and team members on the same subject. It’ll be something like a chat room.</li>\r\n</ol>\r\n<p>Messages model:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/models.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Message</span><span style=\"color: #d0d0d0;\">(models.Model):</span>\r\n <span style=\"color: #d0d0d0;\">author</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ForeignKey(Users,</span> <span style=\"color: #d0d0d0;\">on_delete=models.CASCADE)</span>\r\n <span style=\"color: #d0d0d0;\">create</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.DateTimeField(auto_now_add=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">db_comment=</span><span style=\"color: #ed9d13;\">'Used for date'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">ticket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ForeignKey(Ticket,</span> <span style=\"color: #d0d0d0;\">on_delete=models.CASCADE)</span>\r\n <span style=\"color: #d0d0d0;\">text</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.TextField(blank=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">null=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">attachments</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ManyToManyField(Attachments)</span>\r\n</pre>\r\n</div>\r\n<p>Attachments model:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/models.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Attachments</span><span style=\"color: #d0d0d0;\">(models.Model):</span>\r\n <span style=\"color: #24909d;\">file</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.FileField(</span><span style=\"color: #d0d0d0;\">)</span>\r\n</pre>\r\n</div>\r\n<p>Ticket model:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/models.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Ticket</span><span style=\"color: #d0d0d0;\">(models.Model):</span>\r\n <span style=\"color: #d0d0d0;\">CREATED</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #3677a9;\">1</span>\r\n <span style=\"color: #d0d0d0;\">IN_PROGRESS</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #3677a9;\">2</span>\r\n <span style=\"color: #d0d0d0;\">ANSWERED</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #3677a9;\">3</span>\r\n <span style=\"color: #d0d0d0;\">DONE</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #3677a9;\">4</span>\r\n <span style=\"color: #d0d0d0;\">STATUS</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">(</span>\r\n <span style=\"color: #d0d0d0;\">(CREATED,</span> <span style=\"color: #ed9d13;\">'Created'</span><span style=\"color: #d0d0d0;\">),</span>\r\n <span style=\"color: #d0d0d0;\">(IN_PROGRESS,</span> <span style=\"color: #ed9d13;\">'In progress'</span><span style=\"color: #d0d0d0;\">),</span>\r\n <span style=\"color: #d0d0d0;\">(ANSWERED,</span> <span style=\"color: #ed9d13;\">'Answered'</span><span style=\"color: #d0d0d0;\">),</span>\r\n <span style=\"color: #d0d0d0;\">(DONE,</span> <span style=\"color: #ed9d13;\">'Done'</span><span style=\"color: #d0d0d0;\">),</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">subject</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.CharField(max_length=</span><span style=\"color: #3677a9;\">128</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">blank=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">status</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.IntegerField(choices=STATUS,</span> <span style=\"color: #d0d0d0;\">default=</span><span style=\"color: #3677a9;\">1</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">contact</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ForeignKey(Users,</span> <span style=\"color: #d0d0d0;\">on_delete=models.CASCADE,</span> <span style=\"color: #d0d0d0;\">blank=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">related_name=</span><span style=\"color: #ed9d13;\">'ticket_contact'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">author</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.ForeignKey(Users,</span> <span style=\"color: #d0d0d0;\">on_delete=models.PROTECT,</span> <span style=\"color: #d0d0d0;\">blank=</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">related_name=</span><span style=\"color: #ed9d13;\">'ticket_author'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">new_message</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">models.BooleanField(default=</span><span style=\"color: #24909d;\">False</span><span style=\"color: #d0d0d0;\">)</span>\r\n</pre>\r\n</div>\r\n<p>Next I'm going to add Telegram Bot support to my system using the pyTelegramBotAPI lib. I want to wrap polling with an infinite loop and exception handling to avoid the bot from stopping polling. Let’s create a directory in dashboard “managment/commands”.</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">dashboard/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">admin.py</span>\r\n <span style=\"color: #d0d0d0;\">apps.py</span>\r\n <span style=\"color: #d0d0d0;\">migrations/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">managment/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">commands/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">models.py</span>\r\n <span style=\"color: #d0d0d0;\">tests.py</span>\r\n <span style=\"color: #d0d0d0;\">urls.py</span>\r\n <span style=\"color: #d0d0d0;\">views.py</span>\r\n</pre>\r\n</div>\r\n<p>Create a file “telegram_bot_handler.py” in commands directory.</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">managment/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">commands/</span>\r\n <span style=\"color: #d0d0d0;\">__init__.py</span>\r\n <span style=\"color: #d0d0d0;\">telegram_bot_handler.py</span>\r\n</pre>\r\n</div>\r\n<p>telegram_bot_handler.py file:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/managment/commands/telegram_bot_handler.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.core.management.base</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">BaseCommand</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.conf</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">settings</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">telebot</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">TeleBot</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">dashboard.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Ticket,</span> <span style=\"color: #d0d0d0;\">Message,</span> <span style=\"color: #d0d0d0;\">Attachments</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">users.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Users</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">common.tools.channel_tools</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">ChannelTools</span>\r\n\r\n<span style=\"color: #d0d0d0;\">bot</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">TeleBot(settings.TELEBOT_API_KEY,</span> <span style=\"color: #d0d0d0;\">threaded=</span><span style=\"color: #24909d;\">False</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Command</span><span style=\"color: #d0d0d0;\">(BaseCommand):</span>\r\n <span style=\"color: #d0d0d0;\">help</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'Implemented to Django application telegram bot setup command'</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">handle</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">*args,</span> <span style=\"color: #d0d0d0;\">**kwargs):</span>\r\n <span style=\"color: #d0d0d0;\">bot.enable_save_next_step_handlers(delay=</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">bot.load_next_step_handlers()</span>\r\n\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(commands=[</span><span style=\"color: #ed9d13;\">'start'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'help'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">send_welcome</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #d0d0d0;\">bot.reply_to(message,</span> <span style=\"color: #ed9d13;\">\"Howdy, how are you doing?\"</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'photo'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">photo_message</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">pass</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'document'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">file_message</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">pass</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'text'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">message_from_user</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">pass</span>\r\n\r\n <span style=\"color: #d0d0d0;\">bot.infinity_polling()</span>\r\n</pre>\r\n</div>\r\n<p>Now let's check if the telegram bot works fine. I will use the following command:</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">py</span> <span style=\"color: #d0d0d0;\">manage.py</span> <span style=\"color: #d0d0d0;\">telegram_bot_control</span>\r\n</pre>\r\n</div>\r\n<p>After this, the telegram bot will start, and should respond to the \"\\start\" command with \"Howdy, how are you doing?\".</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot_1.jpg\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p>I'm ready to add Django-channels to the project. According to the Django-channels <a href=\"https://channels.readthedocs.io/en/latest/installation.html\">documentation</a>. Channels is available on PyPI - to install it run:</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">python</span> <span style=\"color: #d0d0d0;\">-m</span> <span style=\"color: #d0d0d0;\">pip</span> <span style=\"color: #d0d0d0;\">install</span> <span style=\"color: #d0d0d0;\">-U</span> <span style=\"color: #ed9d13;\">'channels[daphne]'</span>\r\n</pre>\r\n</div>\r\n<p>This will install Channels together with the Daphne ASGI application server. If you wish to use a different application server you can `pip install channels`, without the optional `daphne` add-on.</p>\r\n<p>Once that’s done, I'll add <span style=\"color: #ed9d13;\">\"daphne\"</span> to the beginning of the INSTALLED_APPS setting:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>ticket_server/settings.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">INSTALLED_APPS</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">(</span>\r\n <span style=\"color: #ed9d13;\">\"daphne\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.auth\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.contenttypes\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.sessions\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"django.contrib.sites\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n<span style=\"color: #d0d0d0;\">)</span>\r\n</pre>\r\n</div>\r\n<p>This will install the Daphne’s ASGI version of the '<em>runserver</em>' management command.</p>\r\n<p><span style=\"font-weight: 400;\">Set the ASGI_APPLICATION setting in ticket_server/settings.py to point to that routing object as the root application:</span></p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>ticket_server/settings.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">ASGI_APPLICATION</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">\"myproject.asgi.application\"</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">The second important thing I have to do is add the routing.py file to the dashboard application directory. In this file I will do routing configuration for the Django-channels. Routing configuration is similar to the urls.py, it tells Django-channels what code to run when an HTTP request is received.</span></p>\r\n<p><span style=\"font-weight: 400;\">Create the file dashboard/routing.py with following code:</span></p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/routing.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.urls</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">re_path</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">.</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">consumers</span>\r\n\r\n\r\n<span style=\"color: #d0d0d0;\">websocket_urlpatterns</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #d0d0d0;\">re_path(</span><span style=\"color: #ed9d13;\">r\"ws/$\"</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">consumers.TicketConsumers.as_asgi()),</span>\r\n <span style=\"color: #d0d0d0;\">re_path(</span><span style=\"color: #ed9d13;\">r\"ws/chat/(?P<chat_name>\\w+)/$\"</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">consumers.ChatConsumer.as_asgi()),</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">I haven’t create the consumers.py file yet. Let's leave it as it is for now. I </span><span style=\"font-weight: 400;\">call the </span><span style=\"font-weight: 400;\">as_asgi()</span><span style=\"font-weight: 400;\"> classmethod in order to get an ASGI application that will instantiate an instance of our consumer for each user-connection. This is similar to Django’s </span><span style=\"font-weight: 400;\">as_view()</span><span style=\"font-weight: 400;\">, which plays the same role for per-request Django view instances.</span></p>\r\n<p><span style=\"font-weight: 400;\">The next step is to point the main ASGI configuration at the </span><strong>chat.routing</strong><span style=\"font-weight: 400;\"> module. In </span><span style=\"font-weight: 400;\">mysite/asgi.py</span><span style=\"font-weight: 400;\">, import </span><span style=\"font-weight: 400;\">AuthMiddlewareStack</span><span style=\"font-weight: 400;\">, </span><span style=\"font-weight: 400;\">URLRouter</span><span style=\"font-weight: 400;\">, and </span><span style=\"font-weight: 400;\">chat.routing</span><span style=\"font-weight: 400;\">; and insert a </span><span style=\"font-weight: 400;\">'websocket'</span><span style=\"font-weight: 400;\"> key in the </span><span style=\"font-weight: 400;\">ProtocolTypeRouter</span><span style=\"font-weight: 400;\"> list in the following format:</span></p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #447fcf; text-decoration: underline;\">os</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.auth</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">AuthMiddlewareStack</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.routing</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">ProtocolTypeRouter,</span> <span style=\"color: #d0d0d0;\">URLRouter</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.security.websocket</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">AllowedHostsOriginValidator</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.core.asgi</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">get_asgi_application</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #447fcf; text-decoration: underline;\">dashboard.routing</span> <span style=\"color: #6ab825; font-weight: bold;\">as</span> <span style=\"color: #447fcf; text-decoration: underline;\">dr</span>\r\n\r\n<span style=\"color: #d0d0d0;\">os.environ.setdefault(</span><span style=\"color: #ed9d13;\">'DJANGO_SETTINGS_MODULE'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'ticket_server.settings'</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n<span style=\"color: #d0d0d0;\">django_asgi_app</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_asgi_application()</span>\r\n\r\n<span style=\"color: #d0d0d0;\">application</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">ProtocolTypeRouter({</span>\r\n <span style=\"color: #ed9d13;\">'http'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">django_asgi_app,</span>\r\n <span style=\"color: #ed9d13;\">'websocket'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">AllowedHostsOriginValidator(</span>\r\n <span style=\"color: #d0d0d0;\">AuthMiddlewareStack(</span>\r\n <span style=\"color: #d0d0d0;\">URLRouter(</span>\r\n <span style=\"color: #d0d0d0;\">dr.websocket_urlpatterns</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">),</span>\r\n<span style=\"color: #d0d0d0;\">})</span>\r\n</pre>\r\n</div>\r\n<p><span style=\"font-weight: 400;\">Let's get to consumers. What are consumers? Let's look at a typical Django project. As I mentioned earlier, </span> when Django accepts an HTTP request from a client, it looks through URLconf to find a view function and then calls that view function to handle the request and return a response to the client. Django-channels follows a similar pattern, but uses consumers instead of views. When Django-channels accepts a WebSocket connection from a client, it looks through the routing conf to find a consumer and then calls various functions on the consumer to handle any eventa from the connection.</p>\r\n<p><span style=\"font-weight: 400;\">I will create a new file dashboard/consumers.py, where add two consumers:</span></p>\r\n<ul>\r\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">TicketConsumer - to handle events from the main page of the dashboard</span></li>\r\n<li style=\"font-weight: 400;\" aria-level=\"1\">ChatConsumer - to handle events from chats</li>\r\n</ul>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/consumers.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #447fcf; text-decoration: underline;\">json</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">asgiref.sync</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">sync_to_async</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.db</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">database_sync_to_async</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.generic.websocket</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">AsyncWebsocketConsumer</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">telebot</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">TeleBot</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Ticket,</span> <span style=\"color: #d0d0d0;\">Message</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">users.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Users</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.conf</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">settings</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.core.mail</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">send_mail</span>\r\n\r\n<span style=\"color: #d0d0d0;\">bot</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">TeleBot(settings.TELEBOT_API_KEY,</span> <span style=\"color: #d0d0d0;\">threaded=</span><span style=\"color: #24909d;\">False</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">ChatConsumer</span><span style=\"color: #d0d0d0;\">(AsyncWebsocketConsumer):</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">connect</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">):</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.scope[</span><span style=\"color: #ed9d13;\">'url_route'</span><span style=\"color: #d0d0d0;\">][</span><span style=\"color: #ed9d13;\">'kwargs'</span><span style=\"color: #d0d0d0;\">][</span><span style=\"color: #ed9d13;\">'chat_name'</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_group_name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'chat_%s'</span> <span style=\"color: #d0d0d0;\">%</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_name</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.user</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.scope[</span><span style=\"color: #ed9d13;\">'user'</span><span style=\"color: #d0d0d0;\">]</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Join room group</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_add(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_group_name,</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_name</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.accept()</span>\r\n\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">disconnect</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">close_code):</span>\r\n <span style=\"color: #999999; font-style: italic;\"># Leave room group</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_discard(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_group_name,</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_name</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Receive message from WebSocket</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">receive</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">text_data):</span>\r\n <span style=\"color: #d0d0d0;\">text_data_json</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">json.loads(text_data)</span>\r\n <span style=\"color: #d0d0d0;\">message</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">text_data_json[</span><span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">tag_from_admin</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">text_data_json[</span><span style=\"color: #ed9d13;\">'tag_from_admin'</span><span style=\"color: #d0d0d0;\">]</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Save message to db</span>\r\n <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">msg,</span> <span style=\"color: #d0d0d0;\">contact</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.save_message_to_db(user=</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.user,</span> <span style=\"color: #d0d0d0;\">text=message,</span>\r\n <span style=\"color: #d0d0d0;\">chat_id=</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.scope[</span><span style=\"color: #ed9d13;\">'url_route'</span><span style=\"color: #d0d0d0;\">][</span><span style=\"color: #ed9d13;\">'kwargs'</span><span style=\"color: #d0d0d0;\">][</span><span style=\"color: #ed9d13;\">'chat_name'</span><span style=\"color: #d0d0d0;\">],</span> <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to contact</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.send_to_contact(ticket,</span> <span style=\"color: #d0d0d0;\">msg,</span> <span style=\"color: #d0d0d0;\">contact)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to room group</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_send(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.room_group_name,</span>\r\n <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">'type'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #ed9d13;\">'chat_message'</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Receive message from room group</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">chat_message</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">event):</span>\r\n <span style=\"color: #d0d0d0;\">message</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">event[</span><span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to WebSocket</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.send(</span>\r\n <span style=\"color: #d0d0d0;\">text_data=json.dumps(</span>\r\n <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@database_sync_to_async</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">save_message_to_db</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">user,</span> <span style=\"color: #d0d0d0;\">text,</span> <span style=\"color: #d0d0d0;\">chat_id):</span>\r\n <span style=\"color: #999999; font-style: italic;\"># some logic for saving to the database</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">msg,</span> <span style=\"color: #d0d0d0;\">contact</span>\r\n\r\n <span style=\"color: #ffa500;\">@sync_to_async</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">send_to_contact</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">msg,</span> <span style=\"color: #d0d0d0;\">contact):</span>\r\n <span style=\"color: #999999; font-style: italic;\"># some logic for sending messages to the client</span>\r\n</pre>\r\n</div>\r\n<p>Ticket consumer:</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/consumers.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">TicketConsumers</span><span style=\"color: #d0d0d0;\">(AsyncWebsocketConsumer):</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">connect</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">):</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'dashboard'</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'group_{}'</span><span style=\"color: #d0d0d0;\">.format(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_add(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group,</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_name</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.accept()</span>\r\n\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">disconnect</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">close_code):</span>\r\n <span style=\"color: #999999; font-style: italic;\"># Leave room group</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_discard(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group,</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_name)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Receive message from WebSocket</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">receive</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">text_data):</span>\r\n <span style=\"color: #d0d0d0;\">text_data_json</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">json.loads(text_data)</span>\r\n <span style=\"color: #d0d0d0;\">subject</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">text_data_json[</span><span style=\"color: #ed9d13;\">\"subject\"</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">contact_username</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">text_data_json[</span><span style=\"color: #ed9d13;\">\"contact_username\"</span><span style=\"color: #d0d0d0;\">]</span>\r\n\r\n <span style=\"color: #d0d0d0;\">ticket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.save_ticket_to_db(</span>\r\n <span style=\"color: #d0d0d0;\">user=</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.scope[</span><span style=\"color: #ed9d13;\">'user'</span><span style=\"color: #d0d0d0;\">].username,</span>\r\n <span style=\"color: #d0d0d0;\">contact_username=contact_username,</span>\r\n <span style=\"color: #d0d0d0;\">subject=subject</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to room group</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_send(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group,</span>\r\n <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">\"type\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #ed9d13;\">\"dashboard.message\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"subject\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">ticket.subject,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Receive message from room group</span>\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">dashboard_message</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">event):</span>\r\n <span style=\"color: #d0d0d0;\">subject</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">event[</span><span style=\"color: #ed9d13;\">\"subject\"</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to WebSocket</span>\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.send(</span>\r\n <span style=\"color: #d0d0d0;\">text_data=json.dumps(</span>\r\n <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">\"subject\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">subject,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">async</span> <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">update_dashboard</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">event):</span>\r\n <span style=\"color: #d0d0d0;\">message_id</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">event[</span><span style=\"color: #ed9d13;\">\"message_id\"</span><span style=\"color: #d0d0d0;\">]</span>\r\n\r\n <span style=\"color: #d0d0d0;\">await</span> <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.send(</span>\r\n <span style=\"color: #d0d0d0;\">text_data=json.dumps(</span>\r\n <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">\"message_id\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message_id,</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@database_sync_to_async</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">save_ticket_to_db</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">user,</span> <span style=\"color: #d0d0d0;\">contact_username,</span> <span style=\"color: #d0d0d0;\">subject):</span>\r\n <span style=\"color: #999999; font-style: italic;\"># some logic for saving to the database</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">ticket</span>\r\n</pre>\r\n</div>\r\n<p>We need to return to the telegram bot file - <strong><em>telegram_bot_control.py. </em></strong>I will add some mixins with consumer logic to make it more complex and easier to use in several places in my code. Let's create a file common/tools/channel_tools.py.</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>ticket_server/common/tools/channel_tools.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #447fcf; text-decoration: underline;\">channels.layers</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">asgiref.sync</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">async_to_sync</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">ChannelTools</span><span style=\"color: #d0d0d0;\">:</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">__init__</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">group_name,</span> <span style=\"color: #d0d0d0;\">dashboard_group):</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.group_name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">group_name</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">dashboard_group</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">channels.layers.get_channel_layer()</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">send_to_group</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">message,</span> <span style=\"color: #d0d0d0;\">contact,</span> <span style=\"color: #d0d0d0;\">tag_from_admin,</span> <span style=\"color: #d0d0d0;\">file_list):</span>\r\n <span style=\"color: #d0d0d0;\">async_to_sync(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_send)(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.group_name,</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">'type'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #ed9d13;\">'chat_message'</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message.text,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">message.ticket.change_ticket_status(</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">message.ticket.set_new_message(</span><span style=\"color: #24909d;\">True</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">async_to_sync(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_send)(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group,</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">\"type\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #ed9d13;\">\"update.dashboard\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"message_id\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message.ticket.id,</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">update_ticket_list</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">contact):</span>\r\n <span style=\"color: #d0d0d0;\">async_to_sync(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.channel_layer.group_send)(</span>\r\n <span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">.dashboard_group,</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #ed9d13;\">\"type\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #ed9d13;\">\"dashboard.message\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #ed9d13;\">\"subject\"</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">ticket.subject,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span></pre>\r\n</div>\r\n<p>I'll edit<em> </em>telegram_bot_control.py to include logic for working with ChatConsumer and TicketConsumer. I want Telegram handler to send events to Websocket, allowing the admin to receive new chat messages and new tickets.</p>\r\n<div style=\"background: solid gray; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .2em;\"><span style=\"color: #adb5bd;\"><strong><em>dashboard/management/commands/telegram_bot_control.py</em></strong></span></div>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.core.management.base</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">BaseCommand</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">django.conf</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">settings</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">telebot</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">TeleBot</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">dashboard.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Ticket,</span> <span style=\"color: #d0d0d0;\">Message,</span> <span style=\"color: #d0d0d0;\">Attachments</span>\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">users.models</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">Users</span>\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">from</span> <span style=\"color: #447fcf; text-decoration: underline;\">common.tools.channel_tools</span> <span style=\"color: #6ab825; font-weight: bold;\">import</span> <span style=\"color: #d0d0d0;\">ChannelTools</span>\r\n\r\n<span style=\"color: #d0d0d0;\">bot</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">TeleBot(settings.TELEBOT_API_KEY,</span> <span style=\"color: #d0d0d0;\">threaded=</span><span style=\"color: #24909d;\">False</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n\r\n<span style=\"color: #6ab825; font-weight: bold;\">class</span> <span style=\"color: #447fcf; text-decoration: underline;\">Command</span><span style=\"color: #d0d0d0;\">(BaseCommand):</span>\r\n <span style=\"color: #d0d0d0;\">help</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">'Implemented to Django application telegram bot setup command'</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">handle</span><span style=\"color: #d0d0d0;\">(</span><span style=\"color: #24909d;\">self</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">*args,</span> <span style=\"color: #d0d0d0;\">**kwargs):</span>\r\n <span style=\"color: #d0d0d0;\">bot.enable_save_next_step_handlers(delay=</span><span style=\"color: #3677a9;\">2</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">bot.load_next_step_handlers()</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(commands=[</span><span style=\"color: #ed9d13;\">'start'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'help'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">send_welcome</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #d0d0d0;\">bot.reply_to(message,</span> <span style=\"color: #ed9d13;\">\"Howdy, how are you doing?\"</span><span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'photo'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">photo_message</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #d0d0d0;\">username</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">f</span><span style=\"color: #ed9d13;\">'{message.from_user.first_name} [{message.from_user.username}]'</span>\r\n <span style=\"color: #d0d0d0;\">chat_id</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.chat.id</span>\r\n <span style=\"color: #d0d0d0;\">text</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.caption</span>\r\n\r\n <span style=\"color: #d0d0d0;\">contact</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_contact(username,</span> <span style=\"color: #d0d0d0;\">chat_id)</span>\r\n <span style=\"color: #d0d0d0;\">ticket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_ticket(contact)</span>\r\n <span style=\"color: #d0d0d0;\">msg</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_msg(contact,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">text)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">img_list</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">save_attachments(message,</span> <span style=\"color: #d0d0d0;\">msg)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">channels_tools</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">ChannelTools(</span>\r\n <span style=\"color: #d0d0d0;\">group_name=f</span><span style=\"color: #ed9d13;\">\"chat_{ticket[0].id}\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">dashboard_group=</span><span style=\"color: #ed9d13;\">'group_dashboard'</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to chat_group</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.send_to_group(</span>\r\n <span style=\"color: #d0d0d0;\">message=msg,</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">tag_from_admin=</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">file_list=img_list</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Update ticket list if new ticket</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">if</span> <span style=\"color: #d0d0d0;\">ticket[</span><span style=\"color: #3677a9;\">1</span><span style=\"color: #d0d0d0;\">]:</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.update_ticket_list(</span>\r\n <span style=\"color: #d0d0d0;\">ticket=ticket[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'document'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">file_message</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #d0d0d0;\">username</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">f</span><span style=\"color: #ed9d13;\">'{message.from_user.first_name} [{message.from_user.username}]'</span>\r\n <span style=\"color: #d0d0d0;\">chat_id</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.chat.id</span>\r\n <span style=\"color: #d0d0d0;\">text</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.caption</span>\r\n\r\n <span style=\"color: #d0d0d0;\">contact</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_contact(username,</span> <span style=\"color: #d0d0d0;\">chat_id)</span>\r\n <span style=\"color: #d0d0d0;\">ticket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_ticket(contact)</span>\r\n <span style=\"color: #d0d0d0;\">msg</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_msg(contact,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">text)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">file_list</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">save_attachments(message,</span> <span style=\"color: #d0d0d0;\">msg)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">channels_tools</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">ChannelTools(</span>\r\n <span style=\"color: #d0d0d0;\">group_name=f</span><span style=\"color: #ed9d13;\">\"chat_{ticket[0].id}\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">dashboard_group=</span><span style=\"color: #ed9d13;\">'group_dashboard'</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to chat_group</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.send_to_group(</span>\r\n <span style=\"color: #d0d0d0;\">message=msg,</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">tag_from_admin=</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">file_list=file_list</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Update ticket list if new ticket</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">if</span> <span style=\"color: #d0d0d0;\">ticket[</span><span style=\"color: #3677a9;\">1</span><span style=\"color: #d0d0d0;\">]:</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.update_ticket_list(</span>\r\n <span style=\"color: #d0d0d0;\">ticket=ticket[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #ffa500;\">@bot.message_handler</span><span style=\"color: #d0d0d0;\">(content_types=[</span><span style=\"color: #ed9d13;\">'text'</span><span style=\"color: #d0d0d0;\">])</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">message_from_user</span><span style=\"color: #d0d0d0;\">(message):</span>\r\n <span style=\"color: #d0d0d0;\">username</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">f</span><span style=\"color: #ed9d13;\">'{message.from_user.first_name} [{message.from_user.username}]'</span>\r\n <span style=\"color: #d0d0d0;\">chat_id</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.chat.id</span>\r\n <span style=\"color: #d0d0d0;\">text</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.text</span>\r\n\r\n <span style=\"color: #d0d0d0;\">contact</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_contact(username,</span> <span style=\"color: #d0d0d0;\">chat_id)</span>\r\n <span style=\"color: #d0d0d0;\">ticket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_ticket(contact)</span>\r\n <span style=\"color: #d0d0d0;\">msg</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">get_msg(contact,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">text)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">channels_tools</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">ChannelTools(</span>\r\n <span style=\"color: #d0d0d0;\">group_name=f</span><span style=\"color: #ed9d13;\">\"chat_{ticket[0].id}\"</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">dashboard_group=</span><span style=\"color: #ed9d13;\">'group_dashboard'</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Send message to chat_group</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.send_to_group(</span>\r\n <span style=\"color: #d0d0d0;\">message=msg,</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">tag_from_admin=</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">,</span>\r\n <span style=\"color: #d0d0d0;\">file_list=</span><span style=\"color: #24909d;\">None</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\"># Update ticket list if new ticket</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">if</span> <span style=\"color: #d0d0d0;\">ticket[</span><span style=\"color: #3677a9;\">1</span><span style=\"color: #d0d0d0;\">]:</span>\r\n <span style=\"color: #d0d0d0;\">channels_tools.update_ticket_list(</span>\r\n <span style=\"color: #d0d0d0;\">ticket=ticket[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">save_attachments</span><span style=\"color: #d0d0d0;\">(message,</span> <span style=\"color: #d0d0d0;\">msg):</span>\r\n <span style=\"color: #d0d0d0;\">msg_att</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">dict</span><span style=\"color: #d0d0d0;\">()</span>\r\n <span style=\"color: #d0d0d0;\">file_type</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.content_type</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">if</span> <span style=\"color: #d0d0d0;\">file_type</span> <span style=\"color: #d0d0d0;\">==</span> <span style=\"color: #ed9d13;\">'photo'</span><span style=\"color: #d0d0d0;\">:</span>\r\n <span style=\"color: #24909d;\">file</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.photo[-</span><span style=\"color: #3677a9;\">1</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">file_name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">.file_id</span> <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #ed9d13;\">'.jpg'</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">else</span><span style=\"color: #d0d0d0;\">:</span>\r\n <span style=\"color: #24909d;\">file</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">message.document</span>\r\n <span style=\"color: #d0d0d0;\">file_name</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">.file_name</span>\r\n <span style=\"color: #d0d0d0;\">file_id</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">bot.get_file(</span><span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">.file_id)</span>\r\n <span style=\"color: #d0d0d0;\">saved_file</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">bot.download_file(file_id.file_path)</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">with</span> <span style=\"color: #24909d;\">open</span><span style=\"color: #d0d0d0;\">(f</span><span style=\"color: #ed9d13;\">'media\\\\{file_type}\\\\{file_name}'</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #ed9d13;\">'wb'</span><span style=\"color: #d0d0d0;\">)</span> <span style=\"color: #6ab825; font-weight: bold;\">as</span> <span style=\"color: #d0d0d0;\">f:</span>\r\n <span style=\"color: #d0d0d0;\">f.write(saved_file)</span>\r\n <span style=\"color: #d0d0d0;\">att</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">Attachments.objects.create(</span><span style=\"color: #24909d;\">file</span><span style=\"color: #d0d0d0;\">=f</span><span style=\"color: #ed9d13;\">'{file_type}\\\\{file_name}'</span><span style=\"color: #d0d0d0;\">)</span>\r\n <span style=\"color: #d0d0d0;\">msg.attachments.add(att)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">msg_att.update({</span><span style=\"color: #ed9d13;\">'url'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">att.file.url,</span> <span style=\"color: #ed9d13;\">'file_name'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">att.get_file_name(),</span> <span style=\"color: #ed9d13;\">'file_type'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">att.get_file_type()})</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">msg_att</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">get_contact</span><span style=\"color: #d0d0d0;\">(username,</span> <span style=\"color: #d0d0d0;\">contact_id):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">Users.objects.get_or_create(</span>\r\n <span style=\"color: #d0d0d0;\">username=username,</span>\r\n <span style=\"color: #d0d0d0;\">contact_id=contact_id,</span>\r\n <span style=\"color: #d0d0d0;\">contact_type=</span><span style=\"color: #3677a9;\">1</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">get_ticket</span><span style=\"color: #d0d0d0;\">(contact):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">Ticket.objects.get_or_create(</span>\r\n <span style=\"color: #d0d0d0;\">subject=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">].username,</span>\r\n <span style=\"color: #d0d0d0;\">author=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">contact=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">]</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #6ab825; font-weight: bold;\">def</span> <span style=\"color: #447fcf;\">get_msg</span><span style=\"color: #d0d0d0;\">(contact,</span> <span style=\"color: #d0d0d0;\">ticket,</span> <span style=\"color: #d0d0d0;\">text):</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">return</span> <span style=\"color: #d0d0d0;\">Message.objects.create(</span>\r\n <span style=\"color: #d0d0d0;\">author=contact[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">ticket=ticket[</span><span style=\"color: #3677a9;\">0</span><span style=\"color: #d0d0d0;\">],</span>\r\n <span style=\"color: #d0d0d0;\">text=text,</span>\r\n <span style=\"color: #d0d0d0;\">)</span>\r\n\r\n <span style=\"color: #d0d0d0;\">bot.infinity_polling()</span>\r\n</pre>\r\n</div>\r\n<p>The last thing what I will do is to create HTML templates for chat page and ticket page. We need to add next JavaScript script to the templates:</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #6ab825; font-weight: bold;\"><script></span>\r\n <span style=\"color: #ed9d13;\">'use strict'</span><span style=\"color: #d0d0d0;\">;</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">let</span> <span style=\"color: #d0d0d0;\">permission</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">Notification.permission;</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">roomName</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">JSON.parse(</span><span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.getElementById(</span><span style=\"color: #ed9d13;\">'room-name'</span><span style=\"color: #d0d0d0;\">).textContent);</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\">{#Use a chatSocket in a chat html template#}</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">chatSocket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">new</span> <span style=\"color: #d0d0d0;\">WebSocket(</span>\r\n <span style=\"color: #ed9d13;\">'ws://'</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #24909d;\">window</span><span style=\"color: #d0d0d0;\">.location.host</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #ed9d13;\">'/ws/chat/'</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #d0d0d0;\">roomName</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #ed9d13;\">'/'</span>\r\n <span style=\"color: #d0d0d0;\">);</span>\r\n\r\n <span style=\"color: #999999; font-style: italic;\">{#Use a dashboardSocket in a ticket html template#}</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">dashboardSocket</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">new</span> <span style=\"color: #d0d0d0;\">WebSocket(</span>\r\n <span style=\"color: #ed9d13;\">'ws://'</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #24909d;\">window</span><span style=\"color: #d0d0d0;\">.location.host</span>\r\n <span style=\"color: #d0d0d0;\">+</span> <span style=\"color: #ed9d13;\">'/ws/'</span>\r\n <span style=\"color: #d0d0d0;\">);</span> \r\n\r\n <span style=\"color: #d0d0d0;\">chatSocket.onmessage</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">function</span><span style=\"color: #d0d0d0;\">(e)</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">data</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">JSON.parse(e.data);</span>\r\n <span style=\"color: #999999; font-style: italic;\">{#Some logic for page context updating#}</span>\r\n <span style=\"color: #d0d0d0;\">};</span>\r\n\r\n <span style=\"color: #d0d0d0;\">chatSocket.onclose</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">function</span><span style=\"color: #d0d0d0;\">(e)</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #d0d0d0;\">console.error(</span><span style=\"color: #ed9d13;\">'Chat socket closed unexpectedly'</span><span style=\"color: #d0d0d0;\">);</span>\r\n <span style=\"color: #d0d0d0;\">};</span>\r\n\r\n <span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.querySelector(</span><span style=\"color: #ed9d13;\">'#chat-message-input'</span><span style=\"color: #d0d0d0;\">).focus();</span>\r\n <span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.querySelector(</span><span style=\"color: #ed9d13;\">'#chat-message-input'</span><span style=\"color: #d0d0d0;\">).onkeyup</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">function</span><span style=\"color: #d0d0d0;\">(e)</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">if</span> <span style=\"color: #d0d0d0;\">(e.key</span> <span style=\"color: #d0d0d0;\">===</span> <span style=\"color: #ed9d13;\">'Enter'</span><span style=\"color: #d0d0d0;\">)</span> <span style=\"color: #d0d0d0;\">{</span> <span style=\"color: #999999; font-style: italic;\">// enter, return</span>\r\n <span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.querySelector(</span><span style=\"color: #ed9d13;\">'#chat-message-submit'</span><span style=\"color: #d0d0d0;\">).click();</span>\r\n <span style=\"color: #d0d0d0;\">}</span>\r\n <span style=\"color: #d0d0d0;\">};</span>\r\n\r\n <span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.querySelector(</span><span style=\"color: #ed9d13;\">'#chat-message-submit'</span><span style=\"color: #d0d0d0;\">).onclick</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #6ab825; font-weight: bold;\">function</span><span style=\"color: #d0d0d0;\">(e)</span> <span style=\"color: #d0d0d0;\">{</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">messageInputDom</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #24909d;\">document</span><span style=\"color: #d0d0d0;\">.querySelector(</span><span style=\"color: #ed9d13;\">'#chat-message-input'</span><span style=\"color: #d0d0d0;\">);</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\">const</span> <span style=\"color: #d0d0d0;\">message</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">messageInputDom.value;</span>\r\n <span style=\"color: #d0d0d0;\">chatSocket.send(JSON.stringify({</span>\r\n <span style=\"color: #ed9d13;\">'message'</span><span style=\"color: #d0d0d0;\">:</span> <span style=\"color: #d0d0d0;\">message,</span>\r\n <span style=\"color: #d0d0d0;\">...</span>\r\n <span style=\"color: #d0d0d0;\">}));</span>\r\n <span style=\"color: #d0d0d0;\">messageInputDom.value</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #ed9d13;\">''</span><span style=\"color: #d0d0d0;\">;</span>\r\n <span style=\"color: #d0d0d0;\">};</span>\r\n <span style=\"color: #6ab825; font-weight: bold;\"></script></span>\r\n</pre>\r\n</div>\r\n<p>The chatSocket and dashboardSocket variables are used to declare the WebSocket url that the client wants to connect to. We have these URLs defined in the routing.py file</p>\r\n<div style=\"background: #202020; overflow: auto; width: auto; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\r\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #d0d0d0;\">websocket_urlpatterns</span> <span style=\"color: #d0d0d0;\">=</span> <span style=\"color: #d0d0d0;\">[</span>\r\n <span style=\"color: #d0d0d0;\">re_path(</span><span style=\"color: #ed9d13;\">r\"ws/$\"</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">consumers.TicketConsumers.as_asgi()),</span> <span style=\"color: #999999; font-style: italic;\"># 'ws://' + window.location.host + '/ws/'</span>\r\n <span style=\"color: #d0d0d0;\">re_path(</span><span style=\"color: #ed9d13;\">r\"ws/chat/(?P<chat_name>\\w+)/$\"</span><span style=\"color: #d0d0d0;\">,</span> <span style=\"color: #d0d0d0;\">consumers.ChatConsumer.as_asgi()),</span> <span style=\"color: #999999; font-style: italic;\"># 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'</span>\r\n<span style=\"color: #d0d0d0;\">]</span>\r\n</pre>\r\n</div>\r\n<p>Now the ticket system for the support team is ready. Run the server and execute telegtam_bot_control.py and let's chek what we've got:</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-53-56 Dashboard.png\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p>Woah! I received a message from my client. And it is highlighted in yellow, wich indicates that I have unchecked messages.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-54-51 Dashboard.png\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-55-00 Dashboard.png\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p>Well, let's help him.</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-57-18 Dashboard.png\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p>I have checked the file and helped him in a minute. And now I can mark this ticket as \"Done\".</p>\r\n<p><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https://blashchuk.com/media/blog_img/Screenshot 2023-11-06 at 21-57-34 Dashboard.png\" alt=\"\" width=\"90%\" height=\"90%\" /></p>\r\n<p>If you have any questions or suggestions, feel free to contact me at blashchuk.alexander@gmail.com.</p>",
"category": 3
}
]
}