def update_message(self, message_id, category_id, title, body, extended_body, use_textile=False, private=False, notify=None): """ Updates an existing message, optionally sending notifications to a selected list of people. Note that you can also upload files using this function, but you have to format the request as multipart/form-data. (See the ruby Basecamp API wrapper for an example of how to do this.) """ path = '/msg/update/%u' % message_id req = ET.Element('request') req.append( self._create_message_post_elem(category_id, title, body, extended_body, use_textile=False, private=False)) if notify is not None: for person_id in notify: ET.SubElement(req, 'notify').text = str(int(person_id)) return self._request(path, req)
def _create_milestone_elem(self, title, deadline, party_id, notify): milestone = ET.Element('milestone') ET.SubElement(milestone, 'title').text = str(title) ET.SubElement(milestone, 'deadline', type='date').text = str(deadline) ET.SubElement(milestone, 'responsible-party').text = str(party_id) ET.SubElement(milestone, 'notify').text = str(bool(notify)).lower() return milestone
def create_comment(self, post_id, body): """ Create a new comment, associating it with a specific message. """ path = '/msg/create_comment' req = ET.Element('request') comment = ET.SubElement(req, 'comment') ET.SubElement(comment, 'post-id').text = str(int(post_id)) ET.SubElement(comment, 'body').text = str(body) return self._request(path, req)
def update_comment(self, comment_id, body): """ Update a specific comment. This can be used to edit the content of an existing comment. """ path = '/msg/update_comment' req = ET.Element('request') ET.SubElement(req, 'comment_id').text = str(int(comment_id)) comment = ET.SubElement(req, 'comment') ET.SubElement(comment, 'body').text = str(body) return self._request(path, req)
def list_milestones(self, project_id, find=None): """ This lets you query the list of milestones for a project. You can either return all milestones, or only those that are late, completed, or upcoming. """ path = '/projects/%u/milestones/list' % project_id req = ET.Element('request') if find is not None: ET.SubElement(req, 'find').text = str(find) return self._request(path, req)
def move_todo_item(self, item_id, to): """ Changes the position of an item within its parent list. It does not currently support reparenting an item. Position 1 is at the top of the list. Moving an item beyond the end of the list puts it at the bottom of the list. """ path = '/todos/move_item/%u' % item_id req = ET.Element('request') ET.SubElement(req, 'to').text = str(int(to)) return self._request(path, req)
def todo_lists(self, project_id, complete=None): """ This will return the metadata for all of the lists in a given project. You can further constrain the query to only return those lists that are "complete" (have no uncompleted items) or "uncomplete" (have uncompleted items remaining). """ path = '/projects/%u/todos/lists' % project_id req = ET.Element('request') if complete is not None: ET.SubElement(req, 'complete').text = str(bool(complete)).lower() return self._request(path, req)
def update_todo_item(self, item_id, content, party_id=None, notify=False): """ Modifies an existing item. The values work much like the "create item" operation, so you should refer to that for a more detailed explanation. """ path = '/todos/update_item/%u' % item_id req = ET.Element('request') item = ET.Element('request') ET.SubElement(item, 'content').text = str(content) if party_id is not None: ET.SubElement(req, 'responsible-party').text = str(party_id) ET.SubElement(req, 'notify').text = str(bool(notify)).lower() return self._request(path, req)
def move_todo_list(self, list_id, to): """ This allows you to reposition a list relative to the other lists in the project. A list with position 1 will show up at the top of the page. Moving lists around lets you prioritize. Moving a list to a position less than 1, or more than the number of lists in a project, will force the position to be between 1 and the number of lists (inclusive). """ path = '/todos/move_list/%u' % list_id req = ET.Element('request') ET.SubElement(req, 'to').text = str(int(to)) return self._request(path, req)
def message_archive(self, project_id, category_id=None): """ This will return a summary record for each message in a project. If you specify a category_id, only messages in that category will be returned. (Note that a summary record includes only a few bits of information about a post, not the complete record.) """ path = '/projects/%u/msg/archive' % project_id req = ET.Element('request') ET.SubElement(req, 'project-id').text = str(int(project_id)) if category_id is not None: ET.SubElement(req, 'category-id').text = str(int(category_id)) return self._request(path, req)
def create_todo_list(self, project_id, milestone_id=None, private=None, tracked=False, name=None, description=None, template_id=None): """ This will create a new, empty list. You can create the list explicitly, or by giving it a list template id to base the new list off of. """ path = '/projects/%u/todos/create_list' % project_id req = ET.Element('request') if milestone_id is not None: ET.SubElement('milestone-id').text = str(milestone_id) if private is not None: ET.SubElement('private').text = str(bool(private)).lower() ET.SubElement('tracked').text = str(bool(tracked)).lower() if name is not None: ET.SubElement('name').text = str(name) ET.SubElement('description').text = str(description) if template_id is not None: ET.SubElement('use-template').text = 'true' ET.SubElement('template-id').text = str(int(template_id)) return self._request(path, req)
def comments(self, message_id): """ Return the list of comments associated with the specified message. """ path = '/msg/comments/%u' % message_id req = ET.Element('request') return self._request(path, req)
def create_todo_item(self, list_id, content, party_id=None, notify=False): """ This call lets you add an item to an existing list. The item is added to the bottom of the list. If a person is responsible for the item, give their id as the party_id value. If a company is responsible, prefix their company id with a 'c' and use that as the party_id value. If the item has a person as the responsible party, you can use the notify key to indicate whether an email should be sent to that person to tell them about the assignment. """ path = '/todos/create_item/%u' % list_id req = ET.Element('request') ET.SubElement(req, 'content').text = str(content) if party_id is not None: ET.SubElement(req, 'responsible-party').text = str(party_id) ET.SubElement(req, 'notify').text = str(bool(notify)).lower() return self._request(path, req)
def messages(self): if self.cache['messages']: return self.cache['messages'] message_xml = self.bc.message_archive(self.id) messages = [] for post in ET.fromstring(message_xml).findall("post"): messages.append(Message(post)) self.cache['messages'] = messages return self.cache['messages']
def parse_basecamp_xml(xml_object): if hasattr(xml_object, 'getchildren'): nodes = xml_object.getchildren() else: nodes = ET.fromstring(xml_object).getchildren() parsed = parse_tree(nodes) return parsed
def people(self): '''Dictionary of people on the project, keyed by id''' if self.cache['people']: return self.cache['people'] people_xml = self.bc.people_within_project(self.id) for person_node in ET.fromstring(people_xml).findall('person'): p = Person(person_node) self.cache['people'][p.id] = p return self.cache['people']
def __cache_result(self, path, data, result): if data: data = ET.tostring(data) if not self.__test_responses['POST'].has_key(path): self.__test_responses['POST'][path] = {} self.__test_responses['POST'][path][data] = result else: self.__test_responses['GET'][path] = result return result
def __cache_result(self, path, data, result): if data: data = ET.tostring(data) if not self.__test_responses["POST"].has_key(path): self.__test_responses["POST"][path] = {} self.__test_responses["POST"][path][data] = result else: self.__test_responses["GET"][path] = result return result
def todo_lists(self): if self.cache['todo_lists']: return self.cache['todo_lists'] todo_lists_xml = self.bc.todo_lists(self.id) todo_lists = {} for node in ET.fromstring(todo_lists_xml).findall("todo-list"): the_list = ToDoList(node) todo_lists[the_list.name] = the_list self.cache['todo_lists'] = todo_lists return self.cache['todo_lists']
def create_message(self, project_id, category_id, title, body, extended_body, use_textile=False, private=False, notify=None, attachments=None): """ Creates a new message, optionally sending notifications to a selected list of people. Note that you can also upload files using this function, but you need to upload the files first and then attach them. See the description at the top of this document for more information. """ path = '/projects/%u/msg/create' % project_id req = ET.Element('request') req.append( self._create_message_post_elem(category_id, title, body, extended_body, use_textile=False, private=False)) if notify is not None: for person_id in notify: ET.SubElement(req, 'notify').text = str(int(person_id)) # TODO: Implement attachments. if attachments is not None: raise NotSupportedErr('Attachments are currently not implemented.') ##for attachment in attachments: ## attms = ET.SubElement(req, 'attachments') ## if attachment['name']: ## ET.SubElement(attms, 'name').text = str(attachment['name']) ## file_ = ET.SubElement(attms, 'file') ## ET.SubElement(file_, 'file').text = str(attachment['temp_id']) ## ET.SubElement(file_, 'content-type').text \ ## = str(attachment['content_type']) ## ET.SubElement(file_, 'original-filename').text \ ## = str(attachment['original_filename']) return self._request(path, req)
def create_milestones(self, project_id, milestones): """ With this function you can create multiple milestones in a single request. See the "create" function for a description of the individual fields in the milestone. """ path = '/projects/%u/milestones/create' % project_id req = ET.Element('request') for milestone in milestones: req.append(self._create_milestone_elem(*milestone)) return self._request(path, req)
def create_milestone(self, project_id, title, deadline, party_id, notify): """ Creates a single milestone. To create multiple milestones in a single call, see the "create (batch)" function. To make a company responsible for the milestone, prefix the company id with a "c". """ path = '/projects/%u/milestones/create' % project_id req = ET.Element('request') req.append( self._create_milestone_elem(title, deadline, party_id, notify)) return self._request(path, req)
def comments(self): '''Looks through the last 3 messages and returns those comments.''' if self.cache['comments']: return self.cache['comments'] comments = [] for message in self.messages[0:3]: comment_xml = self.bc.comments(message.id) for comment_node in ET.fromstring(comment_xml).findall("comment"): comments.append(Comment(comment_node)) comments.sort() comments.reverse() self.cache['comments'] = comments return self.cache['comments']
def milestones(self): '''Array of all milestones''' if self.cache['milestones']: return self.cache['milestones'] milestone_xml = self.bc.list_milestones(self.id) milestones = [] for node in ET.fromstring(milestone_xml).findall("milestone"): milestones.append(Milestone(node)) milestones.sort() milestones.reverse() self.cache['milestones'] = milestones return self.cache['milestones']
def time_entries(self, start_date=None, end_date=None): '''Array of all time entries''' if self.cache['time_entries']: return self.cache['time_entries'] if not start_date: start_date = datetime.date(1900, 1, 1) if not end_date: end_date = datetime.date.today() time_entry_xml = self.bc.list_time_entries(self.id, start_date, end_date) time_entries = [] for entry in ET.fromstring(time_entry_xml).findall("time-entry"): time_entries.append(TimeEntry(entry)) self.cache['time_entries'] = time_entries return self.cache['time_entries']
def _request(self, path, data=None): if hasattr(data, 'findall'): data = ET.tostring(data) #print "***Sending***\n",data #req = urllib2.Request(url=self.baseURL + path, data=data) #print req.get_method(),req.get_full_url(),req.header_items() #return self.opener.open(req).read() if data: r = requests.post(self.baseURL + path,data=data,headers=dict(self.headers),auth=(self.username,self.password),allow_redirects=True) else: r = requests.get(self.baseURL + path,headers=dict(self.headers),auth=(self.username,self.password)) #print "received ",r.status_code,'"%s"'%r.text return r
def __test_request_local(self, path, data): if data: data = ET.tostring(data) try: return self.__test_responses["POST"][path][data] except KeyError: print "Cache miss: POST %s\n\t%s\n" % (path, data) raise try: return self.__test_responses["GET"][path] except KeyError: print "Cache miss: GET %s" % (path) print "Cache: %s" % (pprint.pformat(self.__test_responses["GET"])) raise
def __test_request_local(self, path, data): if data: data = ET.tostring(data) try: return self.__test_responses['POST'][path][data] except KeyError: print "Cache miss: POST %s\n\t%s\n" % (path, data) raise try: return self.__test_responses['GET'][path] except KeyError: print "Cache miss: GET %s" % (path) print "Cache: %s" % (pprint.pformat(self.__test_responses['GET'])) raise
def update_milestone(self, milestone_id, title, deadline, party_id, notify, move_upcoming_milestones=None, move_upcoming_milestones_off_weekends=None): """ Modifies a single milestone. You can use this to shift the deadline of a single milestone, and optionally shift the deadlines of subsequent milestones as well. """ path = '/milestones/update/%u' % milestone_id req = ET.Element('request') req.append( self._create_milestone_elem(title, deadline, party_id, notify)) if move_upcoming_milestones is not None: ET.SubElement(req, 'move-upcoming-milestones').text \ = str(bool()).lower() if move_upcoming_milestones_off_weekends is not None: ET.SubElement(req, 'move-upcoming-milestones-off-weekends').text \ = str(bool()).lower() return self._request(path, req)
def _create_message_post_elem(self, category_id, title, body, extended_body, use_textile=False, private=False): post = ET.Element('post') ET.SubElement(post, 'category-id').text = str(int(category_id)) ET.SubElement(post, 'title').text = str(title) ET.SubElement(post, 'body').text = str(body) ET.SubElement(post, 'extended-body').text = str(extended_body) if bool(use_textile): ET.SubElement(post, 'use-textile').text = '1' if bool(private): ET.SubElement(post, 'private').text = '1' return post
def update_todo_list(self, list_id, name, description, milestone_id=None, private=None, tracked=None): """ With this call you can alter the metadata for a list. """ path = '/todos/update_list/%u' % list_id req = ET.Element('request') list_ = ET.SubElement('list') ET.SubElement(list_, 'name').text = str(name) ET.SubElement(list_, 'description').text = str(description) if milestone_id is not None: ET.SubElement(list_, 'milestone_id').text = str(int(milestone_id)) if private is not None: ET.SubElement(list_, 'private').text = str(bool(private)).lower() if tracked is not None: ET.SubElement(list_, 'tracked').text = str(bool(tracked)).lower() return self._request(path, req)
def _request(self, path, data=None): if hasattr(data, 'findall'): data = ET.tostring(data) req = urllib2.Request(url=self.baseURL + path, data=data) return self.opener.open(req).read()
def _get_project_info(self): project_xml = self.bc._request("/projects/%s.xml" % self.id) node = ET.fromstring(project_xml) self._name = node.findtext("name") self._status = node.findtext("status") self._last_changed_on = node.findtext("last-changed-on")