Getting started with Django | Create your first Telegram Bot with Django and Telegram Bot API | A Step-by-Step Guide

 


Getting started with Django | Create your first Telegram Bot with Django and Telegram Bot API | A Step-by-Step Guide

Photo by Windows on Unsplash

Introduction

Django is a powerful web framework for Python that allows developers to build and deploy web applications quickly. It provides a lot of built-in features and is highly customizable, making it a popular choice for web development.

Telegram is a messaging platform that allows users to send and receive messages, photos, videos, and other media. It also has a powerful API that allows developers to create bots that can interact with users and perform various tasks.

In this tutorial, we will show you how to get started with Django and create your first Telegram bot using the Django web framework and the Telegram Bot API. We will provide a step-by-step guide on how to set up your development environment, create a Django project, and build a basic Telegram bot that can send and receive messages. By the end of this tutorial, you will have a solid foundation for building more advanced Telegram bots using Django.


Setting up a directory

Firstly, let’s setup a directory.

mkdir attendanceTaker
cd attendanceTaker

Setting up a new environment

Before we do anything else we’ll create a new virtual environment, using venv. This will make sure our package configuration is kept nicely isolated from any other projects we’re working on.

python3 -m venv env
source env/bin/activate

Now that we’re inside a virtual environment, we can install our package requirements.

pip install django
pip install djangorestframework

Getting started

Okay, we’re ready to get coding. To get started, let’s create a new project to work with.

django-admin startproject config 
cd config

Once that’s done we can create an app that we’ll use to create a simple Web API.

python manage.py startapp app

We’ll need to add our new app app and the rest_framework app to INSTALLED_APPS. Let's edit the config/settings.py file:

INSTALLED_APPS = [
...
'rest_framework',
'app',
]

Okay, we’re ready to roll.

Creating a model to work with

Go to app/models.py file and add following lines of code:

from django.db import models


class Person(models.Model):
tg_id = models.CharField(max_length=254)
tg_username = models.CharField(max_length=254)
tg_fullname = models.CharField(max_length=254)
arrived_at = models.DateTimeField(blank=True, default=None, null=True)
left_at = models.DateTimeField(blank=True, default=None, null=True)

def __str__(self):
return self.tg_id

We’ll also need to create an initial migration for our app model, and sync the database for the first time.

python manage.py makemigrations app
python manage.py migrate app

Creating a Serializing class

In app folder create new file serializers.py

from rest_framework import serializers
from .models import Person


class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'

Telegram Bot

Go back to config folder using command:

cd ..

Now create bot.py file.

We will use it for writing our Telegram Bot.

But!

In order to, work with Django Rest Framework properly we need to add in the beginning of bot.py add following:

import sys
import time

sys.dont_write_bytecode = True

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

import django
django.setup()

from app import models, serializers

from asgiref.sync import sync_to_async

Now import all necessary libraries

import logging
from datetime import datetime
import pytz
from config import settings
import pandas

from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Update)

from telegram.ext import (
Application,
CallbackQueryHandler,
CommandHandler,
ContextTypes,
ConversationHandler,
)

Just add following: ๐Ÿ˜‚

# Enable logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)

# States
START_STATE, END_STATE = range(2)

# Callback data
PLUS, MINUS = range(2)

# File path
FILE_PATH = 'file/report.xlsx'

It is the main entry point, of our Python Telegram Bot. ๐Ÿค–

def main():
"""Run the bot."""
application = Application.builder().token(settings.TELEGRAM_BOT_TOKEN).build()

conv_handler = ConversationHandler(
entry_points=[
CommandHandler("start", start),
CommandHandler('report', report)],
states={
START_STATE: [
CallbackQueryHandler(plus, pattern="^" + str(PLUS) + "$"),
CallbackQueryHandler(minus, pattern="^" + str(MINUS) + "$"),
],
END_STATE: [
CallbackQueryHandler(end),
],
},
fallbacks=[CommandHandler("start", start)],
)

