Всем доброго дня! Думаю, статья будет интересна всем, кто пользуется Notion, но по какой-то причине не мог переехать на него полностью.
Предистория
Я разрабатываю свой проект. На лэндинге после ввода емейла выдается ссылка на соцопрос на базе Google Forms. Ответы записываются в табличечку на Google Drive.
Проблема в том, что все свое я ношу с собой сохраняю в Notion. Это банально удобней. Обходился ручным копипастом, пока отзывов было мало. Потом их стало больше — и надо было что-то придумать. Кому интересно, что вышло — добро пожаловать под кат.
Проблема
Google Forms записывают ответы только в табличечку — то есть тут никакого другого рецепта нет. Поэтому у меня родился план: давай через IFTTT слушать апдейты табличечки, пересылать на вебхук новые данные, там их как-то обрабатывать и загружать в Notion.
Для тех, кто не знаком с IFTTT: это сервис, который позволяет делать цепочки из действий. Скажем, «пришел пост в телеграм» — «экспортируем его ВКонтакте».
План начал сбоить: у Notion нет официального API. Но кто-то реверснул его и сделал неофициальное API.
Окончательный план был такой:
- Делаем апплет в IFTTT: «Добавлена строка в табличку — отсылаем ее на сервер
- Делаем непосредственно сервер, который принимает данные и отправляет их в Notion
Вторая проблема появилась, когда выяснилось, что у IFTTT сломалась интеграция с Google Sheets, и поэтому апплет не работает.
Поэтому пришлось изменить план: выкачиваем 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 (где хранятся времена записи). У меня как-то не получилось с русскими названиями добавлять, поэтому я изменил все названия на английские и добавляю по ним.
И теперь код на проверку данных и добавление строки в табличку 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 предлагает бесплатный тариф для
Далее делаем следующее. Идем в heroku создавать новый проект. Далее устанавливаем на свою операционную систему их клиент. А потом делаем все согласно инструкциям, появившимся после создания приложения.
Загрузив все на heroku, переходим в наш апплет и редактируем урл на новый.
Теперь каждый час список должен обновляться. Гипотетически IFTTT может выдавать ошибку, что какой-то у вас долгий реквест, но это не столь важно.
Автор: mixeden