This library provides a Python high-level interface for creating Telegram Bots. It standardizes the coding in the best practice of the web development. The library is based on Django and Python-Telegram-Bot. It also provides viewset interface for managing data with.
If you start a new project, you could use Telegram django bot template with preconfigured settings.
You can install via pip
:
$ pip install telegram_django_bot
Then you can configure it in your app:
- Add "telegram_django_bot" to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
...
'telegram_django_bot',
'django_json_widget', # needed for django admin site
]
If you are planning to use Django in asynchronous mode, then you need to set DJANGO_ALLOW_ASYNC_UNSAFE = True
(otherwise DB writings will be failed).
2. Run python manage.py migrate
to create the telegram_django_bot models (checked that the AUTH_USER_MODEL
selected
in settings).
- Set up constants in Django settings file:
TELEGRAM_ROOT_UTRLCONF
- (same asROOT_URLCONF
for WEB) for using django notation in callback (routing) (strongly recommended)Not necessary, but useful settings:
TELEGRAM_TOKEN
- for adding "triggers",TELEGRAM_TEST_USER_IDS
- for adding tests for your bot,Make sure, that
LANGUAGE_CODE
,LANGUAGE_CODE
,USE_I18N
are also used in the library for language localization.
- This step connects
Telegram Django Bot
withPython-Telegram-Bot
. AddRouterCallbackMessageCommandHandler
in handlers for using TELEGRAM_ROOT_UTRLCONF :
updater = Updater(bot=TG_DJ_Bot(settings.TELEGRAM_TOKEN))
updater.dispatcher.add_handler(RouterCallbackMessageCommandHandler())
or in 20.x version :
bot = TG_DJ_Bot(settings.TELEGRAM_TOKEN)
application = ApplicationBuilder().bot(bot).build()
application.add_handler(RouterCallbackMessageCommandHandler())
Normally, Python-Telegram-Bot gives the next opportunities for bot creation:
- Python Interface for communication with Telegram API;
- Web service to get updates from telegram;
and Django:
- Django ORM (communication with Database);
- Administration panel for management.
Telegram Django Bot provides next special opportunities:
- using Viewsets (typical action with model (create, update, list, delete));
- using Django localization.
- using function routing like urls routing in Django.
- creating general menu items with no-coding (through Django Admin Panel);
- collecting stats from user actions in the bot;
- other useful staff.
The key feature of the lib is TelegramViewSet
- a class for managing Django ORM model. It is designed in a
similar way as Django rest framework Viewset , but has
a significant difference: while DRF Viewset provides a response in serializable format (usually in json format) to frontend app, TelegramViewSet
provides a response to the user in telegram interface in message format with buttons. So, you will manage data and receive
responses in human format by executing TelegramViewSet method. The methods use some kind of templates for generating human
responses (it is possible to overwrite these templates). By default, TelegramViewSet has 5 methods:
create
- create a new instance of the specified ORM model;change
- update instance fields of specified ORM model;show_elem
- show fields of the element and buttons with actions of this instance;show_list
- show list of model elements (with pagination);delete
- delete the instance
So, if, for example, you have a model of some request in your project:
from django.db import models
class Request(models.Model):
text = models.TextField()
importance_level = models.PositiveSmallIntegerField() # for example it will be integer field
project = models.ForeignKey('Project', on_delete=models.CASCADE)
tags = models.ManyToManyField('Tags')
The next piece of code gives the opportunity for full managing (create, update, show, delete) of this model from Telegram:
from telegram_django_bot import forms as td_forms
from telegram_django_bot.td_viewset import TelegramViewSet
class RequestForm(td_forms.TelegramModelForm):
class Meta:
model = Request
fields = ['text', 'importance_level', 'project', 'tags']
class RequestViewSet(TelegramViewSet):
model_form = RequestForm
queryset = Request.objects.all()
viewset_name = 'Request'
If you need, you can add extra actions to RequestViewSet for managing (see details information below) or change existing functions. There are several parameters and secondary functions in TelegramViewSet for customizing logic if it is necessary.
In this example, TelegramModelForm
was used. TelegramModelForm is a descendant of Django ModelForm. So, you could use
labels, clean functions and other parameters and functions for managing logic and displaying.
TelegramViewSet is designed to answer next user actions: clicking buttons and sometimes sending messages. The library imposes
Django URL notation for mapping user actions to TelegramViewSet methods (or usual handlers).
Usually, for correct mapping you just need to set TELEGRAM_ROOT_UTRLCONF
and use RouterCallbackMessageCommandHandler
in
dispatcher as it is mentioned above in the Install paragraph.
For correct mapping RequestViewSet you should write in the TELEGRAM_ROOT_UTRLCONF file something like this:
from django.urls import re_path
from .views import RequestViewSet
urlpatterns = [
re_path(r"^rv/", RequestViewSet, name='RequestViewSet'),
]
From this point, you can use buttons with callback data "rv/<function_code>" for function calling. For example:
- "rv/cr" - RequestViewSet.create method;
- "rv/sl" - RequestViewSet.show_list;
See these examples for great understanding:
In this chapter, we will analyze how everything works. The main task of the library is to unify the code and provide frequently used functions for developing a bot, that is why a lot of logic is based on resources and paradigms of Django <https://www.djangoproject.com/>`_ and Python-Telegram-Bot . Let's analyze key features of the library on the example of Telegram django bot template .
Important
The template is based on 13.x Python-Telegram-Bot version (synchronous version). So, the next examples is suitable for that version. For use with 20.x versions you need to do some modification.
Since Telegram bots are designed as a tool for responding to user requests, writing a bot begins
from the user request handler. For this, the standard tools of the Python-Telegram-Bot library are used ﹣
telegram.ext.Update
:
from telegram.ext import Updater
def main():
...
updater = Updater(bot=TG_DJ_Bot(TELEGRAM_TOKEN))
add_handlers(updater)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
As indicated in the example, to run the bot (Update) you need to specify a few things (the Python-Telegram-Bot
library standard):
1. an instance of the telegram.Bot
model with the specified API token. In this case, a descendant telegram_django_bot.tg_dj_bot.TG_DJ_Bot
of the telegram.Bot
class is used. It has additional functionality for convenience (we will return to it later);
2. Handlers that will be called in response to user requests.
In the example, the list of handlers is specified in the add_handlers
function:
from telegram_django_bot.routing import RouterCallbackMessageCommandHandler
...
def add_handlers(updater: Updater):
dp=updater.dispatcher
dp.add_handler(RouterCallbackMessageCommandHandler())
The example adds 1 super handler RouterCallbackMessageCommandHandler
, which allows you to write handlers
in the style of handling link requests in the same way as it is done in Django
. RouterCallbackMessageCommandHandler
allows you to handle
messages, user commands and button clicks by users. In other words, it replaces the handlers
MessageHandler, CommandHandler, CallbackQueryHandler
. Since the Telegram Django Bot
library is an extension,
it does not prohibit the use of standard handlers of the Python-Telegram-Bot
library for handle user requests.
(sometimes it is simply necessary, for example, if you need to process responses to surveys (you need to use PollAnswerHandler)).
Django notation of routing handlers is that paths to handlers are described in a separate file or files.
As in the Django
standard, the main file (root) for routing is specified in the project settings, where paths to handlers or paths to groups of handlers are stored.
The TELEGRAM_ROOT_UTRLCONF
(same as ROOT_URLCONF
for WEB) attribute is used to specify the path to the file. In the example template, we have the following settings:
bot_conf.settings.py
:
TELEGRAM_ROOT_UTRLCONF = 'bot_conf.utrls'
bot_conf.utrls.py
:
from django.urls import re_path, include
urlpatterns = [
re_path('', include(('base.utrls', 'base'), namespace='base')),
]
That is, only 1 group of handlers is connected in the file (which corresponds to the base
application at the conceptual level). You can
add several groups as well, this can be convenient if you create several folders (applications) for storing code.
As you can see Django
functions are imported without any redefinition.
There is following code in the specified included file base.utrls.py
:
from django.urls import re_path
from django.conf import settings
from telegram_django_bot.user_viewset import UserViewSet
from .views import start, BotMenuElemViewSet, some_debug_func
urlpatterns = [
re_path('start', start, name='start'),
re_path('main_menu', start, name='start'),
re_path('sb/', BotMenuElemViewSet, name='BotMenuElemViewSet'),
re_path('us/', UserViewSet, name='UserViewSet'),
]
if settings.DEBUG:
urlpatterns += [
re_path('some_debug_func', some_debug_func, name='some_debug_func'),
]
So, the end handlers (which are defined in the base.views.py
file) are specified here. Thus, if
user in the bot writes the command /start
, then Updater
receives a message about the user's action and selects
the appropriate for the request handler RouterCallbackMessageCommandHandler
from a set of handlers. Then the
handler RouterCallbackMessageCommandHandler
searches the appropriate for string /start
path in utrls
and
finds a suitable path '' + 'start'
, and then executes corresponding start function.
This distribution of handlers allows you to group part of the handlers into modules and quickly connect or
change them, while not being afraid of confusion which handlers need to be called, as it can be if all paths from
different modules to handlers are described in one place as required by Python-Telegram-Bot
.
In this example file base.utrls.py
also ViewSets are used in addition to simple handler functions like def start
and def some_debug_func
.
ViewSet is an aggregator of several functions. The concept of it is that you quite often need to apply
the same operations for a dataset, such as create, update, show, delete an example of dataset.
There is the class telegram_django_bot.td_viewset.TelegramViewSet
in the library for such purposes. The class manages
the Django ORM database model. TelegramViewSet
has 5 functions for managing the model:
Метод | UTRL | Description |
create | cr | Create model |
change | up | Change model attributes |
delete | de | Deleting a model |
show_elem | se | Display a model |
show_list | sl | Display a list of models |
Thus, if we want to call the BotMenuElemViewSet.create
method to create an element, we need to use
path 'sb/cr', whereby the first part of the path 'sb/' the router RouterCallbackMessageCommandHandler
will execute
the BotMenuElemViewSet
class, namely the TelegramViewSet.dispatch
method, which in turn will call the create
method by analyzing the second part of the path
cr
.
To sum up the scheme of handlers routing, there are following key points:
telegram.ext.Update
is used as a receiver of messages from Telegram;- Standard handlers of the
Python-Telegram-Bot
library can be used as handlers. For convenient use Django's path scheme andTelegramViewSet
you need to useRouterCallbackMessageCommandHandler
. TelegramViewSet
aggregates a set of standard functions for managing data, what is made possible to group code associated with one type of data type in one class (place).
As described above, TelegramViewSet contains standard functions for data manipulation.
Due to such standard data processing methods, it turns out in the example to describe the logic of BotMenuElemViewSet
in 40
lines of code, also using some customization for beautiful data displaying.
To use all the features of the TelegramViewSet in your class, it should be inherited from it, as, for example, this is done
in the BotMenuElemViewSet
:
from telegram_django_bot.td_viewset import TelegramViewSet
class BotMenuElemViewSet(TelegramViewSet):
In order to customize the ViewSet, you must specify 3 required attributes:
viewset_name
- class name, used to display to telegram usersmodel_form
- data form, used to specify which fields of the ORM database model to use in the viewset;queryset
- basic query for getting model elements.
The BotMenuElemViewSet
is used the following values:
from telegram_django_bot import forms as td_forms
from telegram_django_bot.models import BotMenuElem
class BotMenuElemForm(td_forms.TelegramModelForm):
form_name = _("Menu elem")
class Meta:
model = BotMenuElem
fields = ['command', "is_visable", "callbacks_db", "message", "buttons_db"]
class BotMenuElemViewSet(TelegramViewSet):
viewset_name = 'BotMenuElem'
model_form = BotMenuElemForm
queryset = BotMenuElem.objects.all()
where BotMenuElemForm
is a descendant of the Django ModelFrom
class, so it has a similar structure and parameterization methods.
`` form_name `` stands for the name of the form and is used in some messages sent to Telegram users.
TelegramViewSet has quite a lot in common with Viewset analogs tailored for WEB development (for example, django-rest-framework viewsets ). However, as part of the development of Telegram bots, TelegramViewSet has some special features:
- An unusual way to create elements;
- The display of information in bots is limited and most often comes down to displaying text and buttons, so the viewset in addition to business logic includes the creation of standard responses to user actions in the form of messages with buttons.
Since Telegram does not have the ability to create forms (in the classic Web sense) and communication between the bot and the user takes place in a chat, then
the most intuitive solution for filling out a form (creating an element) is filling the form attribute by attribute,
when the first element of the form is filled first, then the second, and so on. With this approach, it is necessary to use temporary storage for remembering
specified values in order to create an element from the form at the end. TelegramModelForm
and TelegramForm
are implemented just
in such way for taking over this process. The difference between these classes and the standard Django classes is precisely
in the modification of the method of filling in the form fields, otherwise they do not differ from standard forms.
TelegramModelForm
and TelegramForm
as Django descendants of ModelForm
and Form
have the following parameters, which you may need to customize:
- The clean function and other form validation process functions ;
labels
- field names;forms.HiddenInput
- designation of hidden fields (hiding fields allows them not to be shown to the user, while using and configuring in forms or inTelegramViewSet
).
TelegramViewSet
is designed to interact with descendants of the TelegramModelForm
class and allows you to use
generate forms with different fields, such as CharField, IntegerField
or ForeignKey, ManyToManyField
. Also, it is a good idea
to use the prechoice_fields_values
dictionary in TelegramViewSet
descendants for improving the convenience of filling out forms for users.
It is possible to store a list of frequently used values of form fields in the prechoice_fields_values
.
This allows users to select the desired values by clicking buttons rather than
writing text manually. The template has an example of using this field:
class BotMenuElemViewSet(TelegramViewSet):
...
prechoice_fields_values = {
'is_visable': (
(True, '👁 Visable'),
(False, '🚫 Disabled'),
)
}
In this case, 2 values are specified for choosing true or false for the boolean field is_visable
. You can also use
prechoice_fields_values
for CharField, IntegerField
or any other fields.
Sometimes the list of values needs to be generated dynamically, in which case you can override
prechoice_fields_values
as a @property
function.
The main function of the class, which is selected the function for managing data by the request of the user, is TelegramViewSet.dispatch
.
Let's analyze its logic in more detail:
def dispatch(self, bot, update, user):
self.bot = bot
self.update = update
self.user = user
if update.callback_query:
utrl = update.callback_query.data
else:
utrl = user.current_utrl
self.utrl = utrl
if settings.DEBUG:
logging.info(f'utrl: {utrl}')
utrl_args = self.get_utrl_params(re.sub(f'^{self.prefix}', '', self.utrl))
if self.has_permissions(bot, update, user, utrl_args):
chat_action, chat_action_args = self.viewset_routing[utrl_args[0]](*utrl_args[1:])
else:
chat_action = self.CHAT_ACTION_MESSAGE
message = _('Sorry, you do not have permissions to this action.')
buttons = []
chat_action_args = (message, buttons)
res = self.send_answer(chat_action, chat_action_args, utrl)
utrl_path = utrl.split(self.ARGS_SEPARATOR_SYMBOL)[0] # log without params as to much varients
add_log_action(self.user.id, utrl_path)
return res
Like a regular handler, the function takes 3 arguments as input: bot, update, user. After saving these arguments in class,
the determination of the current routing path is occurred. It is determined either by pressing a button in the bot (the callback_data
value of the button), or
can be stored in the user attribute user.current_utrl
. The second option is possible if the user manually enters
some information (for example, filled in a text field of form). After that, the arguments are extracted from the path
to call a specific function. Storing and interacting with arguments in a path is similar to how sys.argv
works. So,
for example, the string "sl&1&20"
will be converted to the list ['sl', '1', '20']
. The separator character between attributes
is &
by default and can be changed via the TelegramViewSet.ARGS_SEPARATOR_SYMBOL
variable.
When using TelegramViewSet
you most likely won't have to interact with the argument string directly, since
dispatch
converts a string into arguments. And for creating a callback_data
button string, that the user can call another method from Telegram interface, you should use
TelegramViewSet.gm_callback_data
function. In case you need more low-level interaction with function arguments, then
you can use the construct_utrl
and get_utrl_params
functions.
After receiving the utrl_args arguments and checking access rights, the managing method (action) is directly selected and called.
The first argument, which is the short name for the function, is popped from the utrl_args. All other arguments are passed as parameters
into a function. Inside the function, the necessary business logic and the data formatting for displaying to the user as a response take place.
Any such managing function in the TelegramViewSet
class must return the action type chat_action
and the parameters to that action chat_action_args
.
By default, the class has only 1 action ﹣ CHAT_ACTION_MESSAGE
, which means that the user will receive
a text message (possibly with buttons) as an answer for his/her action. The arguments for this action are the text of the message and a list of buttons (can be None).
After the function is processed, a response is sent to the user by send_answer
function and the user's action is logged.
The methods to call in viewset_routing
are the create, update, delete, show_elem, show_list
methods.
You can also add your own methods. Suppose we want to add a def super_method(self, *args)
method, then
you need to add the following lines in the class:
class SomeViewSetClass(TelegramViewSet):
...
actions = ['create', 'change', 'delete', 'show_elem', 'show_list', 'super_method']
command_routing_super_method = 'sm'
def super_method(self, *args):
...
Where actions
defines the list of available methods and command_routing_<method>
defines the path (url; short name) of the method.
As noted above, the dispatch
method performs a permissions check by calling the has_permissions
method.
The check is performed by the classes specified in permission_classes
and the default class is AllowAny
:
class TelegramViewSet:
permission_classes = [AllowAny]
This section describes the following class functionality that makes it easier to write code:
- External filters;
- Data display setting options;
- Helper functions for displaying data;
- Helper functions of business logic;
Quite often, there is a situation when you need to work not with all the elements of a database table, but with some
group (for example, a group of elements with a specific foreign key). For such purposes, you should use the foreign_filters
list,
which stores the values for filtering when the method is called. How exactly to use these filters is up to you, but
usually it is a good idea to use it in the get_queryset
function. Thus, it is possible to pass to functions
additional arguments that do not break the key logic of standard functions. Using the template example, you can modify
BotMenuElemViewSet
so that if an additional parameter is specified, then the BotMenuElem list displays
only those elements that contain the specified parameter in their command
attribute. To do this, you need to make the following changes to the code:
class BotMenuElemViewSet(TelegramViewSet):
...
foreign_filter_amount = 1
def get_queryset(self):
queryset = super().get_queryset()
if self.foreign_filters[0]:
queryset = queryset.filter(command__contains=self.foreign_filters[0])
return queryset
Where foreign_filter_amount
specifies the number of foreign filters. To call a method with a filter value, you must
specify them right after the function name in the path (utrls): "sb/sl&start&2"
, "sb/sl&start&2&1"
, "sb/sl&hello
.
It is worth noting that if we do not want to specify a filter, then we need to skip the argument in the path (utrls) in the next way: "sb/sl&&2"
.
There is no need to construct and process filters in paths (utrls) directly, since the functions gm_callback_data
and get_utrl_params
know how to work with them. gm_callback_data also has a parameter add_filters
(default True) which defines
whether to include filters in the generated path (utrl) or not. If the value is False , then it is necessary in the function arguments
manually specify filters: self.gm_callback_data('show_list', 'start', add_filters=False)
(will generate "sb/sl&start
).
This allows you to change the value of filters when generating paths.
A more detailed use of external filters can be seen in the example of Drive Bot .
The TelegramViewSet
has the following options for displaying model elements:
updating_fields: list
- list of fields that can be changed (displayed when showing the element (show_elem
);show_cancel_updating_button: bool = True
- shows a cancel button when changing fields, which leads back to the displaying element (show_elem
);deleting_with_confirm: bool = True
- ask the user for confirmation when deleting an element;cancel_adding_button: InlineKeyboardButtonDJ = None
- cancel button when creating an element (create
method);use_name_and_id_in_elem_showing: bool = True
- enables the use of the name and ID of the element when displaying this element (methodsshow_list
andshow_elem
);meta_texts_dict: dict
- a dictionary that stores standard texts for display (texts are used in all methods).
However, these fields are not always enough and you need to redefine the logic of helper functions for a beautiful display of information.
The TelegramViewSet
class describes the following helper functions for generating a response message:
def gm_no_elem
- if no element with this ID was found;def gm_success_created
- successful creation of the model;def gm_next_field
- when moving to the next form attribute;def gm_next_field_choice_buttons
- generates buttons to select options for a specific form attribute (used insidegm_next_field
);def gm_value_error
- error output when adding a form attribute;def gm_self_variant
- generates a message about the need to write the value manually by the user;def gm_show_elem_or_list_fields
- displays model fields in the message (used inshow_elem
withfull_show=True
, and inshow_list
﹣withfull_show=False
);def gm_value_str
- generates a string displaying a specific attribute (used ingm_show_elem_or_list_fields
);def gm_show_elem_create_buttons
- displays available buttons (actions) when showing a model element (callingshow_elem
) ;def gm_show_list_button_names
- generates the names of item buttons when displaying the list (callingshow_list
);
Depending on the need for customization, it is necessary to redefine these functions.
The TelegramViewSet
class uses the following helper functions:
def get_queryset
- allows you to modify Model queries to the database (most often used to filter elements, as in the example above);def create_or_update_helper
- main logic forcreate
andupdate
methods;def show_list_get_queryset
- allows you to customize the selection of items to display in show_list;
When writing your own handlers, it is recommended to use a wrapper like telegram_django_bot.utils.handler_decor
,
which performs the following functions:
- Getting or creating a user in the database;
- In case of an error inside the handler function, returns an error message to the user;
- Logs the handler call;
- Tracks where the user came from;
- Choice of language for sending messages to the user (in the case of localization enabled);
This handler is also used inside RouterCallbackMessageCommandHandler
, and as a result in calling TelegramViewSet
classes.
The library expands the Django localization tools for use in Telegram.
To support the use of different languages, the main elements of the Python-Telegram-Bot library are redefined in telegram_django_bot.telegram_lib_redefinition
:
telegram.Bot
->telegram_django_bot.BotDJ
;telegram.ReplyMarkup
->telegram_django_bot.ReplyMarkupDJ
;telegram.KeyboardButton
->telegram_django_bot.KeyboardButtonDJ
;telegram.InlineKeyboardButton
->telegram_django_bot.InlineKeyboardButtonDJ
;telegram.InlineKeyboardMarkup
->telegram_django_bot.InlineKeyboardMarkupDJ
.
When using these classes in code, multilingual support comes down to the following steps:
- Specifying the necessary settings in the settings.py file:
LANGUAGES
- list of languages,LANGUAGE_CODE
- default language; - Create folder with translations:
$ django-admin makemessages -l <language_code>
- Necessary texts for translation are wrapped in
gettext
andgettext_lazy
fromdjango.utils.translation
(how it works in Django read here ) - Run command
$ django-admin makemessages -a
to update texts for translation (created in locale folder) - Generation of translation files
$ django-admin compilemessages
.
Only a part of the functions uses localization in the template. It is made for easy understanding. The usage of localization can be seen in the example
functions some_debug_func
.
The library provides some additional tools for the convenience of developing and managing the bot.
For the correct work of TelegramViewSet
and other components the Django ORM model representing the user in Telegram must be inherited
from telegram_django_bot.models.TelegramUser
, as these components use its fields. TelegramUser
inherited from
django.contrib.auth.models.AbstractUser
(which allows you to authorize users on the site if necessary) and has
the following additional fields:
id
- redefined to use user ID from telegrams;seed_code
- arbitrary value from 1 to 100 to randomly group users for tests and analysis;telegram_username
- username of the user in telegram;telegram_language_code
- telegram language code (some languages have dialects and as a result the code designation is more than 2 symbols);timezone
- the user's time zone (for determining the time);current_utrl
- path (utrl) of the last user action (used inTelegramViewSet
);current_utrl_code_dttm
- time of the last action, when saving the path;current_utrl_context_db
- path context (utrl);current_utrl_form_db
- intermediate data for the form. Acts as a temporary data store when filling out a form;
Fields current_utrl_<suffix>
are needed for TelegramViewSet
, TelegramModelForm
and are needed in exceptional cases
when writing code. The model also has the following methods (property) to simplify interaction with model fields:
current_utrl_form
(property) - returns the current temporarily stored path form data (utrl);current_utrl_context
(property) - returns the current path context (utrl);save_form_in_db
- saves the form in thecurrent_utrl_form_db
field;save_context_in_db
- saves the context in the fieldcurrent_utrl_context_db
;clear_status
- clears the data associated with the used path (fieldscurrent_utrl_<suffix>
) ;language_code
(property) - returns the language code in which messages should be generated for the user;
If you want, you can create your self Django ORM model representing the user, you just need to copy
id, telegram_username, telegram_language_code, current_utrl, current_utrl_code_dttm, current_utrl_context_db, current_utrl_form_db
and corresponding functions.
The library also describes additional models to improve the usability of the bot:
ActionLog
- stores user actions. Records help to collect analytics and make triggers that work on certain actions;TeleDeepLink
- stores data on which links new users have clicked (to analyze input traffic);BotMenuElem
- Quite often a bot needs messages that have only static data. These pages can be help and start messages.BotMenuElem
allows you to configure such pages through the admin panel, without having to write anything in the code. InBotMenuElem
there is the ability to customize pages depending on the starting deeplinks.BotMenuElem
can not only add buttons to the message, but also send different files. To do this, you must specifymedia
and the file formatmessage_format
.BotMenuElem
allows you to quickly change bot menu blocks without having to make changes to the code;BotMenuElemAttrText
- helper model forBotMenuElem
, responsible for translating texts into other languages. The elements themselves are created depending on the specified languages in theLANGUAGES
settings. You only need to fill in the translation in thetranslated_text
field;Trigger
- allows you to create triggers depending on certain actions. For example, remind the user that he has left incomplete order, or give a discount if it is inactive for a long time. For triggers to work, you need to add tasks fromtelegram_django_bot.tasks.create_triggers
to CeleryBeat schedule;UserTrigger
- helper model forTrigger
, controlling to whom triggers have already been sent;
To improve convenience, TG_DJ_Bot
has several high-level functions:
send_format_message
- Allows you to send a message of an arbitrary type (internally, depending on themessage_format
selects the appropriate method of thePython-Telegram-Bot
library). An important feature of this function is that if the user clicks on the button, then the previous message of the bot is changed, rather than a new one being sent. If, nevertheless you need to send a new message to the user, then you need to set the parameteronly_send=True
;edit_or_send
- wrapper of thesend_format_message
method for sending text messages with buttons;send_botmenuelem
- Sends aBotMenuElem
to the user. Theupdate
argument can be empty;task_send_message_handler
- created for sending messages to many users. Handles situations where the user blocked the bot, deleted or when the limit for sending messages to users is reached;
The following additional functions are provided in the libraries:
telegram_django_bot.utils.add_log_action
- to create a user ActionLog;telegram_django_bot.utils.CalendarPagination
- class for generating a calendar with buttons;telegram_django_bot.user_viewset.UserViewSet
- telegram user class for changing language and time zone;
In this section, we will analyze the work of RouterCallbackMessageCommandHandler
and telegram_reverse
in a little more detail.
As described earlier RouterCallbackMessageCommandHandler
is used to be able to write handlers in the style
Django. Also RouterCallbackMessageCommandHandler
provides the ability to handle calls to BotMenuElem
as
through commands, and through callback. This is achieved by using the functions all_command_bme_handler
and
all_callback_bme_handler
. By default, BotMenuElem
call handling is enabled and handled after
no suitable path was found in the description of utrls (paths in Django notation). If there is no BotMenuElem
element
match is found, the BotMenuElem
is considered to be configured incorrectly and an error message is returned to the user.
You can create a class with the only_utrl=True
attribute, what is disable calls to BotMenuElem
.
The example template contains the use of the telegram_reverse
function, the essence of which is to generate a path (string) to the
handler specified in the function argument. The function is analogous to the reverse Django function
and avoids errors when changing paths.
The library also extends the django.test.TestCase
capabilities for use with Telegram through the TD_TestCase
class.
The simplest approach for testing the bot is to generate messages that the bot expects from Telegram and
send a response to Telegram (to check that the bot's response messages are in the correct format). Class TD_TestCase
has a function create_update
for easy and fast creation of Telegram.Update
which generates the request
telegram user. So the overall design looks like this:
- A
Telegram.Update
is created for emitting a user request; - The
handle_update
lambda function, which usesRouterCallbackMessageCommandHandler
, is called with created update. It does its staff and as a result sends a real message to the test user. Due to this, the correctness of the response data format is checked by Telegram; - The correctness of the sent data and changes in the database is checked using the standard tools
django.test.TestCase
.
You need to specify at least one test user ID in the TELEGRAM_TEST_USER_IDS
settings section for correct tests work.
Messages will be sent to that user, so the bot needs to have permission to write to that user.
In the tests
folder you could find test examples.