def test_auto_message(self): """ Verify that creating and modifying a DFRD Node will trigger Message creation. """ node = Node() node.owner = self.user node.title = 'New deferred node' node.scheduled_date = dt.datetime.now().date() dfrd = TodoState.objects.get(abbreviation='DFRD') nxt = TodoState.objects.get(abbreviation='NEXT') # Create a new deferred node node.todo_state = dfrd self.assertRaises(Message.DoesNotExist, lambda x: node.deferred_message, 'New Node() starts out with a message') node.save() node = Node.objects.get(pk=node.pk) self.assertTrue(isinstance(node.deferred_message, Message)) self.assertEqual(node.deferred_message.handler_path, 'plugins.deferred') # Now make the node NEXT and see that the message disappears node.todo_state = nxt node.save() node = Node.objects.get(pk=node.pk) self.assertRaises(Message.DoesNotExist, lambda x: node.deferred_message, 'New Node() starts out with a message')
def test_auto_message(self): """ Verify that creating and modifying a DFRD Node will trigger Message creation. """ node = Node() node.owner = self.user node.title = 'New deferred node' node.scheduled_date = dt.datetime.now().date() dfrd = TodoState.objects.get(abbreviation='DFRD') nxt = TodoState.objects.get(abbreviation='NEXT') # Create a new deferred node node.todo_state = dfrd self.assertRaises( Message.DoesNotExist, lambda x: node.deferred_message, 'New Node() starts out with a message' ) node.save() node = Node.objects.get(pk=node.pk) self.assertTrue( isinstance(node.deferred_message, Message) ) self.assertEqual( node.deferred_message.handler_path, 'plugins.deferred' ) # Now make the node NEXT and see that the message disappears node.todo_state = nxt node.save() node = Node.objects.get(pk=node.pk) self.assertRaises( Message.DoesNotExist, lambda x: node.deferred_message, 'New Node() starts out with a message' )
def import_structure(file=None, string=None, request=None, scope=None): """ Parses either an org-mode file or an org-mode string and saves the resulting heirerarchy to the OrgWolf models in the gtd module. # TODO: rewrite this without PyOrgMode """ # We want pre & post save signals skipped if file and string: raise AttributeError("Please supply either a file or a string, not both.") elif string: source = StringIO(string) elif file: source = io.open(file, 'r', encoding='utf8') if not scope: # Automatic scope detection scope_match = re.search(r'([^/]+)\.org', file) if scope_match: scope_string = scope_match.groups()[0] scope = Scope.objects.filter(name__iexact=scope_string) if scope.exists(): scope = scope[0] else: scope = Scope(name=scope_string, display=scope_string) scope.save() else: raise AttributeError("Please supply a file or a string") # First, build a list of dictionaries that hold the pieces of each line. data_list = [] if request: current_user = request.user else: current_user = User.objects.get(id=1) for line in source: data_list.append({'original': line}) def save_text(parent, text): # A helper function the tries to save some new text if parent: parent.text = current_text parent.save() elif current_text: # Warn the user about some dropped text print("---") print("Warning, dropping text (no parent node)") print("Text: %s", current_text) print("---") # Now go through each line and see if it matches a regex current_indent = 0 # counter current_order = 0 parent_stack = [] todo_state_list = TodoState.objects.all() # Todo: filter by current user current_text = '' for line in data_list: heading_match = HEADING_RE.search(line['original'].strip("\n")) if heading_match: # It's a heading line_indent = len(heading_match.groups()[0]) line['todo'] = heading_match.groups()[1] line['priority'] = heading_match.groups()[2] line['heading'] = heading_match.groups()[3] line['tag_string'] = heading_match.groups()[4] new_node = Node() if line_indent > current_indent: # New child # TODO: what if the user skips a level current_indent = current_indent + 1 current_order = 0 # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' elif line_indent == current_indent: # Another child # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' # Adjust the parent parent_stack.pop() elif line_indent < current_indent: # Back up to parent # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' parent_stack.pop() for x in range(current_indent - line_indent): # Move up the stack current_order = parent_stack.head.value.order parent_stack.pop() current_indent = current_indent - 1 # See if the 'todo' captured by the regex matches a current TodoState, # if not then it's parts of the heading # TODO: find a way to not destroy whitespace if line['todo']: found = False for todo_state in todo_state_list: if todo_state.abbreviation.lower() == line['todo'].lower(): new_node.todo_state = todo_state found = True break if found == False: if line['heading']: line['heading'] = line['todo'] + ' ' + str(line['heading']) else: line['heading'] = line['todo'] if current_indent > 1: new_node.parent = parent_stack.head.value if line['heading']: new_node.title = line['heading'] else: new_node.title = '' new_node.owner = current_user new_node.order = current_order + 10 if line['priority']: new_node.priority = line['priority'] else: new_node.priority = '' if line['tag_string']: new_node.tag_string = line['tag_string'] else: new_node.tag_string = '' new_node.auto_close = False # Disable closed timestamp new_node.save() # Add scope (passed as argument or auto-detected) if scope: new_node.scope.add(scope) # Update current state variables current_order = new_node.order parent_stack.push(new_node) else: # Some sort of text item # Test to see if it's a scheduled, deadline or closed modifier time_sensitive_match = TIME_SENSITIVE_RE.findall(line['original']) if time_sensitive_match: parent = parent_stack.head.value for match in time_sensitive_match: # Bump a match for "CLOSED:" up to the 0 and 1 position if match[2] and match[3]: match = (match[2], match[3], "", "") date_match = DATE_RE.search(match[1]).groups() if date_match: # Set some variables to make things easier to read year = int(date_match[0]) month = int(date_match[1]) day = int(date_match[2]) if date_match[4]: hour = int(date_match[4]) else: hour = 0 if date_match[5]: minute = int(date_match[5]) else: minute = 0 naive_datetime = datetime(year, month, day, hour, minute) new_datetime = timezone.get_current_timezone().localize(naive_datetime) # TODO: set to user's preferred timezone if date_match[4] and date_match[5]: time_specific = True else: time_specific = False if date_match[6]: # repeating parent.repeats = True parent.repeating_number = date_match[6][1] parent.repeating_unit = date_match[6][2] if date_match[6][0] == "+": parent.repeats_from_completion = False elif date_match[6][0] == ".": parent.repeats_from_completion = True # Set the appropriate fields if match[0] == "SCHEDULED:": parent.scheduled_date = new_datetime.date() if time_specific: parent.scheduled_time = new_datetime.time() elif match[0] == "DEADLINE:": parent.deadline_date = new_datetime.date() if time_specific: parent.deadline_time = new_datetime.date() elif match[0] == "CLOSED:": parent.closed = new_datetime parent.auto_close = False # Disable closed timestamp parent.save() else: # It's just a regular text item current_text += line['original']
def import_structure(file=None, string=None, request=None, scope=None): """ Parses either an org-mode file or an org-mode string and saves the resulting heirerarchy to the OrgWolf models in the gtd module. # TODO: rewrite this without PyOrgMode """ # We want pre & post save signals skipped if file and string: raise AttributeError( "Please supply either a file or a string, not both.") elif string: source = StringIO(string) elif file: source = io.open(file, 'r', encoding='utf8') if not scope: # Automatic scope detection scope_match = re.search(r'([^/]+)\.org', file) if scope_match: scope_string = scope_match.groups()[0] scope = Scope.objects.filter(name__iexact=scope_string) if scope.exists(): scope = scope[0] else: scope = Scope(name=scope_string, display=scope_string) scope.save() else: raise AttributeError("Please supply a file or a string") # First, build a list of dictionaries that hold the pieces of each line. data_list = [] if request: current_user = request.user else: current_user = User.objects.get(id=1) for line in source: data_list.append({'original': line}) def save_text(parent, text): # A helper function the tries to save some new text if parent: parent.text = current_text parent.save() elif current_text: # Warn the user about some dropped text print("---") print("Warning, dropping text (no parent node)") print("Text: %s", current_text) print("---") # Now go through each line and see if it matches a regex current_indent = 0 # counter current_order = 0 parent_stack = [] todo_state_list = TodoState.objects.all() # Todo: filter by current user current_text = '' for line in data_list: heading_match = HEADING_RE.search(line['original'].strip("\n")) if heading_match: # It's a heading line_indent = len(heading_match.groups()[0]) line['todo'] = heading_match.groups()[1] line['priority'] = heading_match.groups()[2] line['heading'] = heading_match.groups()[3] line['tag_string'] = heading_match.groups()[4] new_node = Node() if line_indent > current_indent: # New child # TODO: what if the user skips a level current_indent = current_indent + 1 current_order = 0 # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' elif line_indent == current_indent: # Another child # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' # Adjust the parent parent_stack.pop() elif line_indent < current_indent: # Back up to parent # Save any text associated with the parent parent = getattr(parent_stack.head, 'value', None) save_text(parent, current_text) current_text = '' parent_stack.pop() for x in range(current_indent - line_indent): # Move up the stack current_order = parent_stack.head.value.order parent_stack.pop() current_indent = current_indent - 1 # See if the 'todo' captured by the regex matches a current TodoState, # if not then it's parts of the heading # TODO: find a way to not destroy whitespace if line['todo']: found = False for todo_state in todo_state_list: if todo_state.abbreviation.lower() == line['todo'].lower(): new_node.todo_state = todo_state found = True break if found == False: if line['heading']: line['heading'] = line['todo'] + ' ' + str( line['heading']) else: line['heading'] = line['todo'] if current_indent > 1: new_node.parent = parent_stack.head.value if line['heading']: new_node.title = line['heading'] else: new_node.title = '' new_node.owner = current_user new_node.order = current_order + 10 if line['priority']: new_node.priority = line['priority'] else: new_node.priority = '' if line['tag_string']: new_node.tag_string = line['tag_string'] else: new_node.tag_string = '' new_node.auto_close = False # Disable closed timestamp new_node.save() # Add scope (passed as argument or auto-detected) if scope: new_node.scope.add(scope) # Update current state variables current_order = new_node.order parent_stack.push(new_node) else: # Some sort of text item # Test to see if it's a scheduled, deadline or closed modifier time_sensitive_match = TIME_SENSITIVE_RE.findall(line['original']) if time_sensitive_match: parent = parent_stack.head.value for match in time_sensitive_match: # Bump a match for "CLOSED:" up to the 0 and 1 position if match[2] and match[3]: match = (match[2], match[3], "", "") date_match = DATE_RE.search(match[1]).groups() if date_match: # Set some variables to make things easier to read year = int(date_match[0]) month = int(date_match[1]) day = int(date_match[2]) if date_match[4]: hour = int(date_match[4]) else: hour = 0 if date_match[5]: minute = int(date_match[5]) else: minute = 0 naive_datetime = datetime(year, month, day, hour, minute) new_datetime = timezone.get_current_timezone( ).localize(naive_datetime ) # TODO: set to user's preferred timezone if date_match[4] and date_match[5]: time_specific = True else: time_specific = False if date_match[6]: # repeating parent.repeats = True parent.repeating_number = date_match[6][1] parent.repeating_unit = date_match[6][2] if date_match[6][0] == "+": parent.repeats_from_completion = False elif date_match[6][0] == ".": parent.repeats_from_completion = True # Set the appropriate fields if match[0] == "SCHEDULED:": parent.scheduled_date = new_datetime.date() if time_specific: parent.scheduled_time = new_datetime.time() elif match[0] == "DEADLINE:": parent.deadline_date = new_datetime.date() if time_specific: parent.deadline_time = new_datetime.date() elif match[0] == "CLOSED:": parent.closed = new_datetime parent.auto_close = False # Disable closed timestamp parent.save() else: # It's just a regular text item current_text += line['original']