Source code for matching.views

import json

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods

from users.models import User

from . import services
from .serializers import serialize_mentee, serialize_mentor
from .services import MatchingError
from .utils import (recalculate_scores_for_mentee, require_auth,
                    require_confirmation, require_not_deleted, require_role)


[docs] @csrf_exempt @require_http_methods(["GET"]) def get_mentor_matches(request): """ Return a ranked list of compatible mentors for the authenticated mentee. Delegates to :func:`services.get_ranked_matches` to compute and sort mentor candidates based on pre-calculated compatibility scores. :param request: The incoming HTTP GET request. Must belong to an authenticated, non-deleted user with the ``MENTEE`` role. :type request: django.http.HttpRequest :returns: - HTTP 200 with ``matches`` (list of serialized mentor objects) and ``total`` (count) on success. - HTTP 401 if the request is unauthenticated. - HTTP 403 if the user does not have the ``MENTEE`` role. - HTTP 404 if the account is pending deletion. :rtype: django.http.JsonResponse **Success response** (HTTP 200):: { "matches": [ { ...mentor fields... }, ... ], "total": 5 } """ if err := require_auth(request): return err if err := require_role(request, 'MENTEE'): return err if err := require_not_deleted(request): return err matches = services.get_ranked_matches(request.user) return JsonResponse({'matches': matches, 'total': len(matches)})
[docs] @csrf_exempt @require_http_methods(["GET"]) def rematch(request): """ Re-fetch the ranked mentor list for the authenticated mentee. Thin wrapper around :func:`get_mentor_matches` kept as a distinct endpoint for semantic clarity in the URL configuration (e.g. ``/matching/rematch/`` vs ``/matching/matches/``). :param request: The incoming HTTP GET request. Must belong to an authenticated, non-deleted user with the ``MENTEE`` role. :type request: django.http.HttpRequest :returns: Identical response to :func:`get_mentor_matches`. :rtype: django.http.JsonResponse """ return get_mentor_matches(request)
[docs] @csrf_exempt @require_http_methods(["POST"]) def select_mentor(request): """ Match the authenticated mentee with a chosen mentor. Delegates to :func:`services.select_mentor`, which validates capacity, blacklist rules, and existing match state before creating the pairing. :param request: The incoming HTTP POST request containing a JSON body with ``mentor_email``. Must belong to an authenticated, non-deleted user with the ``MENTEE`` role. :type request: django.http.HttpRequest :returns: - HTTP 200 with ``success``, ``message``, and serialized ``mentor`` data on success. - HTTP 400 if the request body is invalid JSON or ``mentor_email`` is absent. - HTTP 401 if the request is unauthenticated. - HTTP 403 if the user does not have the ``MENTEE`` role. - HTTP 404 if the account is pending deletion. - Appropriate error status from :exc:`MatchingError` if the pairing is not permitted. :rtype: django.http.JsonResponse :raises MatchingError: Caught internally; returns the error message and its associated HTTP status code. **Request body** (JSON):: { "mentor_email": "mentor@ucla.edu" } **Success response** (HTTP 200):: { "success": true, "message": "Successfully matched with mentor", "mentor": { ...mentor fields... } } """ if err := require_auth(request): return err if err := require_role(request, 'MENTEE'): return err if err := require_not_deleted(request): return err try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({'error': 'Invalid JSON format'}, status=400) mentor_email = data.get('mentor_email') if not mentor_email: return JsonResponse({'error': 'Mentor email is required'}, status=400) try: mentor = services.select_mentor(request.user, mentor_email) except MatchingError as exc: return JsonResponse({'error': str(exc)}, status=exc.status) return JsonResponse({ 'success': True, 'message': 'Successfully matched with mentor', 'mentor': serialize_mentor(mentor), }, status=200)
[docs] @csrf_exempt @require_http_methods(["GET"]) def get_my_mentor(request): """ Return the mentor currently matched to the authenticated mentee. Reads ``matchedMentorEmail`` from the session user and fetches the corresponding ``User`` record via the internal service helper. :param request: The incoming HTTP GET request. Must belong to an authenticated, non-deleted user with the ``MENTEE`` role who is currently matched. :type request: django.http.HttpRequest :returns: - HTTP 200 with a serialized ``mentor`` object on success. - HTTP 400 if ``isMatched`` is ``False``. - HTTP 401 if the request is unauthenticated. - HTTP 403 if the user does not have the ``MENTEE`` role. - HTTP 404 if ``matchedMentorEmail`` is absent or the mentor record no longer exists. :rtype: django.http.JsonResponse :raises MatchingError: Caught internally; returns the error message and its associated HTTP status code. **Success response** (HTTP 200):: { "mentor": { ...mentor fields... } } """ if err := require_auth(request): return err if err := require_role(request, 'MENTEE'): return err if err := require_not_deleted(request): return err if not request.user.isMatched: return JsonResponse({'error': 'You are not currently matched with a mentor'}, status=400) if not request.user.matchedMentorEmail: return JsonResponse({'error': 'No matched mentor email on record'}, status=404) try: mentor = services._get_user_or_raise(request.user.matchedMentorEmail, 'Mentor') except MatchingError as exc: return JsonResponse({'error': str(exc)}, status=exc.status) return JsonResponse({'mentor': serialize_mentor(mentor)}, status=200)
[docs] @csrf_exempt @require_http_methods(["GET"]) def get_my_mentees(request): """ Return all mentees currently assigned to the authenticated mentor. Iterates over ``current_mentees`` on the session user, fetches each ``User`` record, and silently skips any email addresses that no longer correspond to an existing account. :param request: The incoming HTTP GET request. Must belong to an authenticated, non-deleted user with the ``MENTOR`` role. :type request: django.http.HttpRequest :returns: - HTTP 200 with ``mentees`` (list of serialized mentee objects) and ``total`` (count) on success. The list may be empty if the mentor has no current mentees. - HTTP 401 if the request is unauthenticated. - HTTP 403 if the user does not have the ``MENTOR`` role. - HTTP 404 if the account is pending deletion. :rtype: django.http.JsonResponse **Success response** (HTTP 200):: { "mentees": [ { ...mentee fields... }, ... ], "total": 2 } """ if err := require_auth(request): return err if err := require_role(request, 'MENTOR'): return err if err := require_not_deleted(request): return err mentees = [] for mentee_email in (request.user.current_mentees or []): try: mentee = User.objects.get(email=mentee_email) mentees.append(serialize_mentee(mentee)) except User.DoesNotExist: continue return JsonResponse({'mentees': mentees, 'total': len(mentees)})
[docs] @csrf_exempt @require_http_methods(["POST"]) def unmatch(request): """ Remove an existing match between the authenticated user and a counterpart. Behaviour differs by role: - **MENTEE** — requires ``mentor_email`` in the body; delegates to :func:`services.mentee_unmatch`. - **MENTOR** — requires ``mentee_email`` in the body; delegates to :func:`services.mentor_unmatch`. A confirmation string must be present in the request body (validated by :func:`require_confirmation`) before any unmatching logic is executed. :param request: The incoming HTTP POST request containing a JSON body with a confirmation field and either ``mentor_email`` or ``mentee_email`` depending on the caller's role. Must belong to an authenticated, non-deleted user. :type request: django.http.HttpRequest :returns: - HTTP 200 with ``'Successfully unmatched'`` on success. - HTTP 400 if the request body is invalid JSON, the required email field is absent, or the confirmation check fails. - HTTP 401 if the request is unauthenticated. - HTTP 403 if the user's role is neither ``MENTEE`` nor ``MENTOR``. - HTTP 404 if the account is pending deletion. - Appropriate error status from :exc:`MatchingError` if the unmatch operation is not permitted. :rtype: django.http.JsonResponse :raises MatchingError: Caught internally; returns the error message and its associated HTTP status code. **Request body — mentee caller** (JSON):: { "confirmation": "unmatch", "mentor_email": "mentor@ucla.edu" } **Request body — mentor caller** (JSON):: { "confirmation": "unmatch", "mentee_email": "mentee@g.ucla.edu" } """ if err := require_auth(request): return err if err := require_not_deleted(request): return err try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({'error': 'Invalid JSON format'}, status=400) if err := require_confirmation(data): return err try: if request.user.role == 'MENTEE': mentor_email = data.get('mentor_email') if not mentor_email: return JsonResponse({'error': 'Mentor email required'}, status=400) services.mentee_unmatch(request.user, mentor_email) elif request.user.role == 'MENTOR': mentee_email = data.get('mentee_email') if not mentee_email: return JsonResponse({'error': 'Mentee email required'}, status=400) services.mentor_unmatch(request.user, mentee_email) else: return JsonResponse({'error': 'Invalid role'}, status=403) except MatchingError as exc: return JsonResponse({'error': str(exc)}, status=exc.status) return JsonResponse({'message': 'Successfully unmatched'}, status=200)
[docs] @csrf_exempt @require_http_methods(["PUT"]) def update_profile(request): """ Update the authenticated user's profile and refresh match scores if applicable. Applies the supplied field updates via :meth:`User.update_profile`. If the caller is a ``MENTEE``, compatibility scores against all available mentors are immediately recalculated via :func:`recalculate_scores_for_mentee` so that the match dashboard reflects the latest profile state. :param request: The incoming HTTP PUT request containing a JSON body with one or more profile field key-value pairs. Must belong to an authenticated user. :type request: django.http.HttpRequest :returns: - HTTP 200 with a success message on update. - HTTP 400 if the request body is not valid JSON. - HTTP 401 if the request is unauthenticated. :rtype: django.http.JsonResponse **Request body** (JSON):: { "firstName": "Jane", "year": 3, "hobbies": "hiking, chess" } """ if err := require_auth(request): return err try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({'error': 'Invalid JSON'}, status=400) request.user.update_profile(**data) if request.user.role == request.user.Role.MENTEE: recalculate_scores_for_mentee(request.user) return JsonResponse({'message': 'Profile updated successfully'})