django

Автоматически экспортируем Google Forms в Notion с помощью IFTTT и Django

  • среда, 12 июня 2019 г. в 00:20:46
https://habr.com/ru/post/455714/
  • Python
  • Программирование
  • Django
  • Облачные сервисы


Всем доброго дня! Думаю, статья будет интересна всем, кто пользуется Notion, но по какой-то причине не мог переехать на него полностью.

Предистория


Я разрабатываю свой проект. На лэндинге после ввода емейла выдается ссылка на соцопрос на базе Google Forms. Ответы записываются в табличечку на Google Drive.

Проблема в том, что все свое я ношу с собой сохраняю в Notion. Это банально удобней. Обходился ручным копипастом, пока отзывов было мало. Потом их стало больше — и надо было что-то придумать. Кому интересно, что вышло — добро пожаловать под кат.

Проблема


Google Forms записывают ответы только в табличечку — то есть тут никакого другого рецепта нет. Поэтому у меня родился план: давай через IFTTT слушать апдейты табличечки, пересылать на вебхук новые данные, там их как-то обрабатывать и загружать в Notion.

Для тех, кто не знаком с IFTTT: это сервис, который позволяет делать цепочки из действий. Скажем, «пришел пост в телеграм» — «экспортируем его ВКонтакте».

План начал сбоить: у Notion нет официального API. Но кто-то реверснул его и сделал неофициальное API.

image

Окончательный план был такой:

  • Делаем апплет в IFTTT: «Добавлена строка в табличку — отсылаем ее на сервер
  • Делаем непосредственно сервер, который принимает данные и отправляет их в Notion

Вторая проблема появилась, когда выяснилось, что у IFTTT сломалась интеграция с Google Sheets, и поэтому апплет не работает.

image

Поэтому пришлось изменить план: выкачиваем csv'шку с Google Sheets, парсим ее на сервере и кидаем все новое в Notion. IFTTT же используем как триггер для всего процесса.

Часть 1. CSV с Google Sheets


Эта часть, пожалуй, самая легкая. Открываем таблицу для просмотра (чтобы не пришлось возиться с куками). Далее берем и копируем ссылку на экспорт CSV. Для сего действия легким нажатием по клавиатуре набираем Ctrl + Shift + J (то есть открываем консоль разработчика), идем на вкладку Network. gотом жмем на Файл — Скачать — CSV. Видим запрос и копируем ссылку.

Часть 2. Пишем сервер


Так как библиотека у нас питоновская, будем писать на Django.

Теперь немного про структуру конкретно моей таблицы. Таблица в Notion, в отличие от таблицы в Google Sheets, имеет колонку „Reference“. Это ссылка на другую таблицу (в моем случае — на описание функций, которые понравились пользователям). Остальное в целом понятно: просто столбцы с просто данными.

Идем в Notion, уже привычным Ctrl + Shift + J открываем консоль, идем в Application -> Cookies, копируем token_v2 и называем его TOKEN. Потом идем на нужную нам страницу с табличкой и копируем ссылку на нее. Называем NOTION. Если у вас тоже есть Relation, идем на страницу с Relation, копируем ссылку и называем, например, NOTION_FUNCTIONS

Далее пишем следующий код (предварительно импортируем notion):

def index(request):
    if request.method == "POST":
        client = NotionClient(token_v2=TOKEN)

        database = client.get_collection_view(NOTION)
        current_rows = database.default_query().execute()
        database_functions = client.get_collection_view(NOTION_FUNCTIONS)
        current_rows_functions = database_functions.default_query().execute()

В нем мы подключаем NotionClient, говорим „Базы данных? Дайте две!“ и получаем непосредственно данные с этих двух табличек (дефолтным запросом, но можно и с сортировкой, подробнее — в докементации к библиотеке).

Потом мы должны сделать следующее: запросить CSV у гугла и распарсить ее. Делать мы это будем pandas'ом.

result = requests.get(SHEET).content
pandas_result = pd.read_csv(io.StringIO(result.decode('utf-8')))

