def post(self): """ Collect data from the HTML form to fill in a Trello card. That card will be uploaded to Suggestion Box board, on the corresponding list, determined by the "System" attribute given in the form. """ # Get form data date = datetime.now() title = self.get_argument('title') area = self.get_argument('area') system = self.get_argument('system') importance = self.get_argument('importance') difficulty = self.get_argument('difficulty') user = self.get_current_user_name() description = self.get_argument('description') suggestion = self.get_argument('suggestion') client = TrelloClient(api_key = self.application.trello_api_key, api_secret = self.application.trello_api_secret, token = self.application.trello_token) # Get Suggestion Box board boards = client.list_boards() suggestion_box = None for b in boards: if b.name == 'Suggestion Box': suggestion_box = client.get_board(b.id) break # Get the board lists (which correspond to System in the form data) and # concretely get the list where the card will go lists = b.all_lists() card_list = None for l in lists: if l.name == system: card_list = l break # Create new card using the info from the form new_card = card_list.add_card(TITLE_TEMPLATE.format(title=title, area=area)) new_card.set_description(DESCRIPTION_TEMPLATE.format(date = date.ctime(), area=area, system=system, importance=importance, difficulty=difficulty, user=user, description=description, suggestion=suggestion)) # Save the information of the card in the database self.application.suggestions_db.create({'date': date.isoformat(), 'card_id': new_card.id, 'description': new_card.description, 'name': new_card.name, 'url': new_card.url, 'archived': False}) self.set_status(200)
def save_to_trello(self): # print clean_form_data api_key = settings.TRELLO_KEY secret = settings.TRELLO_SECRET token = settings.TRELLO_TOKEN board = settings.TRELLO_REQUEST_BOARD c = TrelloClient(api_key, secret, token) b = c.get_board(board) # Currently we default to adding card to first list l = b.all_lists()[0] label_list = b.get_labels() ds_name = "%s - %s" % (self.dataset_name, self.dataset_source) ds_description = "%s\n%s\nRequested by: %s %s, %s" % \ (self.dataset_name, self.dataset_description, self.user_first_name, self.user_last_name, self.user_email) try: label_to_add = next(x for x in label_list if x.name == 'Request') except StopIteration: label_to_add = b.add_label('Request', "lime") try: card = l.add_card(ds_name, ds_description, [label_to_add]) self.trello_id = card.id except Exception: pass
def get_trello_cards(): """ Makes an API call to Trello to get all of the cards from Weird Canada's New Canadiana board. Requires an OAuth key. :return: list of trello cards """ trello_client = TrelloClient( api_key=os.environ.get('TRELLO_API_KEY'), api_secret=os.environ.get('TRELLO_API_SECRET'), token=os.environ.get('TRELLO_OAUTH_KEY'), token_secret=os.environ.get('TRELLO_OAUTH_SECRET') ) new_canadiana_board_id = os.environ.get('BOARD_ID') new_canadiana_board = trello_client.get_board(new_canadiana_board_id) return new_canadiana_board.open_cards()
class TrelloManager(object): def __init__(self): trello_config = Config.open_api.trello self.client = TrelloClient( api_key=trello_config.API_KEY, api_secret=trello_config.API_SECRET, token=trello_config.TOKEN, ) self.board = self.client.get_board(trello_config.BOARD) def get_list_by_name(self, name): for l in self.board.all_lists(): if l.name == name: return l return None def get_card_count_by_list_name(self, name): l = self.get_list_by_name(name) if l is None: raise ValueError(f"there is no {name} list in trello board.") return len(l.list_cards()) def get_random_card_name(self, list_name: str = "Inbox"): l = self.get_list_by_name(list_name) if l is None or len(l.list_cards()) == 0: return None return random.choice(l.list_cards()).name def add_card(self, list_name: str, card_name): l = self.get_list_by_name(list_name) l.add_card(card_name) def archive_all_cards(self, list_name): l = self.get_list_by_name(list_name) l.archive_all_cards() def clean_board(self, except_list_name=None): l_list = self.board.all_lists() for l in l_list: if except_list_name is not None and l.name in except_list_name: pass else: l.archive_all_cards()
def sync(events, token): trello = TrelloClient(api_key=TRELLO_KEY, token=token) board = trello.get_board('55f7167c46760fcb5d68b385') far_away, less_2_months, less_1_month, less_1_week, today, past = board.all_lists() all_cards = {card_id(c): c for c in board.all_cards()} date_today = datetime.date.today() for e in events: card = all_cards.get(e.id) if not card: card = create_card(e, far_away) create_checklist(card) #fetch card to get due date try: card.fetch() except ResourceUnavailable: print("Oopsie: too many requests! Let's wait 10 seconds!") time.sleep(10) card.fetch() if e.date != card.due_date.date(): print('Changing due date of {} to {}'.format(e.city, e.date)) card.set_due(e.date) distance = (e.date - date_today).days if distance < 0: right_list = past elif distance == 0: right_list = today elif distance < 7: right_list = less_1_week elif distance < 30: right_list = less_1_month elif distance < 60: right_list = less_2_months else: right_list = far_away ensure_card_in_list(card, right_list)
def sync(events, token): trello = TrelloClient(api_key=TRELLO_KEY, token=token) board = trello.get_board("55f7167c46760fcb5d68b385") far_away, less_2_months, less_1_month, less_1_week, today, past = board.all_lists() all_cards = {card_id(c): c for c in board.all_cards()} date_today = datetime.date.today() for e in events: card = all_cards.get(e.id) if not card: card = create_card(e, far_away) create_checklist(card) # fetch card to get due date card.fetch() if e.date != card.due_date.date(): print("Changing due date of {} to {}".format(e.city, e.date)) card.set_due(e.date) distance = (e.date - date_today).days if distance < 0: right_list = past elif distance == 0: right_list = today elif distance < 7: right_list = less_1_week elif distance < 30: right_list = less_1_month elif distance < 60: right_list = less_2_months else: right_list = far_away ensure_card_in_list(card, right_list)
class TrelloCli: def __init__(self, file='config.yml'): """ Load Trello api keys from yaml file""" with open(file, 'r') as stream: try: config = yaml.safe_load(stream) self.__client = TrelloClient( api_key = config['key'], api_secret = config['token'] ) except yaml.YAMLError as exc: print(exc) def get_board(self, board_name): """ Get the board from the board name """ boards = self.__client.list_boards() for board in boards: if board.name == board_name: return self.__client.get_board(board.id) def get_list(self, board, list_name): lists = board.all_lists() for list in lists: if list.name == list_name: return board.get_list(list.id) def get_member(self, board, member_name): members = board.all_members() for member in members: if member.full_name == member_name: return member def display_cards(self, trello_list): cards = trello_list.list_cards() for card in cards: print(card.name)
class trello: def __init__(self, apiKey, TOKEN): self.apiKey = apiKey self.token = TOKEN self.client = TrelloClient(api_key=apiKey, api_secret='your-secret', token=TOKEN, token_secret='your-oauth-token-secret') def printTrello(self): all_boards = self.client.list_boards() last_board = all_boards[-1] print("Boards ") for board in all_boards: print("Board Name :", board.name, " Board ID", board.id) for list in board.all_lists(): print("\t", "ListName :", list.name, "listID :", list.id) for card in list.list_cards(""): print("\t\t", "cardName :", card.name, "cardID :", card.id) #for card in board.all_cards(): # print("\tCard Name :",card.name," Card ID",card.id) ####### BOARD OPERATIONS def getBoard(self, boardID): self.board = self.client.get_board(board_id=boardID) return self.board def getBoardByName(self, boardName): all_boards = self.client.list_boards() for board in all_boards: if board.name == boardName: self.board = board return board return None # close all boards def clearBoards(self): for board in self.client.list_boards(): board.close() def createBoard(self, boardName, organizationID=None, permission_level="private"): self.board = self.client.add_board(board_name=boardName, source_board=None, organization_id=organizationID, permission_level=permission_level) for list in self.board.get_lists(None): self.board.get_list(list.id).close() self.createList("To Do:", self.board.id, 1) self.createList("Doing:", self.board.id, 2) self.createList("Build:", self.board.id, 3) self.createList("Test:", self.board.id, 4) self.createList("Deploy:", self.board.id, 5) return self.board def closeBoardByName(self, boardName=None): if boardName != None: all_boards = self.client.list_boards() for board in all_boards: if board.name == boardName: return board.close() else: if self.board != None: self.closeBoard(self.board.id) def closeBoard(self, boardId=None): if boardId != None: return self.getBoard(boardID=boardId).close() else: if self.board != None: self.board.close() else: return None def boardList(self): return self.client.list_boards() ####### END BOARD OPERATIONS ####### LIST OPERATIONS def getList(self, listID, boardID): return self.client.get_board(board_id=boardID).get_list(list_id=listID) def getListByName(self, listID, boardID): return self.client.get_board(board_id=boardID).get_list(list_id=listID) def createList(self, listName, boardID, sira=None): board = self.client.get_board(boardID) addedlist = board.add_list(listName, sira) return addedlist def closeList(self, listID, boardID): return self.client.get_board(boardID).get_list(listID).close() def closeJustListID(self, listID): # unsafe url = "https://api.trello.com/1/lists/" + listID + "?closed=true&key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("PUT", url, params=querystring) return response.text ####### END LIST OPERATIONS ####### CARD OPERATIONS def getCard(self, cardID): return self.client.get_card(card_id=cardID) def createCard(self, boardID, listID, cardName): self.getList(boardID=boardID, listID=listID).add_card(name=cardName, labels=None, due="", source=None, position=None) def removeCard(self, cardID): url = "https://api.trello.com/1/cards/" + cardID + "?key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("PUT", url, params=querystring) return response.text def moveCard(self, cardID, desListID): self.getCard(cardID=cardID).change_list(list_id=desListID) ####### END CARD OPERATIONS ####### TEAM MEMBER OPERATIONS def addMemberBoard(self, boardID, memberID): board = self.client.get_board(board_id=boardID) board.add_member(memberID) # ORGANIZATION OPERATIONS def getOrganization(self, organizationID): return self.client.get_organization(organizationID) def getOrganizationByName(self, organizationName): for organization in self.listOrganizations(): if organization.name == "": return organization return None def listOrganizations(self): self.client.list_organizations() return self.client.list_organizations() def createOrganization(self, organizationName): url = "https://api.trello.com/1/organizations?displayName=" + organizationName + "&desc=" + organizationName + "&key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("POST", url, params=querystring) organizationID = str.split(response.text, ",")[0].split("\"")[3] return organizationID def addOrganizationMember(self, organizationID, mail, memberType="normal", fullName="member"): configuredMail = str.replace(mail, "@", "%40") url = "https://api.trello.com/1/organizations/" + organizationID + "/members?email=" + configuredMail + "&fullName=" + fullName + "&type=" + memberType + "&key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("PUT", url, params=querystring) data = (json.loads(response.text)) memberID = (data["memberships"][-1]["idMember"]) return memberID def removeOrganizationMember(self, organizationID, memberID): url = "https://api.trello.com/1/organizations/" + organizationID + "/members/" + memberID + "?key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("DELETE", url, params=querystring) return response.text def removeOrganization(self, organizationID): url = "https://api.trello.com/1/organizations/" + organizationID + "?key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("DELETE", url, params=querystring) return response.text def addCommendToCard(self, cardID, commendText): url = "https://api.trello.com/1/cards/" + cardID + "/actions/comments?text=" + commendText + "&key=" + self.apiKey + "&token=" + self.token querystring = {} response = requests.request("POST", url, params=querystring) return response.text
class ServiceTrello(ServicesMgr): """ Serivce Trello """ # Boards own Lists own Cards def __init__(self, token=None, **kwargs): super(ServiceTrello, self).__init__(token, **kwargs) # app name self.app_name = DjangoThConfig.verbose_name # expiration self.expiry = "30days" # scope define the rights access self.scope = 'read,write' self.oauth = 'oauth1' self.service = 'ServiceTrello' base = 'https://www.trello.com' self.AUTH_URL = '{}/1/OAuthAuthorizeToken'.format(base) self.REQ_TOKEN = '{}/1/OAuthGetRequestToken'.format(base) self.ACC_TOKEN = '{}/1/OAuthGetAccessToken'.format(base) self.consumer_key = settings.TH_TRELLO_KEY['consumer_key'] self.consumer_secret = settings.TH_TRELLO_KEY['consumer_secret'] if token: token_key, token_secret = token.split('#TH#') try: self.trello_instance = TrelloClient(self.consumer_key, self.consumer_secret, token_key, token_secret) except ResourceUnavailable as e: us = UserService.objects.get(token=token) logger.error(e.msg, e.error_code) update_result(us.trigger_id, msg=e.msg, status=False) def read_data(self, **kwargs): """ get the data from the service :param kwargs: contain keyword args : trigger_id at least :type kwargs: dict """ trigger_id = kwargs.get('trigger_id') data = list() cache.set('th_trello_' + str(trigger_id), data) return data def save_data(self, trigger_id, **data): """ let's save the data :param trigger_id: trigger ID from which to save data :param data: the data to check to be used and save :type trigger_id: int :type data: dict :return: the status of the save statement :rtype: boolean """ from th_trello.models import Trello data['output_format'] = 'md' title, content = super(ServiceTrello, self).save_data(trigger_id, **data) if len(title): # get the data of this trigger t = Trello.objects.get(trigger_id=trigger_id) # footer of the card footer = self.set_card_footer(data, t) content += footer # 1 - we need to search the list and board where we will # store the card so ... # 1.a search the board_id by its name # by retrieving all the boards boards = self.trello_instance.list_boards() board_id = '' my_list = '' for board in boards: if t.board_name == board.name: board_id = board.id break if board_id: # 1.b search the list_id by its name my_board = self.trello_instance.get_board(board_id) lists = my_board.open_lists() # just get the open list ; not all the archive ones for list_in_board in lists: # search the name of the list we set in the form if t.list_name == list_in_board.name: # return the (trello) list object # to be able to add card at step 3 my_list = my_board.get_list(list_in_board.id) break # we didnt find the list in that board # create it if my_list == '': my_list = my_board.add_list(t.list_name) else: # 2 if board_id and/or list_id does not exist, create it/them my_board = self.trello_instance.add_board(t.board_name) # add the list that didnt exists and # return a (trello) list object my_list = my_board.add_list(t.list_name) # 3 create the card # create the Trello card my_list.add_card(title, content) sentence = str('trello {} created').format(data['link']) logger.debug(sentence) status = True else: sentence = "no token or link provided for trigger ID " \ "{}".format(trigger_id) update_result(trigger_id, msg=sentence, status=False) status = False return status @staticmethod def set_card_footer(data, trigger): """ handle the footer of the note """ footer = '' if data.get('link'): provided_by = _('Provided by') provided_from = _('from') footer_from = "<br/><br/>{} <em>{}</em> {} <a href='{}'>{}</a>" description = trigger.trigger.description footer = footer_from.format(provided_by, description, provided_from, data.get('link'), data.get('link')) import pypandoc footer = pypandoc.convert(footer, 'md', format='html') return footer def auth(self, request): """ let's auth the user to the Service :param request: request object :return: callback url :rtype: string that contains the url to redirect after auth """ request_token = super(ServiceTrello, self).auth(request) callback_url = self.callback_url(request) # URL to redirect user to, to authorize your app auth_url_str = '{auth_url}?oauth_token={token}' auth_url_str += '&scope={scope}&name={name}' auth_url_str += '&expiration={expiry}&oauth_callback={callback_url}' auth_url = auth_url_str.format(auth_url=self.AUTH_URL, token=request_token['oauth_token'], scope=self.scope, name=self.app_name, expiry=self.expiry, callback_url=callback_url) return auth_url def callback(self, request, **kwargs): """ Called from the Service when the user accept to activate it :param request: request object :return: callback url :rtype: string , path to the template """ return super(ServiceTrello, self).callback(request, **kwargs)
import numpy as numpy from datetime import date, datetime from trello import TrelloClient from card_plus import CardPlus # Create connection to Trello trello_client = TrelloClient( api_key = "daad4d77e580ebd5d2d522f439b07f74", api_secret = "c7a8b9ac48bb7074b92161f37f0ccaaf23ee8f165e234bccb86013dd0a28aa19", token = "00dc3572fd9d961e96966c4e2c983f196e69604f42fc9dca71f82484db9f6efd", token_secret = "129721ad1698d337826dcf66443f7e99" ) # Get the board board = trello_client.get_board("7R56bpzE") # Get all the labels for the board label_list = board.get_labels() # Pull all the cards from the board using the basic Card object card_list = board.all_cards() # Initialise the list of the CardPlus objects which is a suclass of the base Card object that stores more data card_plus_list = [] # Convert Card objects to CardPlus objects while adding extra data todays_date = date.today() for card in card_list: # Number of working days since card last updated last_updated_date = card.date_last_activity
class EClaire(object): def __init__(self, credentials, boards=None, qrcode_enabled=True): self.trello_client = TrelloClient(api_key=credentials["public_key"], token=credentials["member_token"]) self.boards = boards self.qrcode_enabled = qrcode_enabled def process_boards(self, dry_run=False, notify_fn=None, notify_config=None): """ Process each board in self.boards """ for name, board_config in self.boards.items(): log.info("Polling %s", name) processed = self.process_board(board_config, dry_run) if board_config.get("notify", False) and notify_fn is not None: for card in processed: notify_fn(card, **notify_config) def process_board(self, board_config, dry_run=False): """ Process each card in a given board """ processed = [] for card in self.fetch_cards(board_id=board_config["id"]): log.info('Printing card "%s"', card.name) pdf = generate_pdf(card=card, qrcode_enabled=self.qrcode_enabled) if not dry_run: print_card(pdf, printer_name=board_config["printer"]) self.update_card(card, board_config) processed.append(card) return processed def fetch_cards(self, board_id): """ Fetch all candidate cards on a board for processing """ data = [] board = self.trello_client.get_board(board_id) for card in board.open_cards(): if card.labels and FILTER_LABEL in (l.name for l in card.labels): card.fetch_actions() data.append(card) return data def discover_labels(self): """ Store object references for special labels """ for name, config in self.boards.items(): board = self.trello_client.get_board(config["id"]) labels = {} for label in board.get_labels(limit=1000): if label.name in SPECIAL_LABELS: labels[label.name] = label missing = set(SPECIAL_LABELS) - set(labels.keys()) if missing: log.fatal('Board "%s" is missing the labels %s', board.name, " and ".join(missing)) log.fatal("Exiting") sys.exit(1) config["labels"] = labels def remove_label(self, card, label): """ Remove a lable from a card. At the time of writing there is no way to remove a label with py-trello """ self.trello_client.fetch_json("/cards/{}/idLabels/{}".format( card.id, label.id), http_method="DELETE") def update_card(self, card, board_config): """ Replace PRINTME label with PRINTED """ printme_label = board_config["labels"]["PRINTME"] printed_label = board_config["labels"]["PRINTED"] self.remove_label(card, printme_label) if printed_label not in card.labels: card.add_label(printed_label) def list_boards(self): """ Fetch all board IDs from trello & print them out """ for board in self.trello_client.list_boards(): print("Board:", board.name) print(" ID:", board.id) print()
class TrelloBoardTestCase(unittest.TestCase): """ Tests for TrelloClient API. Note these test are in order to preserve dependencies, as an API integration cannot be tested independently. """ def setUp(self): self._trello = TrelloClient(os.environ['TRELLO_API_KEY'], token=os.environ['TRELLO_TOKEN']) for b in self._trello.list_boards(): if b.name == os.environ['TRELLO_TEST_BOARD_NAME']: self._board = b break try: self._list = self._board.open_lists()[0] except IndexError: self._list = self._board.add_list('List') def _add_card(self, name, description=None): try: card = self._list.add_card(name, description) self.assertIsNotNone(card, msg="card is None") self.assertIsNotNone(card.id, msg="id not provided") self.assertEquals(card.name, name) return card except Exception as e: print(str(e)) self.fail("Caught Exception adding card") def test40_add_card(self): name = "Testing from Python - no desc" card = self._add_card(name) self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") card2 = self._trello.get_card(card.id) self.assertEqual(card.name, card2.name) def test41_add_card(self): name = "Testing from Python" description = "Description goes here" card = self._add_card(name, description) self.assertEquals(card.description, description) self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") card.fetch() self.assertIsNotNone(card.member_id) self.assertIsNotNone(card.short_id) self.assertIsNotNone(card.list_id) self.assertIsNotNone(card.comments) self.assertIsNotNone(card.checklists) self.assertIsInstance(card.create_date, datetime) def test42_add_card_with_comments(self): name = "Card with comments" comment = "Hello World!" card = self._add_card(name) card.comment(comment) card.fetch(True) self.assertEquals(card.description, '') self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") self.assertEquals(len(card.comments), 1) self.assertEquals(card.comments[0]['data']['text'], comment) def test43_delete_checklist(self): name = "Card with comments" card = self._list.add_card(name) card.fetch(True) name = 'Checklists' checklist = card.add_checklist(name, ['item1', 'item2']) self.assertIsNotNone(checklist, msg="checklist is None") self.assertIsNotNone(checklist.id, msg="id not provided") self.assertEquals(checklist.name, name) checklist.delete() card.delete() def test44_attach_url_to_card(self): name = "Testing from Python - url" card = self._add_card(name) card.attach(name='lwn', url='http://lwn.net/') card.fetch() self.assertEquals(card.badges['attachments'], 1) card.delete() def test52_get_cards(self): cards = self._board.get_cards() self.assertEquals(len(cards), 4) for card in cards: if card.name == 'Testing from Python': self.assertEqual(card.description, 'Description goes here') elif card.name == 'Testing from Python - no desc': self.assertEqual(card.description, '') elif card.name == 'Card with comments': self.assertEqual(card.description, '') else: self.fail(msg='Unexpected card found') self.assertIsInstance(self._board.all_cards(), list) self.assertIsInstance(self._board.open_cards(), list) self.assertIsInstance(self._board.closed_cards(), list) def test52_add_card_set_due(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card(name, description) # Set the due date to be 3 days from now today = datetime.today() day_detla = timedelta(3) due_date = today + day_detla card.set_due(due_date) expected_due_date = card.due # Refresh the due date from cloud card.fetch() actual_due_date = card.due[:10] self.assertEquals(expected_due_date, actual_due_date) def test53_checklist(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card(name, description) name = 'Checklists' checklist = card.add_checklist(name, ['item1', 'item2']) self.assertIsNotNone(checklist, msg="checklist is None") self.assertIsNotNone(checklist.id, msg="id not provided") self.assertEquals(checklist.name, name) checklist.rename('Renamed') self.assertEquals(checklist.name, 'Renamed') def test54_set(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card('noname') card.set_name(name) card.set_description(description) self.assertEquals(card.name, name) self.assertEquals(card.description, description) def test60_delete_cards(self): cards = self._board.get_cards() for card in cards: card.delete() def test70_all_members(self): self.assertTrue(len(self._board.all_members()) > 0) def test71_normal_members(self): self.assertTrue(len(self._board.normal_members()) >= 0) def test72_admin_members(self): self.assertTrue(len(self._board.admin_members()) > 0) def test73_owner_members(self): members = self._board.owner_members() self.assertTrue(len(members) > 0) member = members[0].fetch() self.assertNotEqual(member.status, None) self.assertNotEqual(member.id, None) self.assertNotEqual(member.bio, None) self.assertNotEqual(member.url, None) self.assertNotEqual(member.username, None) self.assertNotEqual(member.full_name, None) self.assertNotEqual(member.initials, None) member2 = self._trello.get_member(member.id) self.assertEqual(member.username, member2.username) def test80_unauthorized(self): client = TrelloClient('a') self.assertRaises(Unauthorized, client.list_boards) def test81_resource_unavailable(self): self.assertRaises(ResourceUnavailable, self._trello.get_card, '0') def test90_get_board(self): board = self._trello.get_board(self._board.id) self.assertEqual(self._board.name, board.name)
class Recent: """ Class used to retrieve recent Trello updates. Uses sarumont's py-trello API wrapper lightly. Currently I just grab the full lump of data from the board API call. For my use of Trello this works just fine, but if you're using it really heavily I could see this using up too much memory, or the resultant xml being too big or something. Could improve that by specifying exactly what we want with the ?filter param, or digging into using the lists/cards apis more directly. However, I'm currently serving over 100 rss feeds on trellorss.appspot.com with this method, so it works well enough, even if it kind of bothers me. """ def __init__(self, api_key, api_private_key, token=None, board_id=None, public_board=False, all_private=False): self.api_key = api_key self.api_private_key = api_private_key self.token = token self.public_only = False if self.token is None: self.public_only = True self.trello = TrelloClient(self.api_key, self.api_private_key, self.token) self.boards = None # Lazy, so doesn't fetch until we ask for them self.board_id = board_id self.public_board = public_board self.all_private = all_private # A list of items currently supported. The user should pass in one of the keys below, # and we use the values when passing it to the Trello API. self.items = config.all_item_types def create_date(self, date): return datetime.strptime(date[:-5], '%Y-%m-%dT%H:%M:%S') def fetch_items(self, item_names): """ Fetch the specified recent activity for item_names """ for item in item_names: if item not in self.items: raise InvalidItem("%s is not a supported item." % item) items = ','.join([self.items[item] for item in item_names]) if self.all_private: return self._get_activity(items, None) else: return self._get_activity(items, self._get_boards()) def _get_boards(self): """ Calls the list_boards() method if we haven't already """ if self.board_id: self.boards = self.trello.get_board(self.board_id) elif self.boards is None: self.boards = self.trello.list_boards() return self.boards def _get_activity(self, action_filter, boards): """Given a action filter, returns those actions for boards from the Trello API""" actions = [] if self.all_private: self.trello.info_for_all_boards(action_filter) actions.append(self.trello.all_info) else: if isinstance(boards, list) is False: boards = [boards] for board in boards: if board.closed is False: board.fetch_actions(action_filter) if len(board.actions) > 0: actions.append(board.actions) return actions
class TrelloBot: # targets = ["Todo", "In progress"] targets = ["Todo"] def __init__( self, conf_path="trello/trello.ini", process_config="config.ini" ): self.config = ConfigParser() self.config.read(conf_path) self.processor = Processor(process_config) self.client = TrelloClient( api_key=self.config["trello"]["key"], api_secret=self.config["trello"]["token"], ) self.id = self.config["trello"]["member_id"] self.board = self.client.get_board( self.config["trello"]["board_id"] ) print(self.board) self.lists = self.get_lists() def update(self): matched_cards = self.get_cards_with_case() self.process_cards_with_case(matched_cards) def get_lists(self): return [ l for l in self.board.get_lists("open") if l.name in self.targets ] def get_all_cards(self): return [ c for l in self.lists for c in l.list_cards() ] def get_cards_with_case(self): cards = self.get_all_cards() matched = [ (card, found) for card, found in [(c, find_case_id(c.name)) for c in cards] if found ] return matched def get_own_comments(self, comments): own_comments = [ c for c in comments if c["idMemberCreator"] == self.id ] return own_comments def is_bot_comment(self, comment): return Markdowner.signature in comment["data"]["text"] def process_cards_with_case(self, matched_list): for card, matched in matched_list: print(card, matched) card.fetch() comments = self.get_own_comments(card.fetch_comments()) bot_comments = [c for c in comments if self.is_bot_comment(c)] cases = { m: self.processor.json_to_case(j) if j else j for m, j in [(m, self.processor.load_json(m)) for m in matched] } comment_text = str(CaseComment(cases, self.processor)) if bot_comments: # get comment id comment_id = bot_comments[0]["id"] card.update_comment(comment_id, comment_text) else: card.comment(comment_text) print("------")
def post(self): """ Collect data from the HTML form to fill in a Trello card. That card will be uploaded to Suggestion Box board, on the corresponding list, determined by the "System" attribute given in the form. """ # Get form data date = datetime.now() title = self.get_argument('title') area = self.get_argument('area') system = self.get_argument('system') importance = self.get_argument('importance') difficulty = self.get_argument('difficulty') user = self.get_current_user() description = self.get_argument('description') suggestion = self.get_argument('suggestion') client = TrelloClient(api_key=self.application.trello_api_key, api_secret=self.application.trello_api_secret, token=self.application.trello_token) # Get Suggestion Box board boards = client.list_boards() suggestion_box = None for b in boards: if b.name == 'Suggestion Box': suggestion_box = client.get_board(b.id) break # Get the board lists (which correspond to System in the form data) and # concretely get the list where the card will go lists = b.all_lists() card_list = None for l in lists: if l.name == system: card_list = l break # Create new card using the info from the form new_card = card_list.add_card( TITLE_TEMPLATE.format(title=title, area=area)) new_card.set_description( DESCRIPTION_TEMPLATE.format(date=date.ctime(), area=area, system=system, importance=importance, difficulty=difficulty, user=user.name, description=description, suggestion=suggestion)) # Save the information of the card in the database self.application.suggestions_db.create({ 'date': date.isoformat(), 'card_id': new_card.id, 'description': new_card.description, 'name': new_card.name, 'url': new_card.url, 'archived': False }) self.set_status(200)
# Подключение к БД conn = mysql.connector.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, password=MYSQL_SECRET, database=MYSQL_DB) cursor = conn.cursor() elif config['db']['db'].strip() == 'sqlite': SQLITE_DB = BASE_PATH + config['sqlite']['pathtodb'] conn = sqlite3.connect(SQLITE_DB) cursor = conn.cursor() else: raise Exception( 'В файле config.ini в контексте [db] не указан тип БД или указан не правильно' ) # Подключение к трелло client = TrelloClient( api_key=TR_API_KEY, token=TR_TOKEN, ) # Получение объекта доски по ее ид BOARD = client.get_board(TR_BOARD_ID) if BOARD.closed: print("Доска указання в конфигурации - закрыта") sys.exit(1) BOARD_SHORT_LINK = BOARD.url
from trello import TrelloClient from pptx import Presentation from pptx.util import Inches, Pt from datetime import * boardID = "bn3F6E4m" # whatever board that is, eg. https://trello.com/b/uDtSJilF/iab207 #init client = TrelloClient( api_key='c75ebe201687acd0b1d4c55ec98aa42f', api_secret='02855a09c9756eb487c4e83b76ff5e3090ad111860b3cdbd0c5476d0b1fcd7e6', token='b03a2e5dd545cc1fbeebc65e520aafaa0faf6a37eee4df6d3a1e21e7da16475a' ) # get board board = client.get_board(boardID) # tap that list list_sections = board.list_lists() # Generate Presentation prs = Presentation() title_slide_layout = prs.slide_layouts[0] slide = prs.slides.add_slide(title_slide_layout) left = top = width = height = Inches(1) background = slide.shapes.add_picture(r'static\background.png', 0, 0) title = slide.shapes.title subtitle = slide.placeholders[1] title.text = "QUT MOTORSPORT"
class Trello: def __init__(self, tconfig, config_debug=False, config_proxy=None): from trello import TrelloClient self.client = TrelloClient(api_key=tconfig['tk'], api_secret=tconfig['ts'], proxies=config_proxy) #if (config_proxy): # self.client = TrelloClient(api_key=tconfig['tk'], api_secret=tconfig['ts'], proxies=config_proxy) #else: # self.client = TrelloClient(api_key=config_key, api_secret=config_secret) self.requests_board_id = tconfig['tboard'] self.debug = config_debug self.board = None self.labels = None self.motives = tconfig["requests"] def getAllboards(self): all_boards = self.client.list_boards() print(">>Requests pending: {}".format(all_boards)) return all_boards def getRequestsBoard(self): self.board = self.client.get_board(self.requests_board_id) lists = self.board.list_lists() pending = lists[0] if (self.debug): pending_cards = pending.list_cards() print(">>Requests pending: {}".format(pending_cards)) return pending def getLabels(self): if (self.labels is None): self.labels = self.board.get_labels() if (self.debug): print(">>Labels: {}".format(self.labels)) return self.labels def getLabel(self, name): if (self.labels is None): self.getLabels() label = next((x for x in self.labels if name in x.name), None) if (self.debug): print(">>Label: {}".format(label)) return label def isCardNameCreated(self, query): #board, if (self.debug): print("Search Card Name: {}".format(query)) boards = list() if (self.board is not None): boards.append(self.board.id) else: boards.append(self.requests_board) cards = self.client.search(query, partial_match=False, models=['cards'], board_ids=boards) if (self.debug): print("Card Name: {} Matches: {}".format(query, len(cards))) print("Results: {}".format(cards)) if (len(cards) > 0): for c in cards: if (c.name == query): return True return False else: return False def addRequests(self, requests, woffu): import json pending = self.getRequestsBoard() for request in requests: print("Request: {} {}".format(request['$id'], request['RequestId'])) checklist_names = list() checklist_states = list() checklist_title = 'Datos de la solicitud' cardname_prefix = '' user_name = '' company_id = None duedate = None labels = list() files = list() # If request status is not accepted yet, not added to Trello board if (request['RequestStatusId'] < 20): print("Obviamos request: Request status {}".format( request['RequestStatusId'])) continue # User name if (request['UserId'] is not None): user = woffu.getUser(request['UserId']) company_id = user['CompanyId'] if (user['FirstName'] and user['LastName']): user_name = str(user['FirstName']) + " " + str( user['LastName']) #checklist_names.append("Nombre: " + user_name) #checklist_states.append(True) else: if (user['FirstName'] or user['LastName']): user_name = (str(user['FirstName']) if user['FirstName'] else "Vacío") + " " + ( str(request['LastName']) if request['LastName'] else "Vacío") #checklist_names.append("Nombre: " + user_name) #checklist_states.append(False) else: user_name = 'Nombre trabajador vacío' #checklist_names.append("Nombre: Vacío") #checklist_states.append(False) # User motive # Comment: If we only consider selected Motives, then empty is no valid if (request['AgreementEventId'] is not None): motive = woffu.getAgreementEvent(request['AgreementEventId']) if (motive['Name'] in self.motives): if (motive['Name'] is not None): checklist_names.append("Motivo: " + str(motive['Name'])) checklist_states.append(True) label = self.getLabel(str(motive['Name'])) if label is not None: labels.append(label) cardname_prefix = str(motive['Name']) + ': ' else: continue # else: # cardname_prefix = 'Motivo vacío: ' # checklist_names.append("Motivo: Vacío") # checklist_states.append(False) # else: # cardname_prefix = 'Motivo vacío: ' # checklist_names.append("Motivo: Vacío") # checklist_states.append(False) # Generate Card Name card_name = ("[" + str(request['RequestId']) + "] [" + str(woffu.getCompanyName(company_id)) + "] " + cardname_prefix + user_name) if (self.isCardNameCreated(card_name)): continue # Start date if (request['StartDate'] is not None): checklist_names.append( "Fecha Inicio: " + str(helper.getDateFormat(request['StartDate']))) checklist_states.append(True) else: checklist_names.append("Fecha Inicio: Vacío") checklist_states.append(False) # End date if (request['EndDate'] is not None): checklist_names.append( "Fecha Fin: " + str(helper.getDateFormat(request['EndDate']))) checklist_states.append(True) else: checklist_names.append("Fecha Fin: Vacío") checklist_states.append(False) # Comments = Description if (request['QuickDescription'] is not None): checklist_names.append("Comentarios: " + str(request['QuickDescription'])) checklist_states.append(True) else: checklist_names.append("Comentarios: Vacío") checklist_states.append(False) # Request document download # Warning: Docs is always NULL, even if request has documents # => Always checking #if (True): #request['Docs'] is not None ): documents = woffu.getRequestsDocuments(request['RequestId']) if (self.debug): print("Documents: {} ".format(documents)) if (documents['Documents'] is not None): for d in documents['Documents']: file = woffu.getDocumentDownload(d["DocumentId"]) file['name'] = d['Name'] files.append(file) if (self.debug): print("Files: {} ".format(files)) if (files): fl = list() for f in files: fl.append(f['name']) checklist_names.append("Doc Adjunto: " + ', '.join(fl)) checklist_states.append(True) else: checklist_names.append("Doc Adjunto: Vacío") checklist_states.append(False) card = pending.add_card(name=card_name, desc=None, labels=labels, due=duedate, source=None, position=None, assign=None) card.add_checklist(checklist_title, checklist_names, checklist_states) if (len(files) > 0): for f in files: if (int(f['length']) < 10485760): card.attach(name=f['name'], mimeType=f['mime'], file=f['content']) else: card.comment( "El fichero " + f['name'] + " es demasiado pesado para adjuntar en Trello :( ") #try: # card.attach(name=f['name'], mimeType=f['mime'], file=f['content']) #finally: # # card.comment("El fichero " + f['name'] + " es demasiado pesado para adjuntar en Trello :(") #return False # card.attach(name=f['name'], mimeType=f['mime'], file=f['content']) if (self.debug): card.comment(str(request)) if (self.debug): pending_cards = pending.list_cards() print(pending_cards) def addUserRequests(self, users, woffu=None): import json import datetime pending = self.getRequestsBoard() for user in users: print("User: {} {}".format(user['$id'], user['UserId'])) labels = list() checklist_names = list() checklist_states = list() checklist_title = '' cardname_prefix = '' duedate = None if (user['Active'] is False): continue # ID if (user['UserId'] is not None): checklist_names.append("ID: " + str(user['UserId'])) checklist_states.append(True) else: checklist_names.append("ID: Vacío") checklist_states.append(False) # User name if (user['FirstName'] and user['LastName']): checklist_names.append("Nombre: " + str(user['FirstName']) + " " + str(user['LastName'])) checklist_states.append(True) else: if (user['FirstName'] or user['LastName']): checklist_names.append("Nombre: " + (str( user['FirstName']) if user['FirstName'] else "Vacío") + " " + (str(user['LastName']) if user['LastName'] else "Vacío")) checklist_states.append(False) else: checklist_names.append("Nombre: Vacío") checklist_states.append(False) # NIN: DNI if (user['NIN'] is not None): checklist_names.append("DNI: " + str(user['NIN'])) checklist_states.append(True) else: checklist_names.append("DNI: Vacío") checklist_states.append(False) # SSN: NAF if (user['SSN'] is not None): checklist_names.append("NSS: " + str(user['SSN'])) checklist_states.append(True) else: checklist_names.append("NSS: Vacío") checklist_states.append(False) # Birthday if (user['Birthday'] is not None): checklist_names.append( "Fecha de Nacimiento: " + str(helper.getDateFormat(user['Birthday']))) checklist_states.append(True) else: checklist_names.append("Fecha de Nacimiento: Vacío") checklist_states.append(False) # Start Date if (user['EmployeeStartDate'] is not None): checklist_names.append( "Fecha Inicio: " + str(helper.getDateFormat(user['EmployeeStartDate']))) checklist_states.append(True) if (datetime.datetime.strptime(user['EmployeeStartDate'], '%Y-%m-%dT%H:%M:%S.%f') > datetime.datetime.now()): # label = self.getLabel("ALTA TRABAJADOR") # if label is not None: # labels.append(label) duedate = user['EmployeeStartDate'] # if (duedate is not None): # checklist_title = "Checklist Alta y Baja Trabajador" # cardname_prefix = "Alta y Baja: " # else: checklist_title = "Checklist Alta Trabajador" cardname_prefix = "Alta usuario: " elif (user['EmployeeEndDate'] is None): cardname_prefix = "Alta usuario: " else: checklist_names.append("Fecha Inicio: Vacío") checklist_states.append(False) # End date if (user['EmployeeEndDate'] is not None): checklist_names.append( "Fecha Fin: " + str(helper.getDateFormat(user['EmployeeEndDate']))) checklist_states.append(True) # label = self.getLabel("BAJA TRABAJADOR") # if label is not None: # labels.append(label) if (duedate is not None): if (datetime.datetime.strptime(user['EmployeeStartDate'], '%Y-%m-%dT%H:%M:%S.%f') > datetime.datetime.now()): checklist_title = "Checklist Alta y Baja Trabajador" cardname_prefix = "Alta usuario: " else: duedate = user['EmployeeEndDate'] checklist_title = "Checklist Baja Trabajador" cardname_prefix = "Baja usuario: " else: duedate = user['EmployeeEndDate'] checklist_title = "Checklist Baja Trabajador" cardname_prefix = "Baja usuario: " # else: # checklist_names.append("Fecha Fin: Vacío") # checklist_states.append(False) # Generate Card name card_name = ("[" + str(user['UserId']) + "] [" + str(woffu.getCompanyName(user['CompanyId'])) + "] " + cardname_prefix + str(user['FirstName']) + " " + str(user['LastName'])) if (self.isCardNameCreated(card_name)): continue if (cardname_prefix is None): continue # Job Title if (user['JobTitleId'] is not None): jobtitle = woffu.getJobTitle(user['JobTitleId']) if (jobtitle['Name'] is not None): checklist_names.append("Cargo: " + str(jobtitle['Name'])) checklist_states.append(True) else: checklist_names.append("Cargo: Vacío") checklist_states.append(False) else: checklist_names.append("Cargo: Vacío") checklist_states.append(False) # Department if (user['DepartmentId'] is not None): department = woffu.getDepartment(user['DepartmentId']) if (department['Name'] is not None): checklist_names.append("Departamento: " + str(department['Name'])) checklist_states.append(True) else: checklist_names.append("Departamento: Vacío") checklist_states.append(False) else: checklist_names.append("Departamento: Vacío") checklist_states.append(False) # Office if (user['OfficeId'] is not None): office = woffu.getOffice(user['OfficeId']) if (office['Name'] is not None): checklist_names.append("Centro de Trabajo: " + str(office['Name'])) checklist_states.append(True) else: checklist_names.append("Centro de Trabajo: Vacío") checklist_states.append(False) else: checklist_names.append("Centro de Trabajo: Vacío") checklist_states.append(False) # # Bank account: avoided # if (None is not None ): # checklist_names.append("Cuenta Bancaria: " + str(user['JobTitleId'])) # checklist_states.append(True) # else: # checklist_names.append("Cuenta Bancaria: Vacío") # checklist_states.append(False) # # # Address: avoided # if (None is not None ): # checklist_names.append("Dirección Postal: " + str(user['JobTitleId'])) # checklist_states.append(True) # else: # checklist_names.append("Dirección Postal: Vacío") # checklist_states.append(False) # E-mail if (user['Email'] is not None): checklist_names.append("E-mail: " + str(user['Email'])) checklist_states.append(True) else: checklist_names.append("E-mail: Vacío") checklist_states.append(False) # Responsable if (user['ResponsibleUserId'] is not None): responsible = woffu.getUser(user['ResponsibleUserId']) if (responsible['FirstName'] is not None): checklist_names.append("Responsable: " + str(responsible['FirstName'] + " " + responsible['LastName'])) checklist_states.append(True) else: checklist_names.append("Responsable: Vacío") checklist_states.append(False) else: checklist_names.append("Responsable: Vacío") checklist_states.append(False) # Supervisor if (user['AuthorizingUserId'] is not None): responsible = woffu.getUser(user['AuthorizingUserId']) if (responsible['FirstName'] is not None): checklist_names.append("Supervisor: " + str(responsible['FirstName'] + " " + responsible['LastName'])) checklist_states.append(True) else: checklist_names.append("Supervisor: Vacío") checklist_states.append(False) else: checklist_names.append("Supervisor: Vacío") checklist_states.append(False) # Attributes user_attributes = woffu.getUserAttributes(user['UserId']) if (user_attributes): #checklist_names.append("Atributos: " + str(responsible['FirstName'] + " " + responsible['LastName'])) #checklist_states.append(True) for s in user_attributes: checklist_names.append( s['Name'] + ": " + str(s['Value'] if s['Value'] is not None else 'Vacío')) checklist_states.append( True if s['Value'] is not None else False) else: checklist_names.append( "Atributos (telf, dirección, etc): Vacío") checklist_states.append(False) # Skills user_skills = woffu.getUserSkills(user['UserId']) if (user_skills): #checklist_names.append("Habilidades: " + str(responsible['FirstName'] + " " + responsible['LastName'])) #checklist_names.append("Habilidades: Sí que tiene :)") #checklist_states.append(True) for s in user_skills: checklist_names.append( s['Name'] + ": " + str(s['Value'] if s['Value'] is not None else 'Vacío')) checklist_states.append(True) else: checklist_names.append("Habilidades: Vacío") checklist_states.append(False) # # Salary # if (None is not None ): # checklist_names.append("Salario: " + str(user['Email'])) # checklist_states.append(True) # else: # checklist_names.append("Salario: Vacío") # checklist_states.append(False) # Schedule if ((user['ScheduleId'] is not None) or (user['InheredScheduleId'] is not None)): #InheredScheduleId? schedule_id = (user['ScheduleId'] if user['ScheduleId'] is not None else user['InheredScheduleId']) schedule = woffu.getSchedule(schedule_id) if (schedule['Name'] is not None): #'TimeFrame' checklist_names.append("Horario: " + str(schedule['Name'])) checklist_states.append(True) else: checklist_names.append("Horario: Vacío") checklist_states.append(False) else: checklist_names.append("Horario: Vacío") checklist_states.append(False) user_contract = woffu.getUserContract(user['UserId']) # User contract if (user_contract): checklist_names.append("Tipo Contrato: " + str(user_contract["ContractTypeName"]. split("_ContractType_", 1)[1])) checklist_states.append(True) checklist_names.append( "Modalidad: " + str(user_contract["ContractModalityName"].split( "_ContractModality_", 1)[1])) checklist_states.append(True) else: checklist_names.append("Tipo Contrato: Vacío") checklist_states.append(False) checklist_names.append("Modalidad: Vacío") checklist_states.append(False) card = pending.add_card(name=card_name, desc=None, labels=labels, due=duedate, source=None, position=None, assign=None) card.add_checklist(checklist_title, checklist_names, checklist_states) if (self.debug): card.comment(str(user)) if (user_attributes): card.comment(str(user_attributes)) if (user_skills): card.comment(str(user_skills)) if (self.debug): pending_cards = pending.list_cards() print(pending_cards)
DEBUG = str2bool(os.getenv('DEBUG', '')) TRELLO_APP_KEY = os.getenv('TRELLO_APP_KEY') TRELLO_SECRET = os.getenv('TRELLO_SECRET') GITHUB_TOKEN = os.getenv('GITHUB_ACCESS_TOKEN') LIST_TONIGHT = "Tonight's Pitches" client = TrelloClient( api_key=TRELLO_APP_KEY, api_secret=TRELLO_SECRET, ) board_url = 'https://trello.com/b/EVvNEGK5/hacknight-projects' m = re.search('^https://trello.com/b/(?P<board_id>.+?)(?:/.*)?$', board_url) board_id = m.group('board_id') board = client.get_board(board_id) lists = board.get_lists('open') [pitch_list] = [l for l in lists if l.name == LIST_TONIGHT] cards = pitch_list.list_cards() def utc_to_local(utc_dt): local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(LOCAL_TZ) return LOCAL_TZ.normalize(local_dt) def last_hacknight(date): date = utc_to_local(date) DAYS_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] offset = (date.weekday() - DAYS_OF_WEEK.index('Tue')) % 7 # If before 5pm, assume we're working on last week if offset == 0 and date.time() < datetime.time(17, 0): offset += 7
def build_file(): client = TrelloClient(**trello_creds.CREDS) sales_board = client.get_board(dsa_config.SALES_BOARD_ID) moves_list_dict = get_moves_list_dict(sales_board) pickle.dump(moves_list_dict, open("moves_list_dict.p","wb")) pp.pprint(moves_list_dict)
class TrelloList(object): """ Sugar class to work with Trello Lists. """ def __init__(self, board_id, list_id, api_key, token=None, **kwargs): """ Validate inputs and connect to Trello API. Exception is thrown if input details are not correct. :param board_id: Trello board ID where the List is located :type board_id: ``str`` :param list_id: Trello List ID itself :type list_id: ``str`` :param api_key: Trello API key :type api_key: ``str`` :param token: Trello API token :type token: ``str`` """ self.board_id = board_id self.list_id = list_id self.api_key = api_key # assume empty string '' as None self.token = token or None self.validate() self._client = TrelloClient(api_key=self.api_key, token=self.token) self._list = self._client.get_board(self.board_id).get_list( self.list_id) def validate(self): """ Ensure that Trello list details are correct. Raise an exception if validation failed. """ if not self.api_key: raise ValueError( '[TrelloListSensor] "api_key" config value is required!') assert isinstance(self.api_key, six.string_types) if self.token: assert isinstance(self.token, six.string_types) if not self.board_id: raise ValueError( '[TrelloListSensor]: "board_id" config value is required!') assert isinstance(self.board_id, six.string_types) if not self.list_id: raise ValueError( '[TrelloListSensor]: "list_id" config value is required!') assert isinstance(self.list_id, six.string_types) @property def key_name(self): """ Generate unique key name for built-in storage based on config values. :rtype: ``str`` """ return '{}.{}.date'.format(self.board_id, self.list_id) def fetch_actions(self, filter=None, since=None): """ Fetch actions for Trello List with possibility to specify filters. Example API request: https://api.trello.com/1/lists/{list_id}/actions?filter=createCard&since=2015-09-14T21:45:56.850Z&key={key_id}&token={token_id} :param filter: Action types to filter, separated by comma or as a sequence. :type filter: ``str`` or ``list`` :param since: Filter actions since specified date. :type since: ``str`` :return: Events occurred in Trello list. :rtype: ``list`` of ``dict`` """ return self._client.fetch_json('/lists/' + self._list.id + '/actions', query_params={ 'filter': filter, 'since': since, })
class Command(BaseCommand): def __init__(self): super().__init__() self.trello_client = TrelloClient(settings.TRELLO_API_KEY, settings.TRELLO_API_TOKEN) self.github_client = Github( settings.GITHUB_TOKEN).get_organization('bluevine-dev') repos = self.github_client.get_repos() unmerged_pull_requests = [] for repo in repos: unmerged_pull_requests += [ pr for pr in repo.get_pulls() if not pr.merged_at ] self.unmerged_pull_requests = { pr.html_url: pr for pr in unmerged_pull_requests } def add_arguments(self, parser): # pylint: disable=no-self-use """ Add extra arguments """ super().add_arguments(parser) parser.add_argument('--csv', action='store_true', default=False, help='attach a csv to the generated email') parser.add_argument('--user', action='store', help='run command for specified user') def handle(self, *args, **options): """ Run command """ username = options.get('user') if username: users = User.objects.filter(username=username) else: users = User.objects.filter(role='TL') # generate unmerged pull request email to all desired users for user in users: member_id = self.trello_client.get_member(user.email).id data = self._get_unmerged_pull_requests(member_id) attachment = None if data and options['csv']: attachment = self.create_pull_requests_csv(data, member_id) logger.info(f'attachment {attachment} was created') self.send_unmerged_pull_requests_data( data=data, recipients=['*****@*****.**'], attachment=attachment) def _get_unmerged_pull_requests(self, member_id): """ Returns all unmerged pull requests related to cards """ boards = self.trello_client.list_boards(board_filter='open') pull_requests_per_board = [] for board in boards: cards = board.get_cards(card_filter='open') # get only member cards cards = [card for card in cards if member_id in card.member_id] pull_requests_per_card = [] for card in cards: attachments = card.get_attachments() pr_attachments = [ attachment for attachment in attachments if all(s in attachment.url for s in ['github.com', 'pull']) ] # check for unmerged pull request in card unmerged_pull_requests = [] for pr_attachment in pr_attachments: if pr_attachment.url in self.unmerged_pull_requests: pull_request_data = { 'name': pr_attachment.name, "url": pr_attachment.url, } unmerged_pull_requests.append(pull_request_data) if unmerged_pull_requests: card_data = { 'name': card.name, 'url': card.url, 'pull_requests': unmerged_pull_requests } pull_requests_per_card.append(card_data) if pull_requests_per_card: board_data = { 'name': board.name, 'cards': pull_requests_per_card } pull_requests_per_board.append(board_data) return pull_requests_per_board @staticmethod def send_unmerged_pull_requests_data(data, recipients, attachment=None): """ Sends an email according to given data """ html_content = Command.create_email_template(data) email_message = EmailMessage( subject='Trello Manager - Unmerged Pull Requests', body=html_content, from_email=settings.EMAIL_HOST_USER, to=recipients) email_message.content_subtype = 'html' if attachment: email_message.attach_file(attachment) email_message.send() logger.info(f'Email was sent to {recipients}') @staticmethod def create_pull_requests_csv(data, member_id): """ create a temporary csv file """ import os, tempfile, csv from datetime import datetime now = datetime.now().strftime("%m_%d_%Y__%H%M%S") file_path = os.path.join( tempfile.gettempdir(), f'unmerged_pull_request_{member_id}_{now}.csv') with open(file_path, 'w') as csv_file: headers = [ 'board name', 'card name', 'card url', 'pull request name', 'pull request url' ] writer = csv.writer(csv_file) writer.writerow(headers) for board in data: cards = board['cards'] for card in cards: pull_requests = card['pull_requests'] for pull_request in pull_requests: writer.writerow([ board['name'], card['name'], card['url'], pull_request['name'], pull_request['url'] ]) return file_path @staticmethod def fetch_data_from_pull_request_url(pull_request_url): """ Parses and returns pull request data from a pull request url """ split_url = pull_request_url.split("/") owner = split_url[3] repo = split_url[4] number = int(split_url[-1]) return owner, repo, number def _fetch_cards_by_member(self, member_id): """ Fetches all the cards for this member """ cards = self.trello_client.fetch_json('/members/' + member_id + '/cards', query_params={ 'filter': 'visible', 'fields': 'name,idBoard,url', 'attachments': 'true' }) return sorted(cards, key=lambda card: card['idBoard']) def _get_board_name_by_id(self, board_id): """ Returns the name of the board """ if board_id not in self.boards_names: self.boards_names[board_id] = self.trello_client.get_board( board_id).name return self.boards_names[board_id] def _get_pull_request_by_url(self, pull_request_url): owner, repo, number = self.fetch_data_from_pull_request_url( pull_request_url) return self.github_client.get_repo(repo).get_pull(int(number)) @staticmethod def create_email_template(data): email_template = Template(EMAIL_TEMPLATE) return email_template.render(Context({'boards': data}))
def trelloinit(config): trelloconfig = config["trello"] client = TrelloClient(api_key=trelloconfig["api_key"], api_secret=trelloconfig["api_secret"]) return (client, client.get_board(trelloconfig["board_id"]))
import csv from trello import TrelloClient # Put here your credentials client = TrelloClient(api_key='API_KEY', token='TOKEN') def get_label(labels, label_name): return next(filter(lambda label: label.name == label_name, labels)) # Put here your board id board = client.get_board("BOARD_ID") # Discover list # for list in board.list_lists(): # print(list.name + ": " + list.id) board_labels = board.get_labels() # Put here columns definition todo_list = board.get_list("7d3895565ecd152b7d30d61e") doing_list = board.get_list("4d189358d5f34a6eb0f41c8a") done_list = board.get_list("2d08934c44d37b268a8a1ecd") # Read file with open('sample.csv') as csv_file: csv_dict = csv.DictReader(csv_file) for row in csv_dict:
def getTableros(filtro=""): filtroPasado = filtro tableros = client.list_boards(board_filter=filtroPasado) lista = [] registros = [(tn.name, tn.id) for tn in tableros] for f in registros: campos = ['nombre_tablero', 'id_tablero'] convertir = dict(zip(campos, f)) lista.append(convertir) tablerosDevolver = json.dumps(lista) return tablerosDevolver # Obtener un tablero por su ID tablero = client.get_board('57581f7d6a945e2f6630a793') print(tablero) # Obtener todas las listas de un tablero print( tablero.all_lists() ) # Obtener de un tablero una lista o columna por su ID lista = tablero.get_list('57582109bba4b95e66dbf4e1') # Obtener de una lista la cantidad de tarjetas que posee lista.cardsCnt() # Obtener todas las tarjetas que posee lista.list_cards() # Listar los tableros Abiertos
from __future__ import print_function # Core import ast # 3rd-party from trello import TrelloClient # Local from auth import py_trello_auth_dict client = TrelloClient( **py_trello_auth_dict ) board = client.get_board ('hPK2DcxK') print("About to munge {!r}".format(board.name)) input_data = [{'name': 'Some Test Book', 'desc': ''' This is _such_ a great book! Here's a quote from it: > Twas brillig, yo > The slithey toves > Were all like up in your shit '''}] with open('/private/tmp/wat') as inf: input_data += ast.literal_eval(inf.read()) lists = board.get_lists(None) if len(lists) != 1:
def main(): parser = ArgumentParser(description='Generate a status page from a Trello ' 'board') parser.add_argument('-k', '--key', dest='key', default=environ.get('TRELLO_KEY'), help='Trello API key') parser.add_argument('-s', '--secret', dest='secret', default=environ.get('TRELLO_SECRET'), help='Trello API secret') parser.add_argument('-t', '--token', dest='token', default=environ.get('TRELLO_TOKEN'), help='Trello API auth token') parser.add_argument('-S', '--token-secret', dest='token_secret', default=environ.get('TRELLO_TOKEN'), help='Trello API auth token secret') parser.add_argument('-b', '--board-id', dest='board', default=environ.get('TRELLO_BOARD_ID'), help='Trello board ID') parser.add_argument('-T', '--custom-template', dest='template', help='Custom jinja2 template to use instead of default') parser.add_argument('-d', '--template-data', dest='template_data', help='If using --custom-template, you can provide a ' 'YAML file to load in data that would be ' 'available in the template the template') parser.add_argument('--skip-css', dest='skip_css', action='store_true', help='Skip copying the default trestus.css to the ' 'output dir.') parser.add_argument('output_path', help='Path to output rendered HTML to') args = parser.parse_args() client = TrelloClient( api_key=args.key, api_secret=args.secret, token=args.token, token_secret=args.token_secret) markdown = Markdown() board = client.get_board(args.board) labels = board.get_labels() service_labels = [l for l in labels if not l.name.startswith('status:')] service_ids = [s.id for s in service_labels] status_types = [l for l in labels if l not in service_labels] lists = board.open_lists() incidents = [] panels = {} systems = {} for card_list in lists: cards = card_list.list_cards() cards.sort(key=lambda c: c.create_date, reverse=True) for card in cards: severity = None for label in card.labels: if not label.name.startswith('status:'): continue severity = label.name.lstrip('status:').lstrip() if label.color == 'red': break card.severity = severity card_service_labels = [l.name for l in card.labels if l.id in service_ids] if not card_service_labels or not card.severity: continue if card_list.name.lower() == 'fixed': card.closed = True else: if card.severity not in panels: panels[card.severity] = [] panels[card.severity] += card_service_labels for service in card_service_labels: if service not in systems: systems[service] = {'status': card_list.name, 'severity': card.severity} card.html_desc = markdown(card.desc) # Working around a bug in latest py-trello which fails to # load/parse comments properly comments = card.fetch_comments(force=True) for comment in comments: comment['html_desc'] = markdown(comment['data']['text']) comment['parsed_date'] = datetime.strptime( comment['date'].replace('-', '').replace(':', ''), '%Y%m%dT%H%M%S.%fZ') card.parsed_comments = comments incidents.append(card) for label in service_labels: if label.name not in systems: systems[label.name] = {'status': 'Operational', 'severity': ''} if args.template_data: template_data = load_yaml(open(args.template_data)) else: template_data = {} env = Environment(loader=FileSystemLoader( path.join(path.dirname(__file__), 'templates'))) if args.template: with open(args.template) as f: template = env.from_string(f.read()) else: template = env.get_template('trestus.html') with open(args.output_path, 'w+') as f: f.write(template.render(incidents=incidents, panels=panels, systems=systems, **template_data)) if not args.skip_css: css_path = path.join(path.dirname(__file__), 'templates', 'trestus.css') copy2(css_path, path.dirname(args.output_path)) print('Status page written to {}'.format(args.output_path)) return 0
api_key=APPKEY, api_secret=TOKEN ) trello_label = input('Enter the Card Label: ') trello_description = input('Description (optional): ') if len(trello_label) == 0: print("ERROR: Please provide a Label to create the card") sys.exit(-1) elif len(trello_description) == 0: trello_description = "" try: my_board = trello.get_board(BOARD_ID) board_lists = my_board.all_lists() #print(board_lists) for boardList in board_lists: boardList.fetch() # print(boardList.name + ": " + boardList.id) theListID = LIST_ID if len(LIST_ID) == 0: theListID = board_lists[0].id trello.get_list(theListID).add_card(trello_label, trello_description) except ResourceUnavailable as e: print('ERROR: %s' % e._msg)
class Trello_Client: def __init__(self, database=None): self.connection_tries = 0 self.client = None self.api_key = None self.token = None if database is None: database = 'sent_to_cal.db' if not os.path.exists(database): open(database, "x") global db_proxy db = SqliteDatabase(database) db_proxy.initialize(db) db_proxy.create_tables([self.LoggedCard]) logging.info("db: %s", db_proxy) def init(self): logging.info("trello_client init") self.login() # self.client = TrelloClient(config.get_client()) def boards(self): return CONFIG.get_config(['TRELLO', 'boards']) or dict() def refresh_credentials(self): logging.warning("Unauthorized. Client: %s", self.client) print("Time to refresh credentials!") print("Log in to trello at https://trello.com,\ then visit https://trello.com/app-key") self.api_key = input("Trello API key: ") CONFIG.write_config(["TRELLO", "api_key"], self.api_key) self.token = input("Trello token: ") CONFIG.write_config(["TRELLO", "token"], self.token) self.login_petition() def login_petition(self): self.client = TrelloClient(self.api_key, token=self.token) def is_logged_in(self): try: self.client.list_boards() return True except exceptions.Unauthorized: logging.warning("Not authorised") logging.warning("Not logged in") return False def login(self): logging.info("Trying to log in.") # GET API INFO self.api_key = CONFIG.get_api_info() self.token = CONFIG.get_token() if(self.api_key is None or self.token is None): self.refresh_credentials() return logging.info( "Trello API key and token: %s", [self.api_key, self.token]) if self.connection_tries > 1: self.refresh_credentials() return self.connection_tries += 1 logging.info("Connections tries: %s", self.connection_tries) self.login_petition() if self.is_logged_in(): logging.info("Log in successful") self.connection_tries = 0 else: logging.warning("Login failed.") self.login() class LoggedCard(Model): card_id = CharField() card_hash = CharField() datetime_added = DateField() session_id = CharField() def __string__(self): return ( self.card_id, "\n", self.card_hash, "\n", self.datetime_added, "\n", self.session_id) class Meta: database = db_proxy def archive_card(self, card): card.set_closed(True) def build_checklist(self, checklists): checklists_out = "" for checklist in checklists: checklists_out += "\nCHECKLIST: " + checklist.name + "\n" for item in checklist.items: checklists_out += "- " + item['name'] + "\n" return checklists_out def get_checklists(self, card): checklists = card.fetch_checklists() checklists_out = self.build_checklist(checklists) return checklists_out def get_description(self, card): checklists = self.get_checklists(card) if checklists: return card.description + "\n" + checklists return card.description def card_hash(self, card): hashable = self.get_description(card) return hashlib.sha256(hashable.encode()).hexdigest() # TEST ME def log__card(self, card_id, card_hash, datetime_added, session_id): self.LoggedCard.create( card_id=card_id, card_hash=card_hash, datetime_added=datetime_added, session_id=session_id) logging.info("Logged card_id: %s", card_id) def log_card(self, card, session): self.log__card(card.id, self.card_hash(card), datetime.now(), session) # TEST ME def is__new_card(self, card_id, card_name): try: self.LoggedCard.get(self.LoggedCard.card_id == card_id) except DoesNotExist: message = "New card: %s" % card_name logging.info(message) logging.info("New card_id: %s", card_id) print(message) return True return False def is_new_card(self, card): return self.is__new_card(card.id, card.name) def get_list_cards(self, board_id, list_id, members=[]): logging.info( "Getting list %s from board %s with members %s", list_id, board_id, members) cards = self.client.get_board(board_id).get_list(list_id).list_cards() return filter(lambda x: x.member_id == members, cards) # TEST ME def board_to_yaml(self, name, board_id): CONFIG.write_config(['TRELLO', 'boards', name, 'id'], board_id) # TEST ME def find__board_id(self, board_name, boards): logging.warning("You don't seem to have a %s b, board!", board_name) logging.info("Available boards: %s", boards) for board_id, board in enumerate(boards): print("%s:%s" % (board_id, board)) board_num = int(input("What is the id of your %s board?" % board_name)) board_id = boards[board_num].id self.board_to_yaml(board_name, board_id) return board_id def find_board_id(self, board_name): boards = self.client.list_boards() return self.find__board_id(board_name, boards) # TEST ME def get_board_id(self, board_name): try: if self.boards()[board_name]['id'] is None: return self.find_board_id(board_name) return self.boards()[board_name]['id'] except (KeyError, exceptions.ResourceUnavailable) as e: logging.warning("Exception %s at get_board_id", e) CONFIG.get_config(['TRELLO', 'boards', board_name, 'id']) return self.get_board_id(board_name) # TEST ME def list_to_yaml(self, board_name, list_name, list_id): CONFIG.write_config( ['TRELLO', 'boards', board_name, list_name, 'id'], list_id) def find_list_id(self, board_config, board_name, list_name): logging.warning( "You don't seem to have a %s list, on your this board!\ Please select one of the following: ", list_name) board_id = str(board_config['id']) for elist_id, elist_name in enumerate( self.client.get_board(board_id).open_lists()): print("%s:%s" % (elist_id, elist_name)) list_num = int(input("What is the id of your %s list?" % (list_name))) list_id = self.client.get_board(board_id).open_lists()[list_num].id self.list_to_yaml(board_config, board_name, list_name, list_id) return list_id # TEST ME def get_list_id(self, board_name, list_name): board_config = self.boards()[board_name] try: if board_config[list_name] is None: return self.find_list_id(board_config, board_name, list_name) return self.boards()[board_name][list_name]["id"] except KeyError: CONFIG.get_config(['TRELLO', 'boards', board_name, list_name]) return self.get_list_id(board_name, list_name)
#!/usr/bin/python3 from trello import TrelloClient import pyperclip import os import constants client = TrelloClient( api_key=constants.TRELLO_API_KEY, api_secret=constants.TRELLO_API_SECRET, ) all_boards = client.list_boards() con_list= client.get_board(constants.TRELLO_LEARNING_BOARD_ID).get_lists(list_filter=None) data=str(pyperclip.paste()) print(con_list[0].add_card(data)) os.system('notify-send "New Card Added" "'+data+'"')
class TaskManager(): _client_trello = None _board = None _board_labels = None _config_file = '' _dict_label = {} def __init__(self, config_file): self._config_file = config_file config = ConfigParser.RawConfigParser() config.read(config_file) api_key = config.get('Management.Task', 'api.key') oath_token = config.get('Management.Task', 'oath.token') id_board = config.get('Management.Task', 'id.board') self._client_trello = TrelloClient(api_key, token=oath_token) self._board = self._client_trello.get_board(id_board) self._board_labels = self._board.get_labels() list_label = [label for label in self._board_labels if label.name != '' and label.color] for label in list_label: self._dict_label[label.name] = label @staticmethod def validate_connection(): url_test = 'https://api.trello.com/1' try: result = requests.request('GET', url_test, timeout=5) return result.status_code == 200 except ConnectionError as e: logger.warning("couldn't connect to trello, see error: %s" % e.message) return False except RuntimeError as e: logger.warning("couldn't connect to trello, timeout error: %s" % e.message) return False def refresh_list_id(self): dao_object = SdaTrackerDao(self._config_file) for a_list in self._board.all_lists(): code_env = a_list.name id_list_tracker = a_list.id dao_object.update_list_tracker(code_env, id_list_tracker) def get_card_ticket(self, id_card_tracker): return self._client_trello.get_card(id_card_tracker) if id_card_tracker else None def send_ticket_card(self, dict_board_ticket): """Send new card or update it""" result_card = None dict_board = dict_board_ticket['dict_board'] id_card_tracker = dict_board['id_card_tracker'] try: action = None # get trello's card a_card = self.get_card_ticket(id_card_tracker) list_artifact = dict_board_ticket['artifacts'] # get id_list trello id_list_tracker = dict_board['id_list_tracker'] # id_ticket = card.name id_ticket = dict_board['id_ticket'] # card's description string_json = json.dumps(list_artifact, indent=2) labels_artifact = self.get_labels_artifact(list_artifact) if a_card: action = "UPDATE" #print "update card" for label in a_card.labels: a_card.client.fetch_json( '/cards/' + a_card.id + '/idLabels/' + label.id, http_method='DELETE') a_card.set_description(string_json) result_card = a_card else: action = "NEW" #print "new card" a_list = self._board.get_list(id_list_tracker) new_card = a_list.add_card(id_ticket, string_json) result_card = new_card result_card.add_label(self._dict_label['requested']) for label in labels_artifact: result_card.add_label(label) return {"result": "OK", "action": action, "result_card": result_card} except RuntimeError as e: return {"result": "ERROR", "description": e.message} def get_labels_artifact(self, list_artifact): list_label = [] for dict_artifact in list_artifact: artifact = dict_artifact['artifact'] ls = [label for label in self._board_labels if label.name == artifact] if len(ls) == 0: label = self._board.add_label(artifact, None) self._board_labels = self._board.get_labels() list_label.append(label) else: list_label.append(ls[0]) return list_label
class ServiceTrello(ServicesMgr): # Boards own Lists own Cards def __init__(self, token=None): super(ServiceTrello, self).__init__(token) # app name self.app_name = DjangoThConfig.verbose_name # expiration self.expiry = "30days" # scope define the rights access self.scope = 'read,write' base = 'https://www.trello.com' self.AUTH_URL = '{}/1/OAuthAuthorizeToken'.format(base) self.REQ_TOKEN = '{}/1/OAuthGetRequestToken'.format(base) self.ACC_TOKEN = '{}/1/OAuthGetAccessToken'.format(base) self.consumer_key = settings.TH_TRELLO['consumer_key'] self.consumer_secret = settings.TH_TRELLO['consumer_secret'] if token: token_key, token_secret = token.split('#TH#') self.trello_instance = TrelloClient(self.consumer_key, self.consumer_secret, token_key, token_secret) def read_data(self, **kwargs): """ get the data from the service :param kwargs: contain keyword args : trigger_id at least :type kwargs: dict """ trigger_id = kwargs['trigger_id'] data = list() cache.set('th_trello_' + str(trigger_id), data) def process_data(self, **kwargs): """ get the data from the cache :param kwargs: contain keyword args : trigger_id at least :type kwargs: dict """ kw = {'cache_stack': 'th_trello', 'trigger_id': str(kwargs['trigger_id'])} return super(ServiceTrello, self).process_data(**kw) def save_data(self, trigger_id, **data): """ let's save the data :param trigger_id: trigger ID from which to save data :param data: the data to check to be used and save :type trigger_id: int :type data: dict :return: the status of the save statement :rtype: boolean """ from th_trello.models import Trello status = False kwargs = {'output_format': 'md'} title, content = super(ServiceTrello, self).save_data(trigger_id, data, **kwargs) if len(title): # get the data of this trigger t = Trello.objects.get(trigger_id=trigger_id) # footer of the card footer = self.set_card_footer(data, t) content += footer # 1 - we need to search the list and board where we will # store the card so ... # 1.a search the board_id by its name # by retreiving all the boards boards = self.trello_instance.list_boards() board_id = '' my_list = '' for board in boards: if t.board_name == board.name.decode('utf-8'): board_id = board.id break if board_id: # 1.b search the list_id by its name my_board = self.trello_instance.get_board(board_id) lists = my_board.open_lists() # just get the open list ; not all the archive ones for list_in_board in lists: # search the name of the list we set in the form if t.list_name == list_in_board.name.decode('utf-8'): # return the (trello) list object # to be able to add card at step 3 my_list = my_board.get_list(list_in_board.id) break # we didnt find the list in that board # create it if my_list == '': my_list = my_board.add_list(t.list_name) else: # 2 if board_id and/or list_id does not exist, create it/them my_board = self.trello_instance.add_board(t.board_name) # add the list that didnt exists and # return a (trello) list object my_list = my_board.add_list(t.list_name) # 3 create the card # create the Trello card my_list.add_card(title, content) sentance = str('trello {} created').format(data['link']) logger.debug(sentance) status = True else: sentance = "no token or link provided for trigger ID {}" logger.critical(sentance.format(trigger_id)) status = False return status def set_card_footer(self, data, trigger): """ handle the footer of the note """ footer = '' if 'link' in data: provided_by = _('Provided by') provided_from = _('from') footer_from = "<br/><br/>{} <em>{}</em> {} <a href='{}'>{}</a>" description = trigger.trigger.description footer = footer_from.format( provided_by, description, provided_from, data['link'], data['link']) return footer def auth(self, request): """ let's auth the user to the Service """ request_token = super(ServiceTrello, self).auth(request) callback_url = self.callback_url(request, 'trello') # URL to redirect user to, to authorize your app auth_url_str = '{auth_url}?oauth_token={token}' auth_url_str += '&scope={scope}&name={name}' auth_url_str += '&expiration={expiry}&oauth_callback={callback_url}' auth_url = auth_url_str.format(auth_url=self.AUTH_URL, token=request_token['oauth_token'], scope=self.scope, name=self.app_name, expiry=self.expiry, callback_url=callback_url) return auth_url def callback(self, request, **kwargs): """ Called from the Service when the user accept to activate it """ kwargs = {'access_token': '', 'service': 'ServiceTrello', 'return': 'trello'} return super(ServiceTrello, self).callback(request, **kwargs)
def get_trello_board(api_key, token, board_id): client = TrelloClient(api_key=api_key, token=token) return client.get_board(board_id)
def _trello(secrets): for key in ["TRELLO_TOKEN", "TRELLO_KEY", "TRELLO_BOARD_ID"]: if not getattr(secrets, key): raise Exception("{} is not set".format(key)) # https://github.com/sarumont/py-trello/tree/master/trello from trello import TrelloClient from trello.exceptions import ResourceUnavailable client = TrelloClient( api_key=secrets.TRELLO_KEY, api_secret=secrets.TRELLO_TOKEN, ) board = client.get_board(secrets.TRELLO_BOARD_ID) # Webhook def set_webhook(url): id_model = board.id desc = "Demo Trello-Github Integration" log_transmission( TRELLO, "set webhook: {}".format([url, id_model, desc])) # original create_hook is not used. See: https://github.com/sarumont/py-trello/pull/323 # hook = client.create_hook(url, id_model, desc, token=secrets.TRELLO_TOKEN) token = secrets.TRELLO_TOKEN res = client.fetch_json( "tokens/{}/webhooks/".format(token), http_method="POST", post_args={ "callbackURL": url, "idModel": id_model, "description": desc, }, ) log("Trello response: %s" % json.dumps(res)) def delete_webhooks(): for hook in client.list_hooks(secrets.TRELLO_TOKEN): if hook.id_model == board.id: log_transmission( TRELLO, "delete webhook: {}".format([hook.callback_url])) hook.delete() # Trello cards def card_create(name, issue_id): description = "https://github.com/{}/issues/{}".format( secrets.GITHUB_REPO, issue_id) log_transmission(TRELLO, "create: {}".format([name, description])) card_list = board.open_lists()[0] card = card_list.add_card(name, description) return card.id def card_add_labels(card_id, tlabel_ids): log_transmission( TRELLO, "add labels to card#{}: {}".format(card_id, tlabel_ids)) card = client.get_card(card_id) for label_id in tlabel_ids: try: label = client.get_label(label_id, board.id) except ResourceUnavailable: log("Label is deleted in trello: %s" % label_id, LOG_WARNING) continue if label_id in card.idLabels: log("Label is already in card: %s" % label) continue card.add_label(label) def card_remove_labels(card_id, tlabel_ids): log_transmission( TRELLO, "remove labels from card#{}: {}".format( card_id, tlabel_ids)) card = client.get_card(card_id) for label_id in tlabel_ids: label = client.get_label(label_id, board.id) if label_id not in card.idLabels: log("Label is already removed: %s" % label) continue card.remove_label(label) def card_add_message(card_id, message): log_transmission( TRELLO, "add message to card#{}: {}".format(card_id, message)) card = client.get_card(card_id) card.comment(message) # Trello labels def label_create(name, color): log_transmission(TRELLO, "create label: %s" % (name)) label = board.add_label(name, color) return label.id def label_delete(tlabel_id): log_transmission(TRELLO, "delete label: %s" % (tlabel_id)) board.delete_label(tlabel_id) def label_update(tlabel_id, new_name, new_color): log_transmission( TRELLO, "label#{} update: {}".format(tlabel_id, [new_name, new_color]), ) res = client.fetch_json( "/labels/{}".format(tlabel_id), http_method="PUT", post_args={ "id": tlabel_id, "name": new_name, "color": new_color }, ) log("Trello response: {}".format(res)) def get_labels_colors(): return {lb.id: lb.color for lb in board.get_labels()} def get_all_cards(): return [{ "id": card.id, "idLabels": card.idLabels } for card in board.all_cards()] return AttrDict({ "set_webhook": set_webhook, "delete_webhooks": delete_webhooks, "card_create": card_create, "card_add_labels": card_add_labels, "card_remove_labels": card_remove_labels, "card_add_message": card_add_message, "label_create": label_create, "label_delete": label_delete, "label_update": label_update, "get_labels_colors": get_labels_colors, "get_all_cards": get_all_cards, })
def main(): parser = ArgumentParser(description='Generate a status page from a Trello ' 'board') parser.add_argument('-k', '--key', dest='key', default=environ.get('TRELLO_KEY'), help='Trello API key') parser.add_argument('-s', '--secret', dest='secret', default=environ.get('TRELLO_SECRET'), help='Trello API secret') parser.add_argument('-t', '--token', dest='token', default=environ.get('TRELLO_TOKEN'), help='Trello API auth token') parser.add_argument('-S', '--token-secret', dest='token_secret', default=environ.get('TRELLO_TOKEN'), help='Trello API auth token secret') parser.add_argument('-b', '--board-id', dest='board', default=environ.get('TRELLO_BOARD_ID'), help='Trello board ID') parser.add_argument( '-T', '--custom-template', dest='template', help='Custom jinja2 template to use instead of default') parser.add_argument('-d', '--template-data', dest='template_data', help='If using --custom-template, you can provide a ' 'YAML file to load in data that would be ' 'available in the template the template') parser.add_argument('--skip-css', dest='skip_css', action='store_true', help='Skip copying the default trestus.css to the ' 'output dir.') parser.add_argument('output_path', help='Path to output rendered HTML to') args = parser.parse_args() client = TrelloClient(api_key=args.key, api_secret=args.secret, token=args.token, token_secret=args.token_secret) markdown = Markdown() board = client.get_board(args.board) labels = board.get_labels() service_labels = [l for l in labels if not l.name.startswith('status:')] service_ids = [s.id for s in service_labels] status_types = [l for l in labels if l not in service_labels] lists = board.open_lists() incidents = [] panels = {} systems = {} for card_list in lists: cards = card_list.list_cards() cards.sort(key=lambda c: c.create_date, reverse=True) for card in cards: severity = None for label in card.labels: if not label.name.startswith('status:'): continue severity = label.name.lstrip('status:').lstrip() if label.color == 'red': break card.severity = severity card_service_labels = [ l.name for l in card.labels if l.id in service_ids ] if not card_service_labels or not card.severity: continue if card_list.name.lower() == 'fixed': card.closed = True else: if card.severity not in panels: panels[card.severity] = [] panels[card.severity] += card_service_labels for service in card_service_labels: if service not in systems: systems[service] = { 'status': card_list.name, 'severity': card.severity } card.html_desc = markdown(card.desc) # Working around a bug in latest py-trello which fails to # load/parse comments properly comments = card.fetch_comments(force=True) for comment in comments: comment['html_desc'] = markdown(comment['data']['text']) comment['parsed_date'] = datetime.strptime( comment['date'].replace('-', '').replace(':', ''), '%Y%m%dT%H%M%S.%fZ') card.parsed_comments = comments incidents.append(card) for label in service_labels: if label.name not in systems: systems[label.name] = {'status': 'Operational', 'severity': ''} if args.template_data: template_data = load_yaml(open(args.template_data)) else: template_data = {} env = Environment(loader=FileSystemLoader( path.join(path.dirname(__file__), 'templates'))) if args.template: with open(args.template) as f: template = env.from_string(f.read()) else: template = env.get_template('trestus.html') with open(args.output_path, 'w+') as f: f.write( template.render(incidents=incidents, panels=panels, systems=systems, **template_data)) if not args.skip_css: css_path = path.join(path.dirname(__file__), 'templates', 'trestus.css') copy2(css_path, path.dirname(args.output_path)) print('Status page written to {}'.format(args.output_path)) return 0
print("Please configure this program with the config file in \n\"{}\"". format(CONFIG_FILE)) exit() config = load_config() if not config["params"]["api_key"]: api = toml.loads(open("api.toml").read()) config["params"]["api_key"], config["params"]["token"] = api[ "api_key"], api["token"] client = TrelloClient(api_key=config["params"]["api_key"], token=config["params"]["token"]) board = config["board"] try: tb = client.get_board(board["id"]) except trello.exceptions.ResourceUnavailable: println("ERROR: Failed GET board, check that your config file is correct") for li in config["list"]: tl = tb.get_list(li["id"]) print("{}> {}".format(FORMAT_TITLE, tl.name)) print(FORMAT_TASK, end="") for i, card in enumerate(tl.list_cards(), start=1): print("{}. {}".format(i, card.name)) print() END = FORMAT_RESET if len(sys.argv) > 1: END = sys.argv[1] print(END)
def populate_board(url, target=None, update=False): '''takes a URL for a glass door posting, and creates a trello list with helpful job hunt items ''' if target: auth = ret_labels(target) if update == True: auth = update_config(target) else: auth = load_config() client = TrelloClient( api_key=auth['key'], token=auth['token']) # gets board Labels board = client.get_board(board_id=auth['board_id']) link_l = client.get_label(auth['link_l'], auth['board_id']) desc_l = client.get_label(auth['desc_l'], auth['board_id']) check_l = client.get_label(auth['check_l'], auth['board_id']) # check URL for type url_clean = url.strip('https://').split('/') if url_clean[0] == 'www.glassdoor.com': url_type = '/'.join(url_clean[:2]) if url_clean[0] == 'www.linkedin.com': url_type = '/'.join(url_clean[:3]) else: url_type = url_clean[0] # use url type to call scrape function if url_type == 'www.glassdoor.com/job-listing': # scrapes GD for job info data = pull_data_gd(url) elif url_type == 'boards.greenhouse.io': data = pull_data_gh(url) elif url_type == 'jobs.lever.co': data = pull_data_lev(url) elif url_type == 'www.linkedin.com/jobs/view': data = pull_data_li(url) elif url_type == 'careers.jobscore.com': data = pull_data_js(url) else: print("Sorry, either {} isn't supported yet, or something unexpectedly changed\n".format(url_clean[0])) print("Feel free to implement and send a PR, or email: [email protected]") return None data['post_url'] = url list_name = data['company'] + ':\n' + data['job'] + '\n\nAdded: ' + str(datetime.now()) j_title = data['company'] + ': ' + data['job'] # create list object new_list = board.add_list(name=list_name, pos=None) # add cards to list object description = new_list.add_card(name='{} \nDetails:'.format(j_title), desc=data['desc']) attachCard = new_list.add_card(name='{} \nLinks:'.format(j_title)) attachCard.attach(name=data['ap_type'], url=data['ap_url']) attachCard.attach(name='Original posting', url=data['post_url']) # attempts to build a direct search link for linkedin which targets company and department if available if data['dept'] != '': li_url = 'https://www.linkedin.com/search/results/people/?keywords=' + data['company'].replace(' ','%20') + '%20' + data['dept'].replace(' ', '%20') attachCard.attach(name='Linkedin search, Company + Department', url= li_url) else: li_url = 'https://www.linkedin.com/search/results/people/?keywords=' + data['company'].replace(' ', '%20') attachCard.attach(name='Linkedin search, Company', url=li_url) goog_base = 'https://www.google.com/#safe=off&q=' goog_company = goog_base+data['company'].replace(' ','+') attachCard.attach(name='Google search, Company',url=goog_company) gd_search_hack = 'https://www.glassdoor.com/Reviews/{}-reviews-SRCH_KE0,{}.htm'.format(data['company'], len(data['company'])) dt = datetime.now() + timedelta(days=3) cl = ['Research Hiring manager [Linkedin]({})'.format(li_url), 'Research company, [glassdoor]({}), [google]({})'.format(gd_search_hack, goog_company), 'Polish Resume [creddle](https://resume.creddle.io/)', 'Craft Cover Letter, [Drive](https://drive.google.com)', 'Attempt to make contact on personal level [Linkedin]({})'.format(li_url), 'Complete application [Application link]({})'.format(data['ap_url']), 'Send application', 'Wait three days: {}'.format(str(dt.date())), 'Send follow up email'] clCard = new_list.add_card('To Do List') clCard.add_checklist(title='To Do!\n\n{}'.format(j_title), items=cl) clCard.set_due(dt.date()) notes_card = new_list.add_card(name='Notes') notes_card.comment('Added Via tgd on: {}\n Refered through {}'.format(datetime.now(), data['referer'])) # add color coded labels to cards attachCard.add_label(link_l) description.add_label(desc_l) clCard.add_label(check_l) print('\n' + 'Success! \n\nAdded {} to you board'.format(j_title) + '\n\nCheck it out here:\nhttps://trello.com/b/{}'.format(auth['board_id'] + '\n'))
class TarDriver: #Конструктор класса def __init__( self, trello_apiKey='', #apiKey для подключения к trello trello_token='', #apiToken для подключения к trello local_timezone='Asia/Tomsk'): self.API_KEY = trello_apiKey self.TOKEN = trello_token self.local_timezone = tz(local_timezone) self.filter_dates = [] self.database_is_updating = False #Подключение к Trello try: self.trello_client = TrelloClient( api_key=self.API_KEY, token=self.TOKEN, ) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to connect to Trello via API: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Connection to Trello established successful' ) #Создание файла БД и таблиц в БД try: self.db = TinyDB('tar_database.json') #self.db.drop_tables() !!!!!!!!!!!!!!!!!!!!! self.report = self.db.table('report') self.worktime = self.db.table('worktime') self.local_boards = self.db.table('boards') self.local_lists = self.db.table('lists') self.local_cards = self.db.table('cards') self.local_persons = self.db.table('persons') self.local_cards_has_persons = self.db.table('cards_has_persons') except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to setup tar_database: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] tar_database created' ) self.basic_template = [{ 0: 'Перечень Задач', 'cards': [ 'П3 - Описание автоматизируемых функций', 'П5 - Описание информационного обеспечения', 'В1 - Описание входных сигналов и данных', 'В2 - Описание выходных сигналов (сообщений)', 'ПА - Описание программного обеспечения', 'ПБ - Описание алгоритма', 'П6 - Описание информационной базы', 'С9 - Чертеж форм (видеокадра)', 'И3 - Руководство оператора', 'И3 - Руководство программиста', 'И4 - Инструкция по ведению БД', 'ПМИ - Программа и методика испытаний', 'ПО ПЛК - Программа контроллера', 'ПО Панели - Программа панели оператора', 'ПО АРМ - Программа рабочего места оператора', 'ПО БД - База данных', 'Ежедневная планерка', 'Планирование цели (спринт)', 'Анализ завершения цели (спринта)' ] }, { 1: 'Комплекс Задач', 'cards': [] }, { 2: 'В Работе', 'cards': [] }, { 3: 'Согласование выполнения', 'cards': [] }, { 4: 'Завершены', 'cards': [] }, { 5: 'Отменены', 'cards': [] }] self.db.drop_table('worktime') self.worktime.insert({ 'work_day_starts': '09:00:00', 'work_day_ends': '18:00:00', 'work_day_duration': '09:00:00', 'lunch_hours_starts': '13:00:00', 'lunch_hours_ends': '14:00:00', 'lunch_duration': '01:00:00', 'day_work_hours': '08:00:00', 'work_days': '5', 'week_work_hours': '1 day, 16:00:00', 'update_period': '00:02:00' }) def add_board(self, board): #Добавление новой доски в БД try: self.local_boards.insert({ 'board_id': board.id, 'board_name': board.name, 'board_description': board.description, 'board_last_modified': str(board.date_last_activity) }) for list_ in board.list_lists(): self.local_lists.insert({ 'list_id': list_.id, 'list_name': list_.name, 'list_last_modified': str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")), 'board_id': board.id, 'board_name': board.name }) for card in list_.list_cards(): self.local_cards.insert({ 'card_id': card.id, 'card_name': card.name, 'list_id': list_.id, 'list_name': list_.name, 'board_id': board.id, 'board_name': board.name }) if len(card.member_id) > 0: for person in self.team: if person.id in card.member_id: query_result = self.local_persons.get( where('person_id') == str(person.id)) self.local_cards_has_persons.insert({ 'card_id': card.id, 'card_name': card.name, 'person_id': person.id, 'person_name': query_result['person_fullname'], 'list_id': list_.id, 'list_name': list_.name, 'board_id': board.id, 'board_name': board.name }) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to add "{board.name}": {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] "{board.name}" added successful' ) def delete_board(self, board_id, board_name=''): #Удаление доски из БД try: #Удаляем записи из таблицы local_cards_has_persons self.local_cards_has_persons.remove( where('board_id') == str(board_id)) #Удаляем записи из таблицы local_cards self.local_cards.remove(where('board_id') == str(board_id)) #Удаляем записи из таблицы local_lists self.local_lists.remove(where('board_id') == str(board_id)) #Удаляем записи из таблицы local_boards self.local_boards.remove(where('board_id') == str(board_id)) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to delete {board_id}: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] {board_id} deleted successful' ) def update_board(self, board): #Обновление доски в БД datetime_format = "%Y-%m-%d %H:%M:%S.%f%z" try: query_result = self.local_boards.get( where('board_id') == str(board.id)) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Updating "{board.name}"...{err}' ) self.delete_board(board_id=board.id, board_name=board.name) self.add_board(board=board) else: board_date_last_activity = self.unify_time( datetime.strptime(query_result['board_last_modified'], datetime_format)) if self.unify_time( board.date_last_activity) > board_date_last_activity: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Updating "{board.name}"...' ) self.delete_board(board_id=board.id, board_name=board.name) self.add_board(board=board) def fill_main_boards(self): #Заполнение таблиц local_boards, local_lists, local_cards print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Filling "local_boards / local_lists / local_cards" tables...' ) try: for board in self.trello_client.list_boards(): self.add_board(board=board) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to fill "local_boards / local_lists / local_cards" tables: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] "local_boards / local_lists / local_cards" tables filled successful' ) def fill_persons(self, team_board_name='КАДРЫ'): #Заполнение таблицы local_persons try: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Filling "local_persons" table...' ) for board in self.trello_client.list_boards(): if board.name == team_board_name: self.team = board.get_members(filters='all') for list_ in board.list_lists(): for card in list_.list_cards(): if len(card.member_id) > 0: for person in self.team: if person.id in card.member_id: self.local_persons.insert({ 'person_id': person.id, 'person_username': person.username, 'person_fullname': card.name, 'status': list_.name, 'last_modified': str(datetime.now().strftime( "%Y-%m-%d %H:%M:%S")) }) break except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to fill "local_persons" table: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] "local_persons" table filled successful' ) def fill_cards_has_persons(self): #Заполнение таблицы cards_has_persons try: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Filling "cards_has_persons" table...' ) for board in self.trello_client.list_boards(): for list_ in board.list_lists(): for card in list_.list_cards(): if len(card.member_id) > 0: for person in self.team: if person.id in card.member_id: query_result = self.local_persons.get( where('person_id') == str(person.id)) self.local_cards_has_persons.insert({ 'card_id': card.id, 'card_name': card.name, 'person_id': person.id, 'person_name': query_result['person_fullname'], 'list_id': list_.id, 'list_name': list_.name, 'board_id': board.id, 'board_name': board.name }) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] Failed to fill "cards_has_persons" table: {err}' ) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] "cards_has_persons" table filled successful' ) def fill_database(self): self.fill_persons() self.fill_main_boards() def update_database(self, update_on_change=False): time_format = "%H:%M:%S" self.db.drop_table('persons') self.fill_persons() update = True while update: self.database_is_updating = True update_period_time = (time.strptime(self.get_update_period(), time_format)) update_period_seconds = timedelta( hours=update_period_time.tm_hour, minutes=update_period_time.tm_min, seconds=update_period_time.tm_sec).total_seconds() print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Checking for updates...' ) trello_boards = self.trello_client.list_boards() local_boards = self.local_boards.all() if len(trello_boards) > len( local_boards): #в trello добавили доску #ищем какую tempBoards = [] for board in local_boards: tempBoards.append(board['board_id']) for board in trello_boards: if board.id not in tempBoards: #новая доска обнаружена self.add_board(board=board) print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Checking for updates finished' ) elif len(trello_boards) < len( local_boards): #в trello удалили доску #ищем какую tempBoards = [] for board in trello_boards: tempBoards.append(board.id) for board in local_boards: if board[ 'board_id'] not in tempBoards: #новая доска обнаружена self.delete_board(board_id=board['board_id'], board_name=board['board_name']) print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Checking for updates finished' ) else: #обновляем все доски. Новых / удаленных не обнаружено for board in trello_boards: self.update_board(board=board) print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Checking for updates finished' ) if not update_on_change: time.sleep(update_period_seconds) self.database_is_updating = False def get_persons_active_tasks(self, person_id, active_list_name='В Работе'): query_result = self.local_cards_has_persons.search( (where('person_id') == str(person_id)) & (where('list_name') == str(active_list_name))) return len(query_result) # !!!!! Изменить чтоб читал пользователей доски, возможно вернуть board_has_persons def get_project_members(self, board_id): temp_persons = [] board_persons = [] query_result = self.local_cards_has_persons.search( where('board_id') == str(board_id)) for result in query_result: if result['person_id'] not in temp_persons: temp_persons.append(result['person_id']) board_persons.append({ 'person_id': result['person_id'], 'person_name': result['person_name'] }) return board_persons def get_tasks_on_board(self, board_id, list_name='В работе'): tasks = [] query_result = self.local_cards.search( where('board_id') == str(board_id)) for result in query_result: if result['list_name'] == list_name: query_result_ = self.local_cards_has_persons.search( where('list_id') == str(result['list_id'])) for result_ in query_result_: if result_['card_name'] == result['card_name']: task = { 'task_name': result['card_name'], 'task_member': result_['person_name'], 'card_in_work_time': result['card_in_work_time'] } tasks.append(task) break return tasks def get_lists_by_board_id(self, board_id): query_result = self.local_lists.search( (where('board_id') == str(board_id))) return query_result def get_active_tasks_by_person(self, person_id): query_result = self.local_cards_has_persons.search( (where('person_id') == str(person_id)) & ((where('list_name') == str('В Работе')))) return query_result def get_curr_stage_percent(self, board_id, board_template): tasks_planned = self.local_cards.search( (where('board_id') == str(board_id)) & (where('list_name') == str('Комплекс задач'))) tasks_in_progress = self.local_cards.search( (where('board_id') == str(board_id)) & (where('list_name') == str('В Работе'))) tasks_on_hold = self.local_cards.search( (where('board_id') == str(board_id)) & (where('list_name') == str('Согласование Выполнения'))) tasks_done = self.local_cards.search( (where('board_id') == str(board_id)) & (where('list_name') == str('Завершены'))) if (len(tasks_planned) + len(tasks_in_progress) + len(tasks_on_hold) + len(tasks_done)) == 0: return 0 else: return round((len(tasks_done) / (len(tasks_planned) + len(tasks_in_progress) + len(tasks_on_hold) + len(tasks_done))) * 100.0) def create_new_project(self, project_template, project_name='Новый проект', project_description=''): self.trello_client.add_board(board_name=project_name, source_board=None, organization_id=None, permission_level='private', default_lists=False) for board in self.trello_client.list_boards(): if board.name == project_name: board.set_description(desc=project_description) for list_ in range(len(project_template) - 1, -1, -1): board.add_list(name=project_template[list_].get(list_), pos=None) for _list in board.list_lists(): for list_ in range(0, len(project_template)): if _list.name == project_template[list_].get(list_): for card in project_template[list_]['cards']: _list.add_card(name=card, desc=None, labels=None, due="null", source=None, position=None, assign=None, keep_from_source="all") print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG] Card {card} was added' ) break def utc_to_local(self, utc_dt): return utc_dt.replace(tzinfo=timezone.utc, microsecond=0).astimezone(tz=None) def set_workhours(self, workhours=['09:00:00', '18:00:00']): format_ = '%H:%M:%S' try: work_day_starts = datetime.strptime(workhours[0], format_).time() work_day_ends = datetime.strptime(workhours[1], format_).time() except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] {err}' ) pass #self.worktime.update({ 'work_day_starts': str(datetime.strptime('9:00:00', format_).time())}) #self.worktime.update({ 'work_day_ends': str(datetime.strptime('18:00:00', format_).time())}) else: if work_day_starts < work_day_ends: self.worktime.update({'work_day_starts': str(work_day_starts)}) self.worktime.update({'work_day_ends': str(work_day_ends)}) work_day_duration = timedelta(hours = work_day_ends.hour, minutes = work_day_ends.minute, seconds = work_day_ends.second) \ - timedelta(hours = work_day_starts.hour, minutes = work_day_starts.minute, seconds = work_day_starts.second) self.worktime.update( {'work_day_duration': str(work_day_duration)}) self.calculate_work_hours() def get_workhours(self): return self.worktime.get(where('work_day_starts') != None) def set_lunch_hours(self, lunch_hours=['13:00:00', '14:00:00']): format_ = '%H:%M:%S' try: lunch_hours_starts = datetime.strptime(lunch_hours[0], format_).time() lunch_hours_ends = datetime.strptime(lunch_hours[1], format_).time() except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] {err}' ) pass else: if lunch_hours_starts < lunch_hours_ends: self.worktime.update( {'lunch_hours_starts': str(lunch_hours_starts)}) self.worktime.update( {'lunch_hours_ends': str(lunch_hours_ends)}) lunch_duration = timedelta(hours = lunch_hours_ends.hour, minutes = lunch_hours_ends.minute, seconds = lunch_hours_ends.second) \ - timedelta(hours = lunch_hours_starts.hour, minutes = lunch_hours_starts.minute, seconds = lunch_hours_starts.second) self.worktime.update({'lunch_duration': str(lunch_duration)}) self.calculate_work_hours() def get_lunch_hours(self): return self.worktime.get(where('lunch_hours_starts') != None) def calculate_work_hours(self): format_ = '%H:%M:%S' str_work_day_duration = self.worktime.get( where('work_day_duration') != None)['work_day_duration'] str_lunch_duration = self.worktime.get( where('lunch_duration') != None)['lunch_duration'] time_work_day_duration = datetime.strptime(str_work_day_duration, format_).time() time_lunch_duration = datetime.strptime(str_lunch_duration, format_).time() day_work_hours = timedelta(hours = time_work_day_duration.hour, minutes = time_work_day_duration.minute, seconds = time_work_day_duration.second) \ - timedelta(hours = time_lunch_duration.hour, minutes = time_lunch_duration.minute, seconds = time_lunch_duration.second) self.worktime.update({'day_work_hours': str(day_work_hours)}) work_days = self.worktime.get(where('work_days') != None)['work_days'] week_work_hours = timedelta(seconds=int(work_days) * day_work_hours.total_seconds()) self.worktime.update({'week_work_hours': str(week_work_hours)}) def is_integer(self, n): try: float(n) except ValueError: return False else: return float(n).is_integer() def set_workdays(self, workdays='5'): if self.is_integer(workdays): if (int(workdays) >= 1) and (int(workdays) <= 7): self.worktime.update({'work_days': str(workdays)}) else: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR] workdays not a number' ) def get_workdays(self): try: return ((self.worktime.get( where('work_days') != None))['work_days']) except: return '--:--' def set_database_update_period(self, update_period='01:00:00'): format_ = '%H:%M:%S' try: update_period = datetime.strptime(update_period, format_).time() except Exception as err: pass else: self.worktime.update({'update_period': str(update_period)}) def get_update_period(self): try: return ((self.worktime.get( where('update_period') != None))['update_period']) except: return '--:--' def unify_time(self, datetime): return datetime.astimezone(self.local_timezone).replace(microsecond=0) def filter_work_hours(self, start_date, end_date): time_format = "%H:%M:%S" result_time = timedelta(hours=0, minutes=0, seconds=0) calculated_end_time = timedelta(hours=0, minutes=0, seconds=0) twelve_hours_delta = timedelta(hours=12, minutes=0) twenty_four_hours_delta = timedelta(hours=23, minutes=59, seconds=59) work_day_starts = self.worktime.get( where('work_day_starts') != None)['work_day_starts'] work_day_ends = self.worktime.get( where('work_day_ends') != None)['work_day_ends'] lunch_starts = self.worktime.get( where('lunch_hours_starts') != None)['lunch_hours_starts'] lunch_ends = self.worktime.get( where('lunch_hours_ends') != None)['lunch_hours_ends'] day_work_hours = self.worktime.get( where('day_work_hours') != None)['day_work_hours'] work_day_starts = datetime.strptime(work_day_starts, time_format).time() work_day_ends = datetime.strptime(work_day_ends, time_format).time() lunch_starts = datetime.strptime(lunch_starts, time_format).time() lunch_ends = datetime.strptime(lunch_ends, time_format).time() day_work_hours = datetime.strptime(day_work_hours, time_format).time() while start_date <= end_date: till_the_end_of_he_day_delta = twenty_four_hours_delta - timedelta( hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second) calculated_end_time = (start_date + till_the_end_of_he_day_delta) if calculated_end_time >= end_date: if calculated_end_time.time() > end_date.time(): calculated_end_time = end_date if start_date.weekday( ) < 5: #этот день не выходной // сделать параметром чтоб менять первый день недели if (calculated_end_time.time() < work_day_starts ): #промежуток кончился раньше рабочего дня pass elif (calculated_end_time.time() > work_day_starts) and ( calculated_end_time.time() <= lunch_starts ): #промежуток кончился после начала рабочего дня но раньше обеда: if start_date.time() <= work_day_starts: result_time += timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=work_day_starts.hour, minutes=work_day_starts.minute, seconds=work_day_starts.second) else: result_time += timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second) elif (calculated_end_time.time() > lunch_starts) and ( calculated_end_time.time() < lunch_ends ): #промежуток кончился после начала обеда но раньше конца обеда: if start_date.time() <= work_day_starts: result_time += timedelta(hours=lunch_starts.hour, minutes=lunch_starts.minute, seconds=lunch_starts.second) - \ timedelta(hours=work_day_starts.hour, minutes=work_day_starts.minute, seconds=work_day_starts.second) elif (start_date.time() > work_day_starts) and ( start_date.time() < lunch_starts): result_time += timedelta(hours=lunch_starts.hour, minutes=lunch_starts.minute, seconds=lunch_starts.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second) elif (start_date.time() >= lunch_starts): pass elif (calculated_end_time.time() >= lunch_ends) and ( calculated_end_time.time() < work_day_ends ): #промежуток кончился после конца обеда но раньше конца дня if start_date.time() <= work_day_starts: result_time += (timedelta(hours=lunch_starts.hour, minutes=lunch_starts.minute, seconds=lunch_starts.second) - \ timedelta(hours=work_day_starts.hour, minutes=work_day_starts.minute, seconds=work_day_starts.second)) + \ (timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=lunch_ends.hour, minutes=lunch_ends.minute, seconds=lunch_ends.second)) elif (start_date.time() > work_day_starts) and ( start_date.time() < lunch_starts): result_time += (timedelta(hours=lunch_starts.hour, minutes=lunch_starts.minute, seconds=lunch_starts.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second)) + \ (timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=lunch_ends.hour, minutes=lunch_ends.minute, seconds=lunch_ends.second)) elif (start_date.time() >= lunch_starts) and (start_date.time() < lunch_ends): result_time += (timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=lunch_ends.hour, minutes=lunch_ends.minute, seconds=lunch_ends.second)) elif (start_date.time() >= lunch_ends): result_time += (timedelta(hours=calculated_end_time.hour, minutes=calculated_end_time.minute, seconds=calculated_end_time.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second)) elif (calculated_end_time.time() >= work_day_ends): #промежуток кончился позже рабочего дня if start_date.time() <= work_day_starts: result_time += timedelta(hours=day_work_hours.hour, minutes=day_work_hours.minute, seconds=day_work_hours.second) elif (start_date.time() > work_day_starts) and ( start_date.time() < lunch_starts): result_time += (timedelta(hours=lunch_starts.hour, minutes=lunch_starts.minute, seconds=lunch_starts.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second)) + \ (timedelta(hours=work_day_ends.hour, minutes=work_day_ends.minute, seconds=work_day_ends.second) - \ timedelta(hours=lunch_ends.hour, minutes=lunch_ends.minute, seconds=lunch_ends.second)) elif (start_date.time() >= lunch_starts) and (start_date.time() < lunch_ends): result_time += (timedelta(hours=work_day_ends.hour, minutes=work_day_ends.minute, seconds=work_day_ends.second) - \ timedelta(hours=lunch_ends.hour, minutes=lunch_ends.minute, seconds=lunch_ends.second)) elif (start_date.time() >= lunch_ends) and (start_date.time() <= work_day_ends): result_time += (timedelta(hours=work_day_ends.hour, minutes=work_day_ends.minute, seconds=work_day_ends.second) - \ timedelta(hours=start_date.hour, minutes=start_date.minute, seconds=start_date.second)) elif (start_date.time() > work_day_ends): pass start_date += (till_the_end_of_he_day_delta + timedelta(minutes=1)) return result_time def filter_reports_time(self, start_date, end_date, disable_filter=False): datetime_format = "%Y-%m-%d %H:%M:%S" filter_start_date = self.unify_time( datetime.strptime(self.filter_dates[0], datetime_format)) filter_end_date = self.unify_time( datetime.strptime(self.filter_dates[1], datetime_format)) result_time = timedelta(hours=0, minutes=0, seconds=0) if not disable_filter: #1 if (start_date < filter_start_date) and (end_date < filter_start_date): return result_time #2 elif (start_date < filter_start_date) and ( (end_date > filter_start_date) and (end_date < filter_end_date)): start_date = filter_start_date return self.filter_work_hours(start_date=start_date, end_date=end_date) #3 elif (start_date < filter_start_date) and (end_date > filter_end_date): start_date = filter_start_date end_date = filter_end_date return self.filter_work_hours(start_date=start_date, end_date=end_date) #4 elif ((start_date > filter_start_date) and (start_date < filter_end_date)) and (end_date < filter_end_date): self.filter_work_hours(start_date=start_date, end_date=end_date) #5 elif ((start_date > filter_start_date) and (start_date < filter_end_date)) and (end_date > filter_end_date): end_date = filter_end_date return self.filter_work_hours(start_date=start_date, end_date=end_date) #6 elif (start_date > filter_end_date): return result_time else: #print("filter enabled!") return self.filter_work_hours(start_date=start_date, end_date=end_date) def get_card_stats_by_lists(self, card, disable_filter=False): board = self.trello_client.get_board(board_id=card.board_id) lists = board.list_lists() time_in_lists = { list_.id: { "time": timedelta(minutes=0) } for list_ in lists } ordered_list_movements = sorted(card.list_movements(), key=itemgetter("datetime")) if len(ordered_list_movements) == 0: time_in_lists[card.list_id]['time'] += self.filter_reports_time( start_date=card.created_date, end_date=self.unify_time(datetime.now()), disable_filter=disable_filter) #!!!!!!! elif len(ordered_list_movements) == 1: time_start = card.created_date time_end = self.unify_time(ordered_list_movements[0]['datetime']) list_id = ordered_list_movements[0]['source']['id'] time_in_lists[list_id]['time'] += self.filter_reports_time( start_date=time_start, end_date=time_end, disable_filter=disable_filter) time_start = self.unify_time(ordered_list_movements[0]['datetime']) time_end = self.unify_time(datetime.now()) list_id = ordered_list_movements[0]['destination']['id'] time_in_lists[list_id]['time'] += self.filter_reports_time( start_date=time_start, end_date=time_end, disable_filter=disable_filter) else: for change_index in range(0, len(ordered_list_movements)): list_id = ordered_list_movements[change_index]['source']['id'] if change_index == 0: time_in_lists[list_id]['time'] += self.filter_reports_time( start_date=card.created_date, end_date=self.unify_time( ordered_list_movements[change_index]['datetime']), disable_filter=disable_filter) elif change_index > 0: time_start = ordered_list_movements[change_index - 1]['datetime'] time_end = ordered_list_movements[change_index]['datetime'] time_in_lists[list_id]['time'] += self.filter_reports_time( start_date=self.unify_time(time_start), end_date=self.unify_time(time_end), disable_filter=disable_filter) if change_index + 1 == len(ordered_list_movements): time_start = ordered_list_movements[change_index][ 'datetime'] time_end = datetime.now() list_id = ordered_list_movements[change_index][ 'destination']['id'] time_in_lists[list_id][ 'time'] += self.filter_reports_time( start_date=self.unify_time(time_start), end_date=self.unify_time(time_end), disable_filter=disable_filter) return time_in_lists def get_project_report(self, board_id, lists, members): self.db.drop_table('report') for list_id in lists: for member_id in members: query_result = self.local_cards_has_persons.search( (where('board_id') == str(board_id)) & (where('person_id') == str(member_id))) for result in query_result: try: card = self.trello_client.get_card( card_id=result['card_id']) print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [MSG]: got card {result["card_name"], result["card_id"]}' ) except Exception as err: print( f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: [ERROR]: {err}' ) else: card_lists_time = self.get_card_stats_by_lists( card=card) for time in card_lists_time: if time == list_id: key = f'{time}' if card_lists_time.get( key)['time'] > timedelta(minutes=1): list_name_query = self.local_lists.get( (where('list_id') == str(time))) self.report.insert({ 'person_id': result['person_id'], 'person_name': result['person_name'], 'card_id': result['card_id'], 'card_name': result['card_name'], 'list_id': time, 'list_name': list_name_query['list_name'], 'list_time': str(card_lists_time.get(key)['time']), 'board_id': result['board_id'], 'board_name': result['board_name'] }) def convert_seconds_to_readable_time(self, seconds): min, sec = divmod(seconds, 60) hour, min = divmod(min, 60) return "%d:%02d:%02d" % (hour, min, sec)
class TrelloPlugin(DIBasePlugin, DIPluginDashboardMixin): platform = xapi_settings.PLATFORM_TRELLO platform_url = "https://trello.com" #created for "create" actions #added for "add" actions #updated for "update" actions #commented for card "comment" actions # xapi_verbs = ['created', 'added', 'updated', 'commented', 'closed', 'opened'] xapi_verbs = [ xapi_settings.VERB_CREATED, xapi_settings.VERB_ADDED, xapi_settings.VERB_UPDATED, xapi_settings.VERB_COMMENTED, xapi_settings.VERB_CLOSED, xapi_settings.VERB_OPENED ] #Note for "commented" actions #Task for "created", "added", "updated", and "commented" actions #Collection for any "created", "added", "updated" List actions (tentative use) # xapi_objects = ['Note', 'Task', 'Collection', 'Person', 'File', 'checklist-item', 'checklist'] xapi_objects = [ xapi_settings.OBJECT_NOTE, xapi_settings.OBJECT_TASK, xapi_settings.OBJECT_COLLECTION, xapi_settings.OBJECT_PERSON, xapi_settings.OBJECT_FILE, xapi_settings.OBJECT_CHECKLIST_ITEM, xapi_settings.OBJECT_CHECKLIST ] user_api_association_name = 'Trello UID' # eg the username for a signed up user that will appear in data extracted via a social API unit_api_association_name = 'Board ID' # eg hashtags or a group name config_json_keys = ['consumer_key', 'consumer_secret'] #from DIPluginDashboardMixin xapi_objects_to_includein_platformactivitywidget = [ xapi_settings.OBJECT_NOTE ] xapi_verbs_to_includein_verbactivitywidget = [ xapi_settings.VERB_CREATED, xapi_settings.VERB_SHARED, xapi_settings.VERB_LIKED, xapi_settings.VERB_COMMENTED ] #for OAuth1 authentication token_request_url = '' # Trello action type # Note: MoveCard, CloseCard, OpenCard are created for the toolkit to identify what users really did # (The original action type of moving/closing/opening card are all same: updateCard) ACTION_TYPE_COMMENT_CARD = 'commentCard' ACTION_TYPE_CREATE_CARD = 'createCard' ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD = 'updateCheckItemStateOnCard' ACTION_TYPE_UPDATE_CARD = 'updateCard' ACTION_TYPE_ADD_ATTACHMENT_TO_CARD = 'addAttachmentToCard' ACTION_TYPE_ADD_CHECKLIST_TO_CARD = 'addChecklistToCard' ACTION_TYPE_ADD_MEMBER_TO_CARD = 'addMemberToCard' ACTION_TYPE_MOVE_CARD = 'moveCard' ACTION_TYPE_CLOSE_CARD = 'closeCard' ACTION_TYPE_OPEN_CARD = 'openCard' VERB_OBJECT_MAPPER = { xapi_settings.VERB_CREATED: [ACTION_TYPE_CREATE_CARD], xapi_settings.VERB_ADDED: [ ACTION_TYPE_ADD_ATTACHMENT_TO_CARD, ACTION_TYPE_ADD_CHECKLIST_TO_CARD, ACTION_TYPE_ADD_MEMBER_TO_CARD ], xapi_settings.VERB_UPDATED: [ACTION_TYPE_MOVE_CARD, ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD], xapi_settings.VERB_COMMENTED: [ACTION_TYPE_COMMENT_CARD], xapi_settings.VERB_CLOSED: [ACTION_TYPE_CLOSE_CARD], xapi_settings.VERB_OPENED: [ACTION_TYPE_OPEN_CARD] } SEPARATOR_COLON = ": " SEPARATOR_HTML_TAG_BR = "<br>" MESSAGE_CARD_POSITION_CHANGED = 'Card position was changed.' def __init__(self): pass #retreival param is the user_id def perform_import(self, retreival_param, unit, token=None): #from clatoolkit.models import ApiCredentials #Set up trello auth and API self.TrelloCient = TrelloClient( api_key=os.environ.get("TRELLO_API_KEY"), token=token) #Get user-registered board in trello trello_board = self.TrelloCient.get_board(retreival_param) #Get boards activity/action feed trello_board.fetch_actions( 'all' ) #fetch_actions() collects actions feed and stores to trello_board.actions self.import_TrelloActivity(trello_board.actions, unit) def import_TrelloActivity(self, feed, unit): #User needs to sign up username and board (board can be left out but is needed) print 'Beginning Trello import!' for action in list(feed): #We need to connect this with our user profile somewhere when they initially auth u_id = action['idMemberCreator'] author = action['memberCreator']['username'] type = action['type'] #commentCard, updateCard, createList,etc data = action['data'] date = action['date'] board_name = data['board']['name'] # print 'got action type: %s' % (type) #Get all 'commented' verb actions if (type == self.ACTION_TYPE_COMMENT_CARD): target_obj_id = self.create_card_url(data) comment_from_uid = u_id comment_from_name = author comment_message = data['text'] comment_id = target_obj_id + '/' + action['id'] card_name = data['card']['name'] if username_exists(comment_from_uid, unit, self.platform): # Create "other" contextActivity object to store original activity in xAPI card_details = self.TrelloCient.fetch_json( '/cards/' + data['card']['id']) context = get_other_contextActivity( card_details['shortUrl'], 'Verb', type, xapi_settings.get_verb_iri( xapi_settings.VERB_COMMENTED)) context2 = get_other_contextActivity( card_details['shortUrl'], 'Object', data['card']['name'], xapi_settings.get_verb_iri( xapi_settings.VERB_COMMENTED)) other_context_list = [context, context2] user = get_user_from_screen_name(comment_from_uid, self.platform) insert_comment(user, target_obj_id, comment_id, comment_message, date, unit, self.platform, self.platform_url, parent_user_external=card_name, other_contexts=other_context_list) if (type == self.ACTION_TYPE_CREATE_CARD): object_id = self.create_card_url(data) + '/' + action['id'] card_name = data['card']['name'] parent_id = self.create_list_url(data) if username_exists(u_id, unit, self.platform): # Create "other" contextActivity object to store original activity in xAPI card_details = self.TrelloCient.fetch_json( '/cards/' + data['card']['id']) context = get_other_contextActivity( card_details['shortUrl'], 'Verb', type, xapi_settings.get_verb_iri(xapi_settings.VERB_CREATED)) context2 = get_other_contextActivity( card_details['shortUrl'], 'Object', data['list']['name'], xapi_settings.get_verb_iri(xapi_settings.VERB_CREATED)) other_context_list = [context, context2] usr_dict = ClaUserUtil.get_user_details_by_smid( u_id, self.platform) user = get_user_from_screen_name(u_id, self.platform) insert_task(user, object_id, card_name, date, unit, self.platform, self.platform_url, parent_id=parent_id, other_contexts=other_context_list) #Get all 'add' verbs (you tecnically aren't *creating* an attachment on a card so....) if (type in [ self.ACTION_TYPE_ADD_ATTACHMENT_TO_CARD, self.ACTION_TYPE_ADD_CHECKLIST_TO_CARD, self.ACTION_TYPE_ADD_MEMBER_TO_CARD ]): user = get_user_from_screen_name(u_id, self.platform) if user is None: continue card_url = self.create_card_url(data) # Create "other" contextActivity object to store original activity in xAPI other_context_list = [] card_details = self.TrelloCient.fetch_json('/cards/' + data['card']['id']) context = get_other_contextActivity( card_details['shortUrl'], 'Verb', type, xapi_settings.get_verb_iri(xapi_settings.VERB_ADDED)) other_context_list.append(context) context2 = get_other_contextActivity( card_details['shortUrl'], 'Object', data['card']['name'], xapi_settings.get_verb_iri(xapi_settings.VERB_ADDED)) other_context_list = [context, context2] if type == self.ACTION_TYPE_ADD_ATTACHMENT_TO_CARD: attachment = data['attachment'] attachment_id = card_url + '/' + data['attachment']['id'] attachment_data = attachment['name'] object_type = xapi_settings.OBJECT_FILE parent_user_external = '%sc/%s' % (self.platform_url, card_url) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', attachment['url'], xapi_settings.get_verb_iri( xapi_settings.VERB_ADDED))) insert_added_object( user, card_url, attachment_id, attachment_data, date, unit, self.platform, self.platform_url, object_type, parent_user_external=parent_user_external, other_contexts=other_context_list) if type == self.ACTION_TYPE_ADD_MEMBER_TO_CARD: #or 'addMemberToBoard': object_id = card_url + '/' + action['member']['id'] object_data = action['member']['username'] object_type = xapi_settings.OBJECT_PERSON parent_user_external = '%sc/%s' % (self.platform_url, card_url) insert_added_object( user, card_url, object_id, object_data, date, unit, self.platform, self.platform_url, object_type, parent_user_external=parent_user_external, other_contexts=other_context_list) if type == self.ACTION_TYPE_ADD_CHECKLIST_TO_CARD: object_id = card_url + '/' + data['checklist']['id'] object_data = None checklist_items = None object_type = xapi_settings.OBJECT_COLLECTION parent_user_external = '%sc/%s' % (self.platform_url, card_url) #get checklist contents try: checklist = self.TrelloCient.fetch_json( '/checklists/' + data['checklist']['id'], ) checklist_items = checklist['checkItems'] except Exception: print 'Could not retrieve checklist..' continue object_data = data['checklist']['name'] for item in checklist_items: # items are stored individually in other contextActivities other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', item['name'], xapi_settings.get_verb_iri( xapi_settings.VERB_ADDED))) insert_added_object( user, card_url, object_id, object_data, date, unit, self.platform, self.platform_url, object_type, parent_user_external=parent_user_external, other_contexts=other_context_list) if (type in [ self.ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD, self.ACTION_TYPE_UPDATE_CARD ]): user = get_user_from_screen_name(u_id, self.platform) if user is None: continue card_details = self.TrelloCient.fetch_json('/cards/' + data['card']['id']) #many checklist items will be bugged - we require webhooks! if type == self.ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD: # Import check item ID & its state # Use action ID as an object ID to avoid ID conflict. object_id = self.create_card_url(data) + '/' + action['id'] obj_val = data['checkItem']['state'] # Create "other" contextActivity object to store original activity in xAPI other_context_list = [] other_context_list.append( get_other_contextActivity( self.create_checklist_url(data), 'Verb', type, xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', data['checkItem']['name'], xapi_settings.get_verb_iri( xapi_settings.OBJECT_CHECKLIST_ITEM))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', data['card']['name'], xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) insert_updated_object( user, object_id, obj_val, date, unit, self.platform, self.platform_url, xapi_settings.OBJECT_CHECKLIST_ITEM, parent_id=self.create_checklist_url(data), obj_parent_type=xapi_settings.OBJECT_CHECKLIST, other_contexts=other_context_list) #up to us to figure out what's being updated if type == self.ACTION_TYPE_UPDATE_CARD: #Get and store the values that were changed, usually it's only one #TODO: Handle support for multiple changes, if that's possible try: change = [ changed_value for changed_value in data['old'] ] except Exception: print 'Error occurred getting changes...' # Use action ID as an object ID to avoid ID conflict. object_id = self.create_card_url(data) + '/' + action['id'] if change[0] == 'pos': # When user moves card in the same list (change order) object_text = data['card']['name'] # object_text = 'Change order of card: %s to %s' % (data['old']['pos'], data['card']['pos']) # object_text = card_name + object_text other_context_list = [] other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Verb', self.ACTION_TYPE_MOVE_CARD, xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', self.MESSAGE_CARD_POSITION_CHANGED, xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', str(data['old']['pos']), xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', str(data['card']['pos']), xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) insert_updated_object( user, object_id, object_text, date, unit, self.platform, self.platform_url, xapi_settings.OBJECT_TASK, parent_id=self.create_list_url(data), obj_parent_type=xapi_settings.OBJECT_COLLECTION, other_contexts=other_context_list) else: # When user moves card from a list to another if data.has_key('listBefore'): object_text = data['card']['name'] # object_text = 'from %s to %s' % (data['listBefore']['name'], data['listAfter']['name']) # object_text = card_name + object_text other_context_list = [] other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Verb', self.ACTION_TYPE_MOVE_CARD, xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', data['listBefore']['name'], xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', data['listAfter']['name'], xapi_settings.get_verb_iri( xapi_settings.VERB_UPDATED))) insert_updated_object( user, object_id, object_text, date, unit, self.platform, self.platform_url, xapi_settings.OBJECT_TASK, parent_id=self.create_card_url(data), obj_parent_type=xapi_settings. OBJECT_COLLECTION, other_contexts=other_context_list) # When user closes (archives)/opens card elif data['old'][change[0]] is False or data['old'][ change[0]] is True: verb = xapi_settings.VERB_CLOSED verb_iri = xapi_settings.get_verb_iri(verb) action_type = self.ACTION_TYPE_CLOSE_CARD object_text = data['card']['name'] # When card is opened if data['old'][change[0]] is True: verb = xapi_settings.VERB_OPENED verb_iri = xapi_settings.get_verb_iri(verb) action_type = self.ACTION_TYPE_OPEN_CARD other_context_list = [] other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Verb', action_type, verb_iri)) other_context_list.append( get_other_contextActivity( card_details['shortUrl'], 'Object', data['list']['name'], verb_iri)) insert_closedopen_object( user, object_id, object_text, date, unit, self.platform, self.platform_url, xapi_settings.OBJECT_TASK, verb, parent_id=self.create_list_url(data), obj_parent_type=xapi_settings. OBJECT_COLLECTION, other_contexts=other_context_list) def create_card_url(self, data): return self.platform_url + '/c/' + str(data['card']['id']) def create_list_url(self, data): return self.platform_url + '/b/' + str( data['board']['id']) + '/' + str(data['list']['id']) def create_checklist_url(self, data): return self.platform_url + '/b/' + str( data['board']['id']) + '/' + str(data['checklist']['id']) def get_verbs(self): return self.xapi_verbs def get_objects(self): return self.xapi_objects def get_other_contextActivity_types(self, verbs=[]): ret = [] if verbs is None or len(verbs) == 0: ret = [ self.ACTION_TYPE_COMMENT_CARD, self.ACTION_TYPE_CREATE_CARD, self.ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD, self.ACTION_TYPE_UPDATE_CARD, self.ACTION_TYPE_ADD_ATTACHMENT_TO_CARD, self.ACTION_TYPE_ADD_CHECKLIST_TO_CARD, self.ACTION_TYPE_ADD_MEMBER_TO_CARD, self.ACTION_TYPE_MOVE_CARD, self.ACTION_TYPE_CLOSE_CARD, self.ACTION_TYPE_OPEN_CARD ] else: for verb in verbs: action_types = self.VERB_OBJECT_MAPPER[verb] for type in action_types: ret.append(type) return ret def get_display_names(self, mapper): if mapper is None: return mapper ret = {} for key, val in mapper.iteritems(): for action in mapper[key]: ret[action] = self.get_action_type_display_name(action) return ret def get_action_type_display_name(self, action): if action == self.ACTION_TYPE_CREATE_CARD: return 'Created card' elif action == self.ACTION_TYPE_ADD_ATTACHMENT_TO_CARD: return 'Added attachment to card' elif action == self.ACTION_TYPE_ADD_CHECKLIST_TO_CARD: return 'Added checklist to card' elif action == self.ACTION_TYPE_ADD_MEMBER_TO_CARD: return 'Added member to card' elif action == self.ACTION_TYPE_MOVE_CARD: return 'Moved card' elif action == self.ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD: return 'Updated checklist item state' elif action == self.ACTION_TYPE_COMMENT_CARD: return 'Commented on card' elif action == self.ACTION_TYPE_CLOSE_CARD: return 'Closed card' elif action == self.ACTION_TYPE_OPEN_CARD: return 'Opened card' else: return 'Unknown action type' def get_detail_values_by_fetch_results(self, xapi_statements): all_rows = [] # return all_rows for stmt in xapi_statements: single_row = [] # user name name = '' if 'name' in stmt['authority']['member'][0]: name = stmt['authority']['member'][0]['name'] else: name = stmt['authority']['member'][1]['name'] single_row.append(name) # verb or original action type other_context_activities = stmt['context']['contextActivities'][ 'other'] single_row.append( self.get_action_type_from_context(other_context_activities)) # Date dt = Utility.convert_to_datetime_object(stmt['timestamp']) date_str = str(dt.year) + ',' + str(dt.month) + ',' + str(dt.day) # date_str += ' ' + str(dt.hour) + ':' + str(dt.minute) + ':' + str(dt.second) single_row.append(date_str) # Value of an object single_row.append(self.get_object_diaplay_value(stmt)) all_rows.append(single_row) return all_rows def get_action_type_from_context(self, json): return json[0]['definition']['name']['en-US'] def get_object_diaplay_value(self, stmt): other_context_activities = stmt['context']['contextActivities'][ 'other'] action = self.get_action_type_from_context(other_context_activities) object_val = stmt['object']['definition']['name']['en-US'] if len(other_context_activities) <= 1: return object_val object_val = object_val contexts = other_context_activities value = '' index = 1 if action == self.ACTION_TYPE_CREATE_CARD: value = "created %s in %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) elif action == self.ACTION_TYPE_ADD_ATTACHMENT_TO_CARD: value = "attached %s to %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) value = value + self.SEPARATOR_HTML_TAG_BR index = index + 1 value = value + contexts[index]['definition']['name']['en-US'] elif action == self.ACTION_TYPE_ADD_CHECKLIST_TO_CARD: value = "added %s to %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) value = value + self.SEPARATOR_HTML_TAG_BR index = index + 1 for key in range(index, len(contexts)): value = value + ' ' + contexts[key]['definition']['name'][ 'en-US'] + self.SEPARATOR_HTML_TAG_BR elif action == self.ACTION_TYPE_ADD_MEMBER_TO_CARD: value = "added %s to %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) value = value + self.SEPARATOR_HTML_TAG_BR elif action == self.ACTION_TYPE_MOVE_CARD: if contexts[index]['definition']['name'][ 'en-US'] == self.MESSAGE_CARD_POSITION_CHANGED: value = contexts[index]['definition']['name']['en-US'] value = value + self.SEPARATOR_HTML_TAG_BR value = value + self.italicize(object_val) else: value = "moved %s" % (self.italicize(object_val)) value = value + " from %s" % (self.italicize( contexts[index]['definition']['name']['en-US'])) index = index + 1 value = value + " to %s" % (self.italicize( contexts[index]['definition']['name']['en-US'])) elif action == self.ACTION_TYPE_UPDATE_CHECKITEM_STATE_ON_CARD: value = "%s: %s" % (self.italicize( contexts[index]['definition']['name']['en-US']), self.italicize(object_val)) elif action == self.ACTION_TYPE_COMMENT_CARD: value = 'commented in %s' % self.italicize( contexts[index]['definition']['name']['en-US']) value = value + self.SEPARATOR_HTML_TAG_BR value = value + self.italicize( self.replace_linechange_with_br_tag(object_val)) elif action == self.ACTION_TYPE_CLOSE_CARD: value = "closed %s in %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) elif action == self.ACTION_TYPE_OPEN_CARD: value = "opened %s in %s" % ( self.italicize(object_val), self.italicize(contexts[index]['definition']['name']['en-US'])) else: value = self.italicize(object_val) return value def italicize(self, value): return '<i>%s</i>' % (value) def replace_linechange_with_br_tag(self, target): return target.replace('\n', '<br>')
class TrelloCollector(object): """ Class representing all Trello information required to do the SysDesEng reporting. """ def __init__(self, report_config, trello_secret): self.logger = logging.getLogger(__name__) self.client = TrelloClient(api_key = trello_secret[':consumer_key'], api_secret = trello_secret[':consumer_secret'], token = trello_secret[':oauth_token'], token_secret = trello_secret[':oauth_token_secret']) #Extract report configuration parameters trello_sources = report_config[':trello_sources']; #self.report_parameters = report_config[':output_metadata']; gen_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") self.content = { ':output_metadata' : { ':gen_date': gen_date, #Report name is built as :report_name + gen_date (where :report_name is taken from the config) ':trello_sources': { ':boards':{}, ':lists': {}, ':cards': [] }}} self.load_config(trello_sources, self.content[':output_metadata'][':trello_sources']) self.logger.debug("Report output metadata: %s" % (self.content[':output_metadata'])) def load_config(self, config_src, report_metadata): """ load all config data related to trello sources and structure them in the report_metadata""" for card_type in config_src.keys(): #card_type is project|assignment|epic for board_t in config_src[card_type].keys(): board_id = config_src[card_type][board_t][':board_id'] if not board_id in report_metadata: # initialize if the board wasn't present during the iterations over other card_type's if not board_id in report_metadata[':boards']: report_metadata[':boards'][board_id] = {}; report_metadata[':boards'][board_id][':board_id'] = config_src[card_type][board_t][':board_id'] #copy board id report_metadata[':boards'][board_id][':board_name'] = board_t if not ':lists' in report_metadata[':boards'][board_id]: report_metadata[':boards'][board_id][':lists'] = [] #iterate through all the lists and populate them for list_t in config_src[card_type][board_t][':lists'].keys(): self.logger.debug("Adding board %s, list %s to the report" % (config_src[card_type][board_t][':board_id'], config_src[card_type][board_t][':lists'][list_t])) list_id = config_src[card_type][board_t][':lists'][list_t] report_metadata[':lists'][list_id] = {}; report_metadata[':lists'][list_id][':list_id'] = list_id report_metadata[':lists'][list_id][':completed'] = False; report_metadata[':lists'][list_id][':card_type'] = card_type; report_metadata[':lists'][list_id][':board_id'] = board_id report_metadata[':boards'][board_id][':lists'].append(list_id) if ':done_lists' in config_src[card_type][board_t]: for list_t in config_src[card_type][board_t][':done_lists'].keys(): self.logger.debug("Adding board %s, Done list %s to the report" % (config_src[card_type][board_t][':board_id'], config_src[card_type][board_t][':done_lists'][list_t])) list_id = config_src[card_type][board_t][':done_lists'][list_t] report_metadata[':lists'][list_id] = {}; report_metadata[':lists'][list_id][':list_id'] = list_id report_metadata[':lists'][list_id][':completed'] = True; report_metadata[':lists'][list_id][':card_type'] = card_type; report_metadata[':lists'][list_id][':board_id'] = board_id report_metadata[':boards'][board_id][':lists'].append(list_id) def list_boards(self): syseng_boards = self.client.list_boards() for board in syseng_boards: for tlist in board.all_lists(): self.logger.info('board name: %s is here, board ID is: %s; list %s is here, list ID is: %s' % (board.name, board.id, tlist.name, tlist.id)) def parse_trello(self, deep_scan): """ :deep_scan: If deep_scan is True the scan will traverse actions, otherwise just a light scan(much faster) Main function to parse all Trello boards and lists. """ trello_sources = self.content[':output_metadata'][':trello_sources']; self.logger.debug('The sources are %s' % (trello_sources)) for board_id in trello_sources[':boards'].keys(): tr_board = self.client.get_board(board_id); tr_board.fetch(); # get all board properties members = [ (m.id, m.full_name) for m in tr_board.get_members()]; trello_sources[':boards'][board_id][':members'] = members; self.logger.info('----- querying board %s -----' % (trello_sources[':boards'][board_id][':board_name'])) self.logger.debug('Board members are %s' % (trello_sources[':boards'][board_id][':members'])) #trello_sources[board_id][':cards'] = [] cards = tr_board.get_cards(); for card in cards: card_content = {} card_content[':name'] = card.name card_content[':id'] = card.id card_content[':members'] = [] card_content[':board_id'] = tr_board.id for member_id in card.member_ids: for (m_id, m_full_name) in members: if member_id == m_id : card_content[':members'].append((m_id,m_full_name)) card_content[':desc'] = card.desc card_content[':short_url'] = card.url card_content[':labels'] = [(label.name,label.color) for label in card.labels] #self.logger.debug('Card: {0} | LABELES are {1}'.format(card_content[':name'], card_content[':labels'])) card_content[':board_name'] = tr_board.name card_content[':list_id'] = card.list_id if card.due: card_content[':due_date'] = arrow.get(card.due).format('YYYY-MM-DD HH:mm:ss') trello_sources[':cards'].append(card_content); self.logger.debug('%s cards were collected' % (len(cards))) tr_board.fetch_actions(action_filter="commentCard,updateCard:idList,createCard,copyCard,moveCardToBoard,convertToCardFromCheckItem",action_limit=1000); trello_sources[':boards'][board_id][':actions'] = sorted(tr_board.actions,key=lambda act: act['date'], reverse=True) self.logger.debug('%s actions were collected' % (len(trello_sources[':boards'][board_id][':actions']))) #self.logger.debug('Oldest action is %s' % (trello_sources[':boards'][board_id][':actions'][-1])) tr_lists = tr_board.all_lists() for tr_list in tr_lists: if tr_list.id in trello_sources[':lists']: trello_sources[':lists'][tr_list.id][':name'] = tr_list.name; self.logger.info('the lists are %s' % (tr_lists)) return self.content def parse_card_details(self, card_id): card = card_details.CardDetails(card_id, self.client, self.content[':output_metadata']) details = card.fill_details(); #self.logger.debug('Card\'s details are: %s' % (details)) return details
class ServiceTrello(ServicesMgr): """ Serivce Trello """ # Boards own Lists own Cards def __init__(self, token=None, **kwargs): super(ServiceTrello, self).__init__(token, **kwargs) # app name self.app_name = DjangoThConfig.verbose_name # expiration self.expiry = "30days" # scope define the rights access self.scope = 'read,write' self.oauth = 'oauth1' self.service = 'ServiceTrello' base = 'https://www.trello.com' self.AUTH_URL = '{}/1/OAuthAuthorizeToken'.format(base) self.REQ_TOKEN = '{}/1/OAuthGetRequestToken'.format(base) self.ACC_TOKEN = '{}/1/OAuthGetAccessToken'.format(base) self.consumer_key = settings.TH_TRELLO_KEY['consumer_key'] self.consumer_secret = settings.TH_TRELLO_KEY['consumer_secret'] if token: token_key, token_secret = token.split('#TH#') try: self.trello_instance = TrelloClient(self.consumer_key, self.consumer_secret, token_key, token_secret) except ResourceUnavailable as e: us = UserService.objects.get(token=token) logger.error(e.msg, e.error_code) update_result(us.trigger_id, msg=e.msg, status=False) def read_data(self, **kwargs): """ get the data from the service :param kwargs: contain keyword args : trigger_id at least :type kwargs: dict """ trigger_id = kwargs.get('trigger_id') data = list() kwargs['model_name'] = 'Trello' kwargs['app_label'] = 'th_trello' super(ServiceTrello, self).read_data(**kwargs) cache.set('th_trello_' + str(trigger_id), data) return data def save_data(self, trigger_id, **data): """ let's save the data :param trigger_id: trigger ID from which to save data :param data: the data to check to be used and save :type trigger_id: int :type data: dict :return: the status of the save statement :rtype: boolean """ data['output_format'] = 'md' title, content = super(ServiceTrello, self).save_data(trigger_id, **data) if len(title): # get the data of this trigger t = Trello.objects.get(trigger_id=trigger_id) # footer of the card footer = self.set_card_footer(data, t) content += footer # 1 - we need to search the list and board where we will # store the card so ... # 1.a search the board_id by its name # by retrieving all the boards boards = self.trello_instance.list_boards() board_id = '' my_list = '' for board in boards: if t.board_name == board.name: board_id = board.id break if board_id: # 1.b search the list_id by its name my_board = self.trello_instance.get_board(board_id) lists = my_board.open_lists() # just get the open list ; not all the archive ones for list_in_board in lists: # search the name of the list we set in the form if t.list_name == list_in_board.name: # return the (trello) list object to be able to add card at step 3 my_list = my_board.get_list(list_in_board.id) break # we didnt find the list in that board -> create it if my_list == '': my_list = my_board.add_list(t.list_name) else: # 2 if board_id and/or list_id does not exist, create it/them my_board = self.trello_instance.add_board(t.board_name) # add the list that didn't exists and return a (trello) list object my_list = my_board.add_list(t.list_name) # 3 create the card my_list.add_card(title, content) logger.debug(str('trello {} created').format(data['link'])) status = True else: sentence = "no token or link provided for trigger ID {}".format(trigger_id) update_result(trigger_id, msg=sentence, status=False) status = False return status @staticmethod def set_card_footer(data, trigger): """ handle the footer of the note """ footer = '' if data.get('link'): provided_by = _('Provided by') provided_from = _('from') footer_from = "<br/><br/>{} <em>{}</em> {} <a href='{}'>{}</a>" description = trigger.trigger.description footer = footer_from.format(provided_by, description, provided_from, data.get('link'), data.get('link')) import pypandoc footer = pypandoc.convert(footer, 'md', format='html') return footer def auth(self, request): """ let's auth the user to the Service :param request: request object :return: callback url :rtype: string that contains the url to redirect after auth """ request_token = super(ServiceTrello, self).auth(request) callback_url = self.callback_url(request) # URL to redirect user to, to authorize your app auth_url_str = '{auth_url}?oauth_token={token}' auth_url_str += '&scope={scope}&name={name}' auth_url_str += '&expiration={expiry}&oauth_callback={callback_url}' auth_url = auth_url_str.format(auth_url=self.AUTH_URL, token=request_token['oauth_token'], scope=self.scope, name=self.app_name, expiry=self.expiry, callback_url=callback_url) return auth_url def callback(self, request, **kwargs): """ Called from the Service when the user accept to activate it :param request: request object :return: callback url :rtype: string , path to the template """ return super(ServiceTrello, self).callback(request, **kwargs)
class Trello: client = None def __init__(self): self.client = TrelloClient( api_key=os.environ.get("trello_api_key"), api_secret=os.environ.get("trello_api_secret"), token=os.environ.get("trello_token"), ) def get_project_list(self): projects = [] for b in self.client.list_boards(): projects.append({"name": b.name.decode("utf-8"), "id": b.id}) return projects def get_sprints(self, trell_project): board = self.client.get_board(trell_project) sprints = [] for l in board.all_lists(): m = re.search("Sprint (.*?) - Done", l.name.decode("utf-8")) if m is not None: sprints.append({"sprint": m.group(1)}) return sprints def get_story_list(self, trell_project, sprint): board = self.client.get_board(trell_project) stories = [] list = board.all_lists() for l in board.all_lists(): type = None if l.name.decode("utf-8") == "Sprint " + sprint + " - Backlog": type = "Backlog" elif l.name.decode("utf-8") == "Sprint " + sprint + " - Doing": type = "Doing" elif l.name.decode("utf-8") == "Sprint " + sprint + " - Done": type = "Done" if type is not None: for c in l.list_cards(): if type == "Done": if len(c.listCardMove_date()) > 0: # Convert DateTime to Epoch card_date = time.mktime(parser.parse(str(c.latestCardMove_date)).timetuple()) else: # Convert DateTime to Epoch card_date = time.mktime(c.create_date().timetuple()) else: card_date = None split = c.name.decode("utf-8").split("(") points = split[len(split) - 1].replace(")", "") del split[len(split) - 1] name = "(".join(split) stories.append( {"status": type, "name": name, "id": c.id, "points": points, "date_completed": card_date} ) return stories
nucleos_tasks = wks.acell('G6').value it_tasks = wks.acell('G7').value planned_tasks = wks.acell('B%s' % (row_completed_tasks)).value wks.update_cell(2, 7, int(bernies_tasks) + members['Christian Bronstein']) wks.update_cell(3, 7, int(gonz_tasks) + members['Gonzalo Parra']) wks.update_cell(4, 7, int(lermits_tasks) + members['Lermit Rosell']) wks.update_cell(6, 7, int(nucleos_tasks) + labels['Nucleo']) wks.update_cell(7, 7, int(it_tasks) + labels['IT']) wks.update_cell(row_completed_tasks, 3, tasks) percentage = wks.acell('D%s' % (row_completed_tasks)).value return percentage, planned_tasks origin = client.get_board(get_board_id('Origin')) sprint = origin.get_list(get_list_id(origin, 'Done')) sprint.fetch() sprint_tasks = sprint.list_cards() labels, members, task_count, tasks = get_sprint_info() sprint_start_date = date.today() sprint_close_date = sprint_start_date + timedelta(days=7) sprint_lenght = "%s / %s" % (sprint_start_date, sprint_close_date) percentage, planned_tasks = record_on_gsheets(sprint, tasks, members, labels) #Send Slack Message sc.api_call( "chat.postMessage", channel="#sprint", text= ":loudspeaker: *Atencion:* \nQueridos Originarios! Es _Benito_ otra vez.\nEsta vez quiero darles algunos datos del sprint pasado:\n completaron *%s* de *%s* tareas, lo que representa un *%s* del sprint.\n Estuvo bien, pero ser mejor, por ahora no me comere sus zapatos.\n_Happy Planning_ :dog:" % (task_count, planned_tasks, percentage))
class TrelloBoardTestCase(unittest.TestCase): """ Tests for TrelloClient API. Note these test are in order to preserve dependencies, as an API integration cannot be tested independently. """ def setUp(self): self._trello = TrelloClient(os.environ['TRELLO_API_KEY'], token=os.environ['TRELLO_TOKEN']) for b in self._trello.list_boards(): if b.name == os.environ['TRELLO_TEST_BOARD_NAME']: self._board = b break try: self._list = self._board.open_lists()[0] except IndexError: self._list = self._board.add_list('List') def _add_card(self, name, description=None): try: card = self._list.add_card(name, description) self.assertIsNotNone(card, msg="card is None") self.assertIsNotNone(card.id, msg="id not provided") self.assertEquals(card.name, name) return card except Exception as e: print(str(e)) self.fail("Caught Exception adding card") def test40_add_card(self): name = "Testing from Python - no desc" card = self._add_card(name) self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") card2 = self._trello.get_card(card.id) self.assertEqual(card.name, card2.name) def test41_add_card(self): name = "Testing from Python" description = "Description goes here" card = self._add_card(name, description) self.assertEquals(card.description, description) self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") card.fetch() self.assertIsNotNone(card.member_id) self.assertIsNotNone(card.short_id) self.assertIsNotNone(card.list_id) self.assertIsNotNone(card.comments) self.assertIsNotNone(card.checklists) self.assertIsInstance(card.create_date, datetime) def test42_add_card_with_comments(self): name = "Card with comments" comment = "Hello World!" card = self._add_card(name) card.comment(comment) card.fetch(True) self.assertEquals(card.description, '') self.assertIsNotNone(card.closed, msg="closed not provided") self.assertIsNotNone(card.url, msg="url not provided") self.assertEquals(len(card.comments), 1) self.assertEquals(card.comments[0]['data']['text'], comment) def test43_delete_checklist(self): name = "Card with comments" card = self._list.add_card(name) card.fetch(True) name = 'Checklists' checklist = card.add_checklist(name, ['item1', 'item2']) self.assertIsNotNone(checklist, msg="checklist is None") self.assertIsNotNone(checklist.id, msg="id not provided") self.assertEquals(checklist.name, name) checklist.delete() card.delete() def test44_attach_url_to_card(self): name = "Testing from Python - url" card = self._add_card(name) card.attach(name='lwn', url='http://lwn.net/') card.fetch() self.assertEquals(card.badges['attachments'], 1) card.delete() def test52_get_cards(self): cards = self._board.get_cards() self.assertEquals(len(cards), 4) for card in cards: if card.name == 'Testing from Python': self.assertEqual(card.description, 'Description goes here') elif card.name == 'Testing from Python - no desc': self.assertEqual(card.description, '') elif card.name == 'Card with comments': self.assertEqual(card.description, '') else: self.fail(msg='Unexpected card found') self.assertIsInstance(self._board.all_cards(), list) self.assertIsInstance(self._board.open_cards(), list) self.assertIsInstance(self._board.closed_cards(), list) def test52_add_card_set_due(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card(name, description) # Set the due date to be 3 days from now today = datetime.today() day_detla = timedelta(3) due_date = today + day_detla card.set_due(due_date) expected_due_date = card.due # Refresh the due date from cloud card.fetch() actual_due_date = card.due[:10] self.assertEquals(expected_due_date, actual_due_date) # Note that set_due passes only the date, stripping time self.assertEquals(card.due_date.date(), due_date.date()) def test53_checklist(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card(name, description) name = 'Checklists' checklist = card.add_checklist(name, ['item1', 'item2']) self.assertIsNotNone(checklist, msg="checklist is None") self.assertIsNotNone(checklist.id, msg="id not provided") self.assertEquals(checklist.name, name) checklist.rename('Renamed') self.assertEquals(checklist.name, 'Renamed') def test54_set(self): name = "Testing from Python" description = "Description goes here" card = self._list.add_card('noname') card.set_name(name) card.set_description(description) self.assertEquals(card.name, name) self.assertEquals(card.description, description) def test55_set_pos(self): card_names = lambda: [c.name for c in self._list.list_cards()] self._list.add_card('card1') card2 = self._list.add_card('card2') names = card_names() self.assertGreater(names.index('card2'), names.index('card1')) card2.set_pos('top') names = card_names() self.assertGreater(names.index('card1'), names.index('card2')) card2.set_pos('bottom') names = card_names() self.assertGreater(names.index('card2'), names.index('card1')) def test60_delete_cards(self): cards = self._board.get_cards() for card in cards: card.delete() def test70_all_members(self): self.assertTrue(len(self._board.all_members()) > 0) def test71_normal_members(self): self.assertTrue(len(self._board.normal_members()) >= 0) def test72_admin_members(self): self.assertTrue(len(self._board.admin_members()) > 0) def test73_owner_members(self): members = self._board.owner_members() self.assertTrue(len(members) > 0) member = members[0].fetch() self.assertNotEqual(member.status, None) self.assertNotEqual(member.id, None) self.assertNotEqual(member.bio, None) self.assertNotEqual(member.url, None) self.assertNotEqual(member.username, None) self.assertNotEqual(member.full_name, None) self.assertNotEqual(member.initials, None) member2 = self._trello.get_member(member.id) self.assertEqual(member.username, member2.username) def test80_unauthorized(self): client = TrelloClient('a') self.assertRaises(Unauthorized, client.list_boards) def test81_resource_unavailable(self): self.assertRaises(ResourceUnavailable, self._trello.get_card, '0') def test90_get_board(self): board = self._trello.get_board(self._board.id) self.assertEqual(self._board.name, board.name)
def trelloToSQL(): ''' Liest Trello-Karten aus Liste "Texte lektoriert" des "Play-Boards" aus und wandelt Sie mit Pandoc in html um. Inklusive Literaturverzeichnis. Zusätzlich werden ein paar weitere Formatierungen vorgenommen. Das Ergebnis wird dann in die Datenbank geschrieben. Die Trello-Karten werden hinterher verschoben. ''' from Scholien.models import Artikel client = TrelloClient(api_key=settings.TRELLO_API_KEY, token=settings.TRELLO_TOKEN) play_board = client.get_board('55d5dfee98d40fcb68fc0e0b') played_board = client.get_board('55c4665a4afe2f058bd3cb0a') target_list = played_board.get_list('5774e15c515d20dd2aa0b534') for list in play_board.open_lists(): if list.name == "Texte lektoriert": print('%d lektorierte(n) Text(e) gefunden.' % len(list.list_cards())) # Karten werden von unten nach oben abgearbeitet. for card in list.list_cards()[::-1]: title = card.name text = card.desc fobj_out = codecs.open(os.path.join(md_path, "%s.md" % title), "w", "utf-8") # meta = codecs.open("%s" %meta,"r","utf-8") # fobj_out.write(meta.read()) # ids p = re.compile(r"§§.*") id = p.findall(text) id = id[0][2:] if id else title priority = 1 if id[0] == '!' else 0 id = slugify(id) text = p.sub("", text, count=1) fobj_out.write("---\nbibliography: {}\n---\n\n{}\n\n## Literatur".format(bib, text)) fobj_out.close # to html fobj_out = codecs.open(os.path.join(md_path, "%s.md" % title), "r", "utf-8") md = fobj_out.read() extra_args = [] filters = ['pandoc-citeproc'] html = pypandoc.convert(md, 'html', format='md', extra_args=extra_args, filters=filters) # blockquotes mit class versehen p = re.compile("<blockquote>") html = p.sub("<blockquote class=\"blockquote\">", html) # Gedankenstriche ("--" nach "–") p = re.compile("--") html = p.sub("–", html) # Trennungszeichen p = re.compile(r"<p><<<</p>") split = re.split(p, html) public = split[0] # lstrip entfernt mögliche Absätze am Anfang. private = split[1].lstrip() if len(split) > 1 else "" if not private: print('Keinen privaten Teil gefunden für', title) # print(html) try: art_neu = Artikel.objects.create( slug=id, bezeichnung=title, inhalt=public, inhalt_nur_fuer_angemeldet=private, prioritaet=priority ) # *art_neu* currently not in use print('%s erfolgreich in DB übertragen.' % title) except IntegrityError as e: print('Artikel schon vorhanden') except Exception as e: print(title, 'failed:', e) continue card.change_board(played_board.id, target_list.id) card.set_pos('top')
class TrelloList(object): """ Sugar class to work with Trello Lists. """ def __init__(self, board_id, list_id, api_key, token=None, **kwargs): """ Validate inputs and connect to Trello API. Exception is thrown if input details are not correct. :param board_id: Trello board ID where the List is located :type board_id: ``str`` :param list_id: Trello List ID itself :type list_id: ``str`` :param api_key: Trello API key :type api_key: ``str`` :param token: Trello API token :type token: ``str`` """ self.board_id = board_id self.list_id = list_id self.api_key = api_key # assume empty string '' as None self.token = token or None self.validate() self._client = TrelloClient(api_key=self.api_key, token=self.token) self._list = self._client.get_board(self.board_id).get_list(self.list_id) def validate(self): """ Ensure that Trello list details are correct. Raise an exception if validation failed. """ if not self.api_key: raise ValueError('[TrelloListSensor] "api_key" config value is required!') assert isinstance(self.api_key, basestring) if self.token: assert isinstance(self.token, basestring) if not self.board_id: raise ValueError('[TrelloListSensor]: "board_id" config value is required!') assert isinstance(self.board_id, basestring) if not self.list_id: raise ValueError('[TrelloListSensor]: "list_id" config value is required!') assert isinstance(self.list_id, basestring) @property def key_name(self): """ Generate unique key name for built-in storage based on config values. :rtype: ``str`` """ return '{}.{}.date'.format(self.board_id, self.list_id) def fetch_actions(self, filter=None, since=None): """ Fetch actions for Trello List with possibility to specify filters. Example API request: https://api.trello.com/1/lists/{list_id}/actions?filter=createCard&since=2015-09-14T21:45:56.850Z&key={key_id}&token={token_id} :param filter: Action types to filter, separated by comma or as a sequence. :type filter: ``str`` or ``list`` :param since: Filter actions since specified date. :type since: ``str`` :return: Events occurred in Trello list. :rtype: ``list`` of ``dict`` """ return self._client.fetch_json( '/lists/' + self._list.id + '/actions', query_params={ 'filter': filter, 'since': since, })
def main(): parser = OptionParser(usage='Usage: %prog [options]') # look for the config file in ~/.trello.creds JSON # Should contain two keys # api-key # token default_config = os.path.expanduser('~') # TODO only shows Cards with a given Labels # TODO save current preferences into a config file (prefered board) # TODO build unit testing parser.add_option('-c', dest='credentials', help='location of the trello credential ' 'json file (default ~/.trello.creds ', default="{}/.trello.creds".format(default_config)) parser.add_option('-b', '--list-boards', dest='show_boards', help='List all Boards available"', action='store_true') parser.add_option('-B', dest='board', help='Active Board') parser.add_option('-l', '--list-lanes', dest='show_lanes', help='List all Lanes available"', action='store_true') parser.add_option('-L', dest='lanes', help='show only cards in lanes "Lane1,Lane2,Lane3"') parser.add_option('-m', '--list-members', dest='show_members', help='List all members available', action='store_true') parser.add_option('-s', dest='show_labels', help='show labels with cards', action='store_true') parser.add_option('-M', dest='members', help='filter with a specific team member ' '(Names: coma separated list of names bob,sam,Luk all:' 'display everyone, none: display unassigned cards)') opts, args = parser.parse_args() try: with open(opts.credentials) as f: config = json.load(f) trello_api_key = config['api_key'] trello_token = config['token'] except (FileNotFoundError, json.JSONDecodeError): print('Trello Token information file {} ' 'could not be found or parsed.'.format(opts.credentials)) print('') gather_token = input( "Do you want to entre your Trello token information now " "(see https://trello.com/app-key/) ? (Y/n) ") if gather_token == 'n': return 1 trello_api_key = input('Please enter your Trello api key : ') trello_token = input('Please enter your Trello api token : ') save_token = input("Do you want to save those credentials for future " "use of trello-cli? (Y/n) ") if save_token != 'n': try: data = {} data['api_key'] = trello_api_key data['token'] = trello_token with open(opts.credentials, 'w+') as f: json.dump(data, (f)) except (FileNotFoundError, json.JSONDecodeError): # TODO: Probably better error handling can be done here print("Something went wrong saving credentials") return 1 the_boards = {} the_board_lanes = {} the_board_members = {} the_board_cards = [] # TODO Catch error opening Trello here client = TrelloClient(api_key=trello_api_key, token=trello_token) # Get the list of boards available to current user for board in client.list_boards(): the_boards[board.name] = board.id if opts.show_boards: print(board.name) # Exit after returning the board list if opts.show_boards: return 1 # Pick the Active Board if opts.board: the_board = client.get_board(the_boards[opts.board]) else: print("Active board missing, use -B \"Board Name\"") parser.print_help() return 0 # Capture the board members in a dictionary for member in the_board.get_members(): the_board_members[member.full_name] = member.id if opts.show_members: for name in the_board_members.keys(): print(name) return 1 if not opts.show_lanes and not opts.lanes: print("Missing lanes information") parser.print_help() return 1 # Pick The Board Open Lanes for lane in the_board.get_lists('open'): if opts.show_lanes: print(lane.name) else: if opts.lanes: if lane.name in opts.lanes.split(","): the_board_lanes[lane.name] = lane.id else: the_board_lanes[lane.name] = lane.id if opts.show_lanes: return 1 # Capture all the cards from active lane into a dictionary for card in the_board.open_cards(): if card.list_id in the_board_lanes.values(): the_board_cards.append(card) # Simply show all the cards from said Lanes if no member selected if not opts.members: for lane in the_board_lanes: if the_board_cards: print(" {}".format(lane)) drawCards(the_board_cards, opts.show_labels) elif opts.members.split(",")[0] == "none": for lane in the_board_lanes: card_list = [] # Show the card only if nobody assigned to it # and the lane is in the list for card in list( filter( lambda x: len(x.member_id) == 0 and x.list_id == the_board_lanes[lane], the_board_cards)): card_list.append(card) if card_list: print(" {}".format(lane)) drawCards(card_list, opts.show_labels) else: # Print active cards per member/lane for member in the_board_members: if opts.members.split(",")[0] == "all" or listInString( opts.members, member): if list( filter( lambda x: the_board_members[member] in x.member_id, the_board_cards)): print(member) for lane in the_board_lanes: card_list = [] for card in list( filter( lambda x: the_board_members[member] in x. member_id and x.list_id == the_board_lanes[ lane], the_board_cards)): card_list.append(card) if card_list: print(" {}".format(lane)) drawCards(card_list, opts.show_labels) return 0
from fish import ProgressFish ## get the default browser br = mechanize.Browser() ua = ('Mozilla/5.0 (X11; U; Linux i686)' 'Gecko/20071127 Firefox/2.0.0.11') br.addheaders = [('User-Agent', ua)] #br.set_debug_http(True) #br.set_debug_responses(True) #t_com = twill.commands #t_brw = t_com.get_browser() #Trello authentication auth = TrelloClient(os.environ['TRELLO_API_KEY'], os.environ['TRELLO_TOKEN']) gca_board = auth.get_board('4f199b088ab038761f17b066') gca_lists = gca_board.all_lists() total_sites = None chapters_to_check = [] chapters_with_web_sites = [] for gca_list in gca_lists: #print gca_list.id, gca_list.name # we get the 'Chapters to Check' list # with id '4f199b088ab038761f17b06b' if gca_list.id == '4f199b088ab038761f17b06b': # get the cards cards = gca_list.list_cards() total_sites = len(cards) for i, c in enumerate(cards): # do something with the description and try to extract the website address
class ServiceTrello(ServicesMgr): # Boards own Lists own Cards def __init__(self, token=None): # app name self.app_name = DjangoThConfig.verbose_name # expiration self.expiry = "30days" # scope define the rights access self.scope = 'read,write' base = 'https://www.trello.com' self.AUTH_URL = '{}/1/OAuthAuthorizeToken'.format(base) self.REQ_TOKEN = '{}/1/OAuthGetRequestToken'.format(base) self.ACC_TOKEN = '{}/1/OAuthGetAccessToken'.format(base) self.consumer_key = settings.TH_TRELLO['consumer_key'] self.consumer_secret = settings.TH_TRELLO['consumer_secret'] if token: token_key, token_secret = token.split('#TH#') self.trello_instance = TrelloClient(self.consumer_key, self.consumer_secret, token_key, token_secret) def read_data(self, token, trigger_id, date_triggered): """ get the data from the service as the pocket service does not have any date in its API linked to the note, add the triggered date to the dict data thus the service will be triggered when data will be found :param trigger_id: trigger ID to process :param date_triggered: the date of the last trigger :type trigger_id: int :type date_triggered: datetime :return: list of data found from the date_triggered filter :rtype: list """ data = list() cache.set('th_trello_' + str(trigger_id), data) def process_data(self, trigger_id): """ get the data from the cache :param trigger_id: trigger ID from which to save data :type trigger_id: int """ cache_data = cache.get('th_trello_' + str(trigger_id)) return PublishingLimit.get_data('th_trello_', cache_data, trigger_id) def save_data(self, token, trigger_id, **data): """ let's save the data :param trigger_id: trigger ID from which to save data :param **data: the data to check to be used and save :type trigger_id: int :type **data: dict :return: the status of the save statement :rtype: boolean """ from th_trello.models import Trello title = '' content = '' status = False title = self.set_card_title(data) content = self.set_card_content(data) if len(title): # get the data of this trigger t = Trello.objects.get(trigger_id=trigger_id) # 1 - we need to search the list and board where we will # store the card so ... # 1.a search the board_id by its name # by retreiving all the boards boards = self.trello_instance.list_boards() board_id = '' my_board = '' my_list = '' for board in boards: if t.board_name == board.name.decode('utf-8'): board_id = board.id break if board_id: # 1.b search the list_id by its name my_board = self.trello_instance.get_board(board_id) lists = my_board.open_lists() # just get the open list ; not all the archive ones for list_in_board in lists: # search the name of the list we set in the form if t.list_name == list_in_board.name.decode('utf-8'): # return the (trello) list object # to be able to add card at step 3 my_list = my_board.get_list(list_in_board.id) break # we didnt find the list in that board # create it if my_list == '': my_list = my_board.add_list(t.list_name) else: # 2 if board_id and/or list_id does not exist, create it/them my_board = self.trello_instance.add_board(t.board_name) # add the list that didnt exists and # return a (trello) list object my_list = my_board.add_list(t.list_name) # 3 create the card # create the Trello card my_list.add_card(title, content) sentance = str('trello {} created').format(data['link']) logger.debug(sentance) status = True else: sentance = "no token or link provided for trigger ID {}" logger.critical(sentance.format(trigger_id)) status = False return status def set_card_title(self, data): """ handle the title from the data """ title = '' # if no title provided, fallback to the URL which should be provided # by any exiting service title = (data['title'] if 'title' in data else data['link']) return title def set_card_content(self, data): """ handle the content from the data """ content = '' if 'content' in data: if type(data['content']) is list or type(data['content']) is tuple\ or type(data['content']) is dict: if 'value' in data['content'][0]: content = data['content'][0].value else: if type(data['content']) is str: content = data['content'] else: # if not str or list or tuple # or dict it could be feedparser.FeedParserDict # so get the item value content = data['content']['value'] elif 'summary_detail' in data: if type(data['summary_detail']) is list or\ type(data['summary_detail']) is tuple or\ type(data['summary_detail']) is dict: if 'value' in data['summary_detail'][0]: content = data['summary_detail'][0].value else: if type(data['summary_detail']) is str: content = data['summary_detail'] else: # if not str or list or tuple # or dict it could be feedparser.FeedParserDict # so get the item value content = data['summary_detail']['value'] elif 'description' in data: content = data['description'] return content def auth(self, request): """ let's auth the user to the Service """ callback_url = 'http://%s%s' % ( request.get_host(), reverse('trello_callback')) request_token = self.get_request_token() # Save the request token information for later request.session['oauth_token'] = request_token['oauth_token'] request.session['oauth_token_secret'] = request_token[ 'oauth_token_secret'] # URL to redirect user to, to authorize your app auth_url_str = '{auth_url}?oauth_token={token}&scope={scope}&name={name}' auth_url_str += '&expiration={expiry}&oauth_callback={callback_url}' auth_url = auth_url_str.format(auth_url=self.AUTH_URL, token=request_token['oauth_token'], scope=self.scope, name=self.app_name, expiry=self.expiry, callback_url=callback_url) return auth_url def callback(self, request): """ Called from the Service when the user accept to activate it """ try: # finally we save the user auth token # As we already stored the object ServicesActivated # from the UserServiceCreateView now we update the same # object to the database so : # 1) we get the previous objet us = UserService.objects.get( user=request.user, name=ServicesActivated.objects.get(name='ServiceTrello')) # 2) Trello API require to use 4 parms consumer_key/secret + # token_key/secret instead of usually get just the token # from an access_token request. So we need to add a string # seperator for later use to slpit on this one access_token = self.get_access_token( request.session['oauth_token'], request.session['oauth_token_secret'], request.GET.get('oauth_verifier', '') ) us.token = access_token.get('oauth_token') + \ '#TH#' + access_token.get('oauth_token_secret') # 3) and save everything us.save() except KeyError: return '/' return 'trello/callback.html' def get_request_token(self): oauth = OAuth1Session(self.consumer_key, client_secret=self.consumer_secret) return oauth.fetch_request_token(self.REQ_TOKEN) def get_access_token(self, oauth_token, oauth_token_secret, oauth_verifier): # Using OAuth1Session oauth = OAuth1Session(self.consumer_key, client_secret=self.consumer_secret, resource_owner_key=oauth_token, resource_owner_secret=oauth_token_secret, verifier=oauth_verifier) oauth_tokens = oauth.fetch_access_token(self.ACC_TOKEN) return oauth_tokens
class EClaire(object): def __init__(self, credentials, boards=None): self.trello_client = TrelloClient( api_key=credentials['public_key'], token=credentials['member_token'], ) self.boards = boards def process_boards(self, dry_run=False, notify_fn=None, notify_config=None): """ Process each board in self.boards """ for name, board_config in self.boards.iteritems(): log.info('Polling %s', name) processed = self.process_board(board_config, dry_run) if board_config.get('notify', True) and notify_fn is not None: for card in processed: notify_fn(card, **notify_config) def process_board(self, board_config, dry_run=False): """ Process each card in a given board """ processed = [] for card in self.fetch_cards(board_id=board_config['id']): log.info('Printing card "%s"', card.name) pdf = generate_pdf(card) if not dry_run: print_card(pdf, printer_name=board_config['printer']) self.update_card(card, board_config) processed.append(card) return processed def fetch_cards(self, board_id): """ Fetch all candidate cards on a board for processing """ data = [] board = self.trello_client.get_board(board_id) for card in board.all_cards(): if FILTER_LABEL in [l.name for l in card.labels]: card.fetch_actions() data.append(card) return data def discover_labels(self): """ Store object references for special labels """ for name, config in self.boards.iteritems(): board = self.trello_client.get_board(config['id']) labels = {} for label in board.get_labels(limit=100): if label.name in SPECIAL_LABELS: labels[label.name] = label missing = set(SPECIAL_LABELS) - set(labels.keys()) if missing: log.fatal( 'Board "%s" is missing the labels %s', board.name, ' and '.join(missing) ) log.fatal('Exiting') sys.exit(1) config['labels'] = labels def remove_label(self, card, label): """ Remove a lable from a card. At the time of writing there is no way to remove a label with py-trello """ self.trello_client.fetch_json( '/cards/' + card.id + '/idLabels/' + label.id, http_method="DELETE" ) def update_card(self, card, board_config): """ Replace PRINTME label with PRINTED """ printme_label = board_config['labels']['PRINTME'] printed_label = board_config['labels']['PRINTED'] self.remove_label(card, printme_label) if printed_label not in card.labels: card.add_label(printed_label) def list_boards(self): """ Fetch all board IDs from trello & print them out """ for board in self.trello_client.list_boards(): print 'Board:', board.name print ' ID:', board.id print
class TrelloPlugin(DIBasePlugin, DIPluginDashboardMixin): platform = "trello" platform_url = "http://www.trello.com/" #created for "create" actions #added for "add" actions #updated for "update" actions #commented for card "comment" actions xapi_verbs = ['created', 'added', 'updated', 'commented', 'closed', 'opened'] #Note for "commented" actions #Task for "created", "added", "updated", and "commented" actions #Collection for any "created", "added", "updated" List actions (tentative use) xapi_objects = ['Note', 'Task', 'Collection', 'Person', 'File', 'checklist-item', 'checklist'] user_api_association_name = 'Trello UID' # eg the username for a signed up user that will appear in data extracted via a social API unit_api_association_name = 'Board ID' # eg hashtags or a group name config_json_keys = ['consumer_key', 'consumer_secret'] #from DIPluginDashboardMixin xapi_objects_to_includein_platformactivitywidget = ['Note'] xapi_verbs_to_includein_verbactivitywidget = ['created', 'shared', 'liked', 'commented'] #for OAuth1 authentication token_request_url = '' def __init__(self): pass #retreival param is the user_id def perform_import(self, retreival_param, course_code, token=None): #from clatoolkit.models import ApiCredentials #Set up trello auth and API #self.usercontext_storage_dict = json.load(ApiCredentials.objects.get(platform=retreival_param).credentials_json) self.TrelloCient = TrelloClient( api_key=os.environ.get("TRELLO_API_KEY"), #api_secret=self.api_config_dict['api_secret'], token=token ) #Get user-registered board in trello trello_board = self.TrelloCient.get_board(retreival_param) #Get boards activity/action feed trello_board.fetch_actions('all') #fetch_actions() collects actions feed and stores to trello_board.actions #self.key = trello_board.api_key #self.token = trello_board.resource_owner_key self.import_TrelloActivity(trello_board.actions, course_code) def import_TrelloActivity(self, feed, course_code): #User needs to sign up username and board (board can be left out but is needed) #TODO: RP print 'Beginning trello import!' for action in list(feed): #print 'action: %s' % (action) #action = json.load(action) #We need to connect this with our user profile somewhere when they initially auth u_id = action['idMemberCreator'] author = action['memberCreator']['username'] type = action['type'] #commentCard, updateCard, createList,etc data = action['data'] date = action['date'] board_name = data['board']['name'] print 'got action type: %s' % (type) #print 'is action comment? %s' % (type == 'commentCard') #Get all 'commented' verb actions if (type == 'commentCard'): #do stuff target_obj_id = data['card']['id'] #date comment_from_uid = u_id comment_from_name = author comment_message = data['text'] comment_id = action['id'] if username_exists(comment_from_uid, course_code, self.platform): usr_dict = get_userdetails(comment_from_uid, self.platform) insert_comment(usr_dict, target_obj_id, comment_id, comment_message, comment_from_uid, comment_from_name, date, course_code, self.platform, self.platform_url) print 'Inserted comment!' #print 'is action card creation? %s' % (type == 'createCard') #Get all 'create' verb actions if (type == 'createCard'): #, 'createList']): #date #list_id = data['list']['id'] task_id = data['card']['id'] task_name = data['card']['name'] if username_exists(u_id, course_code, self.platform): usr_dict = get_userdetails(u_id, self.platform) insert_task(usr_dict, task_id, task_name, u_id, author, date, course_code, self.platform, self.platform_url) #, list_id=list_id) #TODO: RP print 'Inserted created card!' #Get all 'add' verbs (you tecnically aren't *creating* an attachment on #a card so....) #print 'is action an add event? %s' % (type in # ['addAttachmentToCard', 'addMemberToBoard', # 'emailCard', 'addChecklistToCard' # , 'addMemberToCard']) if (type in ['addAttachmentToCard', 'addMemberToBoard', 'emailCard', 'addChecklistToCard' , 'addMemberToCard']): usr_dict = None if username_exists(u_id, course_code, self.platform): usr_dict = get_userdetails(u_id, self.platform) if type is 'addAttachmentToCard' and usr_dict is not None: target_id = data['card']['id'] attachment = data['attachment'] attachment_id = attachment['id'] attachment_data = '%s - %s' % (attachment['name'], attachment['url']) object_type = 'File' shared_displayname = '%sc/%s' % (self.platform_url, target_id) insert_added_object(usr_dict, target_id, attachment_id, attachment_data, u_id, author, date, course_code, self.platform, self.platform_url, object_type, shared_displayname=shared_displayname) #TODO: RP print 'Added attachment!' if type is 'addMemberToCard' and usr_dict is not None: #or 'addMemberToBoard': target_id = data['card']['id'] object_id = data['idMember'] object_data = action['memeber']['username'] object_type = 'Person' shared_displayname = '%sc/%s' % (self.platform_url, target_id) insert_added_object(usr_dict, target_id, object_id, object_data, u_id, author, date, course_code, self.platform, self.platform_url, object_type, shared_displayname=shared_displayname) #TODO: RP print 'Added add member to card!' if type is 'addChecklistToCard' and usr_dict is not None: target_id = data['card']['id'] object_id = data['idMember'] object_data = None checklist_items = None object_type = 'Collection' shared_displayname = '%sc/%s' % (self.platform_url, target_id) #get checklist contents try: checklist = self.TrelloCient.fetch_json('/checklists/' + data['checklist']['id'],) checklist_items = checklist['checkItems'] except Exception: print 'Could not retrieve checklist..' #data will be a comma separated list of checklist-item ids (e.g.: 'id1,id2,id3...') object_data = ','.join([item['id'] for item in checklist_items]) insert_added_object(usr_dict, target_id, object_id, object_data, u_id, author, date, course_code, self.platform, self.platform_url, object_type, shared_displayname=shared_displayname) #TODO: RP print 'added add checklist to card!' #print 'is action type an update? %s' % (type in # ['updateCheckItemStateOnCard', 'updateBoard', # 'updateCard', 'updateCheckList', # 'updateList', 'updateMember']) #Get all 'updated' verbs if (type in ['updateCheckItemStateOnCard', 'updateBoard', 'updateCard', 'updateCheckList', 'updateList', 'updateMember']): usr_dict = None if username_exists(u_id, course_code, self.platform): usr_dict = get_userdetails(u_id, self.platform) #many checklist items will be bugged - we require webhooks! if type == 'updateCheckItemStateOnCard' and usr_dict is not None: insert_updated_object(usr_dict, data['checkItem']['id'], data['checkItem']['state'], u_id, author, date, course_code, self.platform, self.platform_url, 'checklist-item', obj_parent=data['checklist']['id'], obj_parent_type='checklist') #TODO: RP print 'add update checklist!' #type will only show 'updateCard' #up to us to figure out what's being updated if type == 'updateCard': #TODO: Remove Print print 'data: %s' % (data) #Get and store the values that were changed, usually it's only one #TODO: Handle support for multiple changes, if that's possible try: change = [changed_value for changed_value in data['old']] except Exception: print 'Error occurred getting changes...' #assert len(change) is 1 #TODO: Remove Print print 'got changes: %s' % (change) #Insert all updates that aren't closed if change[0] == 'pos': if 'listBefore' in data: insert_updated_object(usr_dict, data['card']['id'], 'Move card from %s to %s' % (data['listBefore']['name'], data['listAfter']['name']), u_id, author, date, course_code, self.platform, self.platform_url, 'Task', obj_parent=data['list']['name'], obj_parent_type='Collection') else: insert_updated_object(usr_dict, data['card']['id'], 'Move card from %s to %s' % (data['old']['pos'], data['card']['pos']), u_id, author, date, course_code, self.platform, self.platform_url, 'Task', obj_parent=data['list']['name'], obj_parent_type='Collection') #TODO: RP print 'added closed card!' #add in close/open verbs else: if data['old'][change[0]] is False: insert_closedopen_object(usr_dict, data['card']['id'], '%s:%s' % ('Closed', data['card']['name']), u_id, author, date, course_code, self.platform, self.platform_url, 'Task', 'closed', obj_parent=data['list']['name'], obj_parent_type='Collection') #TODO: RP print 'added closed/opened card!' elif data['old'][change[0]] is True: insert_closedopen_object(usr_dict, data['card']['id'], '%s:%s' % ('Opened', data['card']['name']), u_id, author, date, course_code, self.platform, self.platform_url, 'Task', 'opened', obj_parent=data['list']['name'], obj_parent_type='Collection') #TODO: RP print 'added closed/opened card!' def get_verbs(self): return self.xapi_verbs def get_objects(self): return self.xapi_objects
get_trello_list = partial(get_trello_list_from_name, api=api, board=board, list_id_lookup=list_id_lookup) done_today = get_trello_list(name='Done today') tomorrow = get_trello_list(name='Tomorrow') waiting_on = get_trello_list(name='Waiting on') in_progress = get_trello_list(name='In progress') print_cards_from_list(done_today, 'x') print_cards_from_list(in_progress, '-') print_cards_from_list(tomorrow, ' ') print_cards_from_list(waiting_on, '-') if __name__ == '__main__': load_dotenv() client = TrelloClient(api_key=os.getenv('TRELLO_API_KEY'), token=os.getenv('TRELLO_TOKEN'), token_secret=os.getenv('TRELLO_TOKEN_SECRET')) my_week = client.get_board(os.getenv('BOARD_ID')) list_ids = { 'Waiting on': os.getenv('LIST_WAITING_ON_ID'), 'Done today': os.getenv('LIST_DONE_TODAY_ID'), 'Tomorrow': os.getenv('LIST_TOMORROW_ID'), 'In progress': os.getenv('LIST_INPROGRESS_ID'), } print('# {}'.format(DATE)) main_print(client, my_week, list_ids)
class TrelloAPI(): """ docstring """ def __init__(self): """ docstring """ self._api_key = None self._token = None result = self._GetFile() self._SetAPIKeyToken(result) self._client = None self._TrelloClientAccess() self._board = [] self._doing_list = [] self._cards = [] def _TrelloClientAccess(self): """ docstring """ self._client = TrelloClient( api_key=self._api_key, api_secret=self._token, ) def _GetFile(self, path="../../mine/Token.txt"): """ docstring """ with open(path, encoding="utf-8") as target: result = target.readlines() result = list(map(lambda i: i.rstrip("\n"), result)) return result def _SetAPIKeyToken(self, files): """ docstring """ self._api_key, self._token = files def _GetBookList(self): path = self._GetFile("../../mine/list.txt") self._doing_list = self._client.get_list(path[0]) # print(self._doing_list.name) def _GetBoardList(self): path = self._GetFile("../../mine/Borad.txt") self._board = self._client.get_board(path[0]) def _GetCardslist(self): self._cards = self._board.get_cards() def _GetDoingBookList(self): self._GetBookList() self._GetBoardList() self._GetCardslist() result = {} for i in self._cards: if i.list_id == self._doing_list.id: result[i.name.replace("\u3000", " ")] = i return result def _AddDoingBookList(self, book_name): self._GetBookList() self._GetBoardList() result = self._doing_list.add_card(book_name) dt = datetime.date.today() result.comment("READ START " + dt.strftime("%Y-%m-%d")) # print(self._doing_list) return True if result is not None else False def _AddResult(self, result): """ everyday :daily doing today check list add book : move done book_name end read date """ if result["today"]: pass if result["time"]: pass if result["finish"]: pass pass def _AddEveryday(self): pass def _MoveBookIsDone(self): pass def _MoveCard(self, ): """ docstring """ raise NotImplementedError
class TrelloListSensor(PollingSensor): """ Sensor which monitors Trello list for a new actions (events). For reference see Trello API Docs: https://trello.com/docs/api/list/index.html#get-1-lists-idlist-actions """ TRIGGER = 'trello.new_action' def __init__(self, sensor_service, config=None, poll_interval=None): """ Set defaults and validate YAML config metadata. """ super(TrelloListSensor, self).__init__(sensor_service, config, poll_interval) self._logger = self._sensor_service.get_logger(__name__) list_actions_sensor = self._config.get('list_actions_sensor') if not list_actions_sensor: raise ValueError('[TrelloListSensor]: "list_sensor" config value is required!') self._board_id = list_actions_sensor.get('board_id') if not self._board_id: raise ValueError('[TrelloListSensor]: "list_sensor.board_id" config value is required!') assert isinstance(self._board_id, basestring) self._list_id = list_actions_sensor.get('list_id') if not self._list_id: raise ValueError('[TrelloListSensor]: "list_sensor.list_id" config value is required!') assert isinstance(self._list_id, basestring) self._client = None @property def key_name(self): """ Generate unique key name for built-in storage based on config values. :rtype: ``str`` """ return '{}.{}.date'.format(self._board_id, self._list_id) def setup(self): """ Initiate connection to Trello API. """ self._client = TrelloClient(api_key=self._config.get('api_key'), token=self._config.get('token') or None) def poll(self): """ Fetch latest actions for Trello List, filter by type. Start reading feed where we stopped last time by passing `since` date parameter to Trello API. Save latest event `date` in st2 key-value storage. """ _list = self._client.get_board(self._board_id).get_list(self._list_id) monkey_patch_trello(_list) _list.fetch_actions( action_filter=self._config['list_actions_sensor'].get('filter') or None, filter_since=self._sensor_service.get_value(self.key_name) ) for action in reversed(_list.actions): self._sensor_service.dispatch(trigger=self.TRIGGER, payload=action) if is_date(action.get('date')): self._sensor_service.set_value(self.key_name, action.get('date')) def cleanup(self): """ Run the sensor cleanup code (if any). """ pass def add_trigger(self, trigger): """ Runs when trigger is created """ pass def update_trigger(self, trigger): """ Runs when trigger is updated """ pass def remove_trigger(self, trigger): """ Runs when trigger is deleted """ pass