def feedback_create(request):
    """
    Function Summary: This function can be used to create a new Feedback object.
    Path: '/api/feedback/submit'
    Request Type: POST
    Required Login: False

    Args:
        request -- The request made to the server by the client

    Required GET Parameters:
        feedback -- The text feedback from the user

    Possible Error Codes:
        601, 603

    Return:
        Type: JSON
        Data: A JSON object with a 'status' at the top level.
    """
    # Ensure that the API call is using POST request
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(601, FEEDBACK_ERRORS))

    # Ensure the POST parameters have enough data
    if 'feedback' not in request.POST:
        return JsonResponse(Response.get_error_status(603, FEEDBACK_ERRORS))

    # Create new Feedback object
    new_feedback = Feedback.objects.create(feedback=request.POST['feedback'])
    new_feedback.save()
    return JsonResponse(Response.get_success_status())
def position_select_id(request):
    """
        Function Summary: This function is used to get a single Position object's information.
        Path: 'api/position/select'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user
            position_id -- The Position ID to be looked up

        Possible Error Codes:
            300, 301, 302, 303, 304

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using a GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(301, POSITION_ERRORS))

    # Ensure the user is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(300, POSITION_ERRORS))

    # Get the currently logged in Student object.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Ensure that the request's GET parameters include 'position_id'.
    if 'position_id' not in request.GET:
        return JsonResponse(Response.get_error_status(302, POSITION_ERRORS))

    # Lookup the Position using the provided 'position_id'.
    position_lookup = Position.objects.filter(id=request.GET['position_id'])

    # If no Position object exists, return an error status.
    if len(position_lookup) == 0:
        return JsonResponse(Response.get_error_status(303, POSITION_ERRORS))

    # Get the current Position.
    current_position = position_lookup[0]

    # If the logged in User is not the User listed in the Position, return an error status
    if current_position.student != current_student:
        return JsonResponse(Response.get_error_status(304, POSITION_ERRORS))

    # Return a success status with the Position data.
    success_status = Response.get_success_status()
    success_status['data'] = current_position.to_dict()
    return JsonResponse(success_status)
def login(request):
    """
        Function Summary: This function will login a user and return a session_id to use to authenticate into the API.
        Path: '/api/auth/login'
        Request Type: POST
        Required Login: False

        Args:
            request -- The request made to the server by the client

        Required POST parameters:
            username -- The username for the user
            password -- The password for the user

        Possible Error Codes:
            101, 103, 104, 105

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. If successful, it will have a data object containing the 'session_id' for authentication
    """
    # Make sure the request is using POST to pass data
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(101, AUTH_ERRORS))

    # Make sure that the POST data contains a username and password
    if 'username' not in request.POST or 'password' not in request.POST:
        return JsonResponse(Response.get_error_status(103, AUTH_ERRORS))

    # Lookup the user in the DB
    user_lookup = User.objects.filter(username=request.POST['username'])

    # Check that the user exists in the DB
    if len(user_lookup) == 0:
        return JsonResponse(Response.get_error_status(104, AUTH_ERRORS))

    # Check the password of the user
    user = user_lookup[0]
    is_password_correct = user.check_password(request.POST['password'])

    if not is_password_correct:
        return JsonResponse(Response.get_error_status(105, AUTH_ERRORS))

    # Check to see if the user already has a session token active
    session_lookup = Session.objects.filter(user=user)

    if len(session_lookup) == 0:
        current_session = Session.objects.create(user=user)
        current_session.save()
    else:
        current_session = session_lookup[0]

    success_dict = Response.get_success_status()
    success_dict['data'] = {'session_id': current_session.id}
    return JsonResponse(success_dict)
def position_create(request):
    """
        Function Summary: This function is used to create a Position object.
        Path: 'api/position/create'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user
            x -- The x position of the user
            y -- The y position of the user

        Possible Error Codes:
            300, 301, 302

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using a GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(301, POSITION_ERRORS))

    # Ensure that the user is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(300, POSITION_ERRORS))

    # Get the currently logged in Student object.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Try to create a new Position object and return a success status.
    try:
        x = request.GET['x']
        y = request.GET['y']
        time = datetime.datetime.now()
        new_position = Position.objects.create(student=current_student,
                                               x=x,
                                               y=y,
                                               timestamp=time)
        new_position.save()

        success_status = Response.get_success_status()
        success_status['data'] = new_position.to_dict()
        return JsonResponse(success_status)

    except KeyError:
        return JsonResponse(Response.get_error_status(302, POSITION_ERRORS))
