class TapetoTrello(): """ The main class for the API between Tape to Trello functions. """ def __init__(self, api_key, api_secret): self.client = TrelloClient(api_key=api_key, api_secret=api_secret) def export_txt_to_trello(self, filepath='', board_name="TapeBoard"+str(randint(1, 25**4))): """ Given the path to the .txt tape savefile and a name of the new board it creates a new board on trello """ with open(filepath) as tape_file: tape_str = tape_file.read() json_tape = json.loads(tape_str) new_board = self.client.add_board(board_name) for k in json_tape: # creates lists of the new board new_list = new_board.add_list(json_tape[k]["name"]) # creates the cards of the new list list_cards = json_tape[k]["items"] for json_card in list_cards: new_list.add_card( json_card["name"], desc="State: {}".format(json_card["state"].capitalize()))
def handle(self, *args, **options): trello = TrelloClient(settings.TRELLO_API_KEY, settings.TRELLO_TOKEN) boards = trello.list_boards() ####################################################################### # INSTALL THE MASTER BOARD ############################################ ####################################################################### # check if the board exists m = self.__search_board_by_name(settings.TRELLO_MASTER, boards) # if not create it if m is None: m = trello.add_board(settings.TRELLO_MASTER, default_lists=False) try: master = Board.objects.get(remoteid=m.id) except Board.DoesNotExist: master = Board(remoteid=m.id) master.name = m.name master.closed = m.closed master.last_activity = None master.save() self.__install_lists(m, master) self.__install_labels(m, master) self.__install_members(m) ####################################################################### self.__install_projects_boards(trello, master, boards) self.__install_members_boards(trello, master, boards)
def create_trello_board(board_name): """ Create Trello board and returns it :board_name: the board name """ trello_client = TrelloClient(api_key=trello_api_key, api_secret=trello_api_secret, token=trello_token, token_secret=trello_token_secret) return trello_client.add_board(board_name)
class TrelloClass: def __init__(self, api_key, token): self.client = TrelloClient(api_key=api_key, token=token) def get_client(self): return self.client def get_open_boards(self): return self.client.list_boards(board_filter="open") def get_board(self, board_name): return next(self.get_board_gen(board_name)) def get_board_gen(self, board_name): for board in self.client.list_boards(): if board.name == board_name: yield board def create_board(self, board_name): return self.client.add_board(board_name) def close_board(self, board_name): self.get_board(board_name).close() def get_open_lists(self, board_name): return self.get_board(board_name).open_lists() def create_list(self, board_name, list_name): return self.get_board(board_name).add_list(list_name) def get_list(self, board_name, list_name): return next(self.get_list_gen(board_name, list_name)) def get_list_gen(self, board_name, list_name): for trello_list in self.get_open_lists(board_name): if trello_list.name == list_name: yield trello_list def close_list(self, board_name, list_name): self.get_list(board_name, list_name).close() def get_cards_on_board(self, board_name): return self.get_board(board_name).open_cards() def get_cards_on_list(self, board_name, list_name): return self.get_list(board_name, list_name).list_cards() def add_card(self, board_name, list_name, card_name): return self.get_list(board_name, list_name).add_card(card_name) def get_card(self, card_name, board_name): return next(self.get_card_gen(card_name, board_name)) def get_card_gen(self, card_name, board_name): for card in self.get_board(board_name).open_cards(): if card.name == card_name: yield card
def configure(key, token, board_name): config_store() client = TrelloClient(api_key=key, token=token) new_board = client.add_board(board_name) for i in new_board.all_lists(): i.close() links_label = new_board.add_label(name='Links', color='sky') desc_label = new_board.add_label(name='Description', color='purple') checklist_label = new_board.add_label(name='To Do', color='red') config = dict(board_id=new_board.id, link_l=links_label.id, desc_l=desc_label.id, check_l=checklist_label.id, key=key, token=token) with open(os.path.expanduser("~/.trellogd/config.json"), 'w') as c: json.dump(config, c) print('\nSuccessfully Created: config.json') print('Successfully Created: {}'.format(board_name)) print('View board at: https://trello.com/b/{}\n'.format(config['board_id']))
def fetch(): # Create the client connection client = TrelloClient(api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_TOKEN")) # Find / create board targetBoard = next( filter(lambda b: b.name == os.getenv("BOARD_NAME"), client.list_boards()), None) if targetBoard is None: targetBoard = client.add_board(os.getenv("BOARD_NAME")) # Find / create list targetList = next( filter(lambda l: l.name == os.getenv("LIST_NAME"), targetBoard.list_lists()), None) if targetList is None: targetList = targetBoard.add_list(os.getenv("LIST_NAME")) return targetList.list_cards()
def make_trello_board(self): client = TrelloClient( api_key=self.auth["trello"]["api_key"], api_secret=self.auth["trello"]["api_secret"], token=self.auth["trello"]["token"], token_secret=self.auth["trello"]["token_secret"], ) boards = client.list_boards() template = None for board in boards: if board.name == self.trello_template: template = board new_board = client.add_board( "DP: " + self.params["title"], source_board=template, permission_level="private" ) self.params["trello_url"] = new_board.url print("Created Trello board - " + new_board.url)
class TrelloProject(object): def __init__(self, board_name, backlog_list_name, lists_names=[]): self.board_name = board_name self.backlog_name = backlog_list_name self.lists_names = lists_names + [backlog_list_name] self.repos = [] self._trello = TrelloClient(conf.TRELLO_API_KEY, conf.TRELLO_TOKEN) self._bitbucket = Bitbucket(conf.BITBUCKET_USER, conf.BITBUCKET_PASSWORD) def add_bitbucket_repo(self, owner, repo_slug): self.repos.append( ('bitbucket', owner, repo_slug) ) def __find_board(self, board_name): for board in self._trello.list_boards(): if board.name==board_name: return board return None def __find_card(self, lists, label_name): for lst in lists: for card in lst.cards: if card.name==label_name: return card break return None def __sync_bitbucket(self, board, owner, repo_slug, backlog_list, board_lists, board_labels): success, data = self._bitbucket.issue.all(repo_slug=repo_slug, owner=owner) for issue in data['issues']: title = issue.get('title',None) desc = issue.get('content',None) priority = issue.get('priority',None) kind = issue.get('metadata').get('kind') status = issue.get('status',None) card = self.__find_card(board_lists, title) if card is None: card = backlog_list.add_card( name=title, desc=desc ) priority_label = board_labels.get(priority, None) kind_label = board_labels.get(kind, None) status_label = board_labels.get(status, None) if priority_label is None: priority_label = board.add_label(priority, 'red') board_labels[priority] = priority_label if kind_label is None: kind_label = board.add_label(kind, 'green') board_labels[kind] = kind_label if status_label is None: status_label = board.add_label(status, 'sky') board_labels[status] = status_label card_labels = (card.labels if card.labels else []) if priority_label not in card_labels: card.add_label(priority_label) if kind_label not in card_labels: card.add_label(kind_label) if status_label not in card_labels: card.add_label(status_label) def sync(self): board = self.__find_board(self.board_name) # if the board does not exists create it if board is None: board = self._trello.add_board(self.board_name, default_lists=False) # store all the board labels. board_labels = dict([(l.name, l) for l in board.get_labels()]) board_lists = [] lower_names = [x.lower() for x in self.lists_names] for lst in board.list_lists(): list_name = lst.name.lower() if list_name in lower_names: board_lists.append(lst) i = lower_names.index(list_name) self.lists_names.pop(i) lower_names.pop(i) lst.cards = lst.list_cards(card_filter='all') for lst_name in self.lists_names: lst = board.add_list(lst_name) lst.cards = [] board_lists.append(lst) backlog_list = None for lst in board_lists: if lst.name.lower()==self.backlog_name.lower(): backlog_list = lst break for platform, owner, repo_slug in self.repos: if platform=='bitbucket': self.__sync_bitbucket(board, owner, repo_slug, backlog_list, board_lists, board_labels)
class TrelloCmd(Command): "Update trello board from launchpad filters" log = logging.getLogger(__name__) def get_parser(self, prog_name): parser = super(TrelloCmd, self).get_parser(prog_name) parser.add_argument( '--filter', type=str, action='append', required=True, help="List of params for searchTasks", ) parser.add_argument('--project', type=str, action='append', required=True, help="Project") parser.add_argument('--board', type=str, required=True, help="Trello board name") parser.add_argument( '--trello-key', type=str, required=False, help="You can get one at https://trello.com/app-key") parser.add_argument( '--trello-secret', type=str, required=False, help="You can get one at https://trello.com/app-key") parser.add_argument( '--trello-token', type=str, required=False, help="You can get one at https://trello.com/1/connect?" + "key=YOUR_TRELLO_KEY&name=bugfix-app&response_type=token&" + "scope=read,write&expiration=never") parser.add_argument( '--trello-token-secret', type=str, required=False, ) parser.add_argument('--create-board', action='store_true', help='Create Trello board if not exists') parser.add_argument( '--use-labels', nargs='+', help='Labels for cards', default=['tricky', 'low-hanging-fruit', 'tech-debt']) return parser def take_action(self, parsed_args): err_count = 0 logging.getLogger("requests").setLevel(logging.WARNING) self.log.info('Connecting to Launchpad') self.lp = Launchpad.login_with('lp-report-bot', 'production', version='devel') self.tr = TrelloClient(api_key=parsed_args.trello_key, api_secret=parsed_args.trello_secret, token=parsed_args.trello_token, token_secret=parsed_args.trello_token_secret) try: self.board = [ board for board in self.tr.list_boards() if board.name == parsed_args.board ][0] except IndexError: if parsed_args.create_board: self.board = self.tr.add_board(parsed_args.board) # for label in self.board.all_lists(): # #label.delete() # # self.client.fetch_json( # # '/cards/' + self.id, # # http_method='DELETE') for list in self.board.open_lists(): list.close() else: raise Exception( "Board {0} doesn't exist. Use --create-board argument" + " in order to create it".format(parsed_args.board)) self.log.info("Working with board {0}".format(self.board)) self.tag_labels = parsed_args.use_labels self.cards = dict() self.untouched_cards = dict() for card in self.board.open_cards(): groups = re.search('(\d+)', card.name) if not (groups is None): bug_id = groups.group(0) if bug_id not in self.cards: self.untouched_cards[bug_id] = card self.log.debug( "Found existing card for bug {0}".format(bug_id)) self.cards[bug_id] = card else: self.log.info( "Killing duplicate card for bug {0}".format(bug_id)) card.delete() self.log.info("Found {0} existing cards".format( len(self.untouched_cards))) for prj_name in parsed_args.project: prj = self.lp.projects[prj_name] for f in parsed_args.filter: self.log.debug(f) filt = json.loads(f) if filt['milestone']: filt['milestone'] = prj.getMilestone( name=filt['milestone']) if 'assignee' in filt: filt['assignee'] = self.lp.people[filt['assignee']] if 'status' not in filt: filt['status'] = [ 'New', 'Incomplete', 'Opinion', 'Invalid', 'Won\'t Fix', 'Expired', 'Confirmed', 'Triaged', 'In Progress', 'Fix Committed', 'Fix Released' ] self.log.debug(filt) self.log.info("Searching for tasks in project %s" % prj_name) for task in prj.searchTasks(**filt): self.log.info("Proceeding task %s" % task) retries = 3 for i in range(retries): try: self.proceed_task(task) except Exception as e: if i < retries: self.log.exception(e) self.log.warning( "Got an exception for task %s, retrying" % task) continue else: self.log.exception(e) self.log.warning("Failed to proceed task %s" % task) err_count += 1 break for series in prj.series: self.log.info("Searching for tasks in {0}:{1}".format( str(prj.name), str(series.name))) for task in series.searchTasks(**filt): self.log.info("Proceeding task %s" % task) retries = 3 for i in range(retries): try: self.proceed_task(task) except Exception as e: if i < retries: continue else: self.log.exception(e) self.log.warning( "Failed to proceed task %s" % task) err_count += 1 break if self.untouched_cards: self.log.info("%d cards are out of scope" % len(self.untouched_cards)) try: out_of_scope_list = [ list for list in self.board.open_lists() if list.name == 'Trash/Out of scope' ][0] except IndexError: out_of_scope_list = self.board.add_list('Trash/Out of scope') for card in self.untouched_cards.values(): card.change_list(out_of_scope_list.id) self.log.info("Finished with %d errors" % err_count) if err_count > 0: return 1 return 0 def get_task_reviews(self, task): self.log.debug("Searching for reviews for task {0}".format(task)) bug = task.bug gerrits = [ 'https://review.openstack.org/', 'https://review.fuel-infra.org/' ] reviews = [] # Message number 0 is description is_description = True for msg in bug.messages: if is_description: is_description = False continue for g in gerrits: reviews += re.findall(g + '\d+', msg.content) long_reviews = re.findall(g + '#/c/\d+', msg.content) for u in long_reviews: reviews += [u.replace('#/c/', '')] open_reviews = [] for rev_url in set(reviews): [base_url, id] = rev_url.rsplit('/', 1) rest = GerritRestAPI(base_url) try: review = rest.get('/changes/{0}/detail'.format(id)) if review['status'] == 'NEW': status = [] if 'rejected' in review['labels']['Workflow']: status.append('WIP') if 'disliked' in review['labels']['Verified']: status.append('FAIL') open_reviews.append({'url': rev_url, 'status': status}) self.log.debug("Found open review {0}".format(rev_url)) except Exception: pass return open_reviews def get_task_list(self, task): list_name = 'Bad Status' try: if task.status in ['Confirmed']: if task.assignee is None or task.assignee.is_team: list_name = 'Inbox/Need triage' else: list_name = 'Assigned/Investigating' if task.status in ['Incomplete']: list_name = 'Incomplete/Need more info' if task.status in ['Triaged']: list_name = 'Triaged/Ready to be fixed' if task.status in ['In Progress']: if self.get_task_reviews(task): list_name = 'In Progress/Need review' else: list_name = 'In Progress/Working on fix' if task.status in ['Fix Committed', 'Fix Released']: list_name = 'Fix Committed/Done' if task.status in ['Invalid', 'Opinion', 'Won\'t Fix']: list_name = 'Won\'t Fix/Done' if task.status in ['New']: list_name = 'New/Need confirmation' # if ( # not filter(lambda x: x.startswith('team-'), task.bug.tags) # and 'tech-debt' not in task.bug.tags and # task.status in [ # 'New', 'Confirmed', 'Triaged', 'In Progress', # 'Incomplete'] # ): # list_name = 'New/Need confirmation' if 'blocked' in task.bug.tags: list_name = 'Blocked/On hold' return [ list for list in self.board.open_lists() if list.name == list_name ][0] except IndexError: return self.board.add_list(list_name) def get_task_labels(self, task): bug = task.bug tags = list(set(bug.tags).intersection(self.tag_labels)) # Each bug should have either team tag or no-team tag or tech-debt tag team_tags = filter(lambda x: x.startswith('team-'), bug.tags) if team_tags: tags += team_tags else: if 'tech-debt' not in bug.tags: tags += ['no-team'] if not filter(lambda x: x.startswith('area-'), task.bug.tags): tags += ['no-area'] # if task.importance in ['Critical', 'High']: # tags.append('high-priority') return tags def get_card_title(self, task): bug = task.bug assignee_id = "unassigned" if task.assignee_link is not None: assignee_id = task.assignee_link.split('~')[-1] return u'Bug {0} ({1}): {2}'.format(bug.id, assignee_id, bug.title)[:200] def get_card_description(self, task, card_list): bug = task.bug desc = "Created by {0}\n".format(bug.owner_link.split('~')[-1]) desc += bug.web_link + "\n" if card_list.name == 'In Progress/Need review': desc += "Reviews:\n" + "\n".join( map( lambda x: u"{0} {1}".format(x['url'], ':'.join(x[ 'status'])), self.get_task_reviews(task))) + "\n" desc += "\n----------\n" + bug.description return desc[:1000] def proceed_task(self, task): self.log.debug("Processing task {0}".format(task)) bug = task.bug card_list = self.get_task_list(task) if str(bug.id) not in self.cards: self.log.debug("Creating card for bug {0}".format(bug.id)) card = card_list.add_card( self.get_card_title(task), self.get_card_description(task, card_list)) self.cards[bug.id] = card else: self.log.debug("Getting card for task {0}".format(task)) card = self.cards[str(bug.id)] try: del self.untouched_cards[str(bug.id)] except KeyError: pass self.log.debug( ("Updating existing card for bug {0}, moving to {1} list" ).format(bug.id, card_list)) card.change_list(card_list.id) new_name = self.get_card_title(task) if new_name != card.name.decode('utf-8'): card.set_name(new_name) new_desc = self.get_card_description(task, card_list) if new_desc != card.description: card.set_description(new_desc) tags = self.get_task_labels(task) for label in card.labels: if label.name not in tags: # delete_label is not published on pypi yet # card.delete_label(label) card.client.fetch_json('/cards/' + card.id + '/idLabels/' + label.id, http_method='DELETE') for label_name in tags: try: label = [ l for l in self.board.get_labels() if l.name == label_name ][0] except IndexError: label = self.board.add_label(label_name, 'green') try: card.add_label(label) except Exception: pass self.log.debug(task)
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: 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
from trello import TrelloClient URL = 'https://libraryofjuggling.com/TricksByDifficulty.html' # Get this information from load_dotenv() TRELLO_CLIENT = TrelloClient(api_key=os.environ['API_KEY'], api_secret=os.environ['API_SECRET'], token=os.environ['TOKEN']) if __name__ == '__main__': html = requests.get(URL).content soup = BeautifulSoup(html, features='html.parser') # Setup Trello board juggling_board = TRELLO_CLIENT.add_board(board_name='Juggling') juggling_board.add_list('Mastered', pos=3) juggling_board.add_list('Learning', pos=2) trick_list = juggling_board.add_list('Tricks', pos=1) for level, unordered_list in enumerate(soup.find_all('ul', 'MainText'), 2): print(f"Level {level}") trello_label = juggling_board.add_label(f"Level {level}", color=None) tricks = unordered_list.find_all('a') for trick in tricks: url = 'https://libraryofjuggling.com/' + trick.attrs['href'] trick_name = trick.text trick_list.add_card(name=trick_name, desc=url, labels=[trello_label])
file = open("__main__.py", "x") file.close() elif 'node' in projType or 'js' in projType or 'javascript' in projType: file = open("main.js", "x") file.close() os.chdir("..") os.chdir("..") print('main files created') #connects the user to trello print('creating Trello sheet') client = TrelloClient(api_key='PUT API KEY FOR TRELLO HERE', api_secret='PUT API SECRET FOR TRELLO HERE', token='PUT TRELLO CLIENT TOKEN HERE') #creates a trello sheet client.add_board(projName) print("Trello added") #creates a project document print('creating a project document') doc = docx.Document("template.docx") os.chdir("projects") os.chdir(projName) doc.save("project.docx") print('created project document') #creates a github repository print("creating github repository") git = Github("PUT GITHUB CLIENT KEY HERE") me = git.get_user() me.create_repo(projName)
class TrelloMasterBoard(object): def __init__(self, master_boardname, sync_boards, sync_lists): self.master_boardname = master_boardname self.sync_boards = sync_boards self.sync_lists = sync_lists self._trello = TrelloClient(conf.TRELLO_API_KEY, conf.TRELLO_TOKEN) def __find_board(self, board_name): for board in self._trello.list_boards(): if board.name==board_name: return board return None def __get_lists(self, board, lists_names): lists = [] names = [x.lower() for x in lists_names] # search for the lists for lst in board.list_lists(): if lst.name.lower() in names: lists.append(lst) return lists def __get_and_create_lists(self, board, lists_names): """ Search for the lists in the board. If they do not exists create them """ lists = [] names = [x.lower() for x in lists_names] lists_names = list(lists_names) # make a copy # search for the lists for lst in board.list_lists(): name = lst.name.lower() if name in names: lists.append(lst) i = names.index(name) lists_names.pop(i) names.pop(i) # create the non existing lists for lst_name in lists_names: lst = board.add_list(lst_name) lists.append(lst) return lists def __card_id(self, card): if card.description.startswith('card-id:'): return card.description[8:32] else: return card.id def __get_label(self, board, label_name): if not hasattr(board, 'labels'): board.labels = dict([ (l.name, l) for l in board.get_labels(limit=100) ]) label = board.labels.get(label_name, None) if label is None: label = board.add_label(label_name, 'red') board.labels[label_name]=label return label def sync(self): #### FIND THE MASTER BOARD OR CREATE IT ############################################### masterboard = self.__find_board(self.master_boardname) if masterboard is None: masterboard = self._trello.add_board(self.master_boardname, default_lists=False) ####################################################################################### #### GET ALL THE BOARD LISTS ########################################################## mboard_lists = dict([(l.name.lower(), l) for l in self.__get_and_create_lists(masterboard, self.sync_lists)]) mboard_listsids = dict([(l.id, l) for l in mboard_lists.values()]) ####################################################################################### #### GET ALL THE BOARD CARDS IN THE LISTS ############################################# mboard_cardsids = {} for lst in mboard_lists.values(): for c in lst.list_cards(card_filter='all'): mboard_cardsids[self.__card_id(c)] = c ####################################################################################### boards = {} boards_lists = {} boards_cards = {} for board_name in self.sync_boards: board = self.__find_board(board_name) boards[board_name.lower()] = board lists = dict([(l.name.lower(), l) for l in self.__get_and_create_lists(board, self.sync_lists)]) listsids = dict([(l.id, l) for l in lists.values()]) if board.id not in boards_lists: boards_lists[board.id] = {} boards_lists[board.id].update(lists) for list_name, lst in lists.items(): cards = lst.list_cards(card_filter='all') if board_name.lower() not in boards_cards: boards_cards[board_name.lower()] = {} for card in cards: card_id = self.__card_id(card) boards_cards[board_name.lower()][card_id] = card ### GET THE MASTER CARD, IF DOES NOT EXISTS CREATE IT ##################### master_card = mboard_cardsids.get(card_id, None) if master_card is None: master_card = mboard_lists[list_name].add_card(card.name, "card-id:{0}".format( card.id )) master_card.set_closed(card.closed) master_label = self.__get_label(masterboard, board_name) master_card.add_label(master_label) ########################################################################### ### CHECK WITCH CARD SHOULD BE UPDATED #################################### if master_card.dateLastActivity<card.dateLastActivity: card_to = master_card card_from = card lists_id_to = mboard_listsids lists_id_from = listsids lists_to = mboard_lists lists_from = lists else: card_to = card card_from = master_card lists_id_to = listsids lists_id_from = mboard_listsids lists_to = lists lists_from = mboard_lists ########################################################################### ### UPDATE THE NAME IF NECESSARY ########################################## if card_from.name!=card_to.name: card_to.set_name(card_from.name) ########################################################################### ### UPDATE THE NAME IF NECESSARY ########################################## if card_from.closed!=card_to.closed: card_to.set_closed(card_from.closed) ########################################################################### ### UPDATE THE DESCRIPTION IF NECESSARY ################################### from_desc = card_from.description to_desc = card_to.description if from_desc.startswith('card-id'): from_desc = from_desc[33:] if to_desc.startswith('card-id'): to_desc = to_desc[33:] if from_desc!=to_desc: card_to.set_description( "card-id:{0}\n{1}".format( card_id, from_desc )) ########################################################################### ### UPDATE THE LIST IF NECESSARY ########################################## list_to = lists_id_to[card_to.list_id] list_from = lists_id_from[card_from.list_id] if list_to.name.lower()!=list_from.name.lower(): new_lst = lists_to[list_from.name.lower()] card_to.change_list(new_lst.id) ########################################################################### for card_id, card in mboard_cardsids.items(): print(card.plugin_data) labels = card.labels if card.labels else [] for label in labels: board = boards.get(label.name.lower(), None) if board is None: continue if card_id not in boards_cards[board.name.lower()]: old_list = mboard_listsids[card.list_id] new_list = boards_lists[board.id][old_list.name.lower()] new_list.add_card(card.name, "card-id:{0}\n{1}".format(card_id, card.description) )
class TrelloClient: def __init__(self, api_key, api_secret, token, token_secret): self.trello_client = Client(api_key=api_key, api_secret=api_secret, token=token, token_secret=token_secret) self._uid = None self._project = None self._board = None self._lists = None self._board_labels = None self._lists_filter = None self._only_my_cards = False @property def whoami(self): """ Get my Trello UID :return: my Trello UID :rtype: string """ if self._uid is None: self._uid = self.trello_client.get_member('me').id return self._uid def project(self, project): """ Set the class working project :param project: TelloWarrior project object """ if self._project == None or self._project.name != project.name: self._board = self.get_board(project.trello_board_name) self._lists = self.get_lists() self._board_labels = self.get_board_labels() self._lists_filter = project.trello_lists_filter self._only_my_cards = project.only_my_cards def get_board(self, board_name): """ Get a open Trello board from name, if it does not exist create it :param board_name: the board name :return: a Tello board :rtype: Trello board object """ for trello_board in self.trello_client.list_boards( board_filter='open'): if trello_board.name == board_name and not trello_board.closed: logger.debug('Trello board {} found'.format(board_name)) return trello_board logger.debug('Creating Trello board {}'.format(board_name)) return self.trello_client.add_board(board_name) def get_lists(self): """ Get the open lists of a Trello board :return: a list of Trello list objects :rtype: list """ return self._board.open_lists() def get_list(self, list_name): """ Get a Trello list from list name, if it does not exist create it :param list_name: the list name :return: a Tello list :rtype: Trello list object """ if self._lists == None: raise ClientError('get_list') for trello_list in self._lists: if trello_list.name == list_name: logger.debug('Trello list {} found'.format(list_name)) return trello_list logger.debug('Creating Trello list {}'.format(list_name)) trello_list = self._board.add_list(list_name) self._lists.append(trello_list) # Update _lists with new list return trello_list def get_board_labels(self): """ Get the labels of a Trello board :param board_name: the board name :return: a list of Trello label objects :rtype: list """ return self._board.get_labels() def get_board_label(self, label_name): """ Get a Trello board label from label name, if it does not exist create it :param label_name: the label name :return: a Tello label :rtype: Trello label object """ if self._board_labels == None: raise ClientError('get_board_label') for board_label in self._board_labels: if board_label.name == label_name: logger.debug('Trello board label {} found'.format(label_name)) return board_label logger.debug('Creating Trello board label {}'.format(label_name)) board_label = self._board.add_label(label_name, 'black') self._board_labels.append( board_label) # Update _board_labels with new label return board_label def get_cards_dict(self): """ Get all cards of a list of Trello lists in a dictionary :return: a dict with Cards :rtype: dict """ trello_cards_dict = {} if self._lists_filter is not None: trello_lists = filter( lambda trello_list: trello_list.name not in self._lists_filter, self._lists) for trello_list in trello_lists: logger.debug('Getting Trello cards of list {}'.format( trello_list.name)) trello_cards_dict[trello_list.name] = trello_list.list_cards() if self._only_my_cards: trello_cards_dict[trello_list.name] = filter( lambda trello_card: self.whoami in trello_card.member_ids, trello_cards_dict[trello_list.name]) return trello_cards_dict def delete_trello_card(self, trello_card_id): """ Delete (forever) a Trello card by ID :param trello_card_id: ID of Trello card """ try: self.trello_client.get_card(trello_card_id).delete() except ResourceUnavailable: logger.warning( 'Cannot find Trello card with ID {} deleted in Task Warrior. Maybe you also deleted it in Trello?' .format(trello_card_id))
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
def create_new_board(key, token, name_of_board): client = TrelloClient(key, token) client.add_board(name_of_board)
class TrelloBoard(object): def __init__(self, api_key, token): """Creates a TrelloBoard object :param api_key: (str) Your Trello api key https://trello.com/1/appKey/generate :param token: (str) Your Trello token """ self.tc = TrelloClient(api_key=api_key, token=token) self._ab_id_cache = {} self._ab_name_cache = {} self._ab_slack_cache = {} # self._warmup_caches() @property def boards(self): """All the boards that can be accessed :return: (Board) list of Board """ return self.tc.list_boards() @property def addressbook(self): board = self._board("Address Book") ab = {} for l in board.list_lists(list_filter="open"): for card in l.list_cards(): info = yaml.load(card.desc) if info: ab[info["id"]] = { "name": card.name, "slack": info["slack"] } self._ab_id_cache = ab return ab @lru_cache(maxsize=128) def _org_id(self, team_name): """Get the id of a Trello team :param team_name: :return: """ orgs = self.tc.list_organizations() for org in orgs: if org.name == team_name: return org.id @lru_cache(maxsize=128) def _board(self, board_name): logger.debug("Looking up board {}".format(board_name)) board = [b for b in self.boards if b.name == board_name] if board: return board[0] @lru_cache(maxsize=128) def _board_by_url(self, board_url): board = [b for b in self.boards if b.url == board_url] if board: return board[0] @lru_cache(maxsize=128) def _member(self, member_id, board_name): member_id = str(member_id) board = self._board(board_name) if not board: return None for l in board.list_lists(list_filter="open"): for card in l.list_cards(): if card.desc == member_id: return card @lru_cache(maxsize=128) def _label(self, label_name, board_name): board = self._board(board_name) label = [l for l in board.get_labels() if l.name == label_name] if label: return label[0] def _warmup_caches(self): logger.debug("Warming up the caches") ids = self.addressbook try: for meetup_name, slack_name in [(n["name"], n["slack"]) for n in ids.values()]: _ = self.contact_by_name(meetup_name) if slack_name: _ = self.contact_by_slack_name(slack_name) except Exception as e: logger.warning("Exception {} when warming up caches".format(e)) def create_board(self, board_name, team_name=None): logger.debug("Checking for board {} on {} team".format( board_name, team_name)) template = self._board("Meetup Template") board = self._board(board_name) org_id = self._org_id(team_name=team_name) if not board: logger.debug("Adding board {}".format(board_name)) self.tc.add_board(board_name=board_name, source_board=template, organization_id=org_id, permission_level="public") def add_rsvp(self, name, member_id, board_name): logger.debug("Adding rsvp {} to {}".format(name, board_name)) member_id = str(member_id) board = self._board(board_name) if not board: return None if not self._member(member_id, board_name): rsvp_list = board.list_lists(list_filter="open")[0] rsvp_list.add_card(name=name, desc=member_id) def cancel_rsvp(self, member_id, board_name): logger.debug("Cancelling RSVP for members id {} at {}".format( member_id, board_name)) card = self._member(member_id, board_name) logger.debug("Card for member id {} is {}".format(member_id, card)) canceled = self._label("Canceled", board_name) logger.debug("Canceled tag is {}".format(canceled)) if card: card.add_label(canceled) def tables_detail(self, board_name): tables = {} board = self._board(board_name) info_card = None if not board: return None for table in board.list_lists(list_filter="open"): names = [] title = table.name if not table.name.startswith( "RSVP") else "~ without a table ~" for card in table.list_cards(): if card.name != "Info" and not card.labels: names.append(card.name) elif card.name == "Info": info_card = card elif card.labels: for label in card.labels: if label.name == "GM": names.append(card.name + " (GM)") elif label.name == "Canceled": names.append(card.name + " (CANCELED)") else: names.append(card.name) if info_card: full_info = info_card.desc.split("Players: ", 1) blurb = full_info[0] if len(full_info) == 2: players = full_info[1] else: players = "" else: blurb, players = "", "" tables[title] = {"members": names, "blurb": blurb} tables[title]["players"] = players or "Unknown" resp = OrderedDict(sorted(tables.items())) return resp def table(self, board_name, list_name): return self.tables_detail(board_name)[list_name] def contact_by_name(self, member_name): logger.debug("Checking {}".format(member_name)) if self._ab_name_cache.get(member_name): return self._ab_name_cache[member_name] else: board = self._board("Address Book") for l in board.list_lists(list_filter="open"): for card in l.list_cards(): desc = yaml.load(card.desc) if card.name == member_name and desc["slack"]: logger.debug("Desc: {}".format(desc)) self._ab_name_cache[member_name] = yaml.load(card.desc) return self._ab_name_cache[member_name] def contact_by_slack_name(self, slack_name): if self._ab_slack_cache.get(slack_name): return self._ab_slack_cache[slack_name] else: board = self._board("Address Book") try: for l in board.list_lists(list_filter="open"): for card in l.list_cards(): desc = yaml.load(card.desc) if desc["slack"] == slack_name: self._ab_slack_cache[slack_name] = { "name": card.name, "id": desc["id"] } return self._ab_slack_cache[slack_name] except: logger.debug("Nothing found for {}".format(slack_name)) def contact_by_id(self, member_id): if self._ab_id_cache.get(member_id): return self._ab_id_cache[member_id] else: board = self._board("Address Book") for l in board.list_lists(list_filter="open"): for card in l.list_cards(): info = yaml.load(card.desc) if info: if info['id'] == member_id and info["slack"]: self._ab_id_cache[member_id] = { "name": card.name, "slack": info["slack"] } return self._ab_id_cache[member_id] def add_contact(self, member_name, member_id): member_id = str(member_id) if self._ab_id_cache.get(member_id): return True board = self._board("Address Book") ab_list = board.list_lists(list_filter="open")[0] info = yaml.dump({ "id": member_id, "slack": None }, default_flow_style=False) no_slack = self._label("NoSlack", "Address Book") for card in ab_list.list_cards(): desc = yaml.load(card.desc) if desc["id"] == member_id: return True ab_list.add_card(name=member_name, desc=info, labels=[no_slack]) def add_table(self, title, info, board_url): board = self._board_by_url(board_url) table_numbers = [ int(n.name.split(".", 1)[0]) for n in board.list_lists(list_filter="open") if n.name[0].isnumeric() ] ordinal = max(table_numbers) + 1 if table_numbers else 1 title = "{}. {}".format(ordinal, title) table = board.add_list(name=title, pos="bottom") info = "\n\nPlayers:".join(info.split("Players:")) table.add_card("Info", desc=info) return "Table *{}* added to *{}*".format(title, board.name)
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)
class TrelloCmd(Command): "Update trello board from launchpad filters" log = logging.getLogger(__name__) def get_parser(self, prog_name): parser = super(TrelloCmd, self).get_parser(prog_name) parser.add_argument( '--filter', type=str, action='append', required=True, help="List of params for searchTasks", ) parser.add_argument( '--project', type=str, action='append', required=True, help="Project" ) parser.add_argument( '--board', type=str, required=True, help="Trello board name" ) parser.add_argument( '--trello-key', type=str, required=False, help="You can get one at https://trello.com/app-key" ) parser.add_argument( '--trello-secret', type=str, required=False, help="You can get one at https://trello.com/app-key" ) parser.add_argument( '--trello-token', type=str, required=False, help="You can get one at https://trello.com/1/connect?" + "key=YOUR_TRELLO_KEY&name=bugfix-app&response_type=token&" + "scope=read,write&expiration=never" ) parser.add_argument( '--trello-token-secret', type=str, required=False, ) parser.add_argument( '--create-board', action='store_true', help='Create Trello board if not exists' ) parser.add_argument( '--use-labels', nargs='+', help='Labels for cards', default=[ 'tricky', 'low-hanging-fruit', 'tech-debt' ] ) return parser def take_action(self, parsed_args): err_count = 0 logging.getLogger("requests").setLevel(logging.WARNING) self.log.info('Connecting to Launchpad') self.lp = Launchpad.login_with( 'lp-report-bot', 'production', version='devel') self.tr = TrelloClient( api_key=parsed_args.trello_key, api_secret=parsed_args.trello_secret, token=parsed_args.trello_token, token_secret=parsed_args.trello_token_secret) try: self.board = [ board for board in self.tr.list_boards() if board.name == parsed_args.board ][0] except IndexError: if parsed_args.create_board: self.board = self.tr.add_board(parsed_args.board) # for label in self.board.all_lists(): # #label.delete() # # self.client.fetch_json( # # '/cards/' + self.id, # # http_method='DELETE') for list in self.board.open_lists(): list.close() else: raise Exception( "Board {0} doesn't exist. Use --create-board argument" + " in order to create it".format(parsed_args.board)) self.log.info("Working with board {0}".format(self.board)) self.tag_labels = parsed_args.use_labels self.cards = dict() self.untouched_cards = dict() for card in self.board.open_cards(): groups = re.search('(\d+)', card.name) if not (groups is None): bug_id = groups.group(0) if bug_id not in self.cards: self.untouched_cards[bug_id] = card self.log.debug( "Found existing card for bug {0}".format(bug_id)) self.cards[bug_id] = card else: self.log.info( "Killing duplicate card for bug {0}".format(bug_id)) card.delete() self.log.info("Found {0} existing cards".format( len(self.untouched_cards))) for prj_name in parsed_args.project: prj = self.lp.projects[prj_name] for f in parsed_args.filter: self.log.debug(f) filt = json.loads(f) if filt['milestone']: filt['milestone'] = prj.getMilestone( name=filt['milestone']) if 'assignee' in filt: filt['assignee'] = self.lp.people[filt['assignee']] if 'status' not in filt: filt['status'] = [ 'New', 'Incomplete', 'Opinion', 'Invalid', 'Won\'t Fix', 'Expired', 'Confirmed', 'Triaged', 'In Progress', 'Fix Committed', 'Fix Released' ] self.log.debug(filt) self.log.info("Searching for tasks in project %s" % prj_name) for task in prj.searchTasks(**filt): self.log.info("Proceeding task %s" % task) retries = 3 for i in range(retries): try: self.proceed_task(task) except Exception as e: if i < retries: self.log.exception(e) self.log.warning( "Got an exception for task %s, retrying" % task) continue else: self.log.exception(e) self.log.warning( "Failed to proceed task %s" % task) err_count += 1 break for series in prj.series: self.log.info("Searching for tasks in {0}:{1}".format( str(prj.name), str(series.name))) for task in series.searchTasks(**filt): self.log.info("Proceeding task %s" % task) retries = 3 for i in range(retries): try: self.proceed_task(task) except Exception as e: if i < retries: continue else: self.log.exception(e) self.log.warning( "Failed to proceed task %s" % task) err_count += 1 break if self.untouched_cards: self.log.info("%d cards are out of scope" % len( self.untouched_cards)) try: out_of_scope_list = [ list for list in self.board.open_lists() if list.name == 'Trash/Out of scope'][0] except IndexError: out_of_scope_list = self.board.add_list('Trash/Out of scope') for card in self.untouched_cards.values(): card.change_list(out_of_scope_list.id) self.log.info("Finished with %d errors" % err_count) if err_count > 0: return 1 return 0 def get_task_reviews(self, task): self.log.debug("Searching for reviews for task {0}".format(task)) bug = task.bug gerrits = [ 'https://review.openstack.org/', 'https://review.fuel-infra.org/'] reviews = [] # Message number 0 is description is_description = True for msg in bug.messages: if is_description: is_description = False continue for g in gerrits: reviews += re.findall(g + '\d+', msg.content) long_reviews = re.findall(g + '#/c/\d+', msg.content) for u in long_reviews: reviews += [u.replace('#/c/', '')] open_reviews = [] for rev_url in set(reviews): [base_url, id] = rev_url.rsplit('/', 1) rest = GerritRestAPI(base_url) try: review = rest.get('/changes/{0}/detail'.format(id)) if review['status'] == 'NEW': status = [] if 'rejected' in review['labels']['Workflow']: status.append('WIP') if 'disliked' in review['labels']['Verified']: status.append('FAIL') open_reviews.append({'url': rev_url, 'status': status}) self.log.debug("Found open review {0}".format(rev_url)) except Exception: pass return open_reviews def get_task_list(self, task): list_name = 'Bad Status' try: if task.status in ['Confirmed']: if task.assignee is None or task.assignee.is_team: list_name = 'Inbox/Need triage' else: list_name = 'Assigned/Investigating' if task.status in ['Incomplete']: list_name = 'Incomplete/Need more info' if task.status in ['Triaged']: list_name = 'Triaged/Ready to be fixed' if task.status in ['In Progress']: if self.get_task_reviews(task): list_name = 'In Progress/Need review' else: list_name = 'In Progress/Working on fix' if task.status in ['Fix Committed', 'Fix Released']: list_name = 'Fix Committed/Done' if task.status in ['Invalid', 'Opinion', 'Won\'t Fix']: list_name = 'Won\'t Fix/Done' if task.status in ['New']: list_name = 'New/Need confirmation' # if ( # not filter(lambda x: x.startswith('team-'), task.bug.tags) # and 'tech-debt' not in task.bug.tags and # task.status in [ # 'New', 'Confirmed', 'Triaged', 'In Progress', # 'Incomplete'] # ): # list_name = 'New/Need confirmation' if 'blocked' in task.bug.tags: list_name = 'Blocked/On hold' return [ list for list in self.board.open_lists() if list.name == list_name][0] except IndexError: return self.board.add_list(list_name) def get_task_labels(self, task): bug = task.bug tags = list(set(bug.tags).intersection(self.tag_labels)) # Each bug should have either team tag or no-team tag or tech-debt tag team_tags = filter(lambda x: x.startswith('team-'), bug.tags) if team_tags: tags += team_tags else: if 'tech-debt' not in bug.tags: tags += ['no-team'] if not filter(lambda x: x.startswith('area-'), task.bug.tags): tags += ['no-area'] # if task.importance in ['Critical', 'High']: # tags.append('high-priority') return tags def get_card_title(self, task): bug = task.bug assignee_id = "unassigned" if task.assignee_link is not None: assignee_id = task.assignee_link.split('~')[-1] return u'Bug {0} ({1}): {2}'.format( bug.id, assignee_id, bug.title)[:200] def get_card_description(self, task, card_list): bug = task.bug desc = "Created by {0}\n".format(bug.owner_link.split('~')[-1]) desc += bug.web_link + "\n" if card_list.name == 'In Progress/Need review': desc += "Reviews:\n" + "\n".join(map( lambda x: u"{0} {1}".format(x['url'], ':'.join(x['status'])), self.get_task_reviews(task) )) + "\n" desc += "\n----------\n" + bug.description return desc[:1000] def proceed_task(self, task): self.log.debug("Processing task {0}".format(task)) bug = task.bug card_list = self.get_task_list(task) if str(bug.id) not in self.cards: self.log.debug("Creating card for bug {0}".format(bug.id)) card = card_list.add_card( self.get_card_title(task), self.get_card_description(task, card_list)) self.cards[bug.id] = card else: self.log.debug("Getting card for task {0}".format(task)) card = self.cards[str(bug.id)] try: del self.untouched_cards[str(bug.id)] except KeyError: pass self.log.debug( ( "Updating existing card for bug {0}, moving to {1} list" ).format(bug.id, card_list)) card.change_list(card_list.id) new_name = self.get_card_title(task) if new_name != card.name.decode('utf-8'): card.set_name(new_name) new_desc = self.get_card_description(task, card_list) if new_desc != card.description: card.set_description(new_desc) tags = self.get_task_labels(task) for label in card.labels: if label.name not in tags: # delete_label is not published on pypi yet # card.delete_label(label) card.client.fetch_json( '/cards/' + card.id + '/idLabels/' + label.id, http_method='DELETE') for label_name in tags: try: label = [ l for l in self.board.get_labels() if l.name == label_name][0] except IndexError: label = self.board.add_label(label_name, 'green') try: card.add_label(label) except Exception: pass self.log.debug(task)
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 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 TrelloBoard(object): def __init__(self, api_key, token): """Creates a TrelloBoard object :param api_key: (str) Your Trello api key https://trello.com/1/appKey/generate :param token: (str) Your Trello token """ self.tc = TrelloClient(api_key=api_key, token=token) @property def boards(self) -> List[Board]: """All the boards that can be accessed :return: (Board) list of Board """ return self.tc.list_boards() @lru_cache(maxsize=128) def _org_id(self, team_name: str) -> str: """Get the id of a Trello team :param team_name: :return: """ orgs = self.tc.list_organizations() for org in orgs: if org.name == team_name: return org.id @lru_cache(maxsize=128) def _board(self, board_name): logger.debug("Looking up board {}".format(board_name)) board = [b for b in self.boards if b.name == board_name] try: return board[0] except IndexError as e: raise NoBoardError from e @lru_cache(maxsize=128) def _board_by_url(self, board_url): board = [b for b in self.boards if b.url == board_url] if board: return board[0] @lru_cache(maxsize=128) def _member(self, member_id: int, board_name: str) -> Optional[Card]: member_id = str(member_id) try: board = self._board(board_name) except NoBoardError: return for l in board.list_lists(list_filter="open"): for card in l.list_cards(): sleep(0.1) if card.desc == member_id: return card @lru_cache(maxsize=128) def _label(self, label_name, board_name): board = self._board(board_name) label = [l for l in board.get_labels() if l.name == label_name] if label: return label[0] def participants(self, board_name): board = self._board(board_name) members = [] for l in board.list_lists(list_filter="open"): for card in l.list_cards(): sleep(0.1) try: members.append(int(card.desc)) except ValueError: pass return members def create_board(self, board_name, team_name=None): logger.debug("Checking for board {} on {} team".format( board_name, team_name)) template = self._board("Meetup Template") org_id = self._org_id(team_name=team_name) try: self._board(board_name) except NoBoardError: self.tc.add_board(board_name=board_name, source_board=template, organization_id=org_id, permission_level="public") def add_rsvp(self, name, member_id, board_name): logger.debug("Adding rsvp {} to {}".format(name, board_name)) try: board = self._board(board_name) except NoBoardError: logger.debug("Board {} not found".format(board_name)) return if not self._member(member_id, board_name): logger.debug("Member {} does not exist in {}. Adding them.".format( member_id, board_name)) rsvp_list = board.list_lists(list_filter="open")[0] logger.debug("RSVP list for {}: {}".format(board_name, rsvp_list)) rsvp_list.add_card(name=name, desc=str(member_id)) def cancel_rsvp(self, member_id, board_name): logger.debug("Canceling RSVP for members id {} at {}".format( member_id, board_name)) member_card = self._member(member_id, board_name) logger.debug("Card for member id {} is {}".format( member_id, member_card)) canceled = self._label("Canceled", board_name) logger.debug("Canceled tag is {}".format(canceled)) if member_card: member_card.add_label(canceled) def tables_for_event(self, event_name: str) -> Dict[int, GameTable]: tables = {} info_card = None board = self._board(event_name) for board_list in board.list_lists(list_filter="open"): if board_list.name.startswith("RSVP"): title = "Without a table :disappointed:" table_number = 9999 else: table_number, title = board_list.name.split(". ", maxsplit=1) table_number = int(table_number) table = GameTable(number=table_number, title=title) for card in board_list.list_cards(): sleep(0.1) if card.name == "Info": info_card = card elif card.labels: for label in card.labels: if label.name == "GM": table.gm = card.name else: table.add_player(card.name) if info_card: full_info = info_card.desc.split("Players: ", 1) table.blurb = full_info[0] if len(full_info) == 2: try: table.max_players = int(full_info[1]) except ValueError: pass tables[table_number] = table return OrderedDict(sorted(tables.items())) def table(self, board_name: str, table_number: int) -> GameTable: return self.tables_for_event(board_name)[table_number] def add_table(self, title, info, board_url): board = self._board_by_url(board_url) table_numbers = [ int(n.name.split(".", 1)[0]) for n in board.list_lists(list_filter="open") if n.name[0].isnumeric() ] ordinal = max(table_numbers) + 1 if table_numbers else 1 title = "{}. {}".format(ordinal, title) table = board.add_list(name=title, pos="bottom") info = "\n\nPlayers:".join(info.split("Players:")) table.add_card("Info", desc=info) return "Table *{}* added to *{}*".format(title, board.name)