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'})