def demographic_select(request):
    """
        Function Summary: This function is used to get a Student's Demographic information
        Path: 'api/demographic/select'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Possible Error Codes:
            200, 201, 206

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(201, DEMO_ERRORS))

    # Ensure the user is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(200, DEMO_ERRORS))

    # Get the currently logged in Student object.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Get the Demographic object associated with the Student and return it.
    try:
        demo_instance = Demographic.objects.get(student=current_student)
        object_dict = demo_instance.to_dict()
        success_object = Response.get_success_status()
        success_object['data'] = object_dict
        return JsonResponse(success_object)

    # Return error status if the Demographic object does not exist for the Student.
    except Demographic.DoesNotExist:
        return JsonResponse(Response.get_error_status(206, DEMO_ERRORS))
def position_select_all(request):
    """
        Function Summary: This function is used to get all of the Positions objects for a Student.
        Path: 'api/position/all'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Possible Error Codes:
            300, 301

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using a GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(301, POSITION_ERRORS))

    # Ensure the user is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(300, POSITION_ERRORS))

    # Get the currently logged in Student object.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Get all of the dictionary objects of the Position objects for the current Student.
    positions = [
        x.to_dict() for x in Position.objects.filter(student=current_student)
    ]

    # Return the success status with the Position objects.
    success_status = Response.get_success_status()
    success_status['data'] = positions

    return JsonResponse(success_status)
def demographic_delete(request):
    """
        Function Summary: This function is used to delete a Demographic object.
        Path: 'api/demographic/delete'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Possible Error Codes:
            200, 201, 206

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level.
    """
    # Ensure the API call is using GET
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(201, DEMO_ERRORS))

    # Ensure that the user is logged in
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(200, DEMO_ERRORS))

    # Get the Student object
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Try to delete the Demographic object associated with the logged in Student object.
    try:
        demo_instance = Demographic.objects.get(student=current_student)
        demo_instance.delete()
        return JsonResponse(Response.get_success_status())

    # Return an error status if the Demographic object does not exist.
    except Demographic.DoesNotExist:
        return JsonResponse(Response.get_error_status(206, DEMO_ERRORS))
def class_select_all(request):
    """
        Function Summary: This function is used to get all the Class objects of a Student.
        Path: '/api/class/select/all'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Possible Error Codes:
            400, 401

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(401, CLASS_ERRORS))

    # Ensure the User is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(400, CLASS_ERRORS))

    current_user = get_user_by_session(request.GET['session_id'])
    current_student = Student.objects.get(user=current_user)

    # Return success status and all Class objects associated with Student.
    success_status = Response.get_success_status()
    class_lookup = [
        x.class_enrolled
        for x in ClassEnrollment.objects.filter(student=current_student)
    ]
    success_status['data'] = [x.to_dict() for x in class_lookup]

    return JsonResponse(success_status)
def logout(request):
    """
        Function Summary: This function is used to logout a User and remove their session_id if it exists.
        Path: '/api/auth/logout'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server from the client

        Required GET parameters:
            session_id -- The Session ID of the User to be logged out

        Possible Error Codes:
            100, 101

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level.
    """
    # Make sure the request is using POST to pass data
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(101, AUTH_ERRORS))

    # Ensure that the 'session_id' exists in the GET parameters
    if 'session_id' not in request.GET:
        return JsonResponse(Response.get_error_status(100, AUTH_ERRORS))

    # Lookup Session objects using the 'session_id' provided
    session_lookup = Session.objects.filter(id=request.GET['session_id'])

    # Return error if there are no Session objects with the provided 'session_id'
    if len(session_lookup) == 0:
        return JsonResponse(Response.get_success_status())

    # Delete the Session object and return a success
    current_session = session_lookup[0]
    current_session.delete()
    return JsonResponse(Response.get_success_status())
def demographic_form(request):
    """
        Function Summary": This function is used to build a Demographic form.
        Path: 'api/demographic/form'
        Request Type: GET
        Required Login: False

        Args:
            request -- The request made to the server by the client

        Possible Error Codes:
            201

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using GET request
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(201, DEMO_ERRORS))

    form_values = {}

    # Get all the GenderLookup objects.
    genders = GenderLookup.objects.all()
    form_values['genders'] = [g.to_dict() for g in genders]

    # Get all the GradeYearLookup objects.
    grade_years = GradeYearLookup.objects.all()
    form_values['grade_years'] = [g.to_dict() for g in grade_years]

    # Get all the EthnicityLookup objects.
    ethnicities = EthnicityLookup.objects.all()
    form_values['ethnicities'] = [e.to_dict() for e in ethnicities]

    # Get all the RaceLookup objects.
    races = RaceLookup.objects.all()
    form_values['races'] = [r.to_dict() for r in races]

    # Return all of the values and success status
    success_status = Response.get_success_status()
    success_status['data'] = form_values
    return JsonResponse(success_status)
