def dashboard(user_url_segment=None):
    if user_url_segment is None:
        target_user = UserData.get_current_user_data()
    else:
        target_user = UserData.get_from_url_segment(user_url_segment)
    if target_user is None:
        template_values = {
            'target_user': user_url_segment,
        }
        return render_jinja_template("noprofile.html", template_values), 404

    if target_user.username != user_url_segment:
        return redirect('/dashboard/{0}'.format(target_user.username))

    # If looking at the current user's profile, hilight the users name in the
    # nav bar
    if target_user == UserData.get_current_user_data():
        return redirect('/'.format(target_user.username))
    else:
        active = None

    template_values = {
        'target_user': target_user,
    }
    return render_jinja_template("dashboard.html", template_values)
    def get(self):
        user_filter = request.args.get("filter", "both")
        if user_filter not in ["active", "inactive", "both"]:
            raise Exception(user_filter + " is not a valid filter value")

        if user_filter == "active":
            q = UserData.query().filter(UserData.active == True)
        elif user_filter == "inactive":
            q = UserData.query().filter(UserData.active == False)
        else:
            q = UserData.query()

        q = q.order(UserData.first_name)

        data = {"users": []}
        for u in q:
            # TODO (phillip): code to create a user json object is duplicated in multiple
            # places. I should keep it in one spot (maybe UserData)
            data["users"].append(
                {
                    "fname": u.first_name,
                    "lname": u.last_name,
                    "active": u.active,
                    "grad_year": u.graduation_year,
                    "grad_semester": u.graduation_semester,
                    "classification": u.classification,
                    "permissions": u.user_permissions,
                    "user_id": u.user_id,
                }
            )

        return jsonify(**data)
def index():
    template_values = {
        'active_page': 'home',
        'target_user': UserData.get_current_user_data(),
    }
    if UserData.get_current_user_data():
        return render_jinja_template("dashboard.html", template_values)
    else:
        return render_jinja_template("index.html", template_values)
def render_jinja_template(name, context=None):
    """ Renders a jinja template witha given context

    This function will also add global template values.

    Args:
        name: The name of the jinja template to be rendered
        context (dict): A dictionary of values to pass to the template
    """
    # We want to make sure all templates have this function, but we also
    # need to avoid circular dependencies so import this here.
    from permissions import check_perms

    template_values = {
        'user_data': UserData.get_current_user_data(),
        'login_url': url_for('login', next=request.path),
        'logout_url': users.create_logout_url("/"),
        'check_perms': check_perms,
        'is_admin': users.is_current_user_admin(),
    }

    if context != None:
        template_values.update(context)

    return render_template(name, **template_values)
    def post(self, user_id):
        user = UserData.get_user_from_id(user_id)
        if not user:
            raise Exception("I don't know that person")

        #TODO (phillip): The flow of this code looks more complicated and confusing than it
        # needs to be. Try to clean it up
        data = request.form
        p = None
        for exc in user.point_exceptions:
            if exc.point_category == data['point_category']:
                p = exc
        if not p:
            p = PointException()
            p.point_category = data.get('point_category', type=str)
            p.points_needed = data.get('points_needed', type=int)
            user.point_exceptions.append(p)
        else:
            p.points_needed = data.get('points_needed', type=int)
        user.put()

        response = jsonify()
        response.status_code = 201
        response.headers['location'] = "/api/users/" + user.user_id + \
                                       "/point-exceptions/" + \
                                       str(user.point_exceptions.index(p))
        return response
    def get(self, user_id):
        user = UserData.get_user_from_id(user_id)
        data = {
            u'permissions': user.user_permissions,
        }

        return jsonify(**data)
    def put(self):
        data = request.form

        user_data = UserData.get_from_username(data['username'])
        if not user_data:
            raise Exception("I don't know that person")

        event = Event.get_from_name(data['event_name'])
        if not event:
            raise Exception("I don't know that event")

        point_record = PointRecord.query(PointRecord.event_name == event.name,
                                         PointRecord.username == user_data.username).get()
        # TODO (phillip): this might allow me to not create new records every time a
        # new event or user is created because a record will be created when
        # the client tries to modify a record that should exist
        # Create a point record if one does not exist
        if not point_record:
            # TODO (phillip): does this work with the user key as the ancestor?
            point_record = PointRecord(parent=user_data.key)

        point_record.event_name = event.name
        point_record.username = user_data.username
        point_record.points_earned = float(data['points-earned'])
        point_record.put()

        # TODO (phillip): A put request (and really any other request that creates or
        # updates an object) should return the new representation of that
        # object so clients don't need to make more API calls

        response = jsonify()
        response.status_code = 204
        return response
