From c8d053591aa1e1463d8a6cee7c29ed998574c87c Mon Sep 17 00:00:00 2001 From: Prathmesh-28 Date: Tue, 17 Dec 2024 18:23:20 +0530 Subject: [PATCH] Missing Files in Accounts App Causing Errors (#22) --- socbackend/accounts/__init__.py | 0 socbackend/accounts/admin.py | 5 + socbackend/accounts/apps.py | 6 + socbackend/accounts/custom_auth.py | 52 +++++ socbackend/accounts/helpers.py | 79 +++++++ .../accounts/migrations/0001_initial.py | 32 +++ .../0002_alter_userprofile_department.py | 18 ++ .../0003_alter_userprofile_department.py | 18 ++ socbackend/accounts/migrations/__init__.py | 0 socbackend/accounts/models.py | 49 ++++ socbackend/accounts/options.py | 37 +++ socbackend/accounts/optionsold.py | 32 +++ socbackend/accounts/permissions.py | 13 ++ socbackend/accounts/serializers.py | 87 +++++++ socbackend/accounts/tests.py | 3 + socbackend/accounts/urls.py | 20 ++ socbackend/accounts/views.py | 221 ++++++++++++++++++ 17 files changed, 672 insertions(+) create mode 100644 socbackend/accounts/__init__.py create mode 100644 socbackend/accounts/admin.py create mode 100644 socbackend/accounts/apps.py create mode 100644 socbackend/accounts/custom_auth.py create mode 100644 socbackend/accounts/helpers.py create mode 100644 socbackend/accounts/migrations/0001_initial.py create mode 100644 socbackend/accounts/migrations/0002_alter_userprofile_department.py create mode 100644 socbackend/accounts/migrations/0003_alter_userprofile_department.py create mode 100644 socbackend/accounts/migrations/__init__.py create mode 100644 socbackend/accounts/models.py create mode 100644 socbackend/accounts/options.py create mode 100644 socbackend/accounts/optionsold.py create mode 100644 socbackend/accounts/permissions.py create mode 100644 socbackend/accounts/serializers.py create mode 100644 socbackend/accounts/tests.py create mode 100644 socbackend/accounts/urls.py create mode 100644 socbackend/accounts/views.py diff --git a/socbackend/accounts/__init__.py b/socbackend/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/socbackend/accounts/admin.py b/socbackend/accounts/admin.py new file mode 100644 index 0000000..9ef9e3d --- /dev/null +++ b/socbackend/accounts/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import UserProfile + +# Register your models here. +admin.site.register(UserProfile) diff --git a/socbackend/accounts/apps.py b/socbackend/accounts/apps.py new file mode 100644 index 0000000..3e3c765 --- /dev/null +++ b/socbackend/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounts' diff --git a/socbackend/accounts/custom_auth.py b/socbackend/accounts/custom_auth.py new file mode 100644 index 0000000..d1e468b --- /dev/null +++ b/socbackend/accounts/custom_auth.py @@ -0,0 +1,52 @@ +from rest_framework_simplejwt.authentication import JWTAuthentication +import logging +logger = logging.getLogger(__name__) + +import jwt +from django.conf import settings +from rest_framework.exceptions import AuthenticationFailed + +logger = logging.getLogger(__name__) + +class CookieJWTAuthentication(JWTAuthentication): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user_model = get_user_model() + + def authenticate(self, request): + logger.debug("Starting authentication process") + + # Check for JWT in cookies + token = request.COOKIES.get('jwt') + logger.debug(f"Token from cookies: {token}") + + # If not found in cookies, check the Authorization header + if not token: + auth_header = request.headers.get('Authorization') + logger.debug(f"Authorization header: {auth_header}") + if auth_header and auth_header.startswith('Bearer '): + token = auth_header.split(' ')[1] + logger.debug(f"Token from Authorization header: {token}") + + if not token: + logger.debug("No token found") + return None + + try: + payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=['HS256']) + logger.debug(f"Payload: {payload}") + except jwt.ExpiredSignatureError: + logger.error("Token has expired") + raise AuthenticationFailed('Token has expired') + except jwt.InvalidTokenError: + logger.error("Invalid token") + raise AuthenticationFailed('Invalid token') + + try: + user = self.user_model.objects.get(id=payload['user_id']) + logger.debug(f"Authenticated user: {user}") + except self.user_model.DoesNotExist: + logger.error("User not found") + raise AuthenticationFailed('User not found') + + return (user, None) \ No newline at end of file diff --git a/socbackend/accounts/helpers.py b/socbackend/accounts/helpers.py new file mode 100644 index 0000000..74fa114 --- /dev/null +++ b/socbackend/accounts/helpers.py @@ -0,0 +1,79 @@ +import requests +from django.conf import settings +from rest_framework.response import Response +from rest_framework.exceptions import APIException +from rest_framework import status + +IITB_SSO = settings.IITB_SSO + + +class SSOError(APIException): + """Exception for SSO errors.""" + + status_code = status.HTTP_400_BAD_REQUEST + default_detail = "Something went wrong with the SSO" + default_code = "sso_error" + + +def fetch_from_sso(auth_code, redir, request): + """Perform login with code and redir.""" + # Get our access token + post_data = { + "code": auth_code, + "redirect_uri": redir, + "grant_type": "authorization_code", + } + post_data = ( + "code=" + + auth_code + + "&redirect_uri=" + + redir + + "&grant_type=authorization_code" + ) + print(IITB_SSO) + response = requests.post( + IITB_SSO["TOKEN_URL"], + data=post_data, + headers={ + "Authorization": "Basic " + IITB_SSO["CLIENT_SECRET_BASE64"], + "Content-Type": "application/x-www-form-urlencoded", + }, + verify=not settings.SSO_BAD_CERT, + allow_redirects=False, + timeout=10, + ) + response_json = response.json() + + # Check that we have the access token + if "access_token" not in response_json: + raise SSOError( + response_json or "SSO server did not validate request, try again", + ) + + # Get the user's profile + profile_response = requests.get( + IITB_SSO["PROFILE_URL"], + headers={ + "Authorization": "Bearer " + response_json["access_token"], + }, + verify=not settings.SSO_BAD_CERT, + timeout=10, + ) + profile_json = profile_response.json() + + # Check if we got at least the user's SSO id + if "id" not in profile_json: + raise SSOError( + "SSO server did not send requested info, try again", + ) + + # Check that we have basic details like name and roll no. + required_fields = ["first_name", "roll_number", "username"] + if not all( + [((field in profile_json) and profile_json[field]) for field in required_fields] + ): + raise SSOError( + "All required fields not present", + ) + + return profile_json diff --git a/socbackend/accounts/migrations/0001_initial.py b/socbackend/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..e7d9553 --- /dev/null +++ b/socbackend/accounts/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.6 on 2024-04-20 08:49 + +import accounts.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('name', models.CharField(blank=True, default='', max_length=100)), + ('profile_picture', models.ImageField(blank=True, default='', upload_to=accounts.models.upload_to_location)), + ('phone_number', models.CharField(max_length=15)), + ('roll_number', models.CharField(error_messages={'unique': 'A user with that roll number already exists.'}, help_text='Required. 20 characters or fewer.', max_length=20, unique=True, verbose_name='roll number')), + ('year', models.CharField(choices=[('First Year', 'First Year'), ('Second Year', 'Second Year'), ('Third Year', 'Third Year'), ('Fourth Year', 'Fourth Year'), ('Fifth Year', 'Fifth Year'), ('M.Tech', 'M.Tech'), ('Ph.D.', 'Ph.D.')], max_length=100)), + ('department', models.CharField(choices=[('Aerospace Engineering', 'Aerospace Engineering'), ('Biosciences and Bioengineering', 'Biosciences and Bioengineering'), ('Chemical Engineering', 'Chemical Engineering'), ('Civil Engineering', 'Civil Engineering'), ('Computer Science and Engineering', 'Computer Science and Engineering'), ('Earth Sciences', 'Earth Sciences'), ('Electrical Engineering', 'Electrical Engineering'), ('Energy Science and Engineering', 'Energy Science and Engineering'), ('Engineering Physics', 'Engineering Physics'), ('Humanities and Social Sciences', 'Humanities and Social Sciences'), ('Industrial Design Centre', 'Industrial Design Centre'), ('Mathematics', 'Mathematics'), ('Mechanical Engineering', 'Mechanical Engineering'), ('Metallurgical Engineering and Materials Science', 'Metallurgical Engineering and Materials Science'), ('Physics', 'Physics')], max_length=50)), + ('verified', models.BooleanField(default=False)), + ('verification_token', models.CharField(blank=True, default='', max_length=32)), + ], + ), + ] diff --git a/socbackend/accounts/migrations/0002_alter_userprofile_department.py b/socbackend/accounts/migrations/0002_alter_userprofile_department.py new file mode 100644 index 0000000..af87de0 --- /dev/null +++ b/socbackend/accounts/migrations/0002_alter_userprofile_department.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2024-05-08 23:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='department', + field=models.CharField(choices=[('Aerospace Engineering', 'Aerospace Engineering'), ('Biosciences and Bioengineering', 'Biosciences and Bioengineering'), ('Chemical Engineering', 'Chemical Engineering'), ('Chemistry', 'Chemistry'), ('Civil Engineering', 'Civil Engineering'), ('Computer Science and Engineering', 'Computer Science and Engineering'), ('Earth Sciences', 'Earth Sciences'), ('Electrical Engineering', 'Electrical Engineering'), ('Energy Science and Engineering', 'Energy Science and Engineering'), ('Engineering Physics', 'Engineering Physics'), ('Environmental Science and Engineering', 'Environmental Science and Engineering'), ('Humanities and Social Sciences', 'Humanities and Social Sciences'), ('Industrial Design Centre', 'Industrial Design Centre'), ('Mathematics', 'Mathematics'), ('Mechanical Engineering', 'Mechanical Engineering'), ('Metallurgical Engineering and Materials Science', 'Metallurgical Engineering and Materials Science'), ('Physics', 'Physics')], max_length=50), + ), + ] diff --git a/socbackend/accounts/migrations/0003_alter_userprofile_department.py b/socbackend/accounts/migrations/0003_alter_userprofile_department.py new file mode 100644 index 0000000..4871784 --- /dev/null +++ b/socbackend/accounts/migrations/0003_alter_userprofile_department.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-05-11 07:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_alter_userprofile_department'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='department', + field=models.CharField(choices=[('Aerospace Engineering', 'Aerospace Engineering'), ('Biosciences and Bioengineering', 'Biosciences and Bioengineering'), ('Chemical Engineering', 'Chemical Engineering'), ('Chemistry', 'Chemistry'), ('Civil Engineering', 'Civil Engineering'), ('Computer Science and Engineering', 'Computer Science and Engineering'), ('Earth Sciences', 'Earth Sciences'), ('Economics', 'Economics'), ('Electrical Engineering', 'Electrical Engineering'), ('Energy Science and Engineering', 'Energy Science and Engineering'), ('Engineering Physics', 'Engineering Physics'), ('Environmental Science and Engineering', 'Environmental Science and Engineering'), ('Humanities and Social Sciences', 'Humanities and Social Sciences'), ('Industrial Design Centre', 'Industrial Design Centre'), ('Mathematics', 'Mathematics'), ('Mechanical Engineering', 'Mechanical Engineering'), ('Metallurgical Engineering and Materials Science', 'Metallurgical Engineering and Materials Science'), ('Physics', 'Physics'), ('Other', 'Other')], max_length=50), + ), + ] diff --git a/socbackend/accounts/migrations/__init__.py b/socbackend/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/socbackend/accounts/models.py b/socbackend/accounts/models.py new file mode 100644 index 0000000..14877ce --- /dev/null +++ b/socbackend/accounts/models.py @@ -0,0 +1,49 @@ +from django.db import models +from django.contrib.auth.models import User +from .options import DepartmentChoices, YearChoices + +def upload_to_location(instance, filename): + return "profile_pictures/{filename}".format(filename=filename) + + + + + + +# Create your models here. +class UserProfile(models.Model): + user = models.OneToOneField( + User, + on_delete=models.CASCADE, + primary_key=True, + ) + name = models.CharField(max_length=100, blank=True, default="") + profile_picture = models.ImageField( + upload_to=upload_to_location, blank=True, default="" + ) + phone_number = models.CharField(max_length=15, blank=False, null=False) + roll_number = models.CharField( + "roll number", + max_length=20, + unique=True, + help_text="Required. 20 characters or fewer.", + error_messages={ + "unique": "A user with that roll number already exists.", + }, + ) + year = models.CharField(choices=YearChoices.choices, null=False, blank=False, max_length=100) + department = models.CharField( + max_length=50, choices=DepartmentChoices.choices, blank=False, null=False + ) + + verified = models.BooleanField(default=False) + + verification_token = models.CharField(max_length=32, blank=True, default="") + + + + + # Add more fields as required + + def __str__(self): + return f"{self.roll_number} - {self.user.username}" diff --git a/socbackend/accounts/options.py b/socbackend/accounts/options.py new file mode 100644 index 0000000..9000e08 --- /dev/null +++ b/socbackend/accounts/options.py @@ -0,0 +1,37 @@ +from django.db import models + + + +class DepartmentChoices(models.TextChoices): + AEROSPACE_ENGINEERING = "Aerospace Engineering", "Aerospace Engineering" + BIOSCIENCES_AND_BIOENGINEERING = "Biosciences and Bioengineering", "Biosciences and Bioengineering" + CHEMICAL_ENGINEERING = "Chemical Engineering", "Chemical Engineering" + CHEMISTRY = "Chemistry", "Chemistry" + CIVIL_ENGINEERING = "Civil Engineering", "Civil Engineering" + COMPUTER_SCIENCE_AND_ENGINEERING = "Computer Science and Engineering", "Computer Science and Engineering" + EARTH_SCIENCES = "Earth Sciences", "Earth Sciences" + ECONOMICS= "Economics", "Economics" + ELECTRICAL_ENGINEERING = "Electrical Engineering", "Electrical Engineering" + ENERGY_SCIENCE_AND_ENGINEERING = "Energy Science and Engineering", "Energy Science and Engineering" + ENGINEERING_PHYSICS = "Engineering Physics", "Engineering Physics" + ENVIRONMENTAL_SCIENCES= "Environmental Science and Engineering", "Environmental Science and Engineering" + HUMANITIES_AND_SOCIAL_SCIENCES = "Humanities and Social Sciences", "Humanities and Social Sciences" + INDUSTRIAL_DESIGN_CENTRE = "Industrial Design Centre", "Industrial Design Centre" + MATHEMATICS = "Mathematics", "Mathematics" + MECHANICAL_ENGINEERING = "Mechanical Engineering", "Mechanical Engineering" + METALLURGICAL_ENGINEERING_AND_MATERIALS_SCIENCE = "Metallurgical Engineering and Materials Science", "Metallurgical Engineering and Materials Science" + PHYSICS = "Physics", "Physics" + OTHER= "Other", "Other" + + + +from django.db import models + +class YearChoices(models.TextChoices): + First_Year = "First Year", "First Year" + Second_Year = "Second Year", "Second Year" + Third_Year = "Third Year", "Third Year" + Fourth_Year = "Fourth Year", "Fourth Year" + Fifth_Year = "Fifth Year", "Fifth Year" + M_Tech = "M.Tech", "M.Tech" + Ph_D = "Ph.D.", "Ph.D." \ No newline at end of file diff --git a/socbackend/accounts/optionsold.py b/socbackend/accounts/optionsold.py new file mode 100644 index 0000000..a36fe8d --- /dev/null +++ b/socbackend/accounts/optionsold.py @@ -0,0 +1,32 @@ +from django.db import models + + + +class DepartmentChoices(models.TextChoices): + AEROSPACE_ENGINEERING = "Aerospace Engineering", "Aerospace Engineering" + BIOSCIENCES_AND_BIOENGINEERING = "Biosciences and Bioengineering", "Biosciences and Bioengineering" + CHEMICAL_ENGINEERING = "Chemical Engineering", "Chemical Engineering" + CIVIL_ENGINEERING = "Civil Engineering", "Civil Engineering" + COMPUTER_SCIENCE_AND_ENGINEERING = "Computer Science and Engineering", "Computer Science and Engineering" + EARTH_SCIENCES = "Earth Sciences", "Earth Sciences" + ELECTRICAL_ENGINEERING = "Electrical Engineering", "Electrical Engineering" + ENERGY_SCIENCE_AND_ENGINEERING = "Energy Science and Engineering", "Energy Science and Engineering" + ENGINEERING_PHYSICS = "Engineering Physics", "Engineering Physics" + HUMANITIES_AND_SOCIAL_SCIENCES = "Humanities and Social Sciences", "Humanities and Social Sciences" + INDUSTRIAL_DESIGN_CENTRE = "Industrial Design Centre", "Industrial Design Centre" + MATHEMATICS = "Mathematics", "Mathematics" + MECHANICAL_ENGINEERING = "Mechanical Engineering", "Mechanical Engineering" + METALLURGICAL_ENGINEERING_AND_MATERIALS_SCIENCE = "Metallurgical Engineering and Materials Science", "Metallurgical Engineering and Materials Science" + PHYSICS = "Physics", "Physics" + + +from django.db import models + +class YearChoices(models.TextChoices): + First_Year = "First Year", "First Year" + Second_Year = "Second Year", "Second Year" + Third_Year = "Third Year", "Third Year" + Fourth_Year = "Fourth Year", "Fourth Year" + Fifth_Year = "Fifth Year", "Fifth Year" + M_Tech = "M.Tech", "M.Tech" + Ph_D = "Ph.D.", "Ph.D." \ No newline at end of file diff --git a/socbackend/accounts/permissions.py b/socbackend/accounts/permissions.py new file mode 100644 index 0000000..1a7fe24 --- /dev/null +++ b/socbackend/accounts/permissions.py @@ -0,0 +1,13 @@ +from rest_framework.permissions import BasePermission +from django.core.exceptions import ObjectDoesNotExist + + +class HasUserProfile(BasePermission): + message = "User must have created their profile." + + def has_permission(self, request, view): + try: + request.user.userprofile # Will raise an error if the user does not have a profile + return True + except ObjectDoesNotExist: + return False diff --git a/socbackend/accounts/serializers.py b/socbackend/accounts/serializers.py new file mode 100644 index 0000000..a56f789 --- /dev/null +++ b/socbackend/accounts/serializers.py @@ -0,0 +1,87 @@ +from django.contrib.auth.password_validation import validate_password +from rest_framework import serializers +from django.db import transaction + +from .models import UserProfile +from .options import DepartmentChoices +from django.contrib.auth.models import User + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + "first_name", + "last_name", + "username", + "email", + "password", + ] + extra_kwargs = { + "password": {"style": {"input_type": "password"}, "write_only": True} + } + + def validate_password(self, password): + """ + Validate the password against all password validators. + """ + validate_password(password) + + return password + + +class RegisterUserSerializer(serializers.ModelSerializer): + class Meta: + model = UserProfile + exclude = ["verified", "verification_token"] + extra_kwargs = { + "password": {"style": {"input_type": "password"}, "write_only": True} + } + +class UserProfileSerializer(serializers.ModelSerializer): + class Meta: + model = UserProfile + exclude = ["user", "verified", "verification_token"] + extra_kwargs = { + "roll_number": {"read_only": True} + } + + # def create(self, validated_data): + # """ + # Override the create method with objects.create_user, + # since the former saves with an unencrypted password + # """ + # return User.objects.create_user(validated_data) + + +class RegisterUserProfileSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta: + model = UserProfile + fields = "__all__" + # extra_kwargs = { + # "password": {"style": {"input_type": "password"}, "write_only": True} + # } + + @transaction.atomic + def create(self, validated_data): + """ + Override the create method with objects.create_user, + since the former saves with an unencrypted password + """ + user_data = validated_data.pop("user") + user = User.objects.create_user(**user_data) + return UserProfile.objects.create(user=user, **validated_data) + + +class UserAutoCompleteSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(source="user.id") + name = serializers.SerializerMethodField() + + class Meta: + model = UserProfile + fields = ["id", "roll_number", "name"] + + def get_name(self, obj): + return obj.user.get_full_name() diff --git a/socbackend/accounts/tests.py b/socbackend/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/socbackend/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/socbackend/accounts/urls.py b/socbackend/accounts/urls.py new file mode 100644 index 0000000..0ca0fa4 --- /dev/null +++ b/socbackend/accounts/urls.py @@ -0,0 +1,20 @@ +from django.contrib import admin +from django.urls import path + +from . import views + +app_name = "accounts" +urlpatterns = [ + # path("", views.UserListView.as_view(), name="user_list"), + path("token/", views.CustomTokenObtainPairView.as_view(), name="token_obtain_pair"), + # path("list/", views.UserAutoCompleteView.as_view(), name="user_autocomplete"), + path("profile/", views.UserProfileView.as_view(), name="user_profile"), + # path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("register/", views.RegisterUserView.as_view(), name="register_user"), + # path("verify-email//", views.verify_email, name="verify_email"), + path('departments/', views.DepartmentListAPIView.as_view(), name='department-list'), + path('years/', views.YearListAPIView.as_view(), name='year-list'), + path('isloggedin/', views.isloggedin, name='loginstatus'), + path('logout/', views.logout, name='logout'), + +] diff --git a/socbackend/accounts/views.py b/socbackend/accounts/views.py new file mode 100644 index 0000000..650f585 --- /dev/null +++ b/socbackend/accounts/views.py @@ -0,0 +1,221 @@ +import re +from django.http import JsonResponse +from django.db.models import Value as V +from django.db.models.functions import Concat +from rest_framework.generics import CreateAPIView, ListAPIView +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework.exceptions import APIException + +from socbackend.settings import SIMPLE_JWT +from django.core.mail import send_mail + +from .helpers import fetch_from_sso +from .models import UserProfile +from django.contrib.auth.models import User +from .serializers import RegisterUserSerializer, UserAutoCompleteSerializer, UserProfileSerializer + +from projects.models import Mentee + +from .options import DepartmentChoices, YearChoices +from rest_framework.permissions import IsAuthenticated +from rest_framework.decorators import api_view, permission_classes +from rest_framework.response import Response +from django.contrib.auth.models import AnonymousUser + + + + + + +from django.utils.crypto import get_random_string +import os + + +# views.py +from rest_framework import generics +from rest_framework.response import Response +from .models import DepartmentChoices + +class DepartmentListAPIView(APIView): + permission_classes = [AllowAny] + def get(self, request): + departments = DepartmentChoices.choices + return Response(departments) + + +class YearListAPIView(APIView): + permission_classes = [AllowAny] + def get(self, request): + years = YearChoices.choices + return Response(years) + +@api_view(['GET']) +@permission_classes([AllowAny]) +def isloggedin(request): + if isinstance(request.user, AnonymousUser): + return JsonResponse({"status": "NO"}, status=200) + else: + return JsonResponse({"status": "YES"}, status=200) + +def generate_verification_token(): + return get_random_string(length=32) + + +# def verify_email(request, token): +# try: +# user_profile = UserProfile.objects.get(verification_token=token) +# user_profile.verified = True +# user = user_profile.user +# user.is_active = True +# user.save() +# user_profile.save() +# mentee = Mentee.objects.create(user=user_profile) +# mentee.save() + + +# return JsonResponse({"success": "verified"}, status=200) +# except UserProfile.DoesNotExist: +# return JsonResponse({"error": "User does not exist"}, status=400) + +# def send_verification_email(user_profile): +# subject = 'SOC Menteee Registration Verification Link' +# message = f""" +# Hi {user_profile.name}, + +# Please click on the link below to verify your email address and complete your registration. + +# {os.getenv('DOMAIN_NAME')}/verify-email/{user_profile.verification_token} + +# Regards, +# Team WnCC""" +# from_email = 'wncc@iitb.ac.in' # Sender's email address +# recipient_list = [user_profile.roll_number+'@iitb.ac.in'] # Recipient's email address + +# send_mail(subject, message, from_email, recipient_list) + + +def logout(request): + response = JsonResponse({"success": "logged out"}, status=200) + response.delete_cookie(SIMPLE_JWT["AUTH_COOKIE"]) + return response + + +class RegisterUserView(APIView): + permission_classes = [AllowAny] + + def post(self, request): + roll_number = request.data.get("roll_number").lower() + password = request.data.get("password") + + if User.objects.filter(username=roll_number).exists(): + user = User.objects.get(username=roll_number) + if UserProfile.objects.filter(user=user).exists(): + user_profile = UserProfile.objects.get(user=user) + if user_profile.verified: + return Response({"error": "User already exists"}, status=400) + else: + user.delete() + else: + user.delete() + + user = User.objects.create_user(username=roll_number, password=password) + user.is_active = False + user.save() + mutable_copy = request.POST.copy() + mutable_copy["user"] = user.id + + serializer = RegisterUserSerializer(data=mutable_copy) + + if serializer.is_valid(): + serializer.save() + verification_token = generate_verification_token() + user_profile = UserProfile.objects.get(user=user) + user_profile.verification_token = verification_token + user_profile.verified = True + user = user_profile.user + user.is_active = True + user.save() + user_profile.save() + print(f"User profile: {user_profile}") + if user_profile: + mentee = Mentee.objects.create(user=user_profile) + mentee.save() + else: + raise print("User profile does not exist.") + + # send_verification_email(user_profile) + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + +class UserProfileView(APIView): + + permission_classes = [IsAuthenticated] + + def get(self, request): + user_profile = UserProfile.objects.get(user=request.user) + serializer = UserProfileSerializer(user_profile) + return Response(serializer.data) + + def post(self, request): + user_profile = UserProfile.objects.get(user=request.user) + serializer = UserProfileSerializer(user_profile, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=400) + + + + +class UserAutoCompleteView(ListAPIView): + """ + ListAPIView to access user data, for user searching + """ + + serializer_class = UserAutoCompleteSerializer + + def get_queryset(self): + queryset = UserProfile.objects.all() + query = self.request.query_params.get("search") + + if query is not None and query != "": + if re.search(r"\d", query): + queryset = queryset.filter(user__roll_number__iexact=query) + else: + queryset = queryset.annotate( + full_name=Concat("user__first_name", V(" "), "user__last_name") + ) + for term in query.split(): + queryset = queryset.filter(full_name__icontains=term) + + return queryset[:10] + + +class CreateUserProfileView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + data = fetch_from_sso( + request.data["code"], "http://localhost:3000/register", request + ) + except APIException as e: + return Response(e.detail, status=e.status_code) + + return Response(data) + + +class CustomTokenObtainPairView(TokenObtainPairView): + def post(self, request, *args, **kwargs): + mutable_copy = request.POST.copy() + mutable_copy["username"] = mutable_copy["username"].lower() + response = super().post(request, *args, **kwargs) + if "access" in response.data: + access_token = response.data["access"] + response.set_cookie( + key=SIMPLE_JWT["AUTH_COOKIE"], value=access_token, httponly=True + ) + return response