Esempio n. 1
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)
Esempio n. 2
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)
Esempio n. 3
0
class NodeView(APIView):
    """
    API for interacting with Node objects. Unauthenticated requests
    are permitted but do not alter the database. Several query parameters
    have special significance, otherwise query parameters are treated as
    filters:

    - 'context=[integer]': Any query with context (even if it is null) is
    treated as an actions-list. The query indicates which Context object
    should be used.

    - 'field_group=[string]': Allows for an alternate set of fields to
    be returned.

    - 'upcoming=[date-string]': Requests a list of Node objects that are
    due soon. The date-string should be UTC and ISO formatted (YYYY-mm-dd).
    """
    def get(self, request, *args, **kwargs):
        """Returns the details of the node as a json encoded object"""
        SERIALIZERS = {
            'default': NodeSerializer,
            'actions_list': NodeListSerializer,
            'outline': NodeOutlineSerializer,
            'calendar': CalendarSerializer,
            'calendar_deadlines': CalendarDeadlineSerializer,
        }
        import time
        get_dict = request.GET.copy()
        node_id = kwargs.get('pk')
        # Look for the reserved query parameters
        if get_dict.get('upcoming', None):
            # Get Nodes with upcoming deadline
            nodes = self.get_upcoming(request, *args, **kwargs)
            default_serializer = 'actions_list'
        elif get_dict.get('context', None):
            # Context is given, so this is an actions list
            nodes = self.get_actions_list(request, *args, **kwargs)
            default_serializer = 'actions_list'
        elif node_id is not None:
            # A specific Node object is requested
            nodes = get_object_or_404(Node, pk=node_id)
            default_serializer = 'default'
        else:
            nodes = self.get_queryset(request, *args, **kwargs)
            default_serializer = 'default'
        # Check for alternate serializer
        try:
            field_group = get_dict.pop('field_group')[0]
        except KeyError:
            field_group = default_serializer
        Serializer = SERIALIZERS[field_group]
        # Serialize and return the queryset or object
        is_many = isinstance(nodes, QuerySet)
        serializer = Serializer(nodes, many=is_many, request=request)
        return Response(serializer.data)
    
    def get_queryset(self, request, *args, **kwargs):
        """
        Return a queryset for regular GET queries. If a context
        is given as a query parameter, then the get_action_list()
        method is to be used instead.
        """
        BOOLS = ('archived',) # Translate 'False' -> False for these fields
        M2M = ['todo_state'] # For filtering on arrays
        nodes = Node.objects.mine(request.user, get_archived=True)
        get_dict = request.GET.copy()
        # get_dict = dict(request.GET)
        parent_id = get_dict.get('parent_id', None)
        if parent_id == '0':
            nodes = nodes.filter(parent=None)
            get_dict.pop('parent_id')
        # Apply each criterion to the queryset
        for key in get_dict.keys():
            if key in BOOLS:
                query = {key: False if get_dict[key] == 'false' else True}
            elif key in M2M:
            # Convert to (param__in=[]) style list filtering
                value_list = get_dict.getlist(key)
                param = "{}__in".format(key)
                query = {param: value_list}
            else:
                query = {key: get_dict[key]}
            try:
                nodes = nodes.filter(**query)
            except FieldError:
                pass
        nodes = nodes.select_related('owner')
        nodes = nodes.prefetch_related('users', 'focus_areas')
        return nodes

    def get_actions_list(self, request, *args, **kwargs):
        """
        Fetches a queryset for the requested "Next Actions" list.
        Only called if 'context' is passed as a GET query parameter (even if
        it equals None).
        """
        # Filter by parent
        parent_id = request.GET.get('parent', None)
        if parent_id is not None:
            parent = Node.objects.get(pk=parent_id)
            nodes = parent.get_descendants(include_self=True)
        else:
            nodes = Node.objects.all()
        nodes = nodes.assigned(request.user).select_related('todo_state')
        # Filter by todo state
        final_Q = Q()
        todo_states_params = request.GET.getlist('todo_state')
        todo_string = ''
        for todo_state in todo_states_params:
            final_Q = final_Q | Q(todo_state=todo_state)
        nodes = nodes.filter(final_Q)
        # Filter by FocusArea
        focus_area = request.GET.get('focus_area', None)
        if focus_area:
            nodes = nodes.filter(focus_areas=focus_area)
        # Filter by context
        context_id = request.GET.get('context', None)
        if context_id == '0':
            request.session['context_id'] = None
        elif context_id != 'None' and context_id is not None:
            context = Context.objects.get(id=context_id)
            nodes = context.apply(nodes)
            request.session['context_id'] = context_id
            request.session['context_name'] = context.name
        # DB optimization
        nodes = nodes.select_related('owner')
        nodes = nodes.prefetch_related('users', 'focus_areas')
        return nodes

    def get_upcoming(self, request, *args, **kwargs):
        """
        Get QuerySet with deadlines coming up based on
        'upcoming' query parameter.
        """
        deadline_period = 7 # in days
        all_nodes_qs = Node.objects.mine(request.user)
        target_date = dt.datetime.strptime(request.GET['upcoming'],
                                           '%Y-%m-%d').date()
        # Determine query filters for "Upcoming Deadlines" section
        undone_Q = Q(todo_state__closed = False) | Q(todo_state = None)
        deadline = target_date + dt.timedelta(days=deadline_period)
        upcoming_deadline_Q = Q(deadline_date__lte = deadline) # TODO: fix this
        deadline_nodes = all_nodes_qs.filter(undone_Q, upcoming_deadline_Q)
        deadline_nodes = deadline_nodes.order_by("deadline_date")
        # DB optimization
        deadline_nodes = deadline_nodes.select_related('owner')
        deadline_nodes = deadline_nodes.prefetch_related('focus_areas')
        return deadline_nodes

    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)

    def put(self, request, pk=None, *args, **kwargs):
        """
        Edit existing nodes through JSON format:
        {
          id: [node primary key],
          title: 'Everything's shiny, captn',
          todo_state: 2,
          etc...
        }
        """
        if pk is None:
            # Throw error response if user is trying to
            # PUT without specifying a pk
            return HttpResponseNotAllowed(['GET', 'POST'])
        data = request.data.copy()
        # Remove tree metadata from the request
        TREE_FIELDS = ('lft', 'rght', 'level', 'tree_id')
        for key in TREE_FIELDS:
            try:
                data.pop(key)
            except KeyError:
                pass
        # Check the permissions of the Node
        node = get_object_or_404(Node, pk=pk)
        access = node.access_level(request.user)
        if ((request.user.is_anonymous and node.owner is not None) or
            (not request.user.is_anonymous and access != 'write')):
            # Not authorized
            return HttpResponse(
                json.dumps({'status': 'failure',
                            'reason': 'unauthorized'}),
                status=401)
        # Update and return the Node
        node.set_fields(data)
        if not request.user.is_anonymous:
            node.save()
            node = Node.objects.get(pk=node.pk)
        serializer = NodeSerializer(node, request=request)
        return Response(serializer.data)
