Example #1
0
 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')
Example #2
0
 def create_node(self):
     """
     Creates and returns a new Node instance. It is not saved
     into the database.
     """
     new_node = Node()
     new_node.owner = self._msg.owner
     new_node.text = self._msg.message_text
     return new_node
Example #3
0
 def create_node(data, parent=None):
     """
     Recursive function for seting a Node and then processing its children.
     """
     node = Node(title=data['title'])
     node.save()
     node.set_fields(data)
     # Remove tree fields so they can be set by MPTT framework
     for field in TREE_FIELDS:
         setattr(node, field, None)
     node.parent = parent
     node.save()
     pk_list.append(node.pk)
     children = [
         x for x in json_list
         if (x['tree_id'] == data['tree_id'] and x['lft'] > data['lft'] and
             x['rght'] < data['rght'] and x['level'] == data['level'] + 1)
     ]
     for child in children:
         create_node(child, node)
Example #4
0
 def post(self, request, pk=None, *args, **kwargs):
     """
     Create a new Node, conducted through JSON format:
     {
       id: [node primary key],
       title: 'Everything's shiny, captn',
       todo_state: 2,
       etc...
     }
     
     Ignores fields related to MPTT for new nodes as these get
     set automatically based on the 'parent' attribute.
     
     Returns: JSON object of all node fields, with changes.
     """
     data = request.data.copy()
     if pk is not None:
         # Cannot POST if a node is specified by primary key
         return HttpResponseNotAllowed(['GET', 'PUT'])
     # Create new node
     self.node = Node()
     if not request.user.is_anonymous:
         self.node.owner = request.user
     self.node.save()
     # Set fields (ignore mptt fields for new nodes)
     for key in ('id', 'tree_id', 'lft', 'rght', 'level'):
         try:
             data.pop(key)
         except KeyError:
             pass
     self.node.set_fields(data)
     self.node.save()
     # Return newly saved node as json
     self.node = Node.objects.get(pk=self.node.pk)
     serializer = NodeSerializer(self.node, request=request)
     data = serializer.data
     # Don't keep nodes sent via the public interface
     if request.user.is_anonymous:
         self.node.delete()
     return Response(data)
Example #5
0
 def test_defer_message(self):
     """Verify that a message can be rescheduled for the future."""
     dfrd = TodoState.objects.get(abbreviation="DFRD")
     node = Node(title='Sample Node',
                 todo_state=dfrd,
                 owner=User.objects.get(pk=1))
     node.save()
     msg = node.deferred_message
     msg = Message.objects.get(pk=msg.pk)
     # Defer the message and refresh
     new_date = dt.datetime(2014,
                            6,
                            15,
                            tzinfo=timezone.get_current_timezone())
     msg.handler.defer(new_date)
     msg = Message.objects.get(pk=msg.pk)
     self.assertEqual(
         msg.rcvd_date,
         new_date,
     )
     # Check that the Node was also rescheduled
     self.assertEqual(msg.source_node.scheduled_date, new_date.date())
Example #6
0
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']