Django + Zoho CRM: как управлять данными без головной боли
Почти любой современный бизнес, когда приходит время выстраивать отношения с клиентами, полагается на CRM-системы (Customer Relationship Management). И сталкивается с тем, что ожидания не совпадают с реальностью: эффективность не такая высокая, да и управление данными кажется слишком сложным. Один из способов решить эти проблемы — интегрировать сервис со своим веб-приложением.
Привет! Меня зовут Денис, я Python-разработчик в Kokoc Group. Уже около года занимаюсь интеграцией одной из наших платформ на Django с Zoho CRM. Мы соединяем пользователей с широким спектром услуг, требования к обработке заявок и управлению партнерскими и пользовательскими профилями высокие, поэтому Zoho CRM стала ключевым элементом.
В статье хочу показать, как провести ее и сделать процессы проще и эффективнее. Мы рассмотрим все этапы: от подготовки среды до настройки вебхуков и асинхронной обработки данных с помощью Celery. Советую почитать всем разработчикам и менеджерам, так или иначе связанным с работой в CRM-системах.
О чем поговорим:
Как подготовить CRM и Django-приложение к интеграции.
Как взаимодействовать с API Zoho CRM через Python.
Как настроить правила рабочего процесса в Zoho CRM для отправки данных в Django-приложение.
Как обработать данные, полученные от вебхука Zoho CRM, и обновить базу данных Django.
Как использовать Celery для асинхронной обработки данных из Zoho CRM.
Немного о Zoho CRM
Скрытый текст
Zoho CRM — это комплексная система управления отношениями с клиентами (CRM) от компании Zoho. В ней есть инструменты для управления лидами, контактной информацией, сделками, маркетинговыми кампаниями, обслуживанием клиентов и аналитикой — реализованные, как набор модулей:
Лиды (Leads) — модуль для сбора и управления информацией о потенциальных клиентах.
Контакты (Contacts) — модуль для хранения контактной информации о клиентах как физических лицах.
Аккаунты (Accounts) — модуль для хранения информации о компаниях, с которыми вы взаимодействуете.
Продукты (Products) — модуль для хранения информации о продуктах компании.
Сделки (Deals) — Модуль для отслеживания прогресса продаж и управления сделками.
Стандартные модули можно редактировать, как и добавлять новые под свои бизнес-процессы. Еще один плюс — интеграция с другими продуктами Zoho: Zoho Mail, Zoho Campaigns, Zoho Desk, Zoho Books — из которых можно собрать полноценную платформу для управления бизнесом.
Готовим CRM-систему
Первым делом нужно зарегистрироваться с в системе и создать песочницу (дальше работать будем уже в ней).
Заводим аккаунт и переходим в настройки:
Выбираем Sandbox:
Нажимаем Create New Sanbox, заполняем форму, нажимаем Create:
Нажимаем Go to sunbox и оказываемся внутри нашей песочницы (о чем говорит флаг на скриншоте):
Получаем ключи API
Для взаимодействия понадобятся ключи. Идем сюда: https://api-console.zoho.eu/:
Выбираем Self Client, нажимаем Create:
Записываем вот эти значения — они еще понадобятся:
Указываем Scope
В API Zoho, он определяет разрешения, которые приложение запрашивает для доступа к различным ресурсам (подробнее советую почитать тут: https://www.zoho.com/crm/developer/docs/api/v6/scopes.html).
Мы возьмем «ZohoCRM.modules.ALL», который дает доступ ко всем модулям в Zoho CRM. То есть приложение сможет выполнять операции (чтение, создание, обновление, удаление) с любыми модулями: контактами, сделками, компаниями и прочим.
Нажимаем Create и выбираем кабинет, для которого создается ключ:
Выбираем, для какой версии CRM создать токен: для производственной или песочницы. Фича удобная: достаточно взять однажды все токены и больше не возвращаться к этому вопросу.
Получаем токены
Отправляем POST-запрос на этот url со следующими параметрами. Пример:
Заменить токены на условные
https://accounts.zoho.eu/oauth/v2/token? client_id=
В ответ нам придет access_token и refresh_token. Сохраняем и идем дальше.
Готовим Django-приложение
Первым делом устанавливаем пакеты, которые понадобятся для работы:
Django — основной фреймворк для разработки веб-приложений.
djangorestframework — пакет для создания API.
requests — библиотека для отправки HTTP-запросов.
Celery — система для создания и обработки асинхронных задач.
Далее прописываем модели, я для примера буду использовать эти:
Расширенная модель юзера:
class User(AbstractBaseUser):
first_name = models.CharField(_('first name'), max_length=30,
blank=True, null=True)
last_name = models.CharField(_('last name'), max_length=30,
blank=True, null=True)
phone_number = models.CharField(_('phone number'), max_length=20,
unique=True)
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
objects = UserManager()
class Meta:
verbose_name = _('User')
verbose_name_plural = _('Users')
Модель продукта:
class Product(models.Model):
name = models.CharField(verbose_name=_("Product name"),
max_length=200)
description = models.TextField(verbose_name=_("Product description"),
blank=True, null=True)
price = models.DecimalField(verbose_name=_("Price"),
decimal_places=2, max_digits=10)
zoho_product_id = models.CharField(verbose_name=_("Zoho product ID"),
max_length=50, blank=True, null=True, unique=True)
def __str__(self):
return self.name
Модель ExternalResource. Она нужна для хранения данных, полученных при настройке Zoho CRM (Client ID, Client Secret, access_token, refresh_token):
class ExternalResource(models.Model):
name = models.CharField(verbose_name=_("Name"), max_length=200,
unique=True)
client_id = models.CharField(verbose_name=_("Client ID"),
max_length=200, null=True, blank=True)
client_secret = models.CharField(verbose_name=_("Client Secret"),
max_length=200, null=True, blank=True)
token = models.CharField(verbose_name='Token', max_length=256,
null=True, blank=True, default=None)
refresh_token = models.CharField(verbose_name='Refresh token',
max_length=256, null=True, blank=True, default=None)
class Meta:
verbose_name = _('Third-party service')
verbose_name_plural = _('Third-party services')
@classmethod
def get_zoho(cls):
return cls.objects.get(name='ZohoAPI')
Регистрируем ExternalResource в админке. И создаем объект модели с полученными данными:
Настраиваем взаимодействие с Zoho API. Понадобятся следующие классы:
ExternalResourceManager — базовый класс для работы с API внешних сервисов:
class ExternalResourceManager(ABC):
"""
Базовый класс для работы с API внешних сервисов, таких как ZohoCRM.
"""
@abstractmethod
def db_object(self):
"""
Метод реализует получение объекта модели ExternalResource
"""
pass
@abstractmethod
def send_request(self, method, url, data, **kwargs):
"""
Метод реализует отправку запроса к внешнему ресурсу
"""
pass
ZohoBaseManager — класс для работы с ZohoCRM API, реализующий методы отправки данных в ZohoCRM и обновления токенов:
class ZohoBaseManager(ExternalResourceManager):
@cached_property
def db_object(self):
return ExternalResource.get_zoho()
def send_request(self, method, url, data=None, auth=False, **kwargs):
"""
Отправка запроса к ZohoCRM API.
"""
if auth:
kwargs.update({"Authorization": f"Zoho-oauthtoken {self.db_object.token}"})
try:
response = requests.request(method=method, url=url, data=data, headers=kwargs)
except RequestException as e:
pass
# как то обрабатываем исключение...
return response
def handle_response(self, response):
"""
Обработка ответа от API.
Проверяем на наличие ошибок и возвращаем необходимые данные.
"""
if response is None:
raise ValueError("Ответ от API отсутствует")
try:
response_json = response.json()
except ValueError:
raise ValueError("Невозможно преобразовать ответ в JSON")
if response.status_code != 200:
error_message = response_json.get('error', 'Неизвестная ошибка')
raise Exception(f"Ошибка API: {error_message} (код {response.status_code})")
return response_json
def update_token(self):
"""
Отправка запроса к Zoho API для обновления token
"""
url = 'https://accounts.zoho.eu/oauth/v2/token'
data = {
'client_id': self.db_object.client_id,
'client_secret': self.db_object.client_secret,
'refresh_token': self.db_object.refresh_token,
'grant_type': 'refresh_token',
}
response = self.send_request(
method='post',
url=url,
data=data
)
try:
response_json = self.handle_response(response)
except Exception as e:
# как то обрабатываем исключение...
return
new_token = response_json.get('access_token')
self.db_object.token = new_token
self.db_object.save()
RegistrationDataManager — класс для управления данными при регистрации пользователя.
class RegistrationDataManager(ZohoBaseManager):
@staticmethod
def get_registration_data(user):
data = {
"data": [
{
'First_Name': user.first_name,
'Last_Name': user.last_name,
'Email': user.email,
'Mobile': user.phone_number,
}
]
}
return data
def create_lead(self, user):
"""
Отправляет данные для создания лида в ZohoCRM.
"""
data = self.get_registration_data(user)
json_data = json.dumps(data)
url = "https://www.zohoapis.eu/crm/v2/Leads"
method = 'post'
response = self.send_request(method, url, json_data, auth=True)
json_response = self.handle_response(response)
# Обрабатываем ответ от ZohoCRM (проверяем статус,
получаем ID лида и т.д.)
Обеспечиваем бесперебойный доступ к API Zoho CRM
Чтобы доступ оставался постоянным, необходимо обновлять токен. Напишем задачу, которая будет делать это за нас каждые 10 минут.
Создаем Celery-таск, и в нем — экземпляр ZohoBaseManager и вызываем метод update_access_token (), который отвечает за фактическое обновление токена.
@shared_task
def refresh_zoho_token():
"""Обновляет токен доступа для API Zoho CRM."""
manager = ZohoBaseManager()
manager.update_token token()
Настраиваем расписание для выполнения задачи в файле settings.py (там будем определять, как часто вызывается задача). Создаем запись для задачи refresh_zoho_token в словаре CELERY_BEAT_SCHEDULE:
CELERY_BEAT_SCHEDULE = {
'refresh_zoho_token': {
'task': 'zoho_integration.tasks.refresh_zoho_token',
'schedule': crontab(minute='*/10'),
'args': (),
'kwargs': {},
'options': {
'expires': 60.0,
},
},
}
Что именно прописали
Скрытый текст
task — указывает путь к нашей задаче.
schedule — использует crontab (minute='*/10'), то есть «выполнение задачи каждые 10 минут».
args и kwargs — пустые, так как задача не требует дополнительных параметров.
options — гарантирует, что если задача не будет выполнена в течение минуты, то станет считаться устаревшей и отменится.
Создаем лида в ZohoCRM при регистрации пользователя
Здесь все просто:
Используем Django-сигналы, чтобы отслеживать создание пользователей:
@receiver(post_save, sender=User)
def send_user_data(sender, instance, created, **kwargs):
if created:
create_zoho_lead.delay(instance.id)
Используем Celery-задачи для асинхронной отправки данных в Zoho CRM:
@shared_task
def create_zoho_lead(user_id: int) -> None:
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
return
manager = RegistrationDataManager()
manager.create_lead(user)
Вот и все, теперь при каждой регистрации данные о пользователе будут отправляться в ZohoCRM.
Проводим интеграцию с помощью Workflow Rules
Представим, что каждый раз, когда в Zoho создается или обновляется продукт, нам нужно забирать его в свою базу данных. Реализовать это можно как правила рабочего процесса и функций в Zoho CRM.
Подробнее о правила рабочего процесса
Скрытый текст
В Zoho CRM, Workflow Rules —- это по сути триггеры, которые автоматизируют задачи. Правила могут срабатывать автоматически в конкретное время или запускаются, когда:
создается новая запись;
обновляется существующая запись;
удаляется запись;
создаются, изменяются, удаляются примечания к записи.
Создадим правило, которое при создании или обновлении продукта будет присылать нам данные по нему и обновлять значение в базе.
Переходим в настройки и нажимаем на вкладку Workflow Rules:
Create Rule и заполняем форму:
Нажимаем Next и попадаем на эту страницу:
В первом селекте выбираем Record Action. Во втором — Create or Edit и Repeat this workflow whenever a Product is edited. Эти настройки говорят о том, что правило будет срабатывать при каждом редактировании записи:
Нажимаем Next, выбираем All Products (так правило будет применяться ко всем записям):
Выбираем, какое именно действие будет происходить. В нашем случае — Function:
В появившемся окне выбираем Write your own:
Заполняем форму и нажимаем Create:
Попадаем на страницу создания функции:
Разберем типичную функцию в Zoho CRM
Что это вообще такое?
Скрытый текст
Это фрагменты кода, написанные на языке Deluge. Deluge разработан специально для Zoho CRM и позволяет выполнять операции более сложные, чем простое изменение полей или отправка писем. Вот пара примеров:
Обработка данных. Вы можете манипулировать данными из разных записей, использовать условные операторы и циклы для логического управления потоком выполнения.
Вызов API. Вы можете использовать функции для вызова внешних API, например, для получения данных из других сервисов или для отправки запросов в приложение.
В качестве примера возьмем функцию, которая будет отправлять данные о продукте из Zoho CRM в наше Django-приложение.
Указываем, что функция принимает ID записи, и нажимаем Edit Arguments:
В появившемся окне, в поле Argument Name задаем имя аргумента. В поле Argument Value вводим »#». Выбираем Product Id. Нажимаем Save:
Теперь ID продукта будет подставляться автоматически при срабатывании правила и вызове функции. И мы сможем его использовать в коде функции, чтобы получить создаваемую или редактируемую запись.
Вот пример функции с пояснениями:
Напишем view, которая будет принимать запрос
View
class ZohoProductView(APIView)
serializer_class = ZohoProductSerializer
permission_classes = (CheckZohoTokenPermission,)
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
sync_product.delay(data=serializer.validated_data)
return Response(serializer.data, status=status.HTTP_202_ACCEPTED
Serializer
class ZohoProductSerializer(serializers.Serializer):
zoho_product_id = serializers.CharField()
name = serializers.CharField()
description = serializers.CharField()
price = serializers.DecimalField(max_digits=10, decimal_places=2)
Permission
class CheckZohoTokenPermission(BasePermission):
def has_permission(self, request, view):
if request.META.get("HTTP_ZOHO_REQUEST_AUTH_TOKEN") == settings.ZOHO_REQUEST_AUTH_TOKEN:
return True
Celery Task
@shared_task
def sync_product(data):
zoho_product_id = data.get("zoho_product_id")
product = Product.objects.filter(zoho_product_id=zoho_product_id)
if product.exists():
product.update(**data)
else:
Product.objects.create(**data)
Теперь, когда в Zoho CRM, в модуле Products будет создаваться или редактироваться запись, сработает правило и система вызовет нужную функцию. Функция в свою очередь отправит данные на наш Django API-endpoint, который принимает данные о продукте и передает их на обработку в Celery-задачу sync_product. Задача проверит наличие продукта в базе данных по zoho_product_id.
Наконец, если продукт существует, данные обновятся. Если нет — появится новый объект Product с полученными данными.
Такой подход обеспечивает синхронность Zoho CRM и Django-приложением, то есть все изменения в данных продуктов будут отражены в обеих системах.
Заключение
Я постарался максимально полно показать, как интегрировать Zoho CRM с Django-приложением для автоматизации работы с данными о клиентах и продуктах. Но важно помнить, что эта статья — лишь начало, и интеграция с Zoho CRM — это гибкий и мощный инструмент, который можно настроить под любые потребности. Экспериментируйте, чтобы сделать сделать свой бизнес еще эффективнее.
И пара советов напоследок:
Добавьте логирование, чтобы отслеживать ошибки и успешную обработку данных.
Документируйте код. Это поможет и вам, и вашей команде лучше ориентироваться в структуре интеграции и легко вносить изменения в будущем.
Тестируйте. Это поможет убедиться, что интеграция работает корректно и изменения не нарушат ее работу.
Изучите документацию Zoho CRM и Django. В них вы найдете много полезной информации, которая поможет реализовать более сложные сценарии.