PySimpleGuiでTrelloを操作する

PysimpleGuiでTrelloを操作する簡易GUIアプリを作ってみました。



完成系のイメージです。
ここからボードを選んで、ボード内のリストを選択して、カード内容を出力する、というような形です。



このように出力されます。

ざっくりとした実装の流れ

まずはTrelloを操作するTrelloClientクラスを作成します。
このクラスで、リストの名前を出力したり、カードの名前を出力したりするイメージです。
そしてTrelloClientを継承したTrelloGuiクラスを作り、実際の画面としての機能を実装させる、というような流れです。

Trello API Keyの取得

TrelloをPythonから操作するにはAPI KeyとAPI SecretとAPI TokenとUser IDが必要です。
Trelloにログイン後、下記のリンクから取得できます。
https://trello.com/app-key

TrelloClientクラスの作成

早速コードを書いていきましょう。
クラスは一緒のファイルにしてもいいのですが、コードが長いので、モジュールを分けることにします。

#Trello.py

import requests
from datetime import datetime
from datetime import timedelta

class TrelloClient:

  def __init__(self, user_id, key, secret, token):
    self.user_id = user_id
    self.key = key
    self.secret = secret
    self.token = token
    self.URL = 'https://trello.com/1/'

  def get_board_id(self, board_name):
    """
    ボード名からボードidを特定する
    """
    end_point = f"members/{self.user_id}/boards?key={self.key}&token={self.token}&fields=name"
    json_data = requests.get(self.URL + end_point).json()
    for json in json_data:
      if json['name'] == board_name:
        return json['id']

  def get_board_names(self):
    """
    ボード名のリストを返す
    """
    end_point = f"members/{self.user_id}/boards?key={self.key}&token={self.token}&fields=name"
    json_data = requests.get(self.URL + end_point).json()
    return [json['name'] for json in json_data]

  def get_list_id(self, board_id, list_name):
    """
    ボードidとTrelloリスト名からTrelloリストidを特定して返す
    """
    end_point = f"boards/{board_id}/lists?key={self.key}&token={self.token}&fields=name"
    json_data = requests.get(self.URL + end_point).json()
    for json in json_data:
      if json['name'] == list_name:
        return json['id']

  def get_list_ids_and_names(self, board_id):
    """
    idとnameがタプルになったリストを返す
    """
    end_point = f"boards/{board_id}/lists?key={self.key}&token={self.token}&fields=name"
    json_data = requests.get(self.URL + end_point).json()
    return [(json['id'], json['name']) for json in json_data]

  def add_task(self, list_id, card_name, due_date=None, due_time=None, desc=None):
    """
    カードを特定のリストに追加する
    """
    end_point = "cards"
    if due_date and due_time:
      due = datetime.strptime(due_date + ' ' + due_time, '%Y/%m/%d %H:%M')
      # そのまま登録すると13時間後になる仕様のため
      due = due - timedelta(hours=13)
      due = due.isoformat()
    else:
      due = ""

    query = {
      'key': self.key,
      'token': self.token,
      'idList': list_id,
      'name': card_name,
      'desc': desc,
      'due': due}

    requests.request("POST", self.URL + end_point, params=query)

  def get_cards_in_list(self, list_id):
    """
    Trelloリストの中のカードをjson形式で返す
    """
    end_point = f"lists/{list_id}/cards"
    query = {
      'key': self.key,
      'token': self.token
    }

    response = requests.request(
      "GET",
      self.URL + end_point,
      params=query
    )

    return response.json()


このファイルの中で各メソッドを起動して動作を確認してみましょう。

def job():
  user_id = 'user...'
  key = 'your api key'
  secret = 'your secret'
  token = 'your token'
  client = TrelloClient(user_id, key, secret, token)
  # ボード名の取得
  print(client.get_board_names())

>>>
['プレイリスト作成', '好きなアルバム']


プレイリスト作成好きなアルバムという二つのTrelloボードが存在しています。
プレイリスト作成というボード名を指定することでボードIDが取得できます。

board_id = client.get_board_id(board_name='プレイリスト作成')
print(board_id)

>>>
90b7692hoge795fuga71476a


このボードのリスト名を確認するにはこうします。

list_id_and_names=client.get_list_ids_and_names(board_id)
print(list_id_and_names)

>>>
[('90b7692hoge795fuga71476a', 'しばたさとこ島'), ('90b7692hoge795fuga71476a', 'いじわる全集'), 
('90b7692hoge795fuga71476a', '愛の休日'), ('90b7692hoge795fuga71476a', 'プレイリスト')]