def permissions():
    template_values = {
        'active_page': "permissions",
        'users': UserData.query().order(UserData.first_name),
    }

    return render_jinja_template("permissions.html", template_values)
    def put(self, user_id):
        """ Updates the user profile information
        This method can only be called by the user being updated or an officer.
        We do not need to protect against the user changing their point
        exceptions in this method because the method does not update the point
        exceptions.
        """
        user = UserData.get_user_from_id(user_id)
        if not user:
            raise Exception("I don't know that person")

        data = request.form
        user.first_name = data["fname"]
        user.last_name = data["lname"]
        if data["active"] == "true":
            user.active = True
        else:
            user.active = False
        user.classification = data["classification"]
        user.graduation_semester = data["grad_semester"]
        user.graduation_year = data.get("grad_year", type=int)
        user.put()

        # TODO (phillip): A put request (and really any other request that creates or
        # updates an object) should return the new representation of that
        # object so clients don't need to make more API calls

        response = jsonify()
        response.status_code = 204
        # TODO (phillip): I believe only POST requests need to return this header
        response.headers["location"] = "/api/users/" + str(user.user_id)
        return response
def members():
    template_values = {
        'active_page': 'members',
        'users': UserData.query().order(UserData.first_name),
    }

    return render_jinja_template("members.html", template_values)
    def delete(self, user_id, perm):
        user = UserData.get_user_from_id(user_id)
        if perm not in user.user_permissions:
            response = jsonify(message="Resource does not exist")
            response.status_code = 404
            return response

        user.user_permissions.remove(perm)
        user.put()
    def get(self, user_id):
        user = UserData.get_user_from_id(user_id)
        data = {
            "point_exceptions": [{
                "point_category": exc.point_category,
                "points_needed": exc.points_needed,
            } for exc in user.point_exceptions],
        }

        return jsonify(**data)
    def delete(self, user_id, index):
        user = UserData.get_user_from_id(user_id)
        if not user:
            raise Exception("I don't know that person")

        if index > len(user.point_exceptions)-1:
            response = jsonify(message="Resource does not exist")
            response.status_code = 404
            return response

        del user.point_exceptions[index]
        user.put()
def postlogin():
    """ Handler for just after a user has logged in

    This takes care of making sure the user has properly setup their account.
    """
    next_url = request.args.get("next", "/")
    user_data = UserData.get_current_user_data()
    if not user_data:
        # Need to create a user account
        signup_url = url_for("signup", next=next_url)
        return redirect(signup_url)
    else:
        return redirect(next_url)