def demographic_create(request):
    """
        Function Summary: This function is used to create a new demographic object. A success status will be returned with the new Demographic object's information.
        Path: 'api/demographic/create'
        Request Type: POST
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET parameters:
            session_id -- The Session ID of the user making the request

        Required POST Parameters:
            age -- the age of the user
            gender -- the gender of the user
            grade_year -- the grade year of the user
            ethnicity -- the ethnicity of the user
            race -- the race of the user
            major -- the major of the user

        Possible Error Codes:
            200, 201, 203, 204, 205

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the request is using POST
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(201, DEMO_ERRORS))

    # If the user is not logged in, return an error status
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(200, DEMO_ERRORS))

    # Get the logged in Student
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # If a Demographic object already exists, return an error status
    if len(Demographic.objects.filter(student=current_student)) != 0:
        return JsonResponse(Response.get_error_status(204, DEMO_ERRORS))

    # Try to create a new demographic object. Error status will be returned in the data does not validate.
    try:
        new_demographic = Demographic.objects.create(
            student=current_student,
            age=request.POST['age'],
            gender=GenderLookup.objects.get(id=request.POST['gender']),
            grade_year=GradeYearLookup.objects.get(
                id=request.POST['grade_year']),
            ethnicity=EthnicityLookup.objects.get(
                id=request.POST['ethnicity']),
            race=RaceLookup.objects.get(id=request.POST['race']),
            major=request.POST['major'])
        new_demographic.save()
        success_status = Response.get_success_status()
        success_status['data'] = new_demographic.to_dict()
        return JsonResponse(Response.get_success_status())

    except KeyError:
        return JsonResponse(Response.get_error_status(203, DEMO_ERRORS))

    except GenderLookup.DoesNotExist:
        return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    except GradeYearLookup.DoesNotExist:
        return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    except EthnicityLookup.DoesNotExist:
        return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    except RaceLookup.DoesNotExist:
        return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))
def class_summarize_movement(request):
    """
        Function Summary: This function is used to get all the Class objects of a Student
        Path: '/api/class/movement_summary'
        Request Type: POST
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Required POST Parameters:
            class -- The Class ID for the Class object
            start_date -- The start date of the summary
            end_date -- The end date of the summary

        Possible Error Codes:
            400, 401, 403, 407

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using POST request.
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(401, CLASS_ERRORS))

    # Ensure the User is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(400, CLASS_ERRORS))

    # Ensure POST parameters contain required data.
    if 'class' not in request.POST or 'start_date' not in request.POST or 'end_date' not in request.POST:
        return JsonResponse(Response.get_error_status(403, CLASS_ERRORS))

    # Lookup the Class objects. based on the Class ID.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))
    class_lookup = Class.objects.filter(id=request.POST['class'])

    # Ensure the Class object exists.
    if len(class_lookup) == 0:
        return JsonResponse(Response.get_error_status(407, CLASS_ERRORS))

    # Parse the start and end date into DateTime objects.
    current_class = class_lookup[0]
    start_date = datetime.datetime.strptime(request.POST['start_date'],
                                            '%m/%d/%Y').date()
    end_date = datetime.datetime.strptime(request.POST['end_date'],
                                          '%m/%d/%Y').date()
    days_of_the_week = [x.id for x in current_class.days_of_the_week.all()]

    summary = {}

    # Go through each day between the start and end date.
    for n in range((end_date - start_date).days + 1):
        current_date = start_date + datetime.timedelta(days=n)
        # Ensure that the date we are currently on is a day the class falls on.
        if current_date.weekday() not in days_of_the_week:
            continue

        # Create start and end times to filter Position objects.
        start_timestamp = datetime.datetime(
            year=current_date.year,
            month=current_date.month,
            day=current_date.day,
            hour=current_class.start_time.hour,
            minute=current_class.start_time.minute)

        end_timestamp = datetime.datetime(year=current_date.year,
                                          month=current_date.month,
                                          day=current_date.day,
                                          hour=current_class.end_time.hour,
                                          minute=current_class.end_time.minute)

        # Get all positions that fall within the day, start time, and end time
        positions = Position.objects.filter(student=current_student,
                                            timestamp__gte=start_timestamp,
                                            timestamp__lte=end_timestamp)
        summary[str(current_date)] = [p.to_dict() for p in positions]

    # Return success status with movement summary.
    success_status = Response.get_success_status()
    success_status['data'] = summary
    return JsonResponse(success_status)