application.add_handler(conv_handler)

application.run_polling()

Do not forget to add Bot Token. ๐Ÿ—️

Go to config/settings.py and add:

TELEGRAM_BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"

Call main function, to get started. ๐ŸŽฌ

if __name__ == "__main__":
main()

Handling /start command.

Whenever student sends /start to the bot.

Calls following function.

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Send message on `/start`."""

# Get user that sent /start and log his name
user = update.effective_user
logger.info("User %s started the conversation.", user.username)

keyboard = [
[
InlineKeyboardButton("+", callback_data=str(PLUS)),
InlineKeyboardButton("-", callback_data=str(MINUS)),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)

await update.message.reply_text("Choose an option", reply_markup=reply_markup,)

return START_STATE

Next function will be handled, if student sends ➕.

It means that a student came to class.

async def plus(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Show confirm button"""

user = update.effective_user
query = update.callback_query
await query.answer(text='Saved')

message = await query.edit_message_text(text=".")
await context.bot.delete_message(message.chat.id, message.message_id)

await post_person(user)

return END_STATE

Next function will be handled, if student sends➖.

It means that a student left the class. ๐Ÿƒ‍♂️

It works only, if student already came to class. ๐Ÿง‘‍๐ŸŽ“

async def minus(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Show confirm button"""

user = update.effective_user
active_user_id = await get_last_id(user)

if active_user_id:
query = update.callback_query
await query.answer(text="Saved")

message = await query.edit_message_text(text=".")
await context.bot.delete_message(message.chat.id, message.message_id)

print('-'*50)
print('active_user_id:', active_user_id)
await put_person(user, active_user_id)

return END_STATE
else:
query = update.callback_query
await query.answer(text="+ then -")

Registers student as attended the class. ๐Ÿ“

@sync_to_async
def post_person(user):
models.Person(
tg_id=user.id,
tg_username=user.username,
tg_fullname=user.full_name,
arrived_at=get_time(),
).save()

Update student left time ⌛️

@sync_to_async
def put_person(user, user_id):
models.Person.objects.select_related().filter(pk=user_id, tg_id=user.id).update(left_at=get_time())

Following, function filters particular student’s last came time.

@sync_to_async
def get_last_id(user):
last_id = models.Person.objects.select_related() \
.filter(tg_id=user.id, left_at=None).values_list("pk", flat=True).last()
active_id = models.Person.objects.select_related() \
.filter(tg_id=user.id).values_list("pk", flat=True).last()
if last_id >= active_id:
return last_id
else:
return False

Next function collects all students data.

We will use it further to make a report. ๐Ÿงฎ

@sync_to_async
def get_data():
persons = models.Person.objects.all()
serializer = serializers.PersonSerializer(persons, many=True)

all_data = []
for i in range(0, len(serializer.data)):
data = [serializer.data[i]['tg_fullname'], serializer.data[i]['arrived_at'], serializer.data[i]['left_at']]
all_data.append(data)

return all_data

Let’s save data to excel file. ๐Ÿ—ž️

def set_data(info):
pandas.DataFrame(data=info, columns=['name', 'arrived', 'left']).to_excel(FILE_PATH)
return 1

Add function to get full report of attendance.

async def report(update: Update, context: ContextTypes.DEFAULT_TYPE):
active_data = await get_data()

if set_data(active_data):
await update.message.reply_document(
document=open(FILE_PATH, 'rb'),
filename='report.xlsx',
caption='Report'
)

time.sleep(2)
os.remove(FILE_PATH)

return END_STATE

Thanks for reading this article. ๐Ÿ’œ

Full code available on Github, give ⭐️ if you like it.

Consider following on Medium

Tweet me on Twitter

Learn Programming for Free

External links

Do you have any questions or suggestions?

Message me:

Comments

Popular posts from this blog

Make Your 1st Streamlit App with Cohere’s Large Language Models

Compressed NFTs Twitter Thread