def profile(user_url_segment):
    target_user = UserData.get_from_url_segment(user_url_segment)
    if target_user is None:
        template_values = {
            'target_user': user_url_segment,
        }
        return render_jinja_template("noprofile.html", template_values), 404

    if target_user.username != user_url_segment:
        return redirect('/profile/{0}'.format(target_user.username))

    # If looking at the current user's profile, hilight the users name in the
    # nav bar
    if target_user == UserData.get_current_user_data():
        active = 'profile'
    else:
        active = None

    template_values = {
        'active_page': active,
        'target_user': target_user,
    }
    return render_jinja_template("profile.html", template_values)
        def wrapper(*args, **kwargs):
            current_user = UserData.get_current_user_data()
            if not current_user:
                template_values = {
                    u'message': u"Not logged in",
                    u'html_template': u"nologin.html",
                }
                return build_response(output_format, template_values)

            missing_perms = []
            if 'user_url_segment' in kwargs:
                other_user_url_segment = kwargs['user_url_segment']
            elif 'user_id' in kwargs:
                other_user_url_segment = kwargs['user_id']
            else:
                other_user_url_segment = None

            if other_user_url_segment:
                other_user = UserData.get_from_url_segment(other_user_url_segment)
            else:
                other_user = None

            for p in perms:
                if not check_perms(current_user, p, other_user):
                    missing_perms.append(p)

            template_values = {
                u'message': "Don't have permission",
                u'html_template': "nopermission.html",
                u'perms': missing_perms,
            }
            if len(missing_perms) > 0 and logic == 'and':
                return build_response(output_format, template_values)
            elif len(missing_perms) == len(perms) and logic == 'or':
                return build_response(output_format, template_values)

            return f(*args, **kwargs)
    def post(self, user_id):
        user = UserData.get_user_from_id(user_id)
        data = request.form
        perm = data['permission']
        if perm not in user.user_permissions:
            # TODO (phillip): an officer could theoretically give themselves 'admin'
            # privileges using this function. They wouldn't get actual google
            # admin privileges but it would fool my permissions system
            user.user_permissions.append(perm)
        user.put()

        response = jsonify()
        response.status_code = 201
        response.headers['location'] = "/api/users/" + user.user_id + \
                                       "/permissions/" + perm
        return response
    def get(self, user_id, index):
        user = UserData.get_user_from_id(user_id)
        if not user:
            raise Exception("I don't know that person")

        if index > len(user.point_exceptions)-1:
            response = jsonify(message="Resource does not exist")
            response.status_code = 404
            return response

        exc = user.point_exceptions[index]
        data = {
            "point_category": exc.point_category,
            "points_needed": exc.points_needed,
        }

        return jsonify(**data)
    def get(self, user_id):
        user = UserData.get_user_from_id(user_id)
        data = {
            "active": user.active,
            "classification": user.classification,
            "grad_year": user.graduation_year,
            "grad_semester": user.graduation_semester,
            "fname": user.first_name,
            "lname": user.last_name,
            "permissions": user.user_permissions,
            "user_id": user.user_id,
            "point_exceptions": [
                {"point_category": exc.point_category, "points_needed": exc.points_needed}
                for exc in user.point_exceptions
            ],
        }

        return jsonify(**data)
    def post(self):
        """ Adds a new user

        This does not need any extra security because it only changes the
        information for the currently logged in google user. If there is no
        google user logged in an error is thrown.
        """
        # TODO (phillip): this method throws generic exceptions that really can't be
        # properly handled by the client in a user-friendly way. I should
        # really return error codes here so the client knows what went wrong
        # other than a generic server error
        data = request.form
        user = users.get_current_user()
        if not user:
            raise Exception("Where is the user?")

        # NOTE: I am using the user.user_id() as the UserData id so that I can
        # query for UserData with strong consistency. I believe this works as
        # intended and the only issue I can think of is if I decide to allow
        # logging in from something other than a google account (which would mean
        # the user is not connected to a google user with a user_id)
        user_data = UserData(id=user.user_id())
        user_data.user = user
        user_data.user_id = user.user_id()
        if data["fname"] == "":
            raise Exception("The first name was empty")
        user_data.first_name = data["fname"]
        if data["lname"] == "":
            raise Exception("The first name was empty")

        # TODO (phillip): A duplicate username should produce a better error than a 500
        other_users = UserData.query()
        other_user = None
        for user in other_users:
            if user.username == data["fname"] + data["lname"]:
                other_user = user
        if other_user is not None:
            raise Exception("There is already a user with that name.")

        user_data.last_name = data["lname"]
        user_data.graduation_year = int(data["grad_year"])
        user_data.graduation_semester = data["grad_semester"]
        user_data.classification = data["classification"]
        user_data.active = True
        user_data.user_permissions = ["user"]
        user_data.put()

        # TODO (phillip): find a better way that doesn't require you to remember to add
        # this line every single time you want to create a UserData object.
        # Create the necessary point records
        user_data.populate_records()

        response = jsonify()
        response.status_code = 201
        response.headers["location"] = "/api/users/" + str(user_data.user_id)
        return response
    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)