def reset_password(request, reset_code):
    """
        Function Summary: This function is used to reset a password. The function requires a new password in the POST parameters and the proper reset code to reset the password. The user will receive an email notification that their password has been changed.
        Path: '/api/auth/reset_password/<RESET CODE>'
        Request Type: POST
        Required Login: False

        Args:
            request -- The request made to the server by the client
            reset_code -- The reset code emailed to the user

        Required POST parameters:
            new_password -- The new password for the User

        Possible Error Codes:
            101, 103, 108

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level.
    """
    # Check that the request was made using POST
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(101, AUTH_ERRORS))

    # Check that the POST data contains new password
    if 'new_password' not in request.POST:
        return JsonResponse(Response.get_error_status(103, AUTH_ERRORS))

    # Check that the reset code is registered to a user
    student_lookup = Student.objects.filter(reset_password_code=reset_code)

    if len(student_lookup) == 0:
        return JsonResponse(Response.get_error_status(108, AUTH_ERRORS))

    student_account = student_lookup[0]

    # Change the user's password
    new_password = request.POST['new_password']
    student_account.reset_password_code = None
    student_account.user.set_password(new_password)
    student_account.user.save()
    student_account.save()

    # Send the user a notification email
    email_html = render_to_string(
        'reset_password_notification_email.html', {
            'user': {
                'first_name': student_account.user.first_name,
                'last_name': student_account.user.last_name
            }
        })
    text_content = strip_tags(email_html)

    email = EmailMultiAlternatives('ICBA - Password Changed', text_content,
                                   'ICBA-NO-REPLY',
                                   [student_account.user.email])
    email.attach_alternative(email_html, 'text/html')
    email.send()

    return JsonResponse(Response.get_success_status())
