Пример #1
0
    def get_ordered_transcript(profile_uc_dict_list):
        transcript_by_term = {}

        for uc_dict in profile_uc_dict_list:
            (transcript_by_term.setdefault(uc_dict['term_id'],
                                           []).append(uc_dict))

        ordered_transcript = []
        for term_id, uc_dicts in sorted(transcript_by_term.items(),
                                        reverse=True):
            curr_term = m.Term(id=term_id)
            term_dict = {
                'id':
                curr_term.id,
                'name':
                curr_term.name,
                'program_year_id':
                uc_dicts[0].get('program_year_id'),
                'course_ids': [
                    uc_dict['course_id'] for uc_dict in uc_dicts
                    if uc_dict['course_id'] in course_dicts
                ],
            }
            ordered_transcript.append(term_dict)

        return ordered_transcript, transcript_by_term
Пример #2
0
def render_profile_page(profile_user_id, current_user=None):
    # TODO(mack): for dict maps, use .update() rather than overwriting to
    # avoid subtle overwrites by data that has fields filled out

    LAST_TERM_ID = util.get_current_term_id()

    # PART ONE - VALIDATION

    current_user = current_user or view_helpers.get_current_user()

    try:
        if profile_user_id:
            profile_user_id = bson.ObjectId(profile_user_id)
    except:
        logging.warn('Invalid profile_user_id (%s)' % profile_user_id)
        return view_helpers.redirect_to_profile(current_user)

    if not profile_user_id:
        return view_helpers.redirect_to_profile(current_user)

    if profile_user_id == current_user.id:
        own_profile = True
        profile_user = current_user
    else:
        own_profile = False

        # Allow only friends to view profile
        if not (profile_user_id in current_user.friend_ids or
                (current_user.is_admin and flask.request.values.get('admin'))):
            logging.info("User (%s) tried to access non-friend profile (%s)" %
                         (current_user.id, profile_user_id))
            return view_helpers.redirect_to_profile(current_user)

        profile_user = m.User.objects.with_id(profile_user_id)
        # Technically we don't need this check due to above (under normal
        # operation). Though have this anyway as a failsafe
        if profile_user is None:
            logging.warn('profile_user is None')
            return view_helpers.redirect_to_profile(current_user)

    if own_profile:
        profile_user_secret_id = profile_user.get_secret_id()
    else:
        profile_user_secret_id = None

    show_import_schedule = False
    # Redirect the user appropriately... to /onboarding if they have no course
    # history, and to wherever they logged in from if they just logged in
    # TODO(david): Should have frontend decide whether to take us to /profile
    #     or /onboarding and not redirect in one of these two places
    if own_profile:
        redirect_url = flask.request.values.get('next')

        show_onboarding = False
        if not current_user.has_course_history:
            if not current_user.last_show_onboarding:
                show_onboarding = True
            else:
                time_delta = datetime.now() - current_user.last_show_onboarding
                # If they haven't imported any courses yet and the last time
                # the user was on the onboarding page is more than 5 days ago,
                # show the onboarding page again
                if time_delta.days > RESHOW_ONBOARDING_DELAY_DAYS:
                    show_onboarding = True

        # See https://uwflow.uservoice.com/admin/tickets/62
        if profile_user_id == '50b8ce2cd89d62310645ca78':
            show_onboarding = False

        if show_onboarding:
            onboarding_url = '/onboarding'
            if flask.request.query_string:
                onboarding_url = '%s?%s' % (onboarding_url,
                                            flask.request.query_string)
            return flask.make_response(flask.redirect(onboarding_url))
        else:
            redirect_url = flask.request.values.get('next')
            if redirect_url:
                return flask.make_response(flask.redirect(redirect_url))

        # Show the import schedule view if it's been long enough
        if not current_user.has_schedule:
            if current_user.last_show_import_schedule:
                time_delta = (datetime.now() -
                              current_user.last_show_import_schedule)
                # User didn't import schedule yet, reshow every few days
                if time_delta.days > RESHOW_SCHEDULE_DELAY_DAYS:
                    show_import_schedule = True
            else:
                show_import_schedule = True

            if show_import_schedule:
                # TODO(Sandy): Do this on modal dismiss instead
                current_user.last_show_import_schedule = datetime.now()
                current_user.save()

    # PART TWO - DATA FETCHING

    # Get the mutual course ids of friends of profile user
    mutual_course_ids_by_friend = {}
    if own_profile:
        mutual_course_ids_by_friend = profile_user.get_mutual_course_ids(
            view_helpers.get_redis_instance())

    def get_friend_course_ids_in_term(friend_ids, term_id):
        user_courses = m.UserCourse.objects(term_id=term_id,
                                            user_id__in=friend_ids).only(
                                                'user_id', 'course_id')

        last_term_course_ids_by_friend = {}
        for uc in user_courses:
            last_term_course_ids_by_friend.setdefault(uc.user_id,
                                                      []).append(uc.course_id)
        return last_term_course_ids_by_friend

    # Get the course ids of last term courses of friends of profile user
    last_term_course_ids_by_friend = get_friend_course_ids_in_term(
        profile_user.friend_ids, LAST_TERM_ID)

    # Get the course ids of courses profile user has taken
    profile_course_ids = set(profile_user.course_ids)

    # Fetch courses for transcript, which need more detailed information
    # than other courses (such as mutual and last term courses for friends)
    transcript_courses = list(m.Course.objects(id__in=profile_course_ids))

    # Fetch remainining courses that need less data. This will be mutual
    # and last term courses for profile user's friends
    friend_course_ids = set()
    friend_courses = []
    if own_profile:
        for course_ids in mutual_course_ids_by_friend.values():
            friend_course_ids = friend_course_ids.union(course_ids)
        for course_ids in last_term_course_ids_by_friend.values():
            friend_course_ids = friend_course_ids.union(course_ids)
        friend_course_ids = friend_course_ids - profile_course_ids
        friend_courses = m.Course.objects(id__in=friend_course_ids).only(
            'id', 'name')

    # Fetch simplified information for friends of profile user
    # (for friend sidebar)
    friends = profile_user.get_friends()

    # Fetch all professors for all courses
    professor_objs = m.Professor.get_reduced_professors_for_courses(
        transcript_courses)

    # PART THREE - TRANSFORM DATA TO DICTS

    # Convert professors to dicts
    professor_dicts = {}
    for professor_obj in professor_objs:
        professor_dicts[professor_obj['id']] = professor_obj

    # Convert courses to dicts
    course_dict_list, user_course_dict_list, user_course_list = (
        m.Course.get_course_and_user_course_dicts(transcript_courses,
                                                  current_user,
                                                  include_friends=own_profile))
    course_dicts = {}
    for course_dict in course_dict_list:
        course_dicts[course_dict['id']] = course_dict
    user_course_dicts = {}
    for user_course_dict in user_course_dict_list:
        user_course_dicts[user_course_dict['id']] = user_course_dict

    profile_uc_dict_list = []

    # We only need to fetch usercourses for profile user if it is not the
    # current user since m.Course.get_course_and_user_course_dicts() will
    # have already fetched usercourses for the current user
    if not own_profile:
        # Get the user courses of profile user
        profile_uc_dict_list = [
            uc.to_dict() for uc in profile_user.get_user_courses()
        ]
        # Get a mapping from course id to user_course for profile user
        profile_user_course_by_course = {}
        for uc_dict in profile_uc_dict_list:
            profile_user_course_by_course[uc_dict['course_id']] = uc_dict

    # Fill in with information about profile user
    for course in transcript_courses:
        course_dict = course_dicts[course.id]

        if not own_profile:
            # This has already been done for current user
            profile_uc_dict = profile_user_course_by_course.get(course.id)
            profile_user_course_id = profile_uc_dict['id']
            user_course_dicts[profile_user_course_id] = profile_uc_dict

            # Since we only fetched the user courses of the logged in user in
            # m.Course.get_course_and_user_course_dicts() above, gotta also
            # add the user courses of the profile user here
            user_course_dict_list.append(profile_uc_dict)
        else:
            profile_user_course_id = course_dict.get('user_course_id')
            if profile_user_course_id:
                profile_uc_dict_list.append(
                    user_course_dicts[profile_user_course_id])

        course_dict['profile_user_course_id'] = profile_user_course_id

    for course in friend_courses:
        course_dicts[course.id] = course.to_dict()

    def filter_course_ids(course_ids):
        return [
            course_id for course_id in course_ids if course_id in course_dicts
        ]

    # Convert friend users to dicts
    user_dicts = {}
    # TODO(mack): should really be named current_term
    last_term = m.Term(id=LAST_TERM_ID)
    for friend in friends:
        user_dict = friend.to_dict(extended=False)

        if own_profile:
            user_dict.update({
                'last_term_name':
                last_term.name,
                'last_term_course_ids':
                filter_course_ids(
                    last_term_course_ids_by_friend.get(friend.id, [])),
                'mutual_course_ids':
                filter_course_ids(
                    mutual_course_ids_by_friend.get(friend.id, [])),
            })

        user_dicts[friend.id] = user_dict

    # Convert profile user to dict
    # TODO(mack): This must be after friend user dicts since it can override
    # data in it. Remove this restriction
    profile_dict = profile_user.to_dict(include_course_ids=True)
    profile_dict.update({
        'last_program_year_id':
        profile_user.get_latest_program_year_id(),
    })
    user_dicts.setdefault(profile_user.id, {}).update(profile_dict)

    # Convert current user to dict
    # TODO(mack): This must be after friend user dicts since it can override
    # data in it. Remove this restriction
    if not own_profile:
        user_dicts.setdefault(current_user.id, {}).update(
            current_user.to_dict(include_course_ids=True))

    def get_ordered_transcript(profile_uc_dict_list):
        transcript_by_term = {}

        for uc_dict in profile_uc_dict_list:
            (transcript_by_term.setdefault(uc_dict['term_id'],
                                           []).append(uc_dict))

        ordered_transcript = []
        for term_id, uc_dicts in sorted(transcript_by_term.items(),
                                        reverse=True):
            curr_term = m.Term(id=term_id)
            term_dict = {
                'id':
                curr_term.id,
                'name':
                curr_term.name,
                'program_year_id':
                uc_dicts[0].get('program_year_id'),
                'course_ids': [
                    uc_dict['course_id'] for uc_dict in uc_dicts
                    if uc_dict['course_id'] in course_dicts
                ],
            }
            ordered_transcript.append(term_dict)

        return ordered_transcript, transcript_by_term

    # Store courses by term as transcript using the current user's friends
    ordered_transcript, transcript_by_term = get_ordered_transcript(
        profile_uc_dict_list)

    # Fetch exam schedules and schedule items
    current_term_id = util.get_current_term_id()

    current_term_courses = transcript_by_term.get(current_term_id, [])
    current_course_ids = [c['course_id'] for c in current_term_courses]

    exam_objs = profile_user.get_current_term_exams(current_course_ids)
    exam_dicts = [e.to_dict() for e in exam_objs]
    exam_updated_date = None
    if exam_objs:
        exam_updated_date = exam_objs[0].id.generation_time

    # Set the course to prompt the user to review if it's time
    course_id_to_review = None
    if own_profile and profile_user.should_prompt_review():
        profile_user_courses = filter(lambda uc: uc.user_id == profile_user.id,
                                      user_course_list)
        uc_to_review = m.UserCourse.select_course_to_review(
            profile_user_courses)
        course_id_to_review = uc_to_review and uc_to_review.course_id

        if uc_to_review:
            uc_to_review.select_for_review(current_user)

    # NOTE: This implictly requires that the courses on the schedule are on the
    # transcript, since these course objects are needed by the schedule  on the
    # frontend. This should be the case since when we add a schedule item, a
    # corresponding item is added to the transcript.
    schedule_item_dicts = profile_user.get_schedule_item_dicts(exam_objs)
    failed_schedule_item_dicts = profile_user.get_failed_schedule_item_dicts()

    referrals = m.User.objects(referrer_id=current_user.id)
    referral_objs = [referral.to_dict() for referral in referrals]

    rmclogger.log_event(
        rmclogger.LOG_CATEGORY_IMPRESSION,
        rmclogger.LOG_EVENT_PROFILE,
        {
            'current_user': current_user.id,
            'profile_user': profile_user.id,
        },
    )

    schedule_screenshot.update_screenshot_async(profile_user)

    scholarships_dict = []

    if profile_user.id == current_user.id:
        scholarships = m.Scholarship.objects()
        # Filter scholarships based on program
        closed_scholarship_ids_set = set(profile_user.closed_scholarship_ids)
        scholarships = [
            s for s in scholarships
            if profile_user.short_program_name in s.programs
            and s.id not in closed_scholarship_ids_set
        ]
        scholarships_dict = [s.to_dict() for s in scholarships]

    recommendation_dict = []
    recommended_course_ids = []
    if profile_user.id == current_user.id:
        recommended_course_ids = current_user.recommended_courses
        recommendation_dict = [
            m.Course.objects(id=course_id).first().to_dict()
            for course_id in recommended_course_ids
        ]

    return flask.render_template(
        'profile_page.html',
        page_script='profile_page.js',
        transcript_obj=ordered_transcript,
        user_objs=user_dicts.values(),
        referral_objs=referral_objs,
        user_course_objs=user_course_dicts.values(),
        course_objs=course_dicts.values(),
        professor_objs=professor_dicts.values(),
        # TODO(mack): currently needed by jinja to do server-side rendering
        # figure out a cleaner way to do this w/o passing another param
        profile_obj=profile_dict,
        profile_user_id=profile_user.id,
        current_user_id=current_user.id,
        profile_user_secret_id=profile_user_secret_id,
        own_profile=own_profile,
        has_courses=profile_user.has_course_history,
        exam_objs=exam_dicts,
        exam_updated_date=exam_updated_date,
        schedule_item_objs=schedule_item_dicts,
        failed_schedule_item_objs=failed_schedule_item_dicts,
        has_shortlisted=profile_user.has_shortlisted,
        show_import_schedule=show_import_schedule,
        show_import_schedule_button=own_profile
        and (not profile_user.has_schedule),
        course_id_to_review=course_id_to_review,
        scholarship_objs=scholarships_dict,
        recommended_objs=recommendation_dict,
    )