timestamps = pandas_result[["Отметка времени"]].values
ages = pandas_result[["Ваш возраст"]].values
sexes = pandas_result[["Ваш пол"]].values
cities = pandas_result[["Ваш город"]].values
socials = pandas_result[["Ссылка на соцсеть (просто чтобы проанализировать получше)"]].values
agreements = pandas_result[["Можно ли вам написать, если есть какой-то вопрос."]].values
control_usages = pandas_result[["Какие примеры из области управления вас заинтересовали"]].values
health_usages = pandas_result[["Какие примеры использования из области здоровья вас  заинтересовали"]].values
prices = pandas_result[["За какую цену вы бы готовы были купить устройство. Можно с пояснением :)"]].values
mentions = pandas_result[["Предложения, замечания, негативные моменты по лендингу или в целом"]].values

Потом мы должны пройтись по всем данным с этой таблички и проверить, добавлены ли они в Notion или еще нет. Для этого мы и запрашивали данные с табличек.

def checkTimestamp(rows, timestamp):
    for i in range(0, len(rows)):
        row = rows[i]

        if row.name == timestamp:
            return True

    return False

Отдельно стоит сказать про „row.name“, потому что внимательный читатель наверняка спросит: а это вообще что такое-то?

Это название колонки в Notion (где хранятся времена записи). У меня как-то не получилось с русскими названиями добавлять, поэтому я изменил все названия на английские и добавляю по ним.

image

И теперь код на проверку данных и добавление строки в табличку Notion:

for i in range(0, len(timestamps)):
     if not checkTimestamp(current_rows, timestamps[i]):
           row = database.collection.add_row()

           health_usage = health_usages[i][0]
           control_usage = control_usages[i][0]
           ticks = health_usage + "," + control_usage

           row.title = timestamps[i][0]
           row.age = ages[i][0]
           row.sex = sexes[i][0]
           row.social_network = checkEmptiness(socials[i][0])
           row.can_we_write_you = checkEmptiness(agreements[i][0])
           row.city = checkEmptiness(cities[i][0])
           row.controlling_examples = checkEmptiness(control_usages[i][0])
           row.health_examples = checkEmptiness(health_usages[i][0])
           row.cost = checkEmptiness(prices[i][0])
           row.noticements = checkEmptiness(mentions[i][0])
           row.castdev_relation = findIds(current_rows_functions, ticks)

checkEmptiness — это функция, которая проверяет, нулевая ли штука в нее была передана. Notion как-то с неохотой работал, когда я ему кормил нулевые поля, поэтому стоит написать.

Теперь перейдем к разбору Relation'ов, потому что в официальной документации я про это не видел. Чтобы сделать ссылку на строчку с другой базы данных, надо взять ее (этой строки) айдишник и передать. Соответственно, если подразумевается массив ссылок на строки из другой таблички, надо взять массив их айдишников. Я лично добавлял Relation'ы по названиям функций.

def findIds(current_rows, titles):
    print("titles", titles)
    print("current rows", current_rows)
    array = []

    for a in range(0, len(current_rows)):
        if current_rows[a].name in titles:
            array.append(current_rows[a].id)

    print("Ids", array)
    return array

В конце после создания строчек добавляем ответ, чтобы на том конце знали, что запрос дошел.

return HttpResponse("Hello, habr.")

Тащемта с самым главным по серверу закончили, переходим к IFTTT.

Часть 3. IFTTT


Переходим на вкладку создания апплетов. Выбираем триггер (в нашем случае — это Date&time), ставим „каждый час“. Выбираем триггерируемым (то есть „that“) Webhook, указываем наш (пока что) локальный адрес, дабы потестить. Ну и все. Тестим.

Часть 4. Heroku


Вы думали, для чего мы возились с вот с этим триггерением со стороны IFTTT — это для того, чтобы не платить. Heroku предлагает бесплатный тариф для хостинга нашей штучки. Главное — чтобы сервис спал минимум 6 часов. А он точно будет спать, потому что мы его зовем работать каждый час, а не каждую минуту.

image

Далее делаем следующее. Идем в heroku создавать новый проект. Далее устанавливаем на свою операционную систему их клиент. А потом делаем все согласно инструкциям, появившимся после создания приложения.

Загрузив все на heroku, переходим в наш апплет и редактируем урл на новый.

Теперь каждый час список должен обновляться. Гипотетически IFTTT может выдавать ошибку, что какой-то у вас долгий реквест, но это не столь важно.