def request_password_reset(request, username):
    """
        Function Summary: This function is used to request a password reset for a User. The user will receive an email containing a reset code if successful. This is only to be used for resetting Student passwords. The reset code is only active for one hour.
        Path: '/api/auth/request_password_reset/<USERNAME>'
        Request Type: GET
        Required Login: False

        Args:
            request -- The request made to the server by the client
            username -- The username provided to request the password reset for

        Possible Error Codes:
            101, 104, 107

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level.
    """
    # Ensure the API call is using GET method
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(101, AUTH_ERRORS))

    # Lookup the user with the provided username
    user_lookup = User.objects.filter(username=username)

    if len(user_lookup) == 0:
        return JsonResponse(Response.get_error_status(104, AUTH_ERRORS))

    user_account = user_lookup[0]
    student_account = Student.objects.get(user=user_account)

    # Check if the student already has a reset code
    if student_account.reset_password_code is not None:
        return JsonResponse(Response.get_error_status(107, AUTH_ERRORS))

    # Generate a reset code
    reset_code = generate_reset_code()
    student_account.reset_password_code = reset_code
    student_account.save()

    # Schedule the removal of the reset code after 1 hour
    expire_reset_code(str(student_account.id),
                      _schedule=timezone.now() + datetime.timedelta(hours=1))

    # Render the email template
    email_html = render_to_string(
        'reset_code_email.html', {
            'user': {
                'first_name': user_account.first_name,
                'last_name': user_account.last_name
            },
            'reset_code': reset_code
        })
    text_content = strip_tags(email_html)

    # Send the email to the user's email
    email = EmailMultiAlternatives('ICBA - Reset Code', text_content,
                                   'ICBA-NO-REPLY', [user_account.email])
    email.attach_alternative(email_html, 'text/html')
    email.send()

    return JsonResponse(Response.get_success_status())
def register(request):
    """
        Function Summary: This function is used to create a new User and Student.
        Path: '/api/auth/register'
        Request Type: POST
        Required Login: False

        Args:
            request -- The request made to the server from the client

        Required POST parameters:
            username -- The username to be created
            password -- The password to be created. This will be hashed by the server
            email -- The email associated with the new account
            first_name -- The first name on the new account
            last_name -- The last name on the new account

        Possible Error Codes:
            101, 103, 106

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level.
    """
    # Make sure the request is using POST to pass data
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(101, AUTH_ERRORS))

    # Make sure that the POST data contains a username, password, email, first name, and last name
    if 'username' not in request.POST or 'password' not in request.POST or 'email' not in request.POST \
            or 'first_name' not in request.POST or 'last_name' not in request.POST:
        return JsonResponse(Response.get_error_status(103, AUTH_ERRORS))

    # Make sure the username isn't already taken
    user_lookup = User.objects.filter(username=request.POST['username'])

    if len(user_lookup) is not 0:
        return JsonResponse(Response.get_error_status(106, AUTH_ERRORS))

    # Validate that the password provided is strong enough
    try:
        validate_password(request.POST['password'])
    except ValidationError:
        return JsonResponse(Response.get_error_status(111, AUTH_ERRORS))

    # Create new user
    new_user = User.objects.create(username=request.POST['username'],
                                   email=request.POST['email'],
                                   first_name=request.POST['first_name'],
                                   last_name=request.POST['last_name'])

    new_user.set_password(request.POST['password'])
    new_user.save()

    # Create student object
    new_student = Student.objects.create(user=new_user)
    new_student.save()

    # Render the email template
    email_html = render_to_string(
        'welcome_email.html', {
            'user': {
                'first_name': new_user.first_name,
                'last_name': new_user.last_name
            }
        })
    text_content = strip_tags(email_html)

    # Send the email to the user's email
    email = EmailMultiAlternatives('Welcome to ICBA!', text_content,
                                   'ICBA-NO-REPLY', [new_user.email])
    email.attach_alternative(email_html, 'text/html')
    email.send()

    return JsonResponse(Response.get_success_status())