ボード名からも推測された通り、お気に入りのプレイリストを作るためのものでした。
せっかくなのでプレイリストに入れられているカードを確認してみましょう。
このようにします。

list_id = client.get_list_id(board_id=board_id, list_name="プレイリスト")
  cards_json = client.get_cards_in_list(list_id=list_id)
  for json in cards_json:
    print(json['name'])

>>>
あなたはあなた
スプライト・フォー・ユー
後悔
芝の青さ
いのちがtoo short!!
遊んで暮らして
コーポオリンピア
ときだより


良い感じですね。
私が好きな柴田聡子さんのプレイリストを整理するためのボードだったということです。
次にPySimpleGuiを使ってGUIアプリを作成していきましょう。

TrelloGuiクラスの作成

紹介したTrelloClientクラスはそれなりに使えますが、色々と問題があります。
例えば今回は特定のアーティストのプレイリストを出力しましたが、別のアーティストのプレイリストを作りたくなるかもしれません。
そうした場合に、出力するためにソースコードの中のボード名やリスト名をその都度書き換えるのは面倒です。
このあたりを動的にするためにTrelloClientを継承したTrelloGuiクラスを作ります。

# gui.py
import PySimpleGUI as sg
from Trello import TrelloClient

class TrelloGui(TrelloClient):

  def __init__(self, user_id, key, secret, token):
     
    super().__init__(user_id=user_id, key=key, secret=secret, token=token)
    # ボード名をインスタンス変数に格納
    self.board_names = super().get_board_names()
    # windowのインスタンス化
    self.window = sg.Window('for Trello...', size=(560, 560), finalize=True).Layout(self.layout())
    # GUI操作に応じてセットされる変数
    self._trello_board_id = None
    self._trello_list_id = None
    self._trello_list_names = None
    self._trello_list_ids = None

  def set_trello_board_id(self, board_id):     
    self._trello_board_id = board_id

  def set_trello_list_id(self, list_id):
    self._trello_list_id = list_id

  def set_trello_list_names(self, ids_and_names):
    self._trello_list_names = [name for list_id, name in ids_and_names]

  def set_trello_list_ids(self, ids_and_names

):
    self._trello_list_ids = [list_id for list_id, name in ids_and_names]

  def layout(self):
    """レイアウトを定義します"""
     
    # カード追加のフレーム
    col1 = [[sg.T('due date', size=(10, 1))],
        [sg.InputText('', size=(10, 1), key="DUE_DATE")]]
    col2 = [[sg.T('time', size=(5, 1))],
        [sg.InputText('', size=(5, 1), key="DUE_TIME")]]
     
    frame_add_card = sg.Frame('Add Card', [
      [sg.T('Card Name')],
      [sg.MLine('', size=(35, 2), key="CARD_NAME")],
      [sg.T('Description')],
      [sg.MLine('', size=(35, 2), key="DESC")],
      [sg.Column(col1), sg.Column(col2)],
      [sg.Submit('ADD', key="ADD_CARD")]
    ])
     
    # カード出力のフレーム
    frame_print_card = sg.Frame('Print Card', [
      [sg.Button('All Card in Board', key='ALL_LIST_PRINT'),
       sg.Button('Just Selected List', key='SELECTED_LIST_PRINT')],
      [sg.MLine('', size=(35, 20), key='PREVIEW', enable_events=True)]
    ])
     
    return [
      [sg.T('Trello GUI',
         size=(30, 1),
         justification='center',
         font=("Helvetica", 20),
         relief=sg.RELIEF_RIDGE)],
      [sg.T('Choice Board')],
      [sg.Combo(values=self.board_names, size=(20, 1), key='BOARD_NAME', enable_events=True)],
      [sg.T('Choice List')],
      [sg.Combo(values=[''], size=(20, 1), key='LIST_NAME', enable_events=True)],
      [frame_add_card, frame_print_card]
    ]

  def event_loop(self):

    while True:
      event, values = self.window.read()
      if event is None:
        print('exit')
        break
      # 選択されたボード名に応じて、Trelloリスト名コンボボックスの更新、
      # インスタンス変数にTrelloリスト情報をセットします。
      if event == 'BOARD_NAME':
        board_name = values['BOARD_NAME']        
     board_id = super().get_board_id(board_name)
        self.set_trello_board_id(board_id)
        id_and_name_of_trello_list = super().get_list_ids_and_names(board_id)
        self.set_trello_list_names(id_and_name_of_trello_list)
        self.set_trello_list_ids(id_and_name_of_trello_list)
        self.window.FindElement('LIST_NAME').Update(values=self._trello_list_names)

      # Trelloリスト名が選択されたら、Trelloリストidをセットします
      if event == 'LIST_NAME':
        list_name = values['LIST_NAME']
        list_id = super().get_list_id(self._trello_board_id, list_name)
        self.set_trello_list_id(list_id)

	  # カードを追加します
      if event == 'ADD_CARD':
        card_name = values['CARD_NAME']
        desc = values['DESC']
        due_date = values['DUE_DATE']
        due_time = values['DUE_TIME']
        super().add_task(self._trello_list_id, card_name, due_date, due_time, desc)

      # 選択されているボードの全カードを出力します
      if event == 'ALL_LIST_PRINT':
        preview_text = ''
        for list_id, list_name in zip(self._trello_list_ids, self._trello_list_names):
          json_data = super().get_cards_in_list(list_id)
          preview_text += f'=====\n{list_name}\n=====\n'
          for json in json_data:
            card_name = json['name']
            print(card_name)
            preview_text += f'{card_name}\n---\n'
        self.window['PREVIEW'].Update(preview_text)

      # 選択されているTrelloリストのカードを出力します
      if event == 'SELECTED_LIST_PRINT':
        preview_text = ''
        list_name = values['LIST_NAME']
        preview_text += f'=====\n{list_name}\n=====\n'
        json_data = super().get_cards_in_list(self._trello_list_id)
        for json in json_data:
      

     card_name = json['name']
          preview_text += f'{card_name}\n---\n'
        self.window['PREVIEW'].Update(preview_text)

