def get(self):
        point_categories = PointCategory.query(ancestor=PointCategory.root_key())
        out = {}
        for p in point_categories:
            if p.parent is None:
                out[p.name] = {
                    "name": p.name,
                    "baby_requirement": p.baby_requirement,
                    "member_requirement": p.member_requirement,
                    "sub_categories": [],
                }
                invalid_keys = []
                for key in p.sub_categories:
                    sub = key.get()

                    # The key in sub_categories is no longer valid
                    if sub is None:
                        invalid_keys.append(key)
                    else:
                        sub_cat = {
                            "name": sub.name,
                            "baby_requirement": sub.baby_requirement,
                            "member_requirement": sub.member_requirement,
                        }
                        out[p.name]['sub_categories'].append(sub_cat)

                for key in invalid_keys:
                    p.sub_categories.remove(key)
                if invalid_keys:
                    p.put()

        return jsonify(**out)
    def post(self):
        """ Creates a new point category or updates a duplicate one

        If this function is given a point category with the same name as one
        that already exists, it will update the existing point category. It
        handles removing and adding necessary keys of parent categories.
        """
        data = request.form
        new_category = None
        new_key = None

        # TODO (phillip): we shouldn't put anything in the datastore if there is an error.
        # Therefore, we should wait until the end of the function to call a
        # put if possible.

        # Check to see if the category already exists
        for category in PointCategory.query():
            if category.name == data['name']:
                new_category = category
                new_key = new_category.key

                parent = new_category.parent
                if parent is not None:
                    # Update old parent sub categories
                    parent.sub_categories.remove(new_key)
                    parent.put()

        # If the category doesn't exist, create a new one
        if not new_category:
            new_category = PointCategory(parent=PointCategory.root_key())
            new_category.name = data['name']
            new_key = new_category.put()

        # If necessary, add the proper key to the parent category
        parent = data.get('parent', None)
        # TODO (phillip): Should I really also be checking for "none"? Is there a
        # better way?
        if parent is not None and parent != "none":
            parent = PointCategory.get_from_name(parent)
            parent.sub_categories.append(new_key)
            parent.put()

        response = jsonify()
        response.status_code = 201
        response.headers['location'] = "/api/point-categories/" + new_category.name.replace(" ", "")
        return response
    def reshape_categories(cat_info):
        """ Reshape flat list of catory information to include structure.

        Args:
            cat_info (dict): A flat dictionary of all categories, their point
                requirements, and their points earned.

        Returns:
            A structured dictionary with each category, its sub-categoires,
            points required, and points earned.
        """
        out = {}

        # TODO (phillip): this code is probably duplicated from the PointCategoryListAPI
        point_categories = PointCategory.query(ancestor=PointCategory.root_key())
        for cat in point_categories:
            if cat.parent is None:
                out[cat.name] = {
                    "required": cat_info["categories"][cat.name]["required"],
                    "received": cat_info["categories"][cat.name]["received"],
                    "level": 1,
                    "sub_categories": {},
                }

                invalid_keys = []
                for key in cat.sub_categories:
                    sub = key.get()

                    # The key in sub_categories is no longer valid
                    if sub is None:
                        invalid_keys.append(key)
                    else:
                        sub_cat = {
                            "required": cat_info["categories"][sub.name]["required"],
                            "received": cat_info["categories"][sub.name]["received"],
                            "level": 2,
                        }
                        out[cat.name]["sub_categories"][sub.name] = sub_cat

                for key in invalid_keys:
                    cat.sub_categories.remove(key)
                if invalid_keys:
                    cat.put()

        return out
    def get(self, user_id):
        output = {u"categories": {}}

        user = UserData.get_user_from_id(user_id)

        point_exceptions = {exc.point_category: exc.points_needed for exc in user.point_exceptions}
        categories = PointCategory.query(ancestor=PointCategory.root_key())
        for cat in categories:
            # TODO (phillip): Write tests to test the behavior when calculating requirement for
            # a category with children having a higher requirement. (i.e. max(self, children))

            # TODO (phillip): remove some of the ugly repeated code below
            # Add each category and the required number of points
            if user.is_baby():
                requirement = cat.baby_requirement
                if requirement is None:
                    requirement = 0

                sub_req_list = []
                for sub in cat.sub_categories:
                    req = sub.get().member_requirement
                    if req is None:
                        sub_req_list.append(0)
                    else:
                        sub_req_list.append(req)
                sub_req = sum(sub_req_list)
                requirement = max(requirement, sub_req)
            else:
                requirement = cat.member_requirement
                if requirement is None:
                    requirement = 0

                # TODO (phillip): add test when one of the requirements is None
                sub_req_list = []
                for sub in cat.sub_categories:
                    req = sub.get().member_requirement
                    if req is None:
                        sub_req_list.append(0)
                    else:
                        sub_req_list.append(req)
                sub_req = sum(sub_req_list)
                requirement = max(requirement, sub_req)

            if cat.name in point_exceptions:
                requirement = point_exceptions[cat.name]

            output["categories"][cat.name] = {u"required": requirement}

            output["categories"][cat.name]["received"] = 0
            if cat.parent is not None:
                output["categories"][cat.name]["level"] = 2
            else:
                output["categories"][cat.name]["level"] = 1

        # NOTE: At this point I am assuming each category has been added to
        # the output. If this is not true, the following code will fail
        # miserably.
        records = PointRecord.query()
        # username = user.username
        records = records.filter(PointRecord.username == user.username)
        for record in records:
            event = Event.get_from_name(record.event_name)
            if event is None:
                logging.error("Uknown event " + record.event_name + " requested for point record: " + str(record))
                record.key.delete()
                continue

            category = event.point_category.get()
            if record.points_earned is None:
                record.points_earned = 0
                record.put()
            output["categories"][category.name]["received"] += record.points_earned

            # Make sure to also count the points for the parent category
            if category.parent is not None:
                output["categories"][category.parent.name]["received"] += record.points_earned
                output["categories"][category.name]["level"] = 2

        output = self.reshape_categories(output)

        return jsonify(**output)