Esempio n. 4
0
class NodeView(APIView):
    """
    API for interacting with Node objects. Unauthenticated requests
    are permitted but do not alter the database. Several query parameters
    have special significance, otherwise query parameters are treated as
    filters:

    - 'context=[integer]': Any query with context (even if it is null) is
    treated as an actions-list. The query indicates which Context object
    should be used.

    - 'field_group=[string]': Allows for an alternate set of fields to
    be returned.

    - 'upcoming=[date-string]': Requests a list of Node objects that are
    due soon. The date-string should be UTC and ISO formatted (YYYY-mm-dd).
    """
    def get(self, request, *args, **kwargs):
        """Returns the details of the node as a json encoded object"""
        SERIALIZERS = {
            'default': NodeSerializer,
            'actions_list': NodeListSerializer,
            'outline': NodeOutlineSerializer,
            'calendar': CalendarSerializer,
            'calendar_deadlines': CalendarDeadlineSerializer,
        }
        get_dict = request.GET.copy()
        node_id = kwargs.get('pk')
        # Look for the reserved query parameters
        if get_dict.get('upcoming', None):
            # Get Nodes with upcoming deadline
            nodes = self.get_upcoming(request, *args, **kwargs)
            default_serializer = 'actions_list'
        elif get_dict.get('context', None):
            # Context is given, so this is an actions list
            nodes = self.get_actions_list(request, *args, **kwargs)
            default_serializer = 'actions_list'
        elif node_id is not None:
            # A specific Node object is requested
            nodes = get_object_or_404(Node, pk=node_id)
            default_serializer = 'default'
        else:
            nodes = self.get_queryset(request, *args, **kwargs)
            default_serializer = 'default'
        # Check for alternate serializer
        try:
            field_group = get_dict.pop('field_group')[0]
        except KeyError:
            field_group = default_serializer
        Serializer = SERIALIZERS[field_group]
        # Serialize and return the queryset or object
        is_many = isinstance(nodes, QuerySet)
        serializer = Serializer(nodes, many=is_many, request=request)
        return Response(serializer.data)

    def get_queryset(self, request, *args, **kwargs):
        """
        Return a queryset for regular GET queries. If a context
        is given as a query parameter, then the get_action_list()
        method is to be used instead.
        """
        BOOLS = ('archived', )  # Translate 'False' -> False for these fields
        M2M = ['todo_state']  # For filtering on arrays
        nodes = Node.objects.mine(request.user, get_archived=True)
        get_dict = request.GET.copy()
        # get_dict = dict(request.GET)
        parent_id = get_dict.get('parent_id', None)
        if parent_id == '0':
            nodes = nodes.filter(parent=None)
            get_dict.pop('parent_id')
        # Apply each criterion to the queryset
        for key in get_dict.keys():
            if key in BOOLS:
                query = {key: False if get_dict[key] == 'false' else True}
            elif key in M2M:
                # Convert to (param__in=[]) style list filtering
                value_list = get_dict.getlist(key)
                param = "{}__in".format(key)
                query = {param: value_list}
            else:
                query = {key: get_dict[key]}
            try:
                nodes = nodes.filter(**query)
            except FieldError:
                pass
        nodes = nodes.select_related('owner')
        nodes = nodes.prefetch_related('users', 'focus_areas')
        return nodes

    def get_actions_list(self, request, *args, **kwargs):
        """
        Fetches a queryset for the requested "Next Actions" list.
        Only called if 'context' is passed as a GET query parameter (even if
        it equals None).
        """
        # Filter by parent
        parent_id = request.GET.get('parent', None)
        if parent_id is not None:
            parent = Node.objects.get(pk=parent_id)
            nodes = parent.get_descendants(include_self=True)
        else:
            nodes = Node.objects.all()
        nodes = nodes.assigned(request.user).select_related('todo_state')
        # Filter by todo state
        final_Q = Q()
        todo_states_params = request.GET.getlist('todo_state')
        todo_string = ''
        for todo_state in todo_states_params:
            final_Q = final_Q | Q(todo_state=todo_state)
        nodes = nodes.filter(final_Q)
        # Filter by FocusArea
        focus_area = request.GET.get('focus_area', None)
        if focus_area:
            nodes = nodes.filter(focus_areas=focus_area)
        # Filter by context
        context_id = request.GET.get('context', None)
        if context_id == '0':
            request.session['context_id'] = None
        elif context_id != 'None' and context_id is not None:
            context = Context.objects.get(id=context_id)
            nodes = context.apply(nodes)
            request.session['context_id'] = context_id
            request.session['context_name'] = context.name
        # DB optimization
        nodes = nodes.select_related('owner')
        nodes = nodes.prefetch_related('users', 'focus_areas')
        return nodes

    def get_upcoming(self, request, *args, **kwargs):
        """
        Get QuerySet with deadlines coming up based on
        'upcoming' query parameter.
        """
        deadline_period = 7  # in days
        all_nodes_qs = Node.objects.mine(request.user)
        target_date = dt.datetime.strptime(request.GET['upcoming'],
                                           '%Y-%m-%d').date()
        # Determine query filters for "Upcoming Deadlines" section
        undone_Q = Q(todo_state__closed=False) | Q(todo_state=None)
        deadline = target_date + dt.timedelta(days=deadline_period)
        upcoming_deadline_Q = Q(deadline_date__lte=deadline)
        scheduled_Q = ~Q(scheduled_date__gt=target_date) | Q(
            deadline_date__lte=target_date)
        deadline_nodes = all_nodes_qs.filter(undone_Q, upcoming_deadline_Q,
                                             scheduled_Q)
        deadline_nodes = deadline_nodes.order_by("deadline_date")
        # DB optimization
        deadline_nodes = deadline_nodes.select_related('owner')
        deadline_nodes = deadline_nodes.prefetch_related('focus_areas')
        return deadline_nodes

    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)

    def put(self, request, pk=None, *args, **kwargs):
        """
        Edit existing nodes through JSON format:
        {
          id: [node primary key],
          title: 'Everything's shiny, captn',
          todo_state: 2,
          etc...
        }
        """
        if pk is None:
            # Throw error response if user is trying to
            # PUT without specifying a pk
            return HttpResponseNotAllowed(['GET', 'POST'])
        data = request.data.copy()
        # Remove tree metadata from the request
        TREE_FIELDS = ('lft', 'rght', 'level', 'tree_id')
        for key in TREE_FIELDS:
            try:
                data.pop(key)
            except KeyError:
                pass
        # Check the permissions of the Node
        node = get_object_or_404(Node, pk=pk)
        access = node.access_level(request.user)
        if ((request.user.is_anonymous and node.owner is not None)
                or (not request.user.is_anonymous and access != 'write')):
            # Not authorized
            return HttpResponse(json.dumps({
                'status': 'failure',
                'reason': 'unauthorized'
            }),
                                status=401)
        # Update and return the Node
        node.set_fields(data)
        if not request.user.is_anonymous:
            node.save()
            node = Node.objects.get(pk=node.pk)
        serializer = NodeSerializer(node, request=request)
        return Response(serializer.data)