def post(self, query_string, data=None, handle_errors=True): """Generates a POST query on the target Trac system. This also alters the given data to include the form token stored on the cookies. Without this token, Trac will refuse form submissions. :param query_string: Starts with a slash, part of the URL between the domain and the parameters (before the ?). :param data: Dictionary of parameters to encode and transmit to the target page. :param handle_errors: Crash with a proper exception according to the HTTP return code (default: True). """ if data: data["__FORM_TOKEN"] = self.get_form_token() r = self.session.post(self.base_url + query_string, data=data) if r.status_code >= 400 and handle_errors: message = text.extract_message(r.text) if not message: message = "{} returned {}".format(self.base_url, r) raise exceptions.FatalError(message) return r
def get(self, query_string, data=None, handle_errors=True): """Generates a GET query on the target Trac system. TODO: extract all the possible error elements as message. :param query_string: Starts with a slash, part of the URL between the domain and the parameters (before the ?). :param data: Dictionary of parameters to encode at the end of the ``query_string``. :param handle_errors: Crash with a proper exception according to the HTTP return code (default: True). """ r = self.session.get(self.base_url + query_string, data=data) if r.status_code >= 400 and handle_errors: message = text.extract_message(r.text) if not message: message = "{} returned {}".format(self.base_url, r) raise exceptions.FatalError(message) # Check the version if we can. self.check_version(r.text) return r
def run_new(self, owner=None): """Create a new ticket and return its id if successful. usage: cm new [owner] """ template = self.resolve_template() if not template: template = DEFAULT_TEMPLATE # Parse the template ahead of time, allowing us to insert the Owner/To. ep = email.parser.Parser() em = ep.parsestr(template) body = em.get_payload() headers = OrderedDict(em.items()) # The owner specified on the command line always prevails. if owner: headers["To"] = owner # If all else fail, assign it to yourself. if not headers["To"]: headers["To"] = self.username valid = False while not valid: # Get the properties at each iteration, in case an admin updated # the list in the mean time. properties = self.get_properties() # Assume the user will produce a valid ticket valid = True # Load the current values in a temp file for editing (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket") fp = os.fdopen(fd, "w") fp.write(self._format_headers(headers)) fp.write("\n\n") fp.write(body) fp.close() self.editor(filename) # Use the email parser to get the headers. ep = email.parser.Parser() with open(filename, "r") as fp: em = ep.parse(fp) os.unlink(filename) body = em.get_payload() headers = OrderedDict(em.items()) errors = [] fuzzy_match_fields = ("Milestone", "Component", "Type", "Version", "Priority") # Ensures all the required fields are filled-in for key in self.required_fields: if key in fuzzy_match_fields: continue if not headers.get(key) or "**ERROR**" in headers[key]: errors.append("Invalid '{}': cannot be blank".format(key)) # Some fields are tolerant to incomplete values, this is where we # try to complete them. for key in fuzzy_match_fields: lkey = key.lower() if lkey not in properties: continue valid_options = properties[lkey]["options"] # The specified value is not available in the multi-choice. if key in headers and headers[key] not in valid_options: m = text.fuzzy_find(headers[key], valid_options) if m: # We found a close match, update the value with it. headers[key] = m else: # We didn't find a close match. If the user entered # something explicitly or if this field is required, # this is an error, else just wipe the value and move # on. if headers[key] or key in self.required_fields: joined_options = ", ".join(valid_options) errors.append(u"Invalid '{}': expected: {}" .format(key, joined_options)) else: headers[key] = "" if errors: valid = False print("\nFound the following errors:") for error in errors: print(u" - {}".format(error)) try: self.input("\n-- Hit Enter to return to editor, "\ "^C to abort --\n") except KeyboardInterrupt: raise exceptions.FatalError("ticket creation interrupted") # Since the body is expected to be using CRLF line termination, we # replace newlines by CRLF if no CRLF is found. if "\r\n" not in body: body = body.replace("\n", "\r\n") fields_data = { "field_summary": headers.get("Subject", ""), "field_type": headers.get("Type", ""), "field_version": headers.get("Version", ""), "field_description": body, "field_milestone": headers.get("Milestone", ""), "field_component": headers.get("Component", ""), "field_owner": headers.get("To", ""), "field_keywords": headers.get("Keywords", ""), "field_cc": headers.get("Cc", ""), "field_attachment": "", } # Assume anything outside of the original headers it to be included as # fields. for key, value in headers.items(): field_name = "field_" + key.lower() if field_name not in fields_data: fields_data[field_name] = value r = self.post("/newticket", fields_data) if r.status_code != 200: message = text.extract_message(r.text) if not message: message = "unable to create new ticket" raise exceptions.RequestException(message) try: ticket_id = int(r.url.split("/")[-1]) except: raise exceptions.RequestException("returned ticket_id is invalid.") self.open_in_browser_on_request(ticket_id) return ["ticket #{} created".format(ticket_id)]
def run_new(self, owner=None): """Create a new ticket and return its id if successful. usage: cm new [owner] """ template = self.resolve_template() if not template: if self.message_file: if self.message_file == "-": template = sys.stdin.read() else: with open(self.message_file) as fp: template = fp.read() else: template = DEFAULT_TEMPLATE # Parse the template ahead of time, allowing us to insert the Owner/To. ep = email.parser.Parser() em = ep.parsestr(template) body = em.get_payload() headers = OrderedDict(em.items()) # The owner specified on the command line always prevails. if owner: headers["To"] = owner # If all else fail, assign it to yourself. if not headers["To"]: headers["To"] = self.username self.login() valid = False while not valid: # Get the properties at each iteration, in case an admin updated # the list in the mean time. options = self.get_property_options() # Assume the user will produce a valid ticket valid = True # Load the current values in a temp file for editing (fd, filename) = tempfile.mkstemp(suffix=".cm.ticket") fp = os.fdopen(fd, "w") fp.write(self._format_headers(headers)) fp.write("\n\n") fp.write(body) fp.close() # When reading the message from stdin, we can't edit, skip editor. if not self.message_file: self.editor(filename) # Use the email parser to get the headers. ep = email.parser.Parser() with open(filename, "r") as fp: em = ep.parse(fp) os.unlink(filename) body = em.get_payload() headers = OrderedDict(em.items()) errors = [] fuzzy_match_fields = ("Milestone", "Component", "Type", "Version", "Priority") # Ensures all the required fields are filled-in for key in self.required_fields: if key in fuzzy_match_fields: continue if not headers.get(key) or "**ERROR**" in headers[key]: errors.append("Invalid '{}': cannot be blank".format(key)) # Some fields are tolerant to incomplete values, this is where we # try to complete them. for key in fuzzy_match_fields: lkey = key.lower() if lkey not in options: continue valid_options = options[lkey] # The specified value is not available in the multi-choice. if key in headers and headers[key] not in valid_options: m = text.fuzzy_find(headers[key], valid_options) if m: # We found a close match, update the value with it. headers[key] = m else: # We didn't find a close match. If the user entered # something explicitly or if this field is required, # this is an error, else just wipe the value and move # on. if headers[key] or key in self.required_fields: joined_options = ", ".join(valid_options) errors.append(u"Invalid '{}': expected: {}".format( key, joined_options)) else: headers[key] = "" if errors: valid = False print("\nFound the following errors:") for error in errors: print(u" - {}".format(error)) try: if not self.message_file: self.input("\n-- Hit Enter to return to editor, " "^C to abort --\n") except KeyboardInterrupt: raise exceptions.FatalError("ticket creation interrupted") # There is no editor loop when reading message from stdin, just # print the errors and exit. if self.message_file: break # Since the body is expected to be using CRLF line termination, we # replace newlines by CRLF if no CRLF is found. if "\r\n" not in body: body = body.replace("\n", "\r\n") fields_data = { "field_summary": headers.get("Subject", ""), "field_type": headers.get("Type", ""), "field_version": headers.get("Version", ""), "field_description": body, "field_milestone": headers.get("Milestone", ""), "field_component": headers.get("Component", ""), "field_owner": headers.get("To", ""), "field_keywords": headers.get("Keywords", ""), "field_cc": headers.get("Cc", ""), "field_attachment": "", } # Assume anything outside of the original headers it to be included as # fields. for key, value in headers.items(): field_name = "field_" + key.lower() if field_name not in fields_data: fields_data[field_name] = value r = self.post("/newticket", fields_data) if r.status_code != 200: message = text.extract_message(r.text) if not message: message = "unable to create new ticket" raise exceptions.RequestException(message) try: ticket_id = int(r.url.split("/")[-1]) except: raise exceptions.RequestException("returned ticket_id is invalid.") self.open_in_browser_on_request(ticket_id) return ["ticket #{} created".format(ticket_id)]