def demographic_update(request):
    """
        Function Summary: This function is used to update an existing demographic object. A success status will be returned with the updated Demographic object's information.
        Path: 'api/demographic/update'
        Request Type: POST
        Required Login: True

        Args:
            request -- the HTTP request made to the url

        Required GET Parameters:
            session_id -- The Session ID of the logged in user

        Optional Request Parameters:
            age -- The age of the user
            gender -- The gender of the user
            grade_year -- The grade year of the user
            ethnicity -- The ethnicity of the user
            race -- The race of the user
            major -- The major of the user

        Possible Error Codes:
            200, 201, 205, 206

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using POST
    if request.method != "POST":
        return JsonResponse(Response.get_error_status(201, DEMO_ERRORS))

    # Ensure the user is logged in. Return an error status if the user is not logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(200, DEMO_ERRORS))

    # Get the logged in Student object
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Try to get the Demographic object registered to the student. Return an error status if the user has not created a Demographic object yet.
    try:
        demo_instance = Demographic.objects.get(student=current_student)

    except Demographic.DoesNotExist:
        return JsonResponse(Response.get_error_status(206, DEMO_ERRORS))

    # If the age is provided in POST data, update the age in the Demographic object.
    if 'age' in request.POST:
        demo_instance.age = request.POST['age']

    # If the gender is provided in POST data, update the gender in the Demographic object.
    if 'gender' in request.POST:
        try:
            demo_instance.gender = GenderLookup.objects.get(
                id=request.POST['gender'])
        except GenderLookup.DoesNotExist:
            return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    # If the grade_year is provided in POST data, update the grade_year in the Demographic object.
    if 'grade_year' in request.POST:
        try:
            demo_instance.grade_year = GradeYearLookup.objects.get(
                id=request.POST['grade_year'])
        except GradeYearLookup.DoesNotExist:
            return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    # If the ethnicity is provided in POST data, update the ethnicity in the Demographic object.
    if 'ethnicity' in request.POST:
        try:
            demo_instance.ethnicity = EthnicityLookup.objects.get(
                id=request.POST['ethnicity'])
        except EthnicityLookup.DoesNotExist:
            return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    # If the race is provided in POST data, update the race in the Demographic object.
    if 'race' in request.POST:
        try:
            demo_instance.race = RaceLookup.objects.get(
                id=request.POST['race'])
        except RaceLookup.DoesNotExist:
            return JsonResponse(Response.get_error_status(205, DEMO_ERRORS))

    # If the major is provided in POST data, update the major in the Demographic object.
    if 'major' in request.POST:
        demo_instance.major = request.POST['major']

    # Save the updated Demographic object and return a success status.
    demo_instance.save()
    success_status = Response.get_success_status()
    success_status['data'] = demo_instance.to_dict()
    return JsonResponse(Response.get_success_status())
def position_summary(request):
    """
        Function Summary: This function is used to get the Position history of a User.
        Path: 'api/position/summary'
        Request Type: GET
        Required Login: True

        Args:
            request -- The request made to the server by the client

        Required GET Parameters:
            session_id -- The Session ID of the logged in user
            start_time -- The start time to search for
            end_time -- The end time to search for

        Possible Error Codes:
            300, 301, 302, 305, 306

        Return:
            Type: JSON
            Data: A JSON object with a 'status' at the top level. Will contain a "data" JSON object.
    """
    # Ensure the API call is using a GET request.
    if request.method != "GET":
        return JsonResponse(Response.get_error_status(301, POSITION_ERRORS))

    # Ensure the user is logged in.
    if not get_user_logged_in(request):
        return JsonResponse(Response.get_error_status(300, POSITION_ERRORS))

    # Get the currently logged in Student object.
    current_student = Student.objects.get(
        user=get_user_by_session(request.GET['session_id']))

    # Ensure that 'start_time' and 'end_time' are in the GET parameters.
    if 'start_time' not in request.GET or 'end_time' not in request.GET:
        return JsonResponse(Response.get_error_status(302, POSITION_ERRORS))

    # Try to parse the start and end times into DateTime objects. Return error status if the string is invalid.
    try:
        start_datetime = datetime.datetime.strptime(request.GET['start_time'],
                                                    '%Y-%m-%d %H:%M:%S')
        end_datetime = datetime.datetime.strptime(request.GET['end_time'],
                                                  '%Y-%m-%d %H:%M:%S')

    except ValueError:
        return JsonResponse(Response.get_error_status(305, POSITION_ERRORS))

    if start_datetime is None or end_datetime is None:
        return JsonResponse(Response.get_error_status(306, POSITION_ERRORS))

    # Return a success status with the position summary information.
    success_object = Response.get_success_status()
    success_object['data'] = [
        x.to_dict() for x in Position.objects.filter(
            student=current_student,
            timestamp__gt=start_datetime,
            timestamp__lt=end_datetime).order_by('timestamp')
    ]

    return JsonResponse(success_object)