def job():
  user_id = 'user...'
  key = 'your api key'
  secret = 'your secret'
  token = 'your token'
	gui = TrelloGui(user_id, key, secret, token)
  gui.event_loop()

if __name__ == '__main__':
  job()


まず、TrelloClientの時と同様にTrelloの認証に必要な情報を渡して、guiをインスタンス化します。
TrelloGuiクラスはTrelloClientクラスの機能を継承しながら、インスタンス時にGUI Windowを作成します。
Trelloの認証情報に合わせて、self.board_namesからコンボボックスの中身を生成してます。



このように表示されます。
好きなアルバムのボードを選んでみましょう。
このコンボボックスを選択すると、以下の処理が実行されます。

if event == 'BOARD_NAME':
	board_name = values['BOARD_NAME']
	board_id = super().get_board_id(board_name)
	self.set_trello_board_id(board_id)
	id_and_name_of_trello_list = super().get_list_ids_and_names(board_id)
	self.set_trello_list_names(id_and_name_of_trello_list)
	self.set_trello_list_ids(id_and_name_of_trello_list)
	self.window.FindElement('LIST_NAME').Update(values=self._trello_list_names)


適宜TrelloClientクラスの機能を使用して、Trelloリスト名を取得し、GUI上のTrelloリスト名を表すコンボボックスを更新しています。


カードを加えるのも簡単にできます。
カネコアヤノさんの新作よすがを加えてみましょう。
普通ならTrelloはこんな使い方はせずにタスク管理に使うことが多いと思うので、例として期限を設定しておきます。



こういう感じで打ち込んでADDボタンを押します。
そうすると以下の処理が実行されます。

if event == 'ADD_CARD':
	card_name = values['CARD_NAME']
	desc = values['DESC']
	due_date = values['DUE_DATE']
	due_time = values['DUE_TIME']
	super().add_task(self._trello_list_id, card_name, due_date, due_time, desc)


ここも親クラスを呼び出してカードを追加する処理をしています。



無事追加されましたね。




期限と詳細もちゃんと書きこまれています。

TrelloはUIに優れているので、入力、つまりカードの追加はGUIをわざわざ使わなくても簡単にできます。
問題は出力のほうです。
Trelloは有料版だとcsvでエクスポートできますが、無料だとjson形式でしかエクスポートできません。
json形式は一般的に使うにはちょっと微妙なので、テキストで一覧に出力できるようにしています。

しばたさとこ島よすがfavoriteリストに移動させて出力します。

favoriteを選択して、Just Selected Listボタンを押しますと、以下の処理が実行されます。

if event == 'SELECTED_LIST_PRINT':
	preview_text = ''
	list_name = values['LIST_NAME']
	preview_text += f'=====\n{list_name}\n=====\n'
	json_data = super().get_cards_in_list(self._trello_list_id)
	for json in json_data:
		card_name = json['name']
		preview_text += f'{card_name}\n---\n'
		self.window['PREVIEW'].Update(preview_text)


ここも親クラスから、カード名の一覧を取得しています。
このように表示されます。



ALL Card in Boardを押すと、選択しているボードのすべてのカード名が出力されます。

ソースコード

https://github.com/qlitre/pysimplegui-trello

TOPページ