Автор оригинала: Bert Carremans.
Используйте API в Google Calendar и API в Google, чтобы выбрать правильный рецепт в нужный день.
Вы также подчеркиваете, когда получаете вопрос «То, что на ужин сегодня вечером?» Ты не одинок. Я предполагаю, что это самый задаваемый вопрос, что и часовые удары 4 стр. Решив, что поесть, может быть утомительной рудой. Особенно, когда у вас есть маленькие дети с различными занятиями после школы.
Чтобы не ходить в супермаркет каждый день, мы обычно пишем меню с рецептами на ближайшую неделю. Таким образом, мы можем купить все наши продукты в одном посещении супермаркета. Это экономит нам много времени. Кроме того, это также экономит нам деньги. Это потому, что мы менее подвержены всем продажам Трюки супермаркеты используют Отказ
Поиск рецептов на целую неделю требует некоторого мышления и планирования. Мы должны учитывать предпочтения поведения всех членов семьи. Кроме того, у нас есть ограниченное время, доступное для приготовления приготовления каждый день. Чтобы сделать это проще, я создал автоматический планировщик еды с этими функциями:
- Извлечь планирование работы для меня и моей жены из наших поделинных календарей Google
- извлечь наши предпочтительные рецепты из электронной таблицы Google,
- Повторите некоторые рецепты каждую неделю в тот же день
- Оставьте одну неделю между между ними до повторения других рецептов
- Мне нравится готовить больше, чем у моей жены. Так на днях, что я не могу готовить, рецепты должны быть закрыты вовремя
- Загрузить недельное меню в календаре Google
Давайте прыгнем прямо в.
Использование API Google Calendar API и API Google Pailes
Во-первых, нам нужно Создать новый проект Google Cloud Отказ Прежде чем мы сможем использовать календарь Google и листы в этом проекте, нам нужно включить API. Это очень хорошо объяснено на веб-страницах ниже:
Когда это сделано, мы продолжаем импортировать необходимые пакеты Python.
import config as cfg import pandas as pd import numpy as np from pathlib import Path from datetime import datetime from datetime import timedelta from googleapiclient.discovery import build from google.oauth2 import service_account
Конфигурация
Для причин конфиденциальности и безопасности я сохраняю несколько параметров в отдельном файле config.py. Мы импортируем файл с псевдонимами CFG
Отказ Я буду обсуждать эти параметры дальше ниже с фиктивными ценностями. Вы можете включить их для своего собственного приложения со значениями, относящимися к вашему делу.
Охватывает
С областями, мы определяем уровни доступа для календарей и листов Google. Нам понадобится прочитать и запись доступа к обоим Календари и лист Отказ Таким образом, мы используем URL-адресы ниже.
SCOPES = ['https://www.googleapis.com/auth/calendar' , 'https://www.googleapis.com/auth/spreadsheets']
Идентификатор листа Google и диапазон
SPREADSHEET_ID =RANGE = 'recepten!A:G'
Нам нужно указать идентификатор листа Google с рецептами. Кроме того, мы указываем диапазон листа, содержащий рецепты.
Вы можете найти удостоверение личности ваших листов Google Щелкнув правой кнопкой мыши на листе в Google Drive. Затем выберите «Получить общую ссылку». Вы можете найти идентификатор после «https://drive.google.com/Open?id=».
В моем листе Google «Recepten» столбцы A до G содержат информацию на каждом рецепте. Скриншот ниже показывает некоторое количество образца. Итак, Диапазон
должен быть установлен на “Recepten! A: G” Отказ
IDS Calendar Google
CALENDARID_1 =CALENDARID_2 = CALENDARID_WEEKMENU =
Нам нужно указать идентификаторы Calendar Google, чтобы получить события. Убедитесь, что у вас есть доступ ко всем календарям, которые вы хотите включить. Вы можете найти идентификатор, выполнив это Сценарий от API Explorer Отказ
Для этого проекта мы выделим события только двух календарей. Но вы можете адаптировать код в цикл более календарей. Я также создал отдельный календарь для загрузки рецептов.
Мероприятия этикетки
BUSY_EVENTS = [] FREE_EVENTS = [ ] ALL_EVENTS = BUSY_EVENTS + FREE_EVENTS
Моя жена работает в сдвигах и добавляет их в свой календарь Google с помощью буквных кодов. Например: «B» означает послеобеденный сдвиг. Это событие является одним из Busy_events
Отказ
Когда у меня есть выходной, я добавляю «праздник» в свой календарь. Это событие является одним из Free_events.
Все события являются полнодневными событиями в календарях Google. Вы можете использовать свои собственные мероприятия схема меток.
Традиции
TRADITIONS = { 'Thursday' : 'fries'}
С Традиции
Я имею в виду, что наша семья имеет несколько дней в неделю, на которой мы готовим определенный рецепт. Как мы из Бельгии, это означает, что есть картофель фри раз в неделю (для нас в четверг). И да, прежде чем вы спросите, это картофель фри с майонезом.
Вы можете указать свои собственные традиции в словаре, с названием дня в качестве ключа и рецепта в качестве значения.
Количество дней, чтобы планировать впереди
Иногда мы не можем пойти в супермаркет в день, когда создано новое меню недели. Нам может понадобиться несколько дней, чтобы планировать заранее. С Nb_days_before
Мы отдаваем себя немного провисанию. Это означает, что новое недельное меню будет сгенерировано определенное количество дней до завершения предыдущего недели.
NB_DAYS_BEFORE = 3
Использование учетной записи службы
Мы будем использовать Сервисная учетная запись использовать API в проекте. Файл Credentancys.json – это файл, который вы можете скачать при включении API.
Мы создаем учетные данные кредиты
с кодом ниже. Эти учетные данные включают аутентификацию в календарях Google и листе Google.
creds = service_account.Credentials.from_service_account_file("credentials.json", scopes=cfg.SCOPES)
Получение событий календаря Google
Начнем с создания объекта сервиса с построить
метод.
service_cal = build('calendar', 'v3', credentials=creds)
Мы заинтересованы только в событиях на предстоящую неделю. Чтобы отфильтровать эти события, мы указываем даты и отформатируем их с помощью изоформат ()
Отказ Параметры Timemin
и Timemax
нужен этот формат.
def format_date(date): date_time = datetime.combine(date, datetime.min.time()) date_time_utc = date_time.isoformat() + 'Z' return date_time_utc
С методом События (). Список Из объекта сервиса мы извлекаем события. Затем извлеченные события отфильтровываются для занятых и свободных событий. Все остальные события на календарях Google не актуальны в этом проекте. Мы сохраняем дату начала и окончания и резюме событий.
def get_event_date(event, timepoint): return event[timepoint].get('dateTime', event[timepoint].get('date')) def get_events_by_calendarId(service, calendarId, timeMin, timeMax, allEvents): events_result = service.events().list(calendarId=calendarId , timeMin=timeMin , timeMax=timeMax , singleEvents=True , orderBy='startTime').execute() events = events_result.get('items', []) events_list = [(get_event_date(e, 'start'), get_event_date(e, 'end'), e['summary'].upper()) for e in events if e['summary'].upper() in allEvents] return unfold_events_list(events_list)
Некоторые события распространяются более одного дня. Например, когда вы отдыхаете более одного дня. Мы разворачиваем эти многодневные события в ежедневных событиях в пределах подходящей недели.
def unfold_events_list(events_list): new_events_list = [] for e in events_list: start = datetime.strptime(e[0], '%Y-%m-%d').date() end = datetime.strptime(e[1], '%Y-%m-%d').date() delta_days = (end - start).days if delta_days > 1: for d in range(delta_days): unfolded_day = start + timedelta(days=d) if unfolded_day >= datetime.now().date() and unfolded_day <= datetime.now().date() + timedelta(days=6): new_events_list.append((unfolded_day, e[2])) else: new_events_list.append((start, e[2])) return new_events_list
Наконец, мы хотим, что Pandas DataFrame с событиями обеих календарей на предстоящую неделю. Чтобы добраться до этого результата, мы преобразуем списки событий в кадры данных и слияние на дату. Мы также добавляем будний день до объединенного кадра данных.
def create_events_df(events_list_1, events_list_2): events_df_1 = pd.DataFrame.from_records(events_list_1, columns=['date', 'events_cal_1']) events_df_2 = pd.DataFrame.from_records(events_list_2, columns=['date', 'events_cal_2']) events_df = events_df_1.merge(events_df_2, on='date', how='outer') events_df.date = pd.to_datetime(events_df.date) events_df.set_index('date', inplace=True) events_df.sort_index(inplace=True) dates = list(pd.period_range(START_DAY, NEXT_WEEK, freq='D').values) new_idx = [] for d in dates: new_idx.append(np.datetime64(d)) events_df = events_df.reindex(new_idx) events_df.reset_index(inplace=True) events_df['weekday'] = events_df.date.apply(lambda x: x.strftime('%A')) events_df.set_index('date', inplace=True) return events_df
Чтобы убедиться, что мы охватываем все даты наступающей недели, мы используем Preg_range
и Reindex
Объединенный кадр данных.
Получение рецептов из листа Google
На данный момент у нас есть кадр данных со всеми днями наступающей недели, а события (если таковые имеются), происходящие в двух календарях. Теперь мы можем начать извлечь рецепты из листа Google и назначить рецепт на каждый день. Как и в случае с API Google Calendar, давайте начнем с создания объекта сервиса для API Google Pailets.
service_sheet = build('sheets', 'v4', credentials=creds)
С методом электронные таблицы (). Значения (). Получить Мы можем извлечь рецепты из листа Google.
def get_recipes(service, spreadsheetId, range): recipes_result = service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=range).execute() recipes = recipes_result.get('values', []) recipes_df = pd.DataFrame.from_records(recipes[1:], columns=recipes[0]) recipes_df.last_date_on_menu = pd.to_datetime(recipes_df.last_date_on_menu, dayfirst=True) recipes_df.set_index('row_number', inplace=True) eligible_recipes = recipes_df[ (recipes_df.last_date_on_menu < PREV_WEEK) | (np.isnat(recipes_df.last_date_on_menu)) ] return recipes_df, eligible_recipes
Далее мы создаем кадр данных с рецептами. Мне нравится работать с Pandas DataFrames, но вы можете использовать другие структуры данных, конечно, конечно.
row_number
это поле, рассчитанное в самом листе Google. Мы используем функцию листа Google Ряд ()
для этого. Это поможет обновить поле last_date_on_menu
в правильном ряду. Мы обновим эту дату, когда рецепт выбран на ближайшую неделю.
Нам нужно убедиться, что рецепт повторяется только через неделю. Итак, мы фильтруем Рецепты_df
По last_date_on_menu
Отказ Эта дата должна быть пустой или до предыдущей недели.
Генерация недели меню
На этом этапе мы назначаем приемлемый рецепт в каждый день на предстоящей неделе.
def generate_weekmenu(service, events_df, traditions, free_events): weekmenu_df = events_df.copy() for i, r in events_df.iterrows(): if r.weekday in traditions.keys(): weekmenu_df.loc[i, 'recipe'] = traditions[r.weekday] weekmenu_df.loc[i, 'description'] = '' else: if r.weekday in ['Saturday', 'Sunday']: row_number = choose_recipe('difficult', i, weekmenu_df, eligible_recipes) update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID) elif r.events_cal_1 in free_events or r.events_cal_2 in free_events \ or pd.isnull(r.events_cal_1) or pd.isnull(r.events_cal_2): row_number = choose_recipe('medium', i, weekmenu_df, eligible_recipes) update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID) else: row_number = choose_recipe('easy', i, weekmenu_df, eligible_recipes) update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID) return weekmenu_df
Для учета планирования работы (занятые и бесплатные события) мы будем использовать Сложность
каждого рецепта. Случайный рецепт предпочтительных сложности будет добавлен в WeekMenu_df.
Наконец, мы бросим его от подходящих рецептов, чтобы избежать дублированных рецептов в той же неделе.
def choose_recipe(difficulty, idx, weekmenu_df, eligible_recipes): choice_idx = np.random.choice(eligible_recipes.query("difficulty == '" + difficulty + "'" ).index.values) weekmenu_df.loc[idx, 'recipe'] = eligible_recipes.loc[choice_idx, 'recipe'] weekmenu_df.loc[idx, 'description'] = eligible_recipes.loc[choice_idx, 'description'] eligible_recipes.drop(choice_idx, inplace=True) return choice_idx
Метод электронные таблицы (). Значения (). Обновление Обновляет лист Google.
def update_sheet(service, row_number, date, spreadsheetId): range = "recepten!F" + str(row_number) values = [[date]] body = {'values' : values} result = service.spreadsheets().values().update(spreadsheetId=spreadsheetId , range=range , valueInputOption='USER_ENTERED' , body=body).execute()
Мы повторяем каждый ряд WeekMenu_df
Отказ Если в будний день является одной из рабочих дней будней, мы назначаем соответствующий рецепт. Для других будних дней мы применяем следующую логику:
- В выходные выберите сложный рецепт
- В течение недели, когда я дома, или у моей жены есть выходной, выбирайте рецепт со средней трудностями
- В течение недели, когда я или моя жена на работе, выберите простой рецепт
Добавление меню недели в календарь Google
Теперь, когда у нас есть меню на ближайшую неделю, мы можем добавить его как события в календарь Google. Я создал отдельный календарь для этого. Поделитесь этим календарем с Client_email
в Complents.json. В настройках вашего календаря вам также необходимо дать ему разрешение на изменения в событиях.
def add_weekmenu_to_calendar(service, weekmenu_df, calendarId): for i, r in weekmenu_df.iterrows(): event = { 'summary': r.recipe, 'description': r.description, 'start': { 'date': i.date().isoformat(), 'timeZone': 'Europe/Brussels' }, 'end': { 'date': i.date().isoformat(), 'timeZone': 'Europe/Brussels' } } event = service.events().insert(calendarId=calendarId, body=event).execute()
Давайте автоматизируем
До сих пор мы учитывали все запрошенные функции для приложения. Но вам все равно придется запустить код вручную, чтобы генерировать меню недели.
Я нашел этот отличный сайт Pythonanywhere Где вы можете запланировать программы Python. Свободная учетная запись для начинающих позволяет ежедневно запланировать одну программу Python. Это именно то, что нам нужно.
Во-первых, нам нужно прошивать все функции вместе и поместить их в файл Python. В этом файле я делаю дополнительную проверку, чтобы увидеть, где мы находимся в текущем меню недели. Я делаю это, глядя на последнюю дату с рецептом в календаре Google с get_date_last_event.
def get_date_last_event(service, calendarId): events_result = service.events().list(calendarId=calendarId , singleEvents=True , orderBy='startTime').execute() date_last_event = events_result.get('items', [])[-1]['start']['date'][:10] date_last_event = datetime.strptime(date_last_event, '%Y-%m-%d').date() return date_last_event
Эта дата хранится в Date_last_recipe.
Если текущий день после Date_last_recipe
минус Nb_days_before
Мы можем генерировать новое недельное меню.
Вы можете найти полный скрипт на Github Отказ
if __name__ == '__main__': # Getting credentials from credentials.json CREDS_PATH = Path.cwd() / "weekmenu" / "credentials.json" creds = service_account.Credentials.from_service_account_file(CREDS_PATH, scopes=cfg.SCOPES) # Creating service objects service_cal = build('calendar', 'v3', credentials=creds) service_sheet = build('sheets', 'v4', credentials=creds) # Defining dates DATE_LAST_RECIPE = get_date_last_event(service_cal, cfg.CALENDARID_WEEKMENU) START_DAY = DATE_LAST_RECIPE + timedelta(days=1) NEXT_WEEK = START_DAY + timedelta(days=6) PREV_WEEK = START_DAY + timedelta(days=-7) START_DAY = format_date(START_DAY) NEXT_WEEK = format_date(NEXT_WEEK) PREV_WEEK = format_date(PREV_WEEK) # Getting the recipes from the Google Sheet recipes_df, eligible_recipes = get_recipes(service_sheet, cfg.SPREADSHEET_ID, cfg.RANGE) # Check if the last weekmenu is still active if DATE_LAST_RECIPE - timedelta(days=cfg.NB_DAYS_BEFORE) < datetime.now().date(): # Getting the events from the Google Calendars events_list_1 = get_events_by_calendarId(service_cal, cfg.CALENDARID_1, START_DAY, NEXT_WEEK, cfg.ALL_EVENTS) events_list_2 = get_events_by_calendarId(service_cal, cfg.CALENDARID_2, START_DAY, NEXT_WEEK, cfg.ALL_EVENTS) # Merge the two events lists events_df = create_events_df(events_list_1, events_list_2) # Generating the weekmenu weekmenu_df = generate_weekmenu(service_sheet, events_df, cfg.TRADITIONS, cfg.FREE_EVENTS) # Adding the weekmenu to a Google Calendar add_weekmenu_to_calendar(service_cal, weekmenu_df, cfg.CALENDARID_WEEKMENU) print('Week menu is added to Google Calendar') else: print('Program stopped. Last week menu is not finished yet.')
На Pythonanywhere я создал подпапку недели меню. Я загрузил следующие файлы config.py, generate_weekmenu.py и complents.json.
Затем я планирую ежедневную задачу, которая запускает сценарий Generate_weekMenu.py в разделе задач. И Voilà, мы все набор.
Результат
После первого пробега скрипта у нас есть хорошее меню в нашем общем календаре Google.
Заключение
Этот скрипт учитывает ваш профессиональный график в ваших календарях Google. Он выбирает ваши предпочтительные рецепты из листа Google. И путем планирования скрипта рецепты появляются автоматически в вашем календаре Google. Это освобождает вас от раздражающей хор, чтобы решить, что есть.
Если вы хотите получить его дальше, вот некоторые идеи для тонкой настройки сценария:
- учитывать время приготовления рецепта
- разрешить традицию иметь хотя бы одну вегетарианскую еду в неделю
- Создайте список продуктовых продуктов для рецептов выбора
Я надеюсь, что вам понравилось читать эту историю. Если у вас есть вопросы или предложения о скрипте, вы можете написать комментарий ниже. И если вам понравилось, не стесняйтесь хлопать за это.