From 0d53086e779cf7b9f2fc438866e82dcd87375e63 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 21 Dec 2022 17:24:44 +0530 Subject: [PATCH 01/50] let's try..... --- website/.env.example | 13 +- website/forum/tokens.py | 2 +- website/interview_exp/api/__init__.py | 0 website/interview_exp/api/filters.py | 13 ++ website/interview_exp/api/permissions.py | 10 ++ website/interview_exp/api/serializers.py | 36 +++++ website/interview_exp/api/urls.py | 20 +++ website/interview_exp/api/views.py | 184 ++++++++++++++++++++++ website/interview_exp/models.py | 12 +- website/user_profile/api/__init__.py | 0 website/user_profile/api/serializers.py | 57 +++++++ website/user_profile/api/urls.py | 29 ++++ website/user_profile/api/views.py | 103 ++++++++++++ website/user_profile/models.py | 37 +++-- website/user_profile/tokens.py | 2 +- website/user_profile/utils.py | 77 ++++++++- website/user_profile/utils_permissions.py | 19 +++ website/website/settings.py | 104 +++++++++--- website/website/urls.py | 46 ++++-- 19 files changed, 701 insertions(+), 63 deletions(-) create mode 100644 website/interview_exp/api/__init__.py create mode 100644 website/interview_exp/api/filters.py create mode 100644 website/interview_exp/api/permissions.py create mode 100644 website/interview_exp/api/serializers.py create mode 100644 website/interview_exp/api/urls.py create mode 100644 website/interview_exp/api/views.py create mode 100644 website/user_profile/api/__init__.py create mode 100644 website/user_profile/api/serializers.py create mode 100644 website/user_profile/api/urls.py create mode 100644 website/user_profile/api/views.py create mode 100644 website/user_profile/utils_permissions.py diff --git a/website/.env.example b/website/.env.example index 34fd7063..1048a138 100644 --- a/website/.env.example +++ b/website/.env.example @@ -1,7 +1,10 @@ -DB_NAME=HELLO_DJANGO -DB_USER=U_HELLO -DB_PASSWORD=hA8(scA@!fg3*sc&xaGh&6%-l<._&xCf -DB_HOST=127.0.0.1 +DB_NAME=HELLO_DJANGO +DB_USER=U_HELLO +DB_PASSWORD=hA8(scA@!fg3*sc&xaGh&6%-l<._&xCf +DB_HOST=127.0.0.1 DB_PORT="" SOCIAL_AUTH_GOOGLE_OAUTH2_KEY="" -SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET="" \ No newline at end of file +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET="" +SEND_EMAILS=False +EMAIL_HOST_USER='' +EMAIL_PASSWORD='' \ No newline at end of file diff --git a/website/forum/tokens.py b/website/forum/tokens.py index 3fa8b747..90e7faa7 100644 --- a/website/forum/tokens.py +++ b/website/forum/tokens.py @@ -1,5 +1,5 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator -from django.utils import six +import six class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): diff --git a/website/interview_exp/api/__init__.py b/website/interview_exp/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/interview_exp/api/filters.py b/website/interview_exp/api/filters.py new file mode 100644 index 00000000..2d1dca5a --- /dev/null +++ b/website/interview_exp/api/filters.py @@ -0,0 +1,13 @@ +from django_filters import rest_framework as filters +from interview_exp.models import Experiences + + +# need to revise the filters if needed +class ExperiencesFilter(filters.FilterSet): + status = filters.ChoiceFilter(field_name='verification_Status', choices=Experiences.verification_Status_choices) + role_type = filters.ChoiceFilter(field_name='role_Type', choices=Experiences.role_Type_choices) + ctc = filters.RangeFilter(field_name='total_Compensation') + + class Meta: + model = Experiences + fields = {} diff --git a/website/interview_exp/api/permissions.py b/website/interview_exp/api/permissions.py new file mode 100644 index 00000000..919e57e0 --- /dev/null +++ b/website/interview_exp/api/permissions.py @@ -0,0 +1,10 @@ +from rest_framework.permissions import IsAuthenticated, BasePermission + + +class RevisionPermissions(IsAuthenticated): + def has_object_permission(self, request, view, obj): + user = request.user + role = user.profile.role + if role in ('1', '2'): + return True + return obj.user == user diff --git a/website/interview_exp/api/serializers.py b/website/interview_exp/api/serializers.py new file mode 100644 index 00000000..dc9f0bcf --- /dev/null +++ b/website/interview_exp/api/serializers.py @@ -0,0 +1,36 @@ +from rest_framework import serializers + +from interview_exp.models import Experiences, Revisions +from user_profile.api.serializers import UserSerializer + + +class IESerializer(serializers.ModelSerializer): + user = UserSerializer(read_only=True) + verifier = UserSerializer(read_only=True) + url = serializers.HyperlinkedIdentityField(view_name='experiences_api:ie_detail', lookup_field='id', + lookup_url_kwarg='slug') + + class Meta: + model = Experiences + fields = '__all__' + read_only_fields = ['user', 'created_at', 'updated_at', 'verifier'] + + +class RevisionSerializer(serializers.ModelSerializer): + experience = IESerializer() + reviewer = UserSerializer() + + # experience = serializers.HyperlinkedRelatedField(view_name='experiences_api:ie_detail', + # lookup_field='id', + # lookup_url_kwarg='slug', + # read_only=True) + # reviewer = serializers.HyperlinkedRelatedField(view_name='user_profile_api:user_detail', + # lookup_field='username', + # lookup_url_kwarg='username', + # read_only=True) + message = serializers.CharField(required=False) + + class Meta: + model = Revisions + fields = '__all__' + read_only_fields = ['created_at', 'updated_at', 'id', 'reviewer'] diff --git a/website/interview_exp/api/urls.py b/website/interview_exp/api/urls.py new file mode 100644 index 00000000..393ddb4f --- /dev/null +++ b/website/interview_exp/api/urls.py @@ -0,0 +1,20 @@ +from django.urls import path + +from .views import ( + IEListView, + RetrieveUpdateIEView, + RevisionsListView, + RetrieveUpdateRevisionView, + CreateRevision, +) +from user_profile.views import activate + +app_name = 'experiences_api' + +urlpatterns = [ + path('', IEListView.as_view(), name='ie_list'), + path('/', RetrieveUpdateIEView.as_view(), name='ie_detail'), + path('/revisions//', CreateRevision.as_view(), name='add_revision'), + path('revisions/', RevisionsListView.as_view(), name='revision_list'), + path('revisions//', RetrieveUpdateRevisionView.as_view(), name='revision_detail'), +] diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py new file mode 100644 index 00000000..6266a15a --- /dev/null +++ b/website/interview_exp/api/views.py @@ -0,0 +1,184 @@ +from django.contrib.auth.models import User +from django.db.models import Q +from django.contrib.sites.shortcuts import get_current_site +from django.template.loader import render_to_string +from django.core.mail import EmailMessage +from django.shortcuts import get_object_or_404 +from django.conf import settings +from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.throttling import AnonRateThrottle +from rest_framework.response import Response +from rest_framework.filters import SearchFilter, OrderingFilter +from rest_framework.generics import ( + CreateAPIView, + ListAPIView, + ListCreateAPIView, + RetrieveUpdateAPIView, +) +from rest_framework.serializers import ValidationError +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from django_filters import rest_framework as filters +from decouple import config +from .filters import ExperiencesFilter +from interview_exp.models import Experiences, Revisions +from user_profile.models import Profile +from user_profile.utils import ThreadedMailing +from user_profile.utils_permissions import ViewUpdatePermission, IsMemberOrAbove + +from .serializers import ( + IESerializer, + RevisionSerializer +) + +# email only for trial purposes +# TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' +TRIAL_REC_MAIL = settings.TRIAL_REC_MAIL + + +class IEListView(ListCreateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = IESerializer + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filterset_class = ExperiencesFilter + search_fields = ['company', 'user__username', 'year'] + ordering_fields = ['updated_at', 'total_Compensation', 'year'] + + def get_queryset(self): + qs = Experiences.objects.all() + user = self.request.user + if user.profile.role == '3': + return qs.filter(Q(user=user) | Q(verification_Status='Approved')) + return qs + + def perform_create(self, serializer): + # TODO + # send thank you mail for posting + user = self.request.user + exp = serializer.save(user=user, verification_Status='Review Pending') + ## mailing part + member_users = User.objects.filter(~Q(profile__role='3')) + domain = get_current_site(self.request).domain + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'new_experience_entry_email.html', + { + 'user': user, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], + + ) + for user in member_users + ] + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, fail_silently=True, verbose=1) + threaded_mail.start() + + +class RetrieveUpdateIEView(RetrieveUpdateAPIView): + queryset = Profile.objects.all() + serializer_class = IESerializer + permission_classes = (ViewUpdatePermission,) + lookup_field = 'id' + lookup_url_kwarg = 'slug' + + def get_queryset(self): + user = self.request.user + qs = Experiences.objects.all() + if user.profile.role == '3': + return qs.filter(Q(user=user) | Q(verification_Status='Approved')) + return qs + + +class RevisionsListView(ListAPIView): + serializer_class = RevisionSerializer + + permission_classes = (IsMemberOrAbove,) + + def get_queryset(self): + qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() + return qs + + +class RetrieveUpdateRevisionView(RetrieveUpdateAPIView): + serializer_class = RevisionSerializer + permission_classes = (IsMemberOrAbove,) + lookup_field = 'id' + lookup_url_kwarg = 'id' + + def get_queryset(self): + qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() + return qs + + +class CreateRevision(CreateAPIView): + serializer_class = RevisionSerializer + permission_classes = (IsMemberOrAbove,) + + def perform_create(self, serializer): + """ + the review codes are: + `acc` -> Accepted + `rev` -> Review Pending + `chg` -> Changes Requested + """ + req = self.request + curr_user = req.user + exp_id = self.kwargs.get('exp_id') + exp = get_object_or_404(Experiences, pk=exp_id) + review_code = self.kwargs.get('review_code', '') + print(self.kwargs) + if review_code == 'acc': # Accepted + exp.verification_Status = 'Accepted' + exp.verifier = curr_user + # TODO + # Send mail to the author about publication + exp.save() + revisions = Revisions.objects.filter(experience=exp) + revisions.delete() + else: + msg = serializer.validated_data.get('message') + + if not msg: + raise ValidationError('Must provide message if not being accepted.') + domain = get_current_site(self.request).domain + exp_author = exp.user + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'changes_requested_email.html', + { + 'user': exp.user, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], # exp.user.email + + ) + ] + + if review_code == 'rev': + # doesn't make sense at all + exp.verification_Status = 'Review Pending' + elif review_code == 'chg': + exp.verification_Status = 'Changes Requested' + exp.save() + revision, rev_created = Revisions.objects.get_or_create(experience=exp, + defaults={'reviewer': curr_user, + 'message': msg}) + else: + raise ValidationError("Invalid code for verification status.") + + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, verbose=1) + threaded_mail.start() diff --git a/website/interview_exp/models.py b/website/interview_exp/models.py index 6879ee91..3de46806 100644 --- a/website/interview_exp/models.py +++ b/website/interview_exp/models.py @@ -4,15 +4,19 @@ from django.core.validators import MaxValueValidator, MinValueValidator from markdownx.models import MarkdownxField from markdownx.utils import markdownify +from django.urls import reverse + # Create your models here. def current_year(): return datetime.date.today().year + def max_value_current_year(value): return MaxValueValidator(current_year())(value) + class Experiences(models.Model): company = models.CharField(max_length=100) year = models.PositiveIntegerField( @@ -46,7 +50,7 @@ class Experiences(models.Model): @property def formatted_markdown(self): return markdownify(self.interview_Questions) - + def __str__(self): return str(self.company) + " " + str(self.user) @@ -54,6 +58,9 @@ def get_cname(self): class_name = "Experiences" return class_name + def get_absolute_url(self): + return reverse('experiences_api:ie_detail', kwargs={'slug': self.id}) + class Meta: managed = True ordering = ['-year', '-created_at'] @@ -77,6 +84,9 @@ def get_cname(self): class_name = "Revisions" return class_name + def get_absolute_url(self): + return reverse('experiences_api:revision_detail', kwargs={'id': self.id}) + class Meta: managed = True ordering = ['-created_at'] diff --git a/website/user_profile/api/__init__.py b/website/user_profile/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/user_profile/api/serializers.py b/website/user_profile/api/serializers.py new file mode 100644 index 00000000..698ba00b --- /dev/null +++ b/website/user_profile/api/serializers.py @@ -0,0 +1,57 @@ +from rest_framework import serializers +from rest_framework.validators import UniqueValidator +from django.contrib.auth.models import User + +from user_profile.models import Profile +from user_profile.utils import LowerEmailField + + +class RegistrationSerializer(serializers.ModelSerializer): + password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) + email = LowerEmailField( + required=True, + allow_blank=False, + label='Email address', + max_length=30, + validators=[UniqueValidator(queryset=User.objects.all())], + ) + + class Meta: + model = User + fields = ['username', 'email', 'password', 'password2'] + extra_kwargs = { + 'password': {'write_only': True} + } + + def save(self): + password = self.validated_data['password'] + password2 = self.validated_data['password2'] + if password != password2: + raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) + account = User( + username=self.validated_data['username'], + email=self.validated_data['email'].lower(), + is_active=False # TO BE CHANGED TO FALSE + ) + account.set_password(password) + account.save() + return account + + +class UserSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='user_profile_api:user_detail', lookup_field='username', + lookup_url_kwarg='username') + + class Meta: + model = User + fields = ['username', 'email', 'url'] + + +class ProfileSerializer(serializers.ModelSerializer): + user = UserSerializer() + role = serializers.CharField(source='get_role_display', ) + + class Meta: + model = Profile + exclude = ('id',) + read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) diff --git a/website/user_profile/api/urls.py b/website/user_profile/api/urls.py new file mode 100644 index 00000000..76794b21 --- /dev/null +++ b/website/user_profile/api/urls.py @@ -0,0 +1,29 @@ +from django.urls import path + +from .views import ( + RegistrationView, + # ProfileRegistrationView, + ListProfileView, + RetrieveUpdateProfileView, + UserSearchView, + username_existence_check, + email_existence_check, +) +from user_profile.views import activate + +app_name = 'user_profile_api' + +urlpatterns = [ + # Profile related(except first) + path('register/', RegistrationView.as_view(), name='user_registration'), + path('', ListProfileView.as_view(), name='all_users_list'), + # path('resend-user-activation/', resendVerificationView, name='resend-user-activation'), + path('/', RetrieveUpdateProfileView.as_view(), name='user_detail'), + path('search', UserSearchView.as_view(), name='user_search'), + path('username-check', username_existence_check, name='username_exists'), + path('email-check', email_existence_check, name='email_exists'), + + # Account Activation + path('activate///', activate, name='activate'), + +] diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py new file mode 100644 index 00000000..14bd8bdf --- /dev/null +++ b/website/user_profile/api/views.py @@ -0,0 +1,103 @@ +from django.contrib.auth.models import User +from rest_framework.permissions import IsAdminUser, IsAuthenticated +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.throttling import AnonRateThrottle +from rest_framework.response import Response +from rest_framework.generics import ( + CreateAPIView, + ListAPIView, + RetrieveUpdateAPIView, +) +from rest_framework.serializers import ValidationError +from django.contrib.sites.shortcuts import get_current_site +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView + +from user_profile.models import Profile +from user_profile.utils import send_verification_mail +from user_profile.utils_permissions import ViewUpdatePermission +from user_profile.utils import ProfileMatcher + +from .serializers import ( + RegistrationSerializer, + UserSerializer, + ProfileSerializer, +) + + +# TODO +# 1) Email verification and resend email verification + +# For customised tokens. Don't use if not needed +class MyTokenObtainPairSerializer(TokenObtainPairSerializer): + def validate(self, attrs): + data = super().validate(attrs) + refresh = self.get_token(self.user) + data['refresh'] = str(refresh) + data['access'] = str(refresh.access_token) + + # data['isStudent'] = self.user.groups.first().name == 'student' + # data['user'] = UserSerializer(self.user).data + return data + + +# For customised tokens. Don't use if not needed +class MyTokenObtainPairView(TokenObtainPairView): + serializer_class = MyTokenObtainPairSerializer + + +class RegistrationView(CreateAPIView): + serializer_class = RegistrationSerializer + + def perform_create(self, serializer): + # is_active set to false in save method of the serializer + user = serializer.save() + domain = get_current_site(self.request).domain + _ = send_verification_mail(domain=domain, user=user) + # auto creation of profile done in model signal + + +class ListProfileView(ListAPIView): + serializer_class = ProfileSerializer + queryset = Profile.objects.all() + + +class RetrieveUpdateProfileView(RetrieveUpdateAPIView): + queryset = Profile.objects.all() + serializer_class = ProfileSerializer + permission_classes = (ViewUpdatePermission,) + lookup_field = 'user__username' + lookup_url_kwarg = 'username' + + def get_queryset(self): + return Profile.objects.all() + + +class UserSearchView(ListAPIView): + serializer_class = ProfileSerializer + + def get_queryset(self): + search_query = self.request.query_params.get('query') + qs = Profile.objects.all() + if not search_query: # returns all profiles in case of empty query + return qs + matcher = ProfileMatcher(query=search_query) + # using generator to save on space + unsorted_matches = ((matcher.matcher(i), i) for i in qs if matcher.matcher(i) >= 0.5) + return [i[1] for i in sorted(unsorted_matches, key=lambda x: x[0], reverse=True)] + + +@api_view(['GET', ]) +def username_existence_check(request): + search_query = request.query_params.get('username') + if (not search_query) or (not User.objects.filter(username=search_query).exists()): + return Response(data={'response': 'Username is available!', 'exists': 0}) + return Response(data={'response': 'Username already exists!', 'exists': 1}) + + +@api_view(['GET', ]) +def email_existence_check(request): + search_query = request.query_params.get('email') + if (not search_query) or (not User.objects.filter(email=search_query.lower()).exists()): + return Response(data={'response': 'Email ID is not used yet!', 'exists': 0}) + return Response(data={'response': 'An account is registered with the email address!', 'exists': 1}) diff --git a/website/user_profile/models.py b/website/user_profile/models.py index 981f2933..683bbdb4 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -10,16 +10,19 @@ from django.contrib.auth import get_user_model from django.db.models.signals import post_save from django.dispatch import receiver +from django.urls import reverse import os from PIL import Image from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile import sys -def content_file_name(instance,filename): - ext="png" - filename= str(instance.user.username)+"."+str(ext) - return os.path.join('images/',filename) + +def content_file_name(instance, filename): + ext = "png" + filename = str(instance.user.username) + "." + str(ext) + return os.path.join('images/', filename) + class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) @@ -30,7 +33,7 @@ class Profile(models.Model): ('2', 'Member'), ('3', 'User') ) - role = models.CharField(max_length=50, choices=role_choices ,default='3') + role = models.CharField(max_length=50, choices=role_choices, default='3') dept = models.CharField(max_length=20, blank=True, null=True) url_CodeChef = models.URLField(blank=True, null=True) url_Codeforces = models.URLField(blank=True, null=True) @@ -39,26 +42,30 @@ class Profile(models.Model): image_url = models.URLField(blank=True, null=True) image = models.ImageField(blank=True, null=True, upload_to=content_file_name) email_confirmed = models.BooleanField(default=False) - created_at = models.DateTimeField(auto_now=False, auto_now_add=True) - updated_at = models.DateTimeField(auto_now=False, auto_now_add=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.user.username - def save(self, *args, **kwargs): + def save(self, *args, **kwargs): if self.image: - img= Image.open(self.image) - output = BytesIO() - img = img.resize((100, 100)) - img.save(output, format='PNG', quality=100) - output.seek(0) - self.image = InMemoryUploadedFile(output, 'ImageField', ".png" , 'image/png', - sys.getsizeof(output), None) + img = Image.open(self.image) + output = BytesIO() + img = img.resize((100, 100)) + img.save(output, format='PNG', quality=100) + output.seek(0) + self.image = InMemoryUploadedFile(output, 'ImageField', ".png", 'image/png', + sys.getsizeof(output), None) super(Profile, self).save() + def get_absolute_url(self): + return reverse('user_profile_api:user_detail', kwargs={'username': self.user.username}) + class Meta: managed = True + @receiver(post_save, sender=User) def update_user_profile(sender, instance, created, **kwargs): if created: diff --git a/website/user_profile/tokens.py b/website/user_profile/tokens.py index 3fa8b747..90e7faa7 100644 --- a/website/user_profile/tokens.py +++ b/website/user_profile/tokens.py @@ -1,5 +1,5 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator -from django.utils import six +import six class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): diff --git a/website/user_profile/utils.py b/website/user_profile/utils.py index 0c6ae2cc..3153dfe8 100644 --- a/website/user_profile/utils.py +++ b/website/user_profile/utils.py @@ -1,4 +1,15 @@ +import threading +import six from difflib import SequenceMatcher +from typing import List, Tuple, Union +from django.core.exceptions import ValidationError +from django.core.mail import EmailMessage +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.template.loader import render_to_string +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.conf import settings +from rest_framework import serializers class ProfileMatcher: @@ -26,4 +37,68 @@ def matcher(self, obj): return 0 def __str__(self): - return 'ProfileMatcher object with query="{}", ratio_threshold={} .'.format(self.query,self.ratio_threshold) + return 'ProfileMatcher object with query="{}", ratio_threshold={} .'.format(self.query, self.ratio_threshold) + + +class TokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + six.text_type(user.pk) + six.text_type(timestamp) + + six.text_type(user.is_active) + ) + + +account_activation_token = TokenGenerator() + + +class LowerEmailField(serializers.EmailField): + def to_representation(self, value): + return str(value) + + def to_internal_value(self, data): + if isinstance(data, bool) or not isinstance(data, (str, int, float,)): + self.fail('invalid') + value = str(data).lower() + return value.strip() if self.trim_whitespace else value + + +class ThreadedMailing(threading.Thread): + def __init__(self, emails: List[EmailMessage], fail_silently: bool = True, verbose: int = 0): + self.email_msgs = emails + self.fail_silently = fail_silently + self.verbose = verbose + threading.Thread.__init__(self) + + def run(self): + if self.verbose > 0: + print('Sending {} emails.'.format(len(self.email_msgs))) + for email_number, email in enumerate(self.email_msgs, start=1): + if self.verbose > 1: + print('Sending email number:', email_number) + print(email) + email.send(fail_silently=self.fail_silently) + + +# needs change, UNFIT FOR USE +def send_verification_mail(domain, user, *args, **kwargs): + if domain is None or user is None: + raise ValidationError('Domain/User instance not provided') + mail_subject = 'Activate your RECursion website account.' + message = render_to_string('account_activation_email.html', { + 'user': user, + 'domain': domain, + 'uid': urlsafe_base64_encode(force_bytes(user.id)), + 'token': account_activation_token.make_token(user), + }) + to_email = settings.TRIAL_REC_MAIL + mail = EmailMessage( + mail_subject, + message, + to=[to_email, ] + ) + + # UNCOMMENT BELOW LINES TO SEND EMAIL + threaded_mail = ThreadedMailing([mail, ]) + threaded_mail.start() + + return message diff --git a/website/user_profile/utils_permissions.py b/website/user_profile/utils_permissions.py new file mode 100644 index 00000000..c5d66e0e --- /dev/null +++ b/website/user_profile/utils_permissions.py @@ -0,0 +1,19 @@ +from rest_framework.permissions import IsAuthenticated, SAFE_METHODS, BasePermission +from user_profile.models import Profile + + +class ViewUpdatePermission(BasePermission): + def has_permission(self, request, view): + return (request.method in SAFE_METHODS) or bool(request.user and request.user.is_authenticated) + + def has_object_permission(self, request, view, obj): + if request.method in SAFE_METHODS: + return True + return bool(request.user and request.user.is_authenticated) and request.user == obj.user + + +class IsMemberOrAbove(IsAuthenticated): + def has_object_permission(self, request, view, obj): + curr_user = request.user + curr_profile = Profile.objects.get(user=curr_user) + return curr_profile.role != '3' diff --git a/website/website/settings.py b/website/website/settings.py index ef4fe2b8..f04929fd 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -9,11 +9,11 @@ import os from decouple import config +from datetime import timedelta # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ @@ -25,7 +25,6 @@ ALLOWED_HOSTS = ['*'] - # Application definition INSTALLED_APPS = [ @@ -46,13 +45,15 @@ 'markdownx', 'blog', 'getting_started', + 'rest_framework', + 'django_filters', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - #'django.middleware.csrf.CsrfViewMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -61,7 +62,7 @@ AUTHENTICATION_BACKENDS = ( 'social_core.backends.open_id.OpenIdAuth', # for Google authentication - 'social_core.backends.google.GoogleOpenId', # for Google authentication + # 'social_core.backends.google.GoogleOpenId', # for Google authentication 'social_core.backends.google.GoogleOAuth2', # for Google authentication 'social_core.backends.facebook.FacebookOAuth2', # for Facebook authentication @@ -84,24 +85,76 @@ 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], + 'libraries': { + 'staticfiles': 'django.templatetags.static', + } }, }, ] WSGI_APPLICATION = 'website.wsgi.application' +# REST FRAMEWORK settings +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny', + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10, + + 'DEFAULT_THROTTLE_RATES': { + 'anon': '40/day', + }, + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', +} + +# REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' } + + +# JWT SETTINGS +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=28), # TO BE REDUCED + 'REFRESH_TOKEN_LIFETIME': timedelta(days=56), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } } - # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators @@ -120,22 +173,20 @@ }, ] - - SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', - + 'website.utils.associate_by_email', - + 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', - + 'website.utils.set_image_for_new_users' ) @@ -144,11 +195,9 @@ LOGOUT_REDIRECT_URL = 'home' LOGIN_REDIRECT_URL = 'home' - SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = config('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY') SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = config('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET') - # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ @@ -162,20 +211,29 @@ USE_TZ = True - +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # to silence the 26 errors # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = '/static/' -MEDIA_URL ='/media/' +MEDIA_URL = '/media/' -MEDIA_ROOT=os.path.join(BASE_DIR,'../website/media') +MEDIA_ROOT = os.path.join(BASE_DIR, '../website/media') EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# email stuff +# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = config("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = config("EMAIL_PASSWORD") + +TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' -#PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) -#STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') -#MEDIA_URL='/media/' -#MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') +# PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) +# STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') +# MEDIA_URL='/media/' +# MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') diff --git a/website/website/urls.py b/website/website/urls.py index 100270cb..1afea5b0 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -15,27 +15,41 @@ """ from django.contrib import admin from django.contrib.auth import views as auth_views -from django.urls import path,include +from django.urls import path, include from django.conf.urls import url, include from forum import views from django.conf import settings from django.conf.urls.static import static +from rest_framework_simplejwt.views import ( + TokenRefreshView, +) +from user_profile.api.views import MyTokenObtainPairView urlpatterns = [ - path('admin/', admin.site.urls), - path('', views.home, name='home'), - path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'), - path('logout/', auth_views.LogoutView.as_view(), name='logout'), - path('oauth/', include('social_django.urls', namespace='social')), - path('forum/',include('forum.urls',namespace='forum')), - path('events/',include('events.urls',namespace='events')), - path('profile/',include('user_profile.urls',namespace='user_profile')), - path('team/',include('team.urls',namespace='team')), - path('blog/',include('blog.urls',namespace='blog')), - path('experience/',include('interview_exp.urls',namespace='interview_exp')), - path('get_started/',include('getting_started.urls',namespace='getting_started')), - # path('members/',include('members.urls')), - url(r'^markdownx/', include('markdownx.urls')), + path('admin/', admin.site.urls), + path('', views.home, name='home'), + path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'), + path('logout/', auth_views.LogoutView.as_view(), name='logout'), + path('oauth/', include('social_django.urls', namespace='social')), + path('forum/', include('forum.urls', namespace='forum')), + path('events/', include('events.urls', namespace='events')), + path('profile/', include('user_profile.urls', namespace='user_profile')), + path('team/', include('team.urls', namespace='team')), + path('blog/', include('blog.urls', namespace='blog')), + path('experience/', include('interview_exp.urls', namespace='interview_exp')), + path('get_started/', include('getting_started.urls', namespace='getting_started')), + # path('members/',include('members.urls')), + url(r'^markdownx/', include('markdownx.urls')), -]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + # API region + + # API URLs + path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), + path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), + + # JWT + path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + + ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From 82c3fd773a9a937320764b24f6f62349af671e30 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 21 Dec 2022 17:25:18 +0530 Subject: [PATCH 02/50] updated requirements --- new_requirements.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 new_requirements.txt diff --git a/new_requirements.txt b/new_requirements.txt new file mode 100644 index 00000000..7608feda --- /dev/null +++ b/new_requirements.txt @@ -0,0 +1,31 @@ +asgiref==3.5.2 +beautifulsoup4==4.11.1 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==2.1.1 +cryptography==38.0.4 +defusedxml==0.7.1 +Django==3.2 +django-filter==22.1 +django-markdownx==3.0.1 +django-widget-tweaks==1.4.12 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +html2markdown==0.1.7 +idna==3.4 +Markdown==3.4.1 +oauthlib==3.2.2 +Pillow==9.3.0 +pycparser==2.21 +PyJWT==2.6.0 +python-decouple==3.6 +python3-openid==3.2.0 +pytz==2022.6 +requests==2.28.1 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.0.0 +social-auth-core==4.3.0 +soupsieve==2.3.2.post1 +sqlparse==0.4.3 +urllib3==1.26.13 From 8b98bebf34e24e2c806220b943cb807223acd4fa Mon Sep 17 00:00:00 2001 From: bishwajit Date: Thu, 22 Dec 2022 01:12:06 +0530 Subject: [PATCH 03/50] initial setup for django-prometheus --- new_requirements.txt | 2 ++ website/website/settings.py | 6 ++++-- website/website/urls.py | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/new_requirements.txt b/new_requirements.txt index 7608feda..b997eb54 100644 --- a/new_requirements.txt +++ b/new_requirements.txt @@ -8,6 +8,7 @@ defusedxml==0.7.1 Django==3.2 django-filter==22.1 django-markdownx==3.0.1 +django-prometheus==2.2.0 django-widget-tweaks==1.4.12 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 @@ -16,6 +17,7 @@ idna==3.4 Markdown==3.4.1 oauthlib==3.2.2 Pillow==9.3.0 +prometheus-client==0.15.0 pycparser==2.21 PyJWT==2.6.0 python-decouple==3.6 diff --git a/website/website/settings.py b/website/website/settings.py index f04929fd..eddd220b 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -47,9 +47,11 @@ 'getting_started', 'rest_framework', 'django_filters', + 'django_prometheus', ] MIDDLEWARE = [ + 'django_prometheus.middleware.PrometheusBeforeMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -57,7 +59,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - + 'django_prometheus.middleware.PrometheusAfterMiddleware', ] AUTHENTICATION_BACKENDS = ( @@ -150,7 +152,7 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', + 'ENGINE': 'django_prometheus.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } diff --git a/website/website/urls.py b/website/website/urls.py index 1afea5b0..112ae674 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -51,5 +51,7 @@ # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + # Prometheus monitoring + path('', include('django_prometheus.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From 97225839a66d5e0d86be7da22a6193cbd939fc31 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Thu, 22 Dec 2022 01:19:47 +0530 Subject: [PATCH 04/50] added to models --- website/interview_exp/models.py | 5 +++-- website/user_profile/models.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/website/interview_exp/models.py b/website/interview_exp/models.py index 3de46806..0e9f9f8d 100644 --- a/website/interview_exp/models.py +++ b/website/interview_exp/models.py @@ -5,6 +5,7 @@ from markdownx.models import MarkdownxField from markdownx.utils import markdownify from django.urls import reverse +from django_prometheus.models import ExportModelOperationsMixin # Create your models here. @@ -17,7 +18,7 @@ def max_value_current_year(value): return MaxValueValidator(current_year())(value) -class Experiences(models.Model): +class Experiences(ExportModelOperationsMixin('experience'), models.Model): company = models.CharField(max_length=100) year = models.PositiveIntegerField( default=current_year(), validators=[MinValueValidator(1984), max_value_current_year]) @@ -68,7 +69,7 @@ class Meta: verbose_name_plural = 'Experiences' -class Revisions(models.Model): +class Revisions(ExportModelOperationsMixin('revision'), models.Model): experience = models.OneToOneField(Experiences, on_delete=models.CASCADE) reviewer = models.ForeignKey(User, on_delete=models.CASCADE) message = models.TextField() diff --git a/website/user_profile/models.py b/website/user_profile/models.py index 683bbdb4..2b7706b4 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -15,6 +15,8 @@ from PIL import Image from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile +from django_prometheus.models import ExportModelOperationsMixin + import sys @@ -24,7 +26,7 @@ def content_file_name(instance, filename): return os.path.join('images/', filename) -class Profile(models.Model): +class Profile(ExportModelOperationsMixin('profile'), models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) name = models.CharField(max_length=100) college = models.CharField(max_length=100) From caf66a60cf4f20b39bdb6df0f9fd70631973220f Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 21 Dec 2022 17:24:44 +0530 Subject: [PATCH 05/50] rebased after making events app live --- website/.env.example | 13 +- website/forum/tokens.py | 2 +- website/interview_exp/api/__init__.py | 0 website/interview_exp/api/filters.py | 13 ++ website/interview_exp/api/permissions.py | 10 ++ website/interview_exp/api/serializers.py | 36 +++++ website/interview_exp/api/urls.py | 20 +++ website/interview_exp/api/views.py | 184 ++++++++++++++++++++++ website/interview_exp/models.py | 12 +- website/user_profile/api/__init__.py | 0 website/user_profile/api/serializers.py | 57 +++++++ website/user_profile/api/urls.py | 29 ++++ website/user_profile/api/views.py | 103 ++++++++++++ website/user_profile/models.py | 37 +++-- website/user_profile/tokens.py | 2 +- website/user_profile/utils.py | 77 ++++++++- website/user_profile/utils_permissions.py | 19 +++ website/website/settings.py | 102 +++++++++--- website/website/urls.py | 18 ++- 19 files changed, 686 insertions(+), 48 deletions(-) create mode 100644 website/interview_exp/api/__init__.py create mode 100644 website/interview_exp/api/filters.py create mode 100644 website/interview_exp/api/permissions.py create mode 100644 website/interview_exp/api/serializers.py create mode 100644 website/interview_exp/api/urls.py create mode 100644 website/interview_exp/api/views.py create mode 100644 website/user_profile/api/__init__.py create mode 100644 website/user_profile/api/serializers.py create mode 100644 website/user_profile/api/urls.py create mode 100644 website/user_profile/api/views.py create mode 100644 website/user_profile/utils_permissions.py diff --git a/website/.env.example b/website/.env.example index 34fd7063..1048a138 100644 --- a/website/.env.example +++ b/website/.env.example @@ -1,7 +1,10 @@ -DB_NAME=HELLO_DJANGO -DB_USER=U_HELLO -DB_PASSWORD=hA8(scA@!fg3*sc&xaGh&6%-l<._&xCf -DB_HOST=127.0.0.1 +DB_NAME=HELLO_DJANGO +DB_USER=U_HELLO +DB_PASSWORD=hA8(scA@!fg3*sc&xaGh&6%-l<._&xCf +DB_HOST=127.0.0.1 DB_PORT="" SOCIAL_AUTH_GOOGLE_OAUTH2_KEY="" -SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET="" \ No newline at end of file +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET="" +SEND_EMAILS=False +EMAIL_HOST_USER='' +EMAIL_PASSWORD='' \ No newline at end of file diff --git a/website/forum/tokens.py b/website/forum/tokens.py index 3fa8b747..90e7faa7 100644 --- a/website/forum/tokens.py +++ b/website/forum/tokens.py @@ -1,5 +1,5 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator -from django.utils import six +import six class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): diff --git a/website/interview_exp/api/__init__.py b/website/interview_exp/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/interview_exp/api/filters.py b/website/interview_exp/api/filters.py new file mode 100644 index 00000000..2d1dca5a --- /dev/null +++ b/website/interview_exp/api/filters.py @@ -0,0 +1,13 @@ +from django_filters import rest_framework as filters +from interview_exp.models import Experiences + + +# need to revise the filters if needed +class ExperiencesFilter(filters.FilterSet): + status = filters.ChoiceFilter(field_name='verification_Status', choices=Experiences.verification_Status_choices) + role_type = filters.ChoiceFilter(field_name='role_Type', choices=Experiences.role_Type_choices) + ctc = filters.RangeFilter(field_name='total_Compensation') + + class Meta: + model = Experiences + fields = {} diff --git a/website/interview_exp/api/permissions.py b/website/interview_exp/api/permissions.py new file mode 100644 index 00000000..919e57e0 --- /dev/null +++ b/website/interview_exp/api/permissions.py @@ -0,0 +1,10 @@ +from rest_framework.permissions import IsAuthenticated, BasePermission + + +class RevisionPermissions(IsAuthenticated): + def has_object_permission(self, request, view, obj): + user = request.user + role = user.profile.role + if role in ('1', '2'): + return True + return obj.user == user diff --git a/website/interview_exp/api/serializers.py b/website/interview_exp/api/serializers.py new file mode 100644 index 00000000..dc9f0bcf --- /dev/null +++ b/website/interview_exp/api/serializers.py @@ -0,0 +1,36 @@ +from rest_framework import serializers + +from interview_exp.models import Experiences, Revisions +from user_profile.api.serializers import UserSerializer + + +class IESerializer(serializers.ModelSerializer): + user = UserSerializer(read_only=True) + verifier = UserSerializer(read_only=True) + url = serializers.HyperlinkedIdentityField(view_name='experiences_api:ie_detail', lookup_field='id', + lookup_url_kwarg='slug') + + class Meta: + model = Experiences + fields = '__all__' + read_only_fields = ['user', 'created_at', 'updated_at', 'verifier'] + + +class RevisionSerializer(serializers.ModelSerializer): + experience = IESerializer() + reviewer = UserSerializer() + + # experience = serializers.HyperlinkedRelatedField(view_name='experiences_api:ie_detail', + # lookup_field='id', + # lookup_url_kwarg='slug', + # read_only=True) + # reviewer = serializers.HyperlinkedRelatedField(view_name='user_profile_api:user_detail', + # lookup_field='username', + # lookup_url_kwarg='username', + # read_only=True) + message = serializers.CharField(required=False) + + class Meta: + model = Revisions + fields = '__all__' + read_only_fields = ['created_at', 'updated_at', 'id', 'reviewer'] diff --git a/website/interview_exp/api/urls.py b/website/interview_exp/api/urls.py new file mode 100644 index 00000000..393ddb4f --- /dev/null +++ b/website/interview_exp/api/urls.py @@ -0,0 +1,20 @@ +from django.urls import path + +from .views import ( + IEListView, + RetrieveUpdateIEView, + RevisionsListView, + RetrieveUpdateRevisionView, + CreateRevision, +) +from user_profile.views import activate + +app_name = 'experiences_api' + +urlpatterns = [ + path('', IEListView.as_view(), name='ie_list'), + path('/', RetrieveUpdateIEView.as_view(), name='ie_detail'), + path('/revisions//', CreateRevision.as_view(), name='add_revision'), + path('revisions/', RevisionsListView.as_view(), name='revision_list'), + path('revisions//', RetrieveUpdateRevisionView.as_view(), name='revision_detail'), +] diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py new file mode 100644 index 00000000..6266a15a --- /dev/null +++ b/website/interview_exp/api/views.py @@ -0,0 +1,184 @@ +from django.contrib.auth.models import User +from django.db.models import Q +from django.contrib.sites.shortcuts import get_current_site +from django.template.loader import render_to_string +from django.core.mail import EmailMessage +from django.shortcuts import get_object_or_404 +from django.conf import settings +from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.throttling import AnonRateThrottle +from rest_framework.response import Response +from rest_framework.filters import SearchFilter, OrderingFilter +from rest_framework.generics import ( + CreateAPIView, + ListAPIView, + ListCreateAPIView, + RetrieveUpdateAPIView, +) +from rest_framework.serializers import ValidationError +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from django_filters import rest_framework as filters +from decouple import config +from .filters import ExperiencesFilter +from interview_exp.models import Experiences, Revisions +from user_profile.models import Profile +from user_profile.utils import ThreadedMailing +from user_profile.utils_permissions import ViewUpdatePermission, IsMemberOrAbove + +from .serializers import ( + IESerializer, + RevisionSerializer +) + +# email only for trial purposes +# TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' +TRIAL_REC_MAIL = settings.TRIAL_REC_MAIL + + +class IEListView(ListCreateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = IESerializer + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filterset_class = ExperiencesFilter + search_fields = ['company', 'user__username', 'year'] + ordering_fields = ['updated_at', 'total_Compensation', 'year'] + + def get_queryset(self): + qs = Experiences.objects.all() + user = self.request.user + if user.profile.role == '3': + return qs.filter(Q(user=user) | Q(verification_Status='Approved')) + return qs + + def perform_create(self, serializer): + # TODO + # send thank you mail for posting + user = self.request.user + exp = serializer.save(user=user, verification_Status='Review Pending') + ## mailing part + member_users = User.objects.filter(~Q(profile__role='3')) + domain = get_current_site(self.request).domain + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'new_experience_entry_email.html', + { + 'user': user, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], + + ) + for user in member_users + ] + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, fail_silently=True, verbose=1) + threaded_mail.start() + + +class RetrieveUpdateIEView(RetrieveUpdateAPIView): + queryset = Profile.objects.all() + serializer_class = IESerializer + permission_classes = (ViewUpdatePermission,) + lookup_field = 'id' + lookup_url_kwarg = 'slug' + + def get_queryset(self): + user = self.request.user + qs = Experiences.objects.all() + if user.profile.role == '3': + return qs.filter(Q(user=user) | Q(verification_Status='Approved')) + return qs + + +class RevisionsListView(ListAPIView): + serializer_class = RevisionSerializer + + permission_classes = (IsMemberOrAbove,) + + def get_queryset(self): + qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() + return qs + + +class RetrieveUpdateRevisionView(RetrieveUpdateAPIView): + serializer_class = RevisionSerializer + permission_classes = (IsMemberOrAbove,) + lookup_field = 'id' + lookup_url_kwarg = 'id' + + def get_queryset(self): + qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() + return qs + + +class CreateRevision(CreateAPIView): + serializer_class = RevisionSerializer + permission_classes = (IsMemberOrAbove,) + + def perform_create(self, serializer): + """ + the review codes are: + `acc` -> Accepted + `rev` -> Review Pending + `chg` -> Changes Requested + """ + req = self.request + curr_user = req.user + exp_id = self.kwargs.get('exp_id') + exp = get_object_or_404(Experiences, pk=exp_id) + review_code = self.kwargs.get('review_code', '') + print(self.kwargs) + if review_code == 'acc': # Accepted + exp.verification_Status = 'Accepted' + exp.verifier = curr_user + # TODO + # Send mail to the author about publication + exp.save() + revisions = Revisions.objects.filter(experience=exp) + revisions.delete() + else: + msg = serializer.validated_data.get('message') + + if not msg: + raise ValidationError('Must provide message if not being accepted.') + domain = get_current_site(self.request).domain + exp_author = exp.user + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'changes_requested_email.html', + { + 'user': exp.user, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], # exp.user.email + + ) + ] + + if review_code == 'rev': + # doesn't make sense at all + exp.verification_Status = 'Review Pending' + elif review_code == 'chg': + exp.verification_Status = 'Changes Requested' + exp.save() + revision, rev_created = Revisions.objects.get_or_create(experience=exp, + defaults={'reviewer': curr_user, + 'message': msg}) + else: + raise ValidationError("Invalid code for verification status.") + + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, verbose=1) + threaded_mail.start() diff --git a/website/interview_exp/models.py b/website/interview_exp/models.py index 6879ee91..3de46806 100644 --- a/website/interview_exp/models.py +++ b/website/interview_exp/models.py @@ -4,15 +4,19 @@ from django.core.validators import MaxValueValidator, MinValueValidator from markdownx.models import MarkdownxField from markdownx.utils import markdownify +from django.urls import reverse + # Create your models here. def current_year(): return datetime.date.today().year + def max_value_current_year(value): return MaxValueValidator(current_year())(value) + class Experiences(models.Model): company = models.CharField(max_length=100) year = models.PositiveIntegerField( @@ -46,7 +50,7 @@ class Experiences(models.Model): @property def formatted_markdown(self): return markdownify(self.interview_Questions) - + def __str__(self): return str(self.company) + " " + str(self.user) @@ -54,6 +58,9 @@ def get_cname(self): class_name = "Experiences" return class_name + def get_absolute_url(self): + return reverse('experiences_api:ie_detail', kwargs={'slug': self.id}) + class Meta: managed = True ordering = ['-year', '-created_at'] @@ -77,6 +84,9 @@ def get_cname(self): class_name = "Revisions" return class_name + def get_absolute_url(self): + return reverse('experiences_api:revision_detail', kwargs={'id': self.id}) + class Meta: managed = True ordering = ['-created_at'] diff --git a/website/user_profile/api/__init__.py b/website/user_profile/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/user_profile/api/serializers.py b/website/user_profile/api/serializers.py new file mode 100644 index 00000000..698ba00b --- /dev/null +++ b/website/user_profile/api/serializers.py @@ -0,0 +1,57 @@ +from rest_framework import serializers +from rest_framework.validators import UniqueValidator +from django.contrib.auth.models import User + +from user_profile.models import Profile +from user_profile.utils import LowerEmailField + + +class RegistrationSerializer(serializers.ModelSerializer): + password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) + email = LowerEmailField( + required=True, + allow_blank=False, + label='Email address', + max_length=30, + validators=[UniqueValidator(queryset=User.objects.all())], + ) + + class Meta: + model = User + fields = ['username', 'email', 'password', 'password2'] + extra_kwargs = { + 'password': {'write_only': True} + } + + def save(self): + password = self.validated_data['password'] + password2 = self.validated_data['password2'] + if password != password2: + raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) + account = User( + username=self.validated_data['username'], + email=self.validated_data['email'].lower(), + is_active=False # TO BE CHANGED TO FALSE + ) + account.set_password(password) + account.save() + return account + + +class UserSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='user_profile_api:user_detail', lookup_field='username', + lookup_url_kwarg='username') + + class Meta: + model = User + fields = ['username', 'email', 'url'] + + +class ProfileSerializer(serializers.ModelSerializer): + user = UserSerializer() + role = serializers.CharField(source='get_role_display', ) + + class Meta: + model = Profile + exclude = ('id',) + read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) diff --git a/website/user_profile/api/urls.py b/website/user_profile/api/urls.py new file mode 100644 index 00000000..76794b21 --- /dev/null +++ b/website/user_profile/api/urls.py @@ -0,0 +1,29 @@ +from django.urls import path + +from .views import ( + RegistrationView, + # ProfileRegistrationView, + ListProfileView, + RetrieveUpdateProfileView, + UserSearchView, + username_existence_check, + email_existence_check, +) +from user_profile.views import activate + +app_name = 'user_profile_api' + +urlpatterns = [ + # Profile related(except first) + path('register/', RegistrationView.as_view(), name='user_registration'), + path('', ListProfileView.as_view(), name='all_users_list'), + # path('resend-user-activation/', resendVerificationView, name='resend-user-activation'), + path('/', RetrieveUpdateProfileView.as_view(), name='user_detail'), + path('search', UserSearchView.as_view(), name='user_search'), + path('username-check', username_existence_check, name='username_exists'), + path('email-check', email_existence_check, name='email_exists'), + + # Account Activation + path('activate///', activate, name='activate'), + +] diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py new file mode 100644 index 00000000..14bd8bdf --- /dev/null +++ b/website/user_profile/api/views.py @@ -0,0 +1,103 @@ +from django.contrib.auth.models import User +from rest_framework.permissions import IsAdminUser, IsAuthenticated +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.throttling import AnonRateThrottle +from rest_framework.response import Response +from rest_framework.generics import ( + CreateAPIView, + ListAPIView, + RetrieveUpdateAPIView, +) +from rest_framework.serializers import ValidationError +from django.contrib.sites.shortcuts import get_current_site +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView + +from user_profile.models import Profile +from user_profile.utils import send_verification_mail +from user_profile.utils_permissions import ViewUpdatePermission +from user_profile.utils import ProfileMatcher + +from .serializers import ( + RegistrationSerializer, + UserSerializer, + ProfileSerializer, +) + + +# TODO +# 1) Email verification and resend email verification + +# For customised tokens. Don't use if not needed +class MyTokenObtainPairSerializer(TokenObtainPairSerializer): + def validate(self, attrs): + data = super().validate(attrs) + refresh = self.get_token(self.user) + data['refresh'] = str(refresh) + data['access'] = str(refresh.access_token) + + # data['isStudent'] = self.user.groups.first().name == 'student' + # data['user'] = UserSerializer(self.user).data + return data + + +# For customised tokens. Don't use if not needed +class MyTokenObtainPairView(TokenObtainPairView): + serializer_class = MyTokenObtainPairSerializer + + +class RegistrationView(CreateAPIView): + serializer_class = RegistrationSerializer + + def perform_create(self, serializer): + # is_active set to false in save method of the serializer + user = serializer.save() + domain = get_current_site(self.request).domain + _ = send_verification_mail(domain=domain, user=user) + # auto creation of profile done in model signal + + +class ListProfileView(ListAPIView): + serializer_class = ProfileSerializer + queryset = Profile.objects.all() + + +class RetrieveUpdateProfileView(RetrieveUpdateAPIView): + queryset = Profile.objects.all() + serializer_class = ProfileSerializer + permission_classes = (ViewUpdatePermission,) + lookup_field = 'user__username' + lookup_url_kwarg = 'username' + + def get_queryset(self): + return Profile.objects.all() + + +class UserSearchView(ListAPIView): + serializer_class = ProfileSerializer + + def get_queryset(self): + search_query = self.request.query_params.get('query') + qs = Profile.objects.all() + if not search_query: # returns all profiles in case of empty query + return qs + matcher = ProfileMatcher(query=search_query) + # using generator to save on space + unsorted_matches = ((matcher.matcher(i), i) for i in qs if matcher.matcher(i) >= 0.5) + return [i[1] for i in sorted(unsorted_matches, key=lambda x: x[0], reverse=True)] + + +@api_view(['GET', ]) +def username_existence_check(request): + search_query = request.query_params.get('username') + if (not search_query) or (not User.objects.filter(username=search_query).exists()): + return Response(data={'response': 'Username is available!', 'exists': 0}) + return Response(data={'response': 'Username already exists!', 'exists': 1}) + + +@api_view(['GET', ]) +def email_existence_check(request): + search_query = request.query_params.get('email') + if (not search_query) or (not User.objects.filter(email=search_query.lower()).exists()): + return Response(data={'response': 'Email ID is not used yet!', 'exists': 0}) + return Response(data={'response': 'An account is registered with the email address!', 'exists': 1}) diff --git a/website/user_profile/models.py b/website/user_profile/models.py index 981f2933..683bbdb4 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -10,16 +10,19 @@ from django.contrib.auth import get_user_model from django.db.models.signals import post_save from django.dispatch import receiver +from django.urls import reverse import os from PIL import Image from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile import sys -def content_file_name(instance,filename): - ext="png" - filename= str(instance.user.username)+"."+str(ext) - return os.path.join('images/',filename) + +def content_file_name(instance, filename): + ext = "png" + filename = str(instance.user.username) + "." + str(ext) + return os.path.join('images/', filename) + class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) @@ -30,7 +33,7 @@ class Profile(models.Model): ('2', 'Member'), ('3', 'User') ) - role = models.CharField(max_length=50, choices=role_choices ,default='3') + role = models.CharField(max_length=50, choices=role_choices, default='3') dept = models.CharField(max_length=20, blank=True, null=True) url_CodeChef = models.URLField(blank=True, null=True) url_Codeforces = models.URLField(blank=True, null=True) @@ -39,26 +42,30 @@ class Profile(models.Model): image_url = models.URLField(blank=True, null=True) image = models.ImageField(blank=True, null=True, upload_to=content_file_name) email_confirmed = models.BooleanField(default=False) - created_at = models.DateTimeField(auto_now=False, auto_now_add=True) - updated_at = models.DateTimeField(auto_now=False, auto_now_add=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.user.username - def save(self, *args, **kwargs): + def save(self, *args, **kwargs): if self.image: - img= Image.open(self.image) - output = BytesIO() - img = img.resize((100, 100)) - img.save(output, format='PNG', quality=100) - output.seek(0) - self.image = InMemoryUploadedFile(output, 'ImageField', ".png" , 'image/png', - sys.getsizeof(output), None) + img = Image.open(self.image) + output = BytesIO() + img = img.resize((100, 100)) + img.save(output, format='PNG', quality=100) + output.seek(0) + self.image = InMemoryUploadedFile(output, 'ImageField', ".png", 'image/png', + sys.getsizeof(output), None) super(Profile, self).save() + def get_absolute_url(self): + return reverse('user_profile_api:user_detail', kwargs={'username': self.user.username}) + class Meta: managed = True + @receiver(post_save, sender=User) def update_user_profile(sender, instance, created, **kwargs): if created: diff --git a/website/user_profile/tokens.py b/website/user_profile/tokens.py index 3fa8b747..90e7faa7 100644 --- a/website/user_profile/tokens.py +++ b/website/user_profile/tokens.py @@ -1,5 +1,5 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator -from django.utils import six +import six class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): diff --git a/website/user_profile/utils.py b/website/user_profile/utils.py index 0c6ae2cc..3153dfe8 100644 --- a/website/user_profile/utils.py +++ b/website/user_profile/utils.py @@ -1,4 +1,15 @@ +import threading +import six from difflib import SequenceMatcher +from typing import List, Tuple, Union +from django.core.exceptions import ValidationError +from django.core.mail import EmailMessage +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.template.loader import render_to_string +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.conf import settings +from rest_framework import serializers class ProfileMatcher: @@ -26,4 +37,68 @@ def matcher(self, obj): return 0 def __str__(self): - return 'ProfileMatcher object with query="{}", ratio_threshold={} .'.format(self.query,self.ratio_threshold) + return 'ProfileMatcher object with query="{}", ratio_threshold={} .'.format(self.query, self.ratio_threshold) + + +class TokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + six.text_type(user.pk) + six.text_type(timestamp) + + six.text_type(user.is_active) + ) + + +account_activation_token = TokenGenerator() + + +class LowerEmailField(serializers.EmailField): + def to_representation(self, value): + return str(value) + + def to_internal_value(self, data): + if isinstance(data, bool) or not isinstance(data, (str, int, float,)): + self.fail('invalid') + value = str(data).lower() + return value.strip() if self.trim_whitespace else value + + +class ThreadedMailing(threading.Thread): + def __init__(self, emails: List[EmailMessage], fail_silently: bool = True, verbose: int = 0): + self.email_msgs = emails + self.fail_silently = fail_silently + self.verbose = verbose + threading.Thread.__init__(self) + + def run(self): + if self.verbose > 0: + print('Sending {} emails.'.format(len(self.email_msgs))) + for email_number, email in enumerate(self.email_msgs, start=1): + if self.verbose > 1: + print('Sending email number:', email_number) + print(email) + email.send(fail_silently=self.fail_silently) + + +# needs change, UNFIT FOR USE +def send_verification_mail(domain, user, *args, **kwargs): + if domain is None or user is None: + raise ValidationError('Domain/User instance not provided') + mail_subject = 'Activate your RECursion website account.' + message = render_to_string('account_activation_email.html', { + 'user': user, + 'domain': domain, + 'uid': urlsafe_base64_encode(force_bytes(user.id)), + 'token': account_activation_token.make_token(user), + }) + to_email = settings.TRIAL_REC_MAIL + mail = EmailMessage( + mail_subject, + message, + to=[to_email, ] + ) + + # UNCOMMENT BELOW LINES TO SEND EMAIL + threaded_mail = ThreadedMailing([mail, ]) + threaded_mail.start() + + return message diff --git a/website/user_profile/utils_permissions.py b/website/user_profile/utils_permissions.py new file mode 100644 index 00000000..c5d66e0e --- /dev/null +++ b/website/user_profile/utils_permissions.py @@ -0,0 +1,19 @@ +from rest_framework.permissions import IsAuthenticated, SAFE_METHODS, BasePermission +from user_profile.models import Profile + + +class ViewUpdatePermission(BasePermission): + def has_permission(self, request, view): + return (request.method in SAFE_METHODS) or bool(request.user and request.user.is_authenticated) + + def has_object_permission(self, request, view, obj): + if request.method in SAFE_METHODS: + return True + return bool(request.user and request.user.is_authenticated) and request.user == obj.user + + +class IsMemberOrAbove(IsAuthenticated): + def has_object_permission(self, request, view, obj): + curr_user = request.user + curr_profile = Profile.objects.get(user=curr_user) + return curr_profile.role != '3' diff --git a/website/website/settings.py b/website/website/settings.py index c4edb438..f94cf39d 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -9,11 +9,11 @@ import os from decouple import config +from datetime import timedelta # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ @@ -25,7 +25,6 @@ ALLOWED_HOSTS = ['*'] - # Application definition INSTALLED_APPS = [ @@ -46,6 +45,8 @@ 'markdownx', 'blog', 'getting_started', + 'rest_framework', + 'django_filters', 'events_calendar', ] @@ -53,7 +54,7 @@ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - #'django.middleware.csrf.CsrfViewMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -85,24 +86,76 @@ 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], + 'libraries': { + 'staticfiles': 'django.templatetags.static', + } }, }, ] WSGI_APPLICATION = 'website.wsgi.application' +# REST FRAMEWORK settings +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny', + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10, + + 'DEFAULT_THROTTLE_RATES': { + 'anon': '40/day', + }, + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', +} + +# REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' } + + +# JWT SETTINGS +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=28), # TO BE REDUCED + 'REFRESH_TOKEN_LIFETIME': timedelta(days=56), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } } - # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators @@ -121,22 +174,20 @@ }, ] - - SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', - + 'website.utils.associate_by_email', - + 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', - + 'website.utils.set_image_for_new_users' ) @@ -145,11 +196,9 @@ LOGOUT_REDIRECT_URL = 'home' LOGIN_REDIRECT_URL = 'home' - SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = config('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY') SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = config('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET') - # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ @@ -163,20 +212,29 @@ USE_TZ = True - +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # to silence the 26 errors # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = '/static/' -MEDIA_URL ='/media/' +MEDIA_URL = '/media/' -MEDIA_ROOT=os.path.join(BASE_DIR,'../website/media') +MEDIA_ROOT = os.path.join(BASE_DIR, '../website/media') EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# email stuff +# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = config("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = config("EMAIL_PASSWORD") + +TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' -#PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) -#STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') -#MEDIA_URL='/media/' -#MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') +# PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) +# STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') +# MEDIA_URL='/media/' +# MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') diff --git a/website/website/urls.py b/website/website/urls.py index f7d73d38..2ef64740 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -15,11 +15,15 @@ """ from django.contrib import admin from django.contrib.auth import views as auth_views -from django.urls import path,include +from django.urls import path, include from django.conf.urls import url, include from forum import views from django.conf import settings from django.conf.urls.static import static +from rest_framework_simplejwt.views import ( + TokenRefreshView, +) +from user_profile.api.views import MyTokenObtainPairView urlpatterns = [ @@ -39,4 +43,14 @@ # path('members/',include('members.urls')), url(r'^markdownx/', include('markdownx.urls')), -]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + # API region + + # API URLs + path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), + path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), + + # JWT + path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + + ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From e2cbbcc10efbc92c6ca7aa7efe67278e817e5a61 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 21 Dec 2022 17:25:18 +0530 Subject: [PATCH 06/50] updated requirements --- new_requirements.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 new_requirements.txt diff --git a/new_requirements.txt b/new_requirements.txt new file mode 100644 index 00000000..7608feda --- /dev/null +++ b/new_requirements.txt @@ -0,0 +1,31 @@ +asgiref==3.5.2 +beautifulsoup4==4.11.1 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==2.1.1 +cryptography==38.0.4 +defusedxml==0.7.1 +Django==3.2 +django-filter==22.1 +django-markdownx==3.0.1 +django-widget-tweaks==1.4.12 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +html2markdown==0.1.7 +idna==3.4 +Markdown==3.4.1 +oauthlib==3.2.2 +Pillow==9.3.0 +pycparser==2.21 +PyJWT==2.6.0 +python-decouple==3.6 +python3-openid==3.2.0 +pytz==2022.6 +requests==2.28.1 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.0.0 +social-auth-core==4.3.0 +soupsieve==2.3.2.post1 +sqlparse==0.4.3 +urllib3==1.26.13 From d9605f6886b42ee4d99e586c6291f008db74d505 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Thu, 22 Dec 2022 01:12:06 +0530 Subject: [PATCH 07/50] initial setup for django-prometheus --- new_requirements.txt | 2 ++ website/website/settings.py | 4 +++- website/website/urls.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/new_requirements.txt b/new_requirements.txt index 7608feda..b997eb54 100644 --- a/new_requirements.txt +++ b/new_requirements.txt @@ -8,6 +8,7 @@ defusedxml==0.7.1 Django==3.2 django-filter==22.1 django-markdownx==3.0.1 +django-prometheus==2.2.0 django-widget-tweaks==1.4.12 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 @@ -16,6 +17,7 @@ idna==3.4 Markdown==3.4.1 oauthlib==3.2.2 Pillow==9.3.0 +prometheus-client==0.15.0 pycparser==2.21 PyJWT==2.6.0 python-decouple==3.6 diff --git a/website/website/settings.py b/website/website/settings.py index f94cf39d..42e40315 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -47,10 +47,12 @@ 'getting_started', 'rest_framework', 'django_filters', + 'django_prometheus', 'events_calendar', ] MIDDLEWARE = [ + 'django_prometheus.middleware.PrometheusBeforeMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -58,7 +60,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - + 'django_prometheus.middleware.PrometheusAfterMiddleware', ] AUTHENTICATION_BACKENDS = ( diff --git a/website/website/urls.py b/website/website/urls.py index 2ef64740..b41172a7 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -52,5 +52,7 @@ # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + # Prometheus monitoring + path('', include('django_prometheus.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From 554929c7a2dd88e7182eb844060637d6f2d51573 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Thu, 22 Dec 2022 01:19:47 +0530 Subject: [PATCH 08/50] added to models --- website/interview_exp/models.py | 5 +++-- website/user_profile/models.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/website/interview_exp/models.py b/website/interview_exp/models.py index 3de46806..0e9f9f8d 100644 --- a/website/interview_exp/models.py +++ b/website/interview_exp/models.py @@ -5,6 +5,7 @@ from markdownx.models import MarkdownxField from markdownx.utils import markdownify from django.urls import reverse +from django_prometheus.models import ExportModelOperationsMixin # Create your models here. @@ -17,7 +18,7 @@ def max_value_current_year(value): return MaxValueValidator(current_year())(value) -class Experiences(models.Model): +class Experiences(ExportModelOperationsMixin('experience'), models.Model): company = models.CharField(max_length=100) year = models.PositiveIntegerField( default=current_year(), validators=[MinValueValidator(1984), max_value_current_year]) @@ -68,7 +69,7 @@ class Meta: verbose_name_plural = 'Experiences' -class Revisions(models.Model): +class Revisions(ExportModelOperationsMixin('revision'), models.Model): experience = models.OneToOneField(Experiences, on_delete=models.CASCADE) reviewer = models.ForeignKey(User, on_delete=models.CASCADE) message = models.TextField() diff --git a/website/user_profile/models.py b/website/user_profile/models.py index 683bbdb4..2b7706b4 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -15,6 +15,8 @@ from PIL import Image from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile +from django_prometheus.models import ExportModelOperationsMixin + import sys @@ -24,7 +26,7 @@ def content_file_name(instance, filename): return os.path.join('images/', filename) -class Profile(models.Model): +class Profile(ExportModelOperationsMixin('profile'), models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) name = models.CharField(max_length=100) college = models.CharField(max_length=100) From 8e7633afe541cc49dd3dce54e3d9e6f7fdec3f2c Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 00:17:35 +0530 Subject: [PATCH 09/50] modified behaviour of experience update. --- website/interview_exp/api/views.py | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index 6266a15a..fbfa39a2 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -96,6 +96,63 @@ def get_queryset(self): return qs.filter(Q(user=user) | Q(verification_Status='Approved')) return qs + def perform_update(self, serializer): + exp = serializer.save() + curr_status = exp.verification_Status + emails = [] + if curr_status == 'Approved': + exp.verification_Status = 'Review Pending' + elif curr_status == 'Changes Requested': + revision = Revisions.objects.filter(experience=exp) + if revision.exists(): + revision = revision.first() + reviewer = revision.reviewer + else: # send the mail to a random member/superuser(exceptional case that revision is not found) + reviewer = Profile.objects.filter(~Q(role='3')).first() + ## mailing part + domain = get_current_site(self.request).domain + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'update_experience_email.html', + { + 'user': reviewer, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], # to=[reviewer.email, ] + ) + ] + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, fail_silently=True, verbose=1) + threaded_mail.start() + else: + member_users = User.objects.filter(~Q(profile__role='3')) + domain = get_current_site(self.request).domain + subject = 'New Activity in Interview Experiences Section' + messages = [ + EmailMessage( + subject, + render_to_string( + 'update_Experience_to_all_email.html', + { + 'user': user, + 'domain': domain, + 'experience': exp, + } + ), + to=[TRIAL_REC_MAIL, ], # to=[reviewer.email, ] + ) + for user in member_users + ] + # uncomment below to mail + threaded_mail = ThreadedMailing(messages, fail_silently=True, verbose=1) + threaded_mail.start() + exp.save() + class RevisionsListView(ListAPIView): serializer_class = RevisionSerializer From b9f38561d731b210b79590a60a6d0b196a18af9c Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 00:26:57 +0530 Subject: [PATCH 10/50] reformat --- website/events_calendar/models.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/website/events_calendar/models.py b/website/events_calendar/models.py index d62cf242..0d73484f 100644 --- a/website/events_calendar/models.py +++ b/website/events_calendar/models.py @@ -4,10 +4,12 @@ from markdownx.utils import markdownify import os -def content_file_name(instance,filename): - ext="png" - filename= str(instance.title)+"."+str(ext) - return os.path.join('images/',filename) + +def content_file_name(instance, filename): + ext = "png" + filename = str(instance.title) + "." + str(ext) + return os.path.join('images/', filename) + class Events_Calendar(models.Model): title = models.CharField(max_length=30) @@ -24,15 +26,15 @@ class Events_Calendar(models.Model): ('NIT Durgapur', 'NIT Durgapur'), ('Global Participants', 'Global Participants'), ) - event_type = models.CharField(max_length=20, choices=event_choices ,default='Class') - target_year = models.CharField(max_length=40, choices=year_choices ,default='First Year') - description = MarkdownxField(null=True,blank=True) + event_type = models.CharField(max_length=20, choices=event_choices, default='Class') + target_year = models.CharField(max_length=40, choices=year_choices, default='First Year') + description = MarkdownxField(null=True, blank=True) image = models.ImageField(blank=True, null=True, upload_to=content_file_name) - link = models.URLField(null=True,blank=True) + link = models.URLField(null=True, blank=True) start_time = models.DateTimeField() end_time = models.DateTimeField() duration = models.CharField(max_length=20, null=True, blank=True) - venue = models.CharField(max_length=20,default="Online",null=True) + venue = models.CharField(max_length=20, default="Online", null=True) created_at = models.DateTimeField(auto_now=False, auto_now_add=True) updated_at = models.DateTimeField(auto_now=True, auto_now_add=False) @@ -40,9 +42,10 @@ class Events_Calendar(models.Model): @property def formatted_markdown(self): return markdownify(self.description) - + def __str__(self): return self.event_type + " - " + self.title + class Meta: managed = True db_table = 'events_calendar' From a424efcfeb046d67c70662ee4d3220cd73442ba6 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 00:36:03 +0530 Subject: [PATCH 11/50] added base API url events --- website/website/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/website/website/urls.py b/website/website/urls.py index b41172a7..af1dc6cb 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -48,6 +48,7 @@ # API URLs path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), + path('api/events/', include('events_calendar.api.urls'), namespace='events_api'), # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), From 2978603de2e329d4262cfadaf714b4bf86537c5b Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 01:07:22 +0530 Subject: [PATCH 12/50] fixed a misplaced ')' --- website/website/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/website/urls.py b/website/website/urls.py index af1dc6cb..082390ac 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -48,7 +48,7 @@ # API URLs path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), - path('api/events/', include('events_calendar.api.urls'), namespace='events_api'), + path('api/events/', include('events_calendar.api.urls', namespace='events_api')), # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), From 956a917552243b22d5ea61894bced606d8552703 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 22:00:26 +0530 Subject: [PATCH 13/50] added get_absolute_url method --- website/events_calendar/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/events_calendar/models.py b/website/events_calendar/models.py index 0d73484f..af90a6ee 100644 --- a/website/events_calendar/models.py +++ b/website/events_calendar/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import User +from django.urls import reverse from markdownx.models import MarkdownxField from markdownx.utils import markdownify import os @@ -46,6 +47,9 @@ def formatted_markdown(self): def __str__(self): return self.event_type + " - " + self.title + def get_absolute_url(self): + return reverse('events_api:event_detail', kwargs={'id': self.id}) + class Meta: managed = True db_table = 'events_calendar' From 71b24db5ce26bbbdb07b861d07b2a6392336c0a9 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 22:01:40 +0530 Subject: [PATCH 14/50] created API code --- website/events_calendar/api/__init__.py | 0 website/events_calendar/api/filters.py | 14 +++++++ website/events_calendar/api/permissions.py | 22 +++++++++++ website/events_calendar/api/serializers.py | 16 ++++++++ website/events_calendar/api/urls.py | 14 +++++++ website/events_calendar/api/views.py | 45 ++++++++++++++++++++++ 6 files changed, 111 insertions(+) create mode 100644 website/events_calendar/api/__init__.py create mode 100644 website/events_calendar/api/filters.py create mode 100644 website/events_calendar/api/permissions.py create mode 100644 website/events_calendar/api/serializers.py create mode 100644 website/events_calendar/api/urls.py create mode 100644 website/events_calendar/api/views.py diff --git a/website/events_calendar/api/__init__.py b/website/events_calendar/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/events_calendar/api/filters.py b/website/events_calendar/api/filters.py new file mode 100644 index 00000000..179d4fbc --- /dev/null +++ b/website/events_calendar/api/filters.py @@ -0,0 +1,14 @@ +from django_filters import rest_framework as filters +from events_calendar.models import Events_Calendar + + +class EventsFilter(filters.FilterSet): + event_type = filters.ChoiceFilter(field_name='event_type', choices=Events_Calendar.event_choices) + target_year = filters.ChoiceFilter(field_name='target_year', choices=Events_Calendar.year_choices) + + class Meta: + model = Events_Calendar + fields = { + 'start_time': ['gte', 'lte'], + 'end_time': ['gte', 'lte'], + } diff --git a/website/events_calendar/api/permissions.py b/website/events_calendar/api/permissions.py new file mode 100644 index 00000000..ee110249 --- /dev/null +++ b/website/events_calendar/api/permissions.py @@ -0,0 +1,22 @@ +from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission, SAFE_METHODS + + +class EventsListCreatePermission(BasePermission): + def has_permission(self, request, view): + if request.method in SAFE_METHODS: + return True + if not request.user.is_authenticated: + return False + role = request.user.profile.role + return role in ('1', '2') + + +class EventRetrieveUpdateDestroyPermission(IsAuthenticatedOrReadOnly): + def has_object_permission(self, request, view, obj): + if request.method in SAFE_METHODS: + return True + if not bool(request.user and request.user.is_authenticated): + return False + role = request.user.profile.role + return role in ('1', '2') + diff --git a/website/events_calendar/api/serializers.py b/website/events_calendar/api/serializers.py new file mode 100644 index 00000000..2920f184 --- /dev/null +++ b/website/events_calendar/api/serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers + +from events_calendar.models import Events_Calendar +from user_profile.api.serializers import UserSerializer + + +class EventsSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='events_api:events_detail', lookup_field='id', + lookup_url_kwarg='slug') + user = UserSerializer(read_only=True) + + class Meta: + model = Events_Calendar + # exclude = ['id', ] + fields = '__all__' + read_only_fields = ['user', 'url', 'duration', ] diff --git a/website/events_calendar/api/urls.py b/website/events_calendar/api/urls.py new file mode 100644 index 00000000..49fab142 --- /dev/null +++ b/website/events_calendar/api/urls.py @@ -0,0 +1,14 @@ +from django.urls import path + +from .views import ( + EventsListCreateView, + EventsRetrieveUpdateDestroyView, + +) + +app_name = 'events_api' + +urlpatterns = [ + path('', EventsListCreateView.as_view(), name='events_list'), + path('/', EventsRetrieveUpdateDestroyView.as_view(), name='events_detail'), +] diff --git a/website/events_calendar/api/views.py b/website/events_calendar/api/views.py new file mode 100644 index 00000000..9e473db1 --- /dev/null +++ b/website/events_calendar/api/views.py @@ -0,0 +1,45 @@ +from django_filters import rest_framework as filters + +from rest_framework.filters import SearchFilter, OrderingFilter +from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from events_calendar.models import Events_Calendar +from events_calendar.views import get_event_duration +from .serializers import EventsSerializer +from .permissions import EventsListCreatePermission, EventRetrieveUpdateDestroyPermission +from .filters import EventsFilter + + +# HIGH POTENTIAL FOR CACHING + +class EventsListCreateView(ListCreateAPIView): + serializer_class = EventsSerializer + permission_classes = (EventsListCreatePermission,) + filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter) + filterset_class = EventsFilter + search_fields = ['title', 'description', 'venue'] + ordering_fields = ['start_time', 'end_time', 'updated_at', 'duration'] + + def get_queryset(self): + return Events_Calendar.objects.select_related('user').all() + + def perform_create(self, serializer): + start_time, end_time = serializer.validated_data.get('start_time'), serializer.validated_data.get('end_time') + serializer.save(user=self.request.user, duration=get_event_duration(start_time, end_time)) + + +class EventsRetrieveUpdateDestroyView(RetrieveUpdateDestroyAPIView): + serializer_class = EventsSerializer + permission_classes = (EventRetrieveUpdateDestroyPermission,) + lookup_field = 'id' + lookup_url_kwarg = 'slug' + + def get_queryset(self): + return Events_Calendar.objects.select_related('user').all() + + def perform_update(self, serializer): + # Saving twice. Need to find a better way to do it + # Preferably using pre_save signals + event = serializer.save() + event.duration = get_event_duration(event.start_time, event.end_time) + event.save() From 54c11f2519a2f60e69e03f1ff50abaffc588b721 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Tue, 27 Dec 2022 22:59:31 +0530 Subject: [PATCH 15/50] added old mailing method(for future benchmarking) --- website/interview_exp/api/views.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index fbfa39a2..81d5a859 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -5,6 +5,7 @@ from django.core.mail import EmailMessage from django.shortcuts import get_object_or_404 from django.conf import settings +from django.core.mail import send_mass_mail from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import AnonRateThrottle @@ -57,7 +58,29 @@ def perform_create(self, serializer): # send thank you mail for posting user = self.request.user exp = serializer.save(user=user, verification_Status='Review Pending') - ## mailing part + + ### mailing part + + # # region old mailing + # profiles = Profile.objects.filter(~Q(role = '3')) + # messages = () + # f=exp + # for profile in profiles: + # user = profile.user + # current_site = get_current_site(self.request) + # subject = 'New Activity in Interview Experiences Section' + # message = render_to_string('new_experience_entry_email.html', { + # 'user': user, + # 'domain': current_site.domain, + # 'experience': Experiences.objects.get(pk=f.id), + # }) + # msg = (subject, message, 'webmaster@localhost', [TRIAL_REC_MAIL,]) + # if msg not in messages: + # messages += (msg,) + # result = send_mass_mail(messages, fail_silently=False) + # # endregion + + # region new mailing member_users = User.objects.filter(~Q(profile__role='3')) domain = get_current_site(self.request).domain subject = 'New Activity in Interview Experiences Section' @@ -78,8 +101,9 @@ def perform_create(self, serializer): for user in member_users ] # uncomment below to mail - threaded_mail = ThreadedMailing(messages, fail_silently=True, verbose=1) + threaded_mail = ThreadedMailing(messages, fail_silently=False, verbose=1) threaded_mail.start() + # endregion class RetrieveUpdateIEView(RetrieveUpdateAPIView): From 75cb8f83a61cb40cdf7c5c9221e6b55150b1e69d Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 28 Dec 2022 00:44:31 +0530 Subject: [PATCH 16/50] added base URL for Team API --- website/website/urls.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/website/website/urls.py b/website/website/urls.py index 082390ac..42fa26eb 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -27,21 +27,21 @@ urlpatterns = [ - path('admin/', admin.site.urls), - path('', views.home, name='home'), - path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'), - path('logout/', auth_views.LogoutView.as_view(), name='logout'), - path('oauth/', include('social_django.urls', namespace='social')), - path('forum/',include('forum.urls',namespace='forum')), - # path('events/',include('events.urls',namespace='events')), - path('events/',include('events_calendar.urls',namespace='events_calendar')), - path('profile/',include('user_profile.urls',namespace='user_profile')), - path('team/',include('team.urls',namespace='team')), - path('blog/',include('blog.urls',namespace='blog')), - path('experience/',include('interview_exp.urls',namespace='interview_exp')), - path('get_started/',include('getting_started.urls',namespace='getting_started')), - # path('members/',include('members.urls')), - url(r'^markdownx/', include('markdownx.urls')), + path('admin/', admin.site.urls), + path('', views.home, name='home'), + path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'), + path('logout/', auth_views.LogoutView.as_view(), name='logout'), + path('oauth/', include('social_django.urls', namespace='social')), + path('forum/', include('forum.urls', namespace='forum')), + # path('events/',include('events.urls',namespace='events')), + path('events/', include('events_calendar.urls', namespace='events_calendar')), + path('profile/', include('user_profile.urls', namespace='user_profile')), + path('team/', include('team.urls', namespace='team')), + path('blog/', include('blog.urls', namespace='blog')), + path('experience/', include('interview_exp.urls', namespace='interview_exp')), + path('get_started/', include('getting_started.urls', namespace='getting_started')), + # path('members/',include('members.urls')), + url(r'^markdownx/', include('markdownx.urls')), # API region @@ -49,6 +49,7 @@ path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), path('api/events/', include('events_calendar.api.urls', namespace='events_api')), + path('api/team/', include('team.api.urls', namespace='team_api')), # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), From 3cab03c17cce24e2b67b79d730fb9cba1525dac3 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Wed, 28 Dec 2022 00:45:36 +0530 Subject: [PATCH 17/50] added basic API for the team app --- website/team/api/__init__.py | 0 website/team/api/filters.py | 10 ++++++++++ website/team/api/permissions.py | 11 +++++++++++ website/team/api/serilazers.py | 9 +++++++++ website/team/api/urls.py | 11 +++++++++++ website/team/api/views.py | 23 +++++++++++++++++++++++ 6 files changed, 64 insertions(+) create mode 100644 website/team/api/__init__.py create mode 100644 website/team/api/filters.py create mode 100644 website/team/api/permissions.py create mode 100644 website/team/api/serilazers.py create mode 100644 website/team/api/urls.py create mode 100644 website/team/api/views.py diff --git a/website/team/api/__init__.py b/website/team/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/team/api/filters.py b/website/team/api/filters.py new file mode 100644 index 00000000..6e5af6af --- /dev/null +++ b/website/team/api/filters.py @@ -0,0 +1,10 @@ +from django_filters import rest_framework as filters +from team.models import Members + + +class MembersFilter(filters.FilterSet): + batch_year = filters.NumberFilter(field_name='batch_year', label='batch') + + class Meta: + model = Members + fields = ['batch_year', ] diff --git a/website/team/api/permissions.py b/website/team/api/permissions.py new file mode 100644 index 00000000..06d8e7ff --- /dev/null +++ b/website/team/api/permissions.py @@ -0,0 +1,11 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS + + +class MembersListCreatePermission(BasePermission): + def has_permission(self, request, view): + if request.method in SAFE_METHODS: + return True + if not request.user.is_authenticated: + return False + role = request.user.profile.role + return role in ('1', '2') diff --git a/website/team/api/serilazers.py b/website/team/api/serilazers.py new file mode 100644 index 00000000..bfd24a6e --- /dev/null +++ b/website/team/api/serilazers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from team.models import Members + + +class MemberSerializer(serializers.ModelSerializer): + class Meta: + model = Members + exclude = ('id',) diff --git a/website/team/api/urls.py b/website/team/api/urls.py new file mode 100644 index 00000000..e87ce589 --- /dev/null +++ b/website/team/api/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from .views import ( + ListCreateMembersView, +) + +app_name = 'team_api' + +urlpatterns = [ + path('', ListCreateMembersView.as_view(), name='members_list'), +] diff --git a/website/team/api/views.py b/website/team/api/views.py new file mode 100644 index 00000000..6818afb0 --- /dev/null +++ b/website/team/api/views.py @@ -0,0 +1,23 @@ +from django_filters import rest_framework as filters +from rest_framework.generics import ListCreateAPIView +from rest_framework.filters import SearchFilter, OrderingFilter + +from team.models import Members +from .serilazers import MemberSerializer +from .permissions import MembersListCreatePermission +from .filters import MembersFilter + + +# SCOPE FOR CACHING # + +class ListCreateMembersView(ListCreateAPIView): + serializer_class = MemberSerializer + permission_classes = (MembersListCreatePermission,) + pagination_class = None + filter_backends = (SearchFilter, OrderingFilter, filters.DjangoFilterBackend) + filterset_class = MembersFilter + search_fields = ['name', 'branch', 'designation'] + ordering_fields = ['batch_year', 'name', 'designation', 'branch'] + + def get_queryset(self): + return Members.objects.all() From a4a2774bc95ec35e2a346a4dcdcc9945290e85f7 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Mon, 2 Jan 2023 19:53:05 +0530 Subject: [PATCH 18/50] added django-cors-headers and python-dateutil --- new_requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/new_requirements.txt b/new_requirements.txt index b997eb54..fa1d4339 100644 --- a/new_requirements.txt +++ b/new_requirements.txt @@ -6,6 +6,7 @@ charset-normalizer==2.1.1 cryptography==38.0.4 defusedxml==0.7.1 Django==3.2 +django-cors-headers==3.13.0 django-filter==22.1 django-markdownx==3.0.1 django-prometheus==2.2.0 @@ -20,6 +21,7 @@ Pillow==9.3.0 prometheus-client==0.15.0 pycparser==2.21 PyJWT==2.6.0 +python-dateutil==2.8.2 python-decouple==3.6 python3-openid==3.2.0 pytz==2022.6 From 63e152e0cf3649e27281e930e574219dbf711093 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Mon, 2 Jan 2023 19:54:24 +0530 Subject: [PATCH 19/50] added django-cors-headers specific configuration --- website/website/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/website/settings.py b/website/website/settings.py index 42e40315..f4cfba39 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -24,6 +24,7 @@ DEBUG = True ALLOWED_HOSTS = ['*'] +CORS_ALLOW_ALL_ORIGINS = True # Application definition @@ -49,10 +50,12 @@ 'django_filters', 'django_prometheus', 'events_calendar', + 'corsheaders', ] MIDDLEWARE = [ 'django_prometheus.middleware.PrometheusBeforeMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', From f09e72cbad8b11112e7e5dec15bdc983d03afeba Mon Sep 17 00:00:00 2001 From: bishwajit Date: Sun, 22 Jan 2023 12:53:37 +0530 Subject: [PATCH 20/50] added home_view() for API --- website/forum/api/__init__.py | 0 website/forum/api/views.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100755 website/forum/api/__init__.py create mode 100755 website/forum/api/views.py diff --git a/website/forum/api/__init__.py b/website/forum/api/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/website/forum/api/views.py b/website/forum/api/views.py new file mode 100755 index 00000000..a3f7f3bb --- /dev/null +++ b/website/forum/api/views.py @@ -0,0 +1,35 @@ +from django.contrib.auth.models import User +from rest_framework.permissions import IsAdminUser, IsAuthenticated +from rest_framework.decorators import api_view, throttle_classes +from rest_framework.throttling import AnonRateThrottle +from rest_framework.response import Response + +from django.utils import timezone +import datetime +from events_calendar.api.serializers import EventsSerializer, Events_Calendar +# from events_calendar.models import Events_Calendar +from user_profile.models import User, Profile +from user_profile.api.serializers import ProfileSerializer, UserSerializer + + +@api_view(['GET', ]) +def home_view(request) -> Response: + data = {} + user = request.user + max_events = 3 + if user.is_authenticated or 1: + data['user_profile'] = ProfileSerializer(Profile.objects.get(user=user), + context={'request': request}).data + else: + data['user_profile'] = {} + + today = timezone.now() + founding_date = datetime.datetime(2014, 9, 1, 00, 00, tzinfo=today.tzinfo) + data['years_of_experience'] = (today - founding_date).days // 365 # roughly + upcoming_events = Events_Calendar.objects.filter( + start_time__range=[today - datetime.timedelta(days=150), today + datetime.timedelta(days=70)] + )[:max_events] # expects QS to be sorted by start date + data['upcoming_events'] = EventsSerializer(upcoming_events, many=True, context={'request': request}).data + data['hours_teaching'] = 300 + Events_Calendar.objects.filter(event_type='Class').count() * 2 + data['contest_count'] = 40 + Events_Calendar.objects.filter(event_type='Contest').count() + return Response(data=data) From 84fa6dbbabbc0924d579c2fe72e4d8e241f91e4b Mon Sep 17 00:00:00 2001 From: bishwajit Date: Sun, 22 Jan 2023 12:53:53 +0530 Subject: [PATCH 21/50] added url for API home view --- website/website/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/website/urls.py b/website/website/urls.py index 42fa26eb..987e076b 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -24,6 +24,7 @@ TokenRefreshView, ) from user_profile.api.views import MyTokenObtainPairView +from forum.api.views import home_view urlpatterns = [ @@ -46,6 +47,7 @@ # API region # API URLs + path('api/', home_view, name='api_home'), path('api/users/', include('user_profile.api.urls', namespace='user_profile_api')), path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), path('api/events/', include('events_calendar.api.urls', namespace='events_api')), From 825a9d2a19f862e61a011237dde48ebc208eb0a4 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Sun, 22 Jan 2023 13:02:41 +0530 Subject: [PATCH 22/50] reduced time range for upcoming_events to 7 days in future --- website/forum/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/forum/api/views.py b/website/forum/api/views.py index a3f7f3bb..9d737d36 100755 --- a/website/forum/api/views.py +++ b/website/forum/api/views.py @@ -27,7 +27,7 @@ def home_view(request) -> Response: founding_date = datetime.datetime(2014, 9, 1, 00, 00, tzinfo=today.tzinfo) data['years_of_experience'] = (today - founding_date).days // 365 # roughly upcoming_events = Events_Calendar.objects.filter( - start_time__range=[today - datetime.timedelta(days=150), today + datetime.timedelta(days=70)] + start_time__range=[today, today + datetime.timedelta(days=7)] )[:max_events] # expects QS to be sorted by start date data['upcoming_events'] = EventsSerializer(upcoming_events, many=True, context={'request': request}).data data['hours_teaching'] = 300 + Events_Calendar.objects.filter(event_type='Class').count() * 2 From 8df59ef048cd3429b80beafbe42bbfa53c32b59f Mon Sep 17 00:00:00 2001 From: bishwajit Date: Sun, 22 Jan 2023 13:02:58 +0530 Subject: [PATCH 23/50] corrected authenticated user check --- website/forum/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/forum/api/views.py b/website/forum/api/views.py index 9d737d36..bbf67fd4 100755 --- a/website/forum/api/views.py +++ b/website/forum/api/views.py @@ -17,7 +17,7 @@ def home_view(request) -> Response: data = {} user = request.user max_events = 3 - if user.is_authenticated or 1: + if user.is_authenticated: data['user_profile'] = ProfileSerializer(Profile.objects.get(user=user), context={'request': request}).data else: From 8cb669c2dc57cc55158da2ea2d3d557dbd486c55 Mon Sep 17 00:00:00 2001 From: bishwajit Date: Sun, 22 Jan 2023 13:03:16 +0530 Subject: [PATCH 24/50] increases max_events to 4 --- website/forum/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/forum/api/views.py b/website/forum/api/views.py index bbf67fd4..6387a44b 100755 --- a/website/forum/api/views.py +++ b/website/forum/api/views.py @@ -16,7 +16,7 @@ def home_view(request) -> Response: data = {} user = request.user - max_events = 3 + max_events = 4 if user.is_authenticated: data['user_profile'] = ProfileSerializer(Profile.objects.get(user=user), context={'request': request}).data From b3d01dc11517a9de6ea443a0d129c7c779d108fe Mon Sep 17 00:00:00 2001 From: bishwajit Date: Fri, 10 Feb 2023 04:40:40 +0530 Subject: [PATCH 25/50] added postman API collection link --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 59a74e66..44bcec80 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,6 @@ SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET='googleSECRET' 4. run ```python manage.py makemigrations``` 5. run ```python manage.py migrate``` 6. run ```python manage.py runserver``` + +POSTMAN API +COLLECTION: [REC DRF](https://postman.com/rec-drf-backend-00/workspace/recursion-drf-port-backend/api/3ccf67a5-e059-4d31-8d22-8f3302f94876/version/b021621d-8a18-4f86-aede-8e2737156e9c) \ No newline at end of file From a0eb788c4f6cdc3da9a0e4edc59bbd44c296d5d0 Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Thu, 28 Sep 2023 23:48:35 +0530 Subject: [PATCH 26/50] added apis for content and subtopics --- website/getting_started/api/serializers.py | 75 ++++++++++++++++++++++ website/getting_started/api/urls.py | 13 ++++ website/getting_started/api/views.py | 23 +++++++ website/website/urls.py | 1 + 4 files changed, 112 insertions(+) create mode 100644 website/getting_started/api/serializers.py create mode 100644 website/getting_started/api/urls.py create mode 100644 website/getting_started/api/views.py diff --git a/website/getting_started/api/serializers.py b/website/getting_started/api/serializers.py new file mode 100644 index 00000000..b4da88f6 --- /dev/null +++ b/website/getting_started/api/serializers.py @@ -0,0 +1,75 @@ +from rest_framework import serializers +from getting_started.models import ( + Level, + Topic, + SubTopic, + Note, + File, + Link, +) + +class SubTopicSerializer(serializers.ModelSerializer): + class Meta: + model = SubTopic + fields = ['id','sub_topic'] + +class TopicSerializer(serializers.ModelSerializer): + subtopic = SubTopicSerializer( + source='subtopic_set', + many=True, + read_only=True + ) + class Meta: + model = Topic + fields = ['Topic_title','subtopic'] + +class LevelSerializer(serializers.ModelSerializer): + topic = TopicSerializer( + source='topic_set', + many=True, + read_only=True + ) + class Meta: + model = Level + fields = '__all__' + +class NoteSerializer(serializers.ModelSerializer): + class Meta: + model = Note + fields = '__all__' + +class LinkSerializer(serializers.ModelSerializer): + class Meta: + model = Link + fields = '__all__' + +class FileSerializer(serializers.ModelSerializer): + link = LinkSerializer( + source='link_set', + many=True, + read_only=True + ) + class Meta: + model = File + fields = '__all__' + + +class SubTopicContentSerializer(serializers.ModelSerializer): + note = NoteSerializer( + source='note_set', + many=True, + read_only=True + ) + file = FileSerializer( + source='file_set', + many=True, + read_only=True + ) + class Meta: + model = SubTopic + fields = "__all__" + + + + + diff --git a/website/getting_started/api/urls.py b/website/getting_started/api/urls.py new file mode 100644 index 00000000..55e04bf0 --- /dev/null +++ b/website/getting_started/api/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from user_profile.views import activate +from .views import ( + TopicListAPIView, + SubTopicRetrieveAPIView, +) + +app_name = 'getting_started_api' + +urlpatterns = [ + path('contents/', TopicListAPIView.as_view(), name='getting_started'), + path('/', SubTopicRetrieveAPIView.as_view(), name='subtopic_detail'), +] diff --git a/website/getting_started/api/views.py b/website/getting_started/api/views.py new file mode 100644 index 00000000..cfce9302 --- /dev/null +++ b/website/getting_started/api/views.py @@ -0,0 +1,23 @@ +from rest_framework.generics import ListAPIView, RetrieveUpdateAPIView +from getting_started.models import Topic, SubTopic, Level +from .serializers import LevelSerializer , SubTopicContentSerializer + + + +class TopicListAPIView(ListAPIView): + queryset = Level.objects.all() + serializer_class = LevelSerializer + + def get_queryset(self): + level = self.request.query_params.get('level', None) + return super().get_queryset() + + +class SubTopicRetrieveAPIView(RetrieveUpdateAPIView): + queryset = SubTopic.objects.all() + serializer_class = SubTopicContentSerializer + lookup_field = 'id' + lookup_url_kwarg = 'subtopic_id' + + def get_queryset(self): + return super().get_queryset() \ No newline at end of file diff --git a/website/website/urls.py b/website/website/urls.py index 987e076b..cff55e62 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -52,6 +52,7 @@ path('api/experiences/', include('interview_exp.api.urls', namespace='experiences_api')), path('api/events/', include('events_calendar.api.urls', namespace='events_api')), path('api/team/', include('team.api.urls', namespace='team_api')), + path('api/getting_started/', include('getting_started.api.urls', namespace='getting_started_api')), # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), From c540fe1e237b3130dc7d96add22cf0da1a926f2f Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Tue, 5 Dec 2023 02:02:30 +0530 Subject: [PATCH 27/50] api endpoints for google login --- new_requirements.txt | 2 +- website/.env.example | 10 ------ website/user_profile/api/views.py | 59 +++++++++++++++++++++++++++++++ website/website/settings.py | 8 +++++ website/website/urls.py | 3 +- 5 files changed, 70 insertions(+), 12 deletions(-) delete mode 100644 website/.env.example diff --git a/new_requirements.txt b/new_requirements.txt index fa1d4339..c64aeb15 100644 --- a/new_requirements.txt +++ b/new_requirements.txt @@ -29,7 +29,7 @@ requests==2.28.1 requests-oauthlib==1.3.1 six==1.16.0 social-auth-app-django==5.0.0 -social-auth-core==4.3.0 +social-auth-core==3.3.3 soupsieve==2.3.2.post1 sqlparse==0.4.3 urllib3==1.26.13 diff --git a/website/.env.example b/website/.env.example deleted file mode 100644 index 1048a138..00000000 --- a/website/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -DB_NAME=HELLO_DJANGO -DB_USER=U_HELLO -DB_PASSWORD=hA8(scA@!fg3*sc&xaGh&6%-l<._&xCf -DB_HOST=127.0.0.1 -DB_PORT="" -SOCIAL_AUTH_GOOGLE_OAUTH2_KEY="" -SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET="" -SEND_EMAILS=False -EMAIL_HOST_USER='' -EMAIL_PASSWORD='' \ No newline at end of file diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index 14bd8bdf..545edf69 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -1,4 +1,6 @@ from django.contrib.auth.models import User +from rest_framework.views import APIView +from rest_framework.response import Response from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import AnonRateThrottle @@ -18,6 +20,11 @@ from user_profile.utils_permissions import ViewUpdatePermission from user_profile.utils import ProfileMatcher +import requests +from rest_framework_simplejwt.tokens import RefreshToken +from django.conf import settings + + from .serializers import ( RegistrationSerializer, UserSerializer, @@ -41,6 +48,58 @@ def validate(self, attrs): return data +#Google Login api handler +class GoogleLoginApi(APIView): + + def generate_token(self,user): + refresh = RefreshToken.for_user(user) + + refresh['email'] = user.email + + return { + 'refresh': str(refresh), + 'access': str(refresh.access_token), + } + + def post(self, request): + access_token = request.data.get('token') + GOOGLE_CLIENT_ID = getattr(settings, "GOOGLE_CLIENT_ID", None) + + print(GOOGLE_CLIENT_ID) + try: + TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+access_token + headers = { + "Authorization": "Bearer {access_token}", + "Content-Type": "application/json" + } + token_info = requests.get(TOKEN_INFO_URL ,data = {} ,headers = headers).json() + if token_info['issued_to'] == GOOGLE_CLIENT_ID: + try: + USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+access_token + headers = { + "Authorization": "Bearer {access_token}", + "Content-Type": "application/json" + } + user_info = requests.get(USER_INFO_URL ,data = {} ,headers = headers).json() + user, created = User.objects.get_or_create( + username = user_info['email'], + first_name = user_info['given_name'], + last_name = user_info['family_name'], + email = user_info['email'] + ) + tokens = self.generate_token(user) + return Response(data={'access': tokens['access'],'refresh':tokens['refresh'], 'response':'valid'}, status=200) + except Exception as e: + print(e) + return Response(data={'response': 'Unauthorized'}, status=401) + else: + return Response(data={'response': 'Unauthorized'}, status=401) + except Exception as e: + print(e) + return Response(data={'response': 'Invalid token'}, status=400) + + + # For customised tokens. Don't use if not needed class MyTokenObtainPairView(TokenObtainPairView): serializer_class = MyTokenObtainPairSerializer diff --git a/website/website/settings.py b/website/website/settings.py index f4cfba39..7e01c2e5 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -8,6 +8,8 @@ """ import os +from dotenv import load_dotenv +load_dotenv() from decouple import config from datetime import timedelta @@ -20,6 +22,10 @@ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '9%b%-x(_!zd(ffdc!s=8j(clv&(_92d!+lh@#o9&t8*y40v1+3' +# GOOGLE_CLIENT_ID +GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', None) + + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -236,6 +242,8 @@ EMAIL_USE_TLS = True EMAIL_HOST_USER = config("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = config("EMAIL_PASSWORD") +GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', None) + TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' diff --git a/website/website/urls.py b/website/website/urls.py index 987e076b..2986174b 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -23,7 +23,7 @@ from rest_framework_simplejwt.views import ( TokenRefreshView, ) -from user_profile.api.views import MyTokenObtainPairView +from user_profile.api.views import MyTokenObtainPairView, GoogleLoginApi from forum.api.views import home_view urlpatterns = [ @@ -55,6 +55,7 @@ # JWT path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/tokenforgoogle/', GoogleLoginApi.as_view(), name='token_for_google'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Prometheus monitoring path('', include('django_prometheus.urls')), From 8b6895e85a99618932ceb7ea13afba3f3489657b Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Tue, 5 Dec 2023 02:05:07 +0530 Subject: [PATCH 28/50] added api endpoints for google login --- website/user_profile/api/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index 545edf69..cd973cfa 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -64,8 +64,6 @@ def generate_token(self,user): def post(self, request): access_token = request.data.get('token') GOOGLE_CLIENT_ID = getattr(settings, "GOOGLE_CLIENT_ID", None) - - print(GOOGLE_CLIENT_ID) try: TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+access_token headers = { From a9bf675fbbebf03dc4e1c3554f871a184b7f61a0 Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Wed, 6 Dec 2023 23:40:49 +0530 Subject: [PATCH 29/50] fixed api and route name --- website/user_profile/api/views.py | 6 +++--- website/website/settings.py | 4 ++-- website/website/urls.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index cd973cfa..a055eb88 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -48,8 +48,8 @@ def validate(self, attrs): return data -#Google Login api handler -class GoogleLoginApi(APIView): +# Google Login api handler +class LoginWithGoogleView(APIView): def generate_token(self,user): refresh = RefreshToken.for_user(user) @@ -80,7 +80,7 @@ def post(self, request): } user_info = requests.get(USER_INFO_URL ,data = {} ,headers = headers).json() user, created = User.objects.get_or_create( - username = user_info['email'], + username = user_info['email'].split('@')[0], first_name = user_info['given_name'], last_name = user_info['family_name'], email = user_info['email'] diff --git a/website/website/settings.py b/website/website/settings.py index 7e01c2e5..eae3e6df 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -23,7 +23,7 @@ SECRET_KEY = '9%b%-x(_!zd(ffdc!s=8j(clv&(_92d!+lh@#o9&t8*y40v1+3' # GOOGLE_CLIENT_ID -GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', None) +GOOGLE_CLIENT_ID = config('GOOGLE_CLIENT_ID', None) # SECURITY WARNING: don't run with debug turned on in production! @@ -242,7 +242,7 @@ EMAIL_USE_TLS = True EMAIL_HOST_USER = config("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = config("EMAIL_PASSWORD") -GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', None) +GOOGLE_CLIENT_ID = config('GOOGLE_CLIENT_ID', None) TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' diff --git a/website/website/urls.py b/website/website/urls.py index 751e4580..86ce87d2 100755 --- a/website/website/urls.py +++ b/website/website/urls.py @@ -23,7 +23,7 @@ from rest_framework_simplejwt.views import ( TokenRefreshView, ) -from user_profile.api.views import MyTokenObtainPairView, GoogleLoginApi +from user_profile.api.views import MyTokenObtainPairView, LoginWithGoogleView from forum.api.views import home_view urlpatterns = [ @@ -55,8 +55,8 @@ path('api/getting_started/', include('getting_started.api.urls', namespace='getting_started_api')), # JWT + path('api/token/google/', LoginWithGoogleView.as_view(), name='token_for_google'), path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), - path('api/tokenforgoogle/', GoogleLoginApi.as_view(), name='token_for_google'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Prometheus monitoring path('', include('django_prometheus.urls')), From e33486ff4f872863a3fffc9516513fe22fa72afb Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Sun, 3 Mar 2024 20:54:31 +0530 Subject: [PATCH 30/50] added views for roles and IE reviews filtering --- website/interview_exp/api/views.py | 12 ++++++++---- website/user_profile/api/urls.py | 3 ++- website/user_profile/api/views.py | 12 ++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index 81d5a859..861fca92 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -47,6 +47,7 @@ class IEListView(ListCreateAPIView): ordering_fields = ['updated_at', 'total_Compensation', 'year'] def get_queryset(self): + print('yes') qs = Experiences.objects.all() user = self.request.user if user.profile.role == '3': @@ -54,6 +55,7 @@ def get_queryset(self): return qs def perform_create(self, serializer): + print("yes") # TODO # send thank you mail for posting user = self.request.user @@ -181,7 +183,7 @@ def perform_update(self, serializer): class RevisionsListView(ListAPIView): serializer_class = RevisionSerializer - permission_classes = (IsMemberOrAbove,) + # permission_classes = (IsMemberOrAbove,) def get_queryset(self): qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() @@ -190,18 +192,20 @@ def get_queryset(self): class RetrieveUpdateRevisionView(RetrieveUpdateAPIView): serializer_class = RevisionSerializer - permission_classes = (IsMemberOrAbove,) - lookup_field = 'id' + # permission_classes = (IsMemberOrAbove,) + lookup_field = 'experience_id' lookup_url_kwarg = 'id' def get_queryset(self): qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() + if (qs[0].experience.verification_Status=="Approved"): + Revisions.objects.filter(experience=qs[0].experience).update(message="No Changes Required") return qs class CreateRevision(CreateAPIView): serializer_class = RevisionSerializer - permission_classes = (IsMemberOrAbove,) + # permission_classes = (IsMemberOrAbove,) def perform_create(self, serializer): """ diff --git a/website/user_profile/api/urls.py b/website/user_profile/api/urls.py index 76794b21..4a1474ce 100644 --- a/website/user_profile/api/urls.py +++ b/website/user_profile/api/urls.py @@ -8,6 +8,7 @@ UserSearchView, username_existence_check, email_existence_check, + GetProfileRoleView ) from user_profile.views import activate @@ -22,7 +23,7 @@ path('search', UserSearchView.as_view(), name='user_search'), path('username-check', username_existence_check, name='username_exists'), path('email-check', email_existence_check, name='email_exists'), - + path('roles', GetProfileRoleView.as_view(), name='roles'), # Account Activation path('activate///', activate, name='activate'), diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index a055eb88..5d1dcd45 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -97,6 +97,18 @@ def post(self, request): return Response(data={'response': 'Invalid token'}, status=400) +# For Getting The Role of the User +class GetProfileRoleView(APIView): + permission_classes = (IsAuthenticated,) + + def post(self, request): + try: + profile = Profile.objects.get(user=request.user) + return Response(data={'role': profile.role}, status=200) + except Exception as e: + print(e) + return Response(data={'response': 'Unauthorized'}, status=401) + # For customised tokens. Don't use if not needed class MyTokenObtainPairView(TokenObtainPairView): From 429cc6d5b6e3b9313c93178acd4c47fd2df7a03a Mon Sep 17 00:00:00 2001 From: Sivam2313 Date: Tue, 5 Mar 2024 22:19:38 +0530 Subject: [PATCH 31/50] changes --- website/interview_exp/api/views.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index 861fca92..0ea536cc 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -47,7 +47,6 @@ class IEListView(ListCreateAPIView): ordering_fields = ['updated_at', 'total_Compensation', 'year'] def get_queryset(self): - print('yes') qs = Experiences.objects.all() user = self.request.user if user.profile.role == '3': @@ -55,7 +54,6 @@ def get_queryset(self): return qs def perform_create(self, serializer): - print("yes") # TODO # send thank you mail for posting user = self.request.user @@ -183,7 +181,7 @@ def perform_update(self, serializer): class RevisionsListView(ListAPIView): serializer_class = RevisionSerializer - # permission_classes = (IsMemberOrAbove,) + permission_classes = (IsMemberOrAbove,) def get_queryset(self): qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() @@ -192,20 +190,20 @@ def get_queryset(self): class RetrieveUpdateRevisionView(RetrieveUpdateAPIView): serializer_class = RevisionSerializer - # permission_classes = (IsMemberOrAbove,) + permission_classes = (IsMemberOrAbove,) lookup_field = 'experience_id' lookup_url_kwarg = 'id' def get_queryset(self): qs = Revisions.objects.prefetch_related('experience').prefetch_related('reviewer').all() - if (qs[0].experience.verification_Status=="Approved"): - Revisions.objects.filter(experience=qs[0].experience).update(message="No Changes Required") + if (qs.first().experience.verification_Status=="Approved"): + Revisions.objects.filter(experience=qs.first().experience).update(message="No Changes Required") return qs class CreateRevision(CreateAPIView): serializer_class = RevisionSerializer - # permission_classes = (IsMemberOrAbove,) + permission_classes = (IsMemberOrAbove,) def perform_create(self, serializer): """ From 50a6530751d69428a16eacc3ad90fe62e5cb60ed Mon Sep 17 00:00:00 2001 From: Moinak Banerjee <32922277+moinak878@users.noreply.github.com> Date: Wed, 29 May 2024 17:01:27 +0530 Subject: [PATCH 32/50] fix google login api --- website/user_profile/api/views.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index 5d1dcd45..2be95f1c 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -68,22 +68,20 @@ def post(self, request): TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+access_token headers = { "Authorization": "Bearer {access_token}", - "Content-Type": "application/json" + "Content-Type": "application/json" } token_info = requests.get(TOKEN_INFO_URL ,data = {} ,headers = headers).json() if token_info['issued_to'] == GOOGLE_CLIENT_ID: try: USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+access_token - headers = { - "Authorization": "Bearer {access_token}", - "Content-Type": "application/json" - } user_info = requests.get(USER_INFO_URL ,data = {} ,headers = headers).json() user, created = User.objects.get_or_create( username = user_info['email'].split('@')[0], - first_name = user_info['given_name'], - last_name = user_info['family_name'], - email = user_info['email'] + defaults = { + 'first_name': user_info.get('name', ''), + 'last_name': user_info.get('given_name', ''), + 'email': user_info['email'] + } ) tokens = self.generate_token(user) return Response(data={'access': tokens['access'],'refresh':tokens['refresh'], 'response':'valid'}, status=200) @@ -97,6 +95,7 @@ def post(self, request): return Response(data={'response': 'Invalid token'}, status=400) + # For Getting The Role of the User class GetProfileRoleView(APIView): permission_classes = (IsAuthenticated,) From 4225ac0d8c2c0f43c2d0299d9945c65143ee7e3a Mon Sep 17 00:00:00 2001 From: Shibshankar0909 <123229119+Shibshankar0909@users.noreply.github.com> Date: Sun, 27 Oct 2024 18:04:11 +0530 Subject: [PATCH 33/50] created API to get alumni year wise --- website/team/api/urls.py | 2 ++ website/team/api/views.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/website/team/api/urls.py b/website/team/api/urls.py index e87ce589..ff69082f 100644 --- a/website/team/api/urls.py +++ b/website/team/api/urls.py @@ -2,10 +2,12 @@ from .views import ( ListCreateMembersView, + AlumniYearWiseView ) app_name = 'team_api' urlpatterns = [ path('', ListCreateMembersView.as_view(), name='members_list'), + path('alumni/', AlumniYearWiseView.as_view(), name='alumni_year_wise'), ] diff --git a/website/team/api/views.py b/website/team/api/views.py index 6818afb0..5f1acd43 100644 --- a/website/team/api/views.py +++ b/website/team/api/views.py @@ -7,7 +7,10 @@ from .permissions import MembersListCreatePermission from .filters import MembersFilter - +from rest_framework import generics +from team.models import Members +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from django_filters import rest_framework as filters # SCOPE FOR CACHING # class ListCreateMembersView(ListCreateAPIView): @@ -21,3 +24,17 @@ class ListCreateMembersView(ListCreateAPIView): def get_queryset(self): return Members.objects.all() + + + + +class AlumniYearWiseView(generics.ListAPIView): + serializer_class = MemberSerializer + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = MembersFilter + + def get_queryset(self): + year = self.request.query_params.get('year', None) + if year: + return Members.objects.filter(batch_year=year).order_by('-batch_year', 'name') + return Members.objects.all().order_by('-batch_year', 'name') # Default to all alumni if no year is specified From aa11ca4976c95d015419c4b0c27031a0cd585ac4 Mon Sep 17 00:00:00 2001 From: Avishek Paul Date: Thu, 9 Jan 2025 19:12:10 +0530 Subject: [PATCH 34/50] deployment changes --- .gitignore | 1 + new_requirements.txt | 2 +- website/package-lock.json | 6 ++++++ website/website/settings.py | 1 - 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 website/package-lock.json diff --git a/.gitignore b/.gitignore index 7b8a34cc..d39a0ebe 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ website/db.sqlit .env .venv env/ +myEnv/ venv/ ENV/ env.bak/ diff --git a/new_requirements.txt b/new_requirements.txt index c64aeb15..38dff1c8 100644 --- a/new_requirements.txt +++ b/new_requirements.txt @@ -28,7 +28,7 @@ pytz==2022.6 requests==2.28.1 requests-oauthlib==1.3.1 six==1.16.0 -social-auth-app-django==5.0.0 +social-auth-app-django==4.0.0 social-auth-core==3.3.3 soupsieve==2.3.2.post1 sqlparse==0.4.3 diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 00000000..4ff32e61 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "website", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/website/website/settings.py b/website/website/settings.py index eae3e6df..7c35f057 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -159,7 +159,6 @@ # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', From d32babbd00bb6898a8ce3d6a249f35df4c92aefd Mon Sep 17 00:00:00 2001 From: Avishek Paul Date: Thu, 9 Jan 2025 20:16:13 +0530 Subject: [PATCH 35/50] changes --- .gitignore | 4 +++- new_requirements.txt | Bin 713 -> 1720 bytes website/website/settings.py | 13 +++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d39a0ebe..7b49a20a 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,6 @@ website/events_calendar/migrations/ website/migrations/ # Media files -media/ \ No newline at end of file +media/ + +staticfiles/ \ No newline at end of file diff --git a/new_requirements.txt b/new_requirements.txt index 38dff1c8df10fc945f38ac97269b2e9e6ffd1acb..4df39f50fb2acc1f7917ef4824da1eb79b3bf28d 100644 GIT binary patch literal 1720 zcmZ{kOOMk)5QO`T#82@mal$SfxN$;4+5-|tgt~U_qVWR4Y?vM=^HxpmzsF{0R)idCsV`6y~>d z^+Ip-ztI1!-gr$^G}FBd?3Hp*4GfiCl=~2HAnryTmHiNVrA)o5qNjOeV|)YB#w2q z*W1R<(sdcLQ>XWL8qo^h$PIi)7Y;?IVevYrF=zFpQqA70UQE=pA0p|k&qzKvpK4*| zPGVY%wFs=|;L3i~IS;YVSznLLlyRqAtvXc8x3lucUQmIn*y=4k&-FX?2&}skCza}s zLh*&3_ym1XCE|sCwml2se30i$3cthxUt*dNTW=guWdlBMxLbhUeO0#bP*LF zq#{knOFd)0rkq|A6X|ZYQdi2Qc+TQ1;gCdH*DAVI?O*?gf2yA-H)f(Mv-{;E8v)?G`-i@g_JbP2$r73tSCt}_u3zPNn6QT znW#rq;1hX0^=TJA#lZKxcPOMg?jqCZ=gzlW@{m1R>ML6<8}sn5)rG6+q@b8nMCD$~ zYtg<}lBXzHggJ|Q8%4F;bPVo*;n_+N=P@d0cGHBQ0e*CN#$H`*m5q(}UHL9Ur(j?* j+{JkM<1EmDxKmRnK>4irc4c~3;sJqkd6+)7B75#XKWztm literal 713 zcmZ8f!H(M?5WVv+AcLK3_Q0*Dl`8F_QqRPUV>Z|j!1X#`-x;!Qw7tPR=Djy>F!|yV z3|hD1AQU@;S&ADLpTiP6tvey5kgSJj7uFASNAN)SXi|p4=a44j-5nA> z6}2;Mv4l%9aokYkBpT5%3&WDZeokKNM!X=wj}J3n0=ZDk)-&%z%6tT4A!Pz&E1De} zoG(=aMfTcc($$7_{-npvvmR5Jpp38}ixTQdvO0S~&t(O3MNA4Dc2`f3%aF_jYe?6s z-u-L*hVksC=;326T4R7{e=L(%yBV5KVp`75hG;tW^UG$vq04}QkKUc>PVMi``><;L zDrnxPdER>mGpZorfsn0_6nafsN%p?I{r*K09xGVJFmr1P;5sp7B~Hj4w?`Rz+^z?b zJbo2h9wN-nK5P(uxnmox`^Xa977XMO;0aCd1^#p|(XV9LVf?svGLb{?jOR2TH!q3liUa9hc_s@Y#|)hSrjpI^y48e*qVa;kN() diff --git a/website/website/settings.py b/website/website/settings.py index 7c35f057..df0c93ef 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -12,6 +12,7 @@ load_dotenv() from decouple import config from datetime import timedelta +import dj_database_url # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -29,7 +30,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["127.0.0.1", "localhost"] + CORS_ALLOW_ALL_ORIGINS = True # Application definition @@ -160,12 +162,10 @@ # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } + 'default': dj_database_url.config( + default="sqlite:///" + os.path.join(BASE_DIR, "db.sqlite3") + ) } - # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators @@ -245,6 +245,7 @@ TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) # STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') From 4f08c83d8a9c5cce5414c3b9b873eb9d190cd4a7 Mon Sep 17 00:00:00 2001 From: apaul42 Date: Tue, 21 Oct 2025 01:35:08 +0530 Subject: [PATCH 36/50] New requirements for 3.11 python added, Blog and interview experience fixed by adding a new static image folder and a static image, fixed email activation --- new_requirements.txt | Bin 1720 -> 1718 bytes new_requirements2.txt | 40 ++++++++++ website/blog/templates/blog/blog_list.html | 73 ++++++++++-------- website/interview_exp/templates/exp_list.html | 55 ++++++++----- website/static/default_profile.png | Bin 0 -> 138120 bytes website/user_profile/utils.py | 17 ++++ website/user_profile/views.py | 58 +++++++------- website/website/settings.py | 22 +++--- 8 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 new_requirements2.txt create mode 100644 website/static/default_profile.png diff --git a/new_requirements.txt b/new_requirements.txt index 4df39f50fb2acc1f7917ef4824da1eb79b3bf28d..375f1a709c2be7197712102ebc36b251a3e981c2 100644 GIT binary patch delta 109 zcmdnNyN!2)1EaD5gC2t^5F0S?GH@}tFk~?#GUPF&GvqVa0--Tb#%S_FM&Zp>j9Zx4 u4S~w^7z`)(vxsi?U`b)*1nD#Zt0`xd6-@@}Dq=_l>ja5{WhXypy$Aqp4;4fJ delta 91 zcmdnSyMuRw1EabTgC2u15F0S?GH@}tFk~?#GUPF&GvqVa0--5T#t?{2HYYP4XJR!2 gDco$xQpw0_0%A@sWL2Deft807WC9pZe#&|g0QfZ#$N&HU diff --git a/new_requirements2.txt b/new_requirements2.txt new file mode 100644 index 00000000..e352f6df --- /dev/null +++ b/new_requirements2.txt @@ -0,0 +1,40 @@ +asgiref==3.8.1 +beautifulsoup4==4.11.1 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==2.1.1 +cryptography==41.0.7 +defusedxml==0.7.1 +dj-database-url==0.5.0 +Django==3.2.25 +django-cors-headers==3.13.0 +django-filter==22.1 +django-markdownx==3.0.1 +django-prometheus==2.2.0 +django-widget-tweaks==1.4.12 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +html2markdown==0.1.7 +idna==3.4 +Markdown==3.4.1 +mysqlclient==2.2.6 +oauthlib==3.2.2 +Pillow==10.1.0 +prometheus-client==0.15.0 +pycparser==2.21 +PyJWT==2.10.1 +python-dateutil==2.8.2 +python-decouple==3.6 +python-dotenv==1.0.1 +python3-openid==3.2.0 +pytz==2023.3.post1 +requests==2.31.0 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.4.2 +social-auth-core==4.5.4 +soupsieve==2.3.2.post1 +sqlparse==0.4.4 +typing_extensions==4.12.2 +tzdata==2024.2 +urllib3==2.1.0 diff --git a/website/blog/templates/blog/blog_list.html b/website/blog/templates/blog/blog_list.html index 91c17084..9a5d2829 100644 --- a/website/blog/templates/blog/blog_list.html +++ b/website/blog/templates/blog/blog_list.html @@ -1,4 +1,6 @@ +{% load static %} {% load blog_tags %} + {% for post in posts %}
    • @@ -7,56 +9,63 @@
      {% for p in profile %} {% if p.user == post.user_id %} - img + {% if p.image %} + img + {% else %} + default + {% endif %} {% endif %} {% endfor %}
      +
      +
      -
    • Posted by {{post.user_id}} on
    • +
    • Posted by {{ post.user_id }} on
    • -
    • {{post.created_at}}
    • + +
    • {{ post.created_at }}

    • +
      • - {% for tagging in taggings %} - {% if tagging.post == post %} - {% for tag in tags %} - {% if tagging.tag == tag %} - {% comment %}
      • {{tag.name}}
      • {% endcomment %} -
      • {{ tag.name }}
      • - {% endif %} - {% endfor %} - {%endif%} + {% for tagging in taggings %} + {% if tagging.post == post %} + {% for tag in tags %} + {% if tagging.tag == tag %} +
      • + {{ tag.name }} +
      • + {% endif %} + {% endfor %} + {% endif %} {% endfor %}
    • - -
      -
      -
      - {%if post.reply_set.count == 1 %} - {{ post.reply_set.count }} comment - {%else %} - {{ post.reply_set.count }} comments - {%endif%} - -
      -
      - {{post.id|postlike_count}} vote(s) -
      -
      + +
      +
      +
      + {% if post.reply_set.count == 1 %} + {{ post.reply_set.count }} comment + {% else %} + {{ post.reply_set.count }} comments + {% endif %}
      - -
      +
      + {{ post.id|postlike_count }} vote(s) +
      +
      +
      +
  • -
    -{% endfor %} \ No newline at end of file +
    +{% endfor %} diff --git a/website/interview_exp/templates/exp_list.html b/website/interview_exp/templates/exp_list.html index 976bac44..6f7c65b1 100644 --- a/website/interview_exp/templates/exp_list.html +++ b/website/interview_exp/templates/exp_list.html @@ -1,32 +1,51 @@ +{% load static %} + {% for experience in experiences %} -
    +
    {% for p in profile %} - {% if p.user == experience.user %} - img - {% endif %} + {% if p.user == experience.user %} + {% if p.image %} + img + {% else %} + default + {% endif %} + {% endif %} {% endfor %}
    +
    - {% if experience.verification_Status == "Approved" %} - {{ experience.company }} Interview Experience {{ experience.year }} # {{ experience.id }} - {% else %} - {{ experience.company }} Interview Experience {{ experience.year }} # {{ experience.id }} - {% endif %} + {% if experience.verification_Status == "Approved" %} + + {{ experience.company }} Interview Experience {{ experience.year }} #{{ experience.id }} + + {% else %} + + {{ experience.company }} Interview Experience {{ experience.year }} #{{ experience.id }} + + {% endif %} + {% if request.user == experience.user %} -
    - -
    +
    + +
    {% endif %} +
      -
    • {{ experience.job_Profile }} / {{experience.role_Type}}
    • +
    • + {{ experience.job_Profile }} / + {{ experience.role_Type }} +
    • -
    • Added by {{experience.user}}
    • - {% load staticfiles %} -
    • {{ experience.updated_at }}
    • +
    • + Added by {{ experience.user }} +
    • +
    • + {{ experience.updated_at }} +
    -
    - {% endfor %} +
    +{% endfor %} diff --git a/website/static/default_profile.png b/website/static/default_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..1a889b9cc83ba61a7f6d4ee7cbc6801ecc4dcc70 GIT binary patch literal 138120 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

    eR^8v^9T1iH9 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�-hbdfm3#+FV8Y-N&q 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 Date: Tue, 21 Oct 2025 16:13:25 +0530 Subject: [PATCH 37/50] Login fixed --- website/import_scripts/import_users.py | 4 ++ website/static/default_profile.png | Bin 138120 -> 0 bytes website/user_profile/views.py | 7 +++ website/website/settings.py | 7 ++- website/website/utils.py | 58 ++++++++++++++++++------- 5 files changed, 59 insertions(+), 17 deletions(-) delete mode 100644 website/static/default_profile.png diff --git a/website/import_scripts/import_users.py b/website/import_scripts/import_users.py index ac782c02..fef5a154 100644 --- a/website/import_scripts/import_users.py +++ b/website/import_scripts/import_users.py @@ -35,6 +35,10 @@ u.profile.college = row['College'] u.profile.dept = row['Dept'] u.profile.email_confirmed=True + #Development + #For development + # image_url = 'http://127.0.0.1:8000/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' + #Production image_url = 'https://recursionnitd.in/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' full_path = 'media/images/' + username + '.png' try: diff --git a/website/static/default_profile.png b/website/static/default_profile.png deleted file mode 100644 index 1a889b9cc83ba61a7f6d4ee7cbc6801ecc4dcc70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138120 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

    eR^8v^9T1iH9 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�-hbdfm3#+FV8Y-N&q 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.'. + """ + try: + # Request with a common User-Agent to avoid blocking by some servers + req = urllib.request.Request(image_url, headers={'User-Agent': 'Mozilla/5.0'}) + with urllib.request.urlopen(req, timeout=10) as resp: + data = resp.read() + # try to get content-type to derive extension + content_type = resp.info().get_content_type() + ext = mimetypes.guess_extension(content_type) or '.png' + filename = f"images/{user.username}{ext}" + # Save using Django File storage so ImageField works as expected + user.profile.image.save(filename, ContentFile(data), save=True) + return True + except (HTTPError, URLError, ValueError, Exception) as e: + # print full exception so you can diagnose (network, ssl, 403, etc.) + print("profile image download failed:", repr(e)) + return False + def set_image_for_new_users(backend, user, response, *args, **kwargs): #import pdb;pdb.set_trace(); try: @@ -32,22 +59,21 @@ def set_image_for_new_users(backend, user, response, *args, **kwargs): user.save() if not user.profile.image: - full_path = 'media/images/' + user.username + '.png' + # try provider picture first try: - image_url = response['picture'] - urllib.request.urlretrieve(image_url, full_path) - user.profile.image = '../' + full_path - user.save() - except: - try: - image_url = 'https://recursionnitd.in/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - urllib.request.urlretrieve(image_url, full_path) - user.profile.image = '../' + full_path - user.save() - except: + image_url = response.get('picture') + if image_url: + ok = _save_image_from_url_to_profile(user, image_url) + if ok: + return + # fallback to site default + image_url = 'https://recursionnitd.in/static/image/profile_pic/' + str(random.randint(1,15)) + '.png' + ok = _save_image_from_url_to_profile(user, image_url) + if not ok: print("Downloadable Image Not Found!") - + except Exception as e: + print("set_image_for_new_users error:", repr(e)) + print("Downloadable Image Not Found!") except Exception as e: - print(e) - print("error") + print("set_image_for_new_users outer error:", repr(e)) pass \ No newline at end of file From 6067caf35bd53a7ecb84720f5f5b22b67e4aed49 Mon Sep 17 00:00:00 2001 From: apaul42 Date: Mon, 27 Oct 2025 15:12:48 +0530 Subject: [PATCH 38/50] changed footer content and fixed profile image problem --- website/forum/templates/base.html | 2 +- website/import_scripts/import_users.py | 22 ++++++----- website/user_profile/views.py | 51 ++++++++++++-------------- website/website/utils.py | 46 ++++++++++++++++++++++- 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/website/forum/templates/base.html b/website/forum/templates/base.html index d5e92ce2..42060061 100644 --- a/website/forum/templates/base.html +++ b/website/forum/templates/base.html @@ -165,7 +165,7 @@

    Contacts

    Phones:
    -
    Ankit Maskara: +918420998766 +
    Prathamesh Mandiye: +918240048380
    diff --git a/website/import_scripts/import_users.py b/website/import_scripts/import_users.py index fef5a154..05fa6afe 100644 --- a/website/import_scripts/import_users.py +++ b/website/import_scripts/import_users.py @@ -6,6 +6,7 @@ import csv from random import choice from string import ascii_uppercase +from website.utils import save_local_profile_pic_to_media randhash = ''.join(choice(ascii_uppercase) for i in range(32)) @@ -38,19 +39,22 @@ #Development #For development # image_url = 'http://127.0.0.1:8000/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - #Production - image_url = 'https://recursionnitd.in/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - full_path = 'media/images/' + username + '.png' + #Production: copy local static into MEDIA instead of fetching external URL try: - urllib.request.urlretrieve(image_url, full_path) - except: + rel_media = save_local_profile_pic_to_media(username) + except Exception as e: + print("import_users: save_local_profile_pic_to_media exception:", repr(e)) + rel_media = False + + if not rel_media: print("Downloadable Image Not Found!") - u.profile.image = '../' + full_path + else: + u.profile.image = rel_media # relative to MEDIA_ROOT u.save() - - - + + + diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 1b1a5eeb..6f3f0487 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -23,6 +23,7 @@ from .tokens import account_activation_token, password_reset_token from django.contrib import messages from .utils import ProfileMatcher, valid_url_extension +from website.utils import save_local_profile_pic_to_media import random from forum.models import * from blog.models import * @@ -162,20 +163,21 @@ def activate(request, uidb64, token, backend='django.contrib.auth.backends.Model user.save() login(request, user, backend='django.contrib.auth.backends.ModelBackend') profile = Profile.objects.get(user = user) - #For development - # image_url = 'http://127.0.0.1:8000/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - #For production - image_url = 'https://recursionnitd.in/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - # valid_url_extension imported from .utils - _ = valid_url_extension(image_url) - full_path = 'media/images/' + profile.user.username + '.png' + # Copy a local static profile pic into MEDIA and set ImageField try: - urllib.request.urlretrieve(image_url, full_path) - except: - return HttpResponse("Downloadable Image Not Found!") - if profile.user == request.user: - profile.image = '../' + full_path + rel_media = save_local_profile_pic_to_media(profile.user.username) + except Exception as e: + print("activate: save_local_profile_pic_to_media exception:", repr(e)) + rel_media = False + + if not rel_media: + print("Downloadable Image Not Found!") + else: + profile.image.name = rel_media # set path relative to MEDIA_ROOT profile.save() + + if profile.user == request.user: + return redirect('user_profile:edit_profile') return redirect('user_profile:edit_profile') else: return render(request, 'account_activation_invalid.html') @@ -189,21 +191,16 @@ def edit_profile(request): form = Profileform(request.POST or None, request.FILES or None, instance=profile) if form.is_valid(): form.save() - if form.cleaned_data['image'] is None or form.cleaned_data['image'] == False: - #Development - #For development - #image_url = 'http://127.0.0.1:8000/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - #Production - image_url = 'https://recursionnitd.in/'+'static/image/profile_pic/' + str(random.randint(1,15)) + '.png' - type = valid_url_extension(image_url) - full_path = 'media/images/' + profile.user.username + '.png' - try: - urllib.request.urlretrieve(image_url, full_path) - except: - return HttpResponse("Downloadable Image Not Found!") - if profile.user == request.user: - profile.image = '../' + full_path - form.save() + # ensure a local default exists if no image supplied + if not profile.image: + try: + rel_media = save_local_profile_pic_to_media(profile.user.username) + except Exception as e: + print("edit_profile: save_local_profile_pic_to_media exception:", repr(e)) + rel_media = False + if rel_media: + profile.image.name = rel_media + profile.save() return HttpResponseRedirect(reverse('user_profile:view_profile', args=(id,))) return render(request, 'create.html', {'form': form, }) diff --git a/website/website/utils.py b/website/website/utils.py index 9f22e401..0beadff5 100644 --- a/website/website/utils.py +++ b/website/website/utils.py @@ -8,6 +8,8 @@ from django.core.files.base import ContentFile from django.conf import settings from django.shortcuts import redirect +from django.contrib.staticfiles import finders +import shutil def associate_by_email(**kwargs): try: @@ -76,4 +78,46 @@ def set_image_for_new_users(backend, user, response, *args, **kwargs): print("Downloadable Image Not Found!") except Exception as e: print("set_image_for_new_users outer error:", repr(e)) - pass \ No newline at end of file + pass + +def save_local_profile_pic_to_media(username): + """ + Copy a random profile pic from static/image/profile_pic/<1-15>.png + into MEDIA_ROOT/images/.png. + + Returns the relative path to MEDIA (e.g. 'images/username.png') on success, + or False on failure. + Works on Linux and Windows by using os.path.join and Django settings. + """ + try: + n = random.randint(1, 15) + rel_static_path = os.path.join('image', 'profile_pic', f'{n}.png') + + # 1) Try Django staticfiles finders (dev & when using collectstatic + staticfiles app) + src = finders.find(rel_static_path) + + # 2) Fallback to STATIC_ROOT (deployed collected static) + if not src and getattr(settings, 'STATIC_ROOT', None): + src = os.path.join(settings.STATIC_ROOT, rel_static_path) + + # 3) Fallback to project-level "static" directory (common in development) + if not src: + src = os.path.join(getattr(settings, 'BASE_DIR', ''), 'static', rel_static_path) + + if not src or not os.path.exists(src): + print("local static profile pic not found:", rel_static_path, "resolved to:", src) + return False + + media_root = getattr(settings, 'MEDIA_ROOT', None) or os.path.join(getattr(settings, 'BASE_DIR', ''), 'media') + dest_dir = os.path.join(media_root, 'images') + os.makedirs(dest_dir, exist_ok=True) + + dest_path = os.path.join(dest_dir, f"{username}.png") + shutil.copyfile(src, dest_path) + + # return path relative to MEDIA_ROOT (ImageField stores relative paths) + relative_media_path = os.path.join('images', f"{username}.png").replace('\\', '/') + return relative_media_path + except Exception as e: + print("save_local_profile_pic_to_media error:", repr(e)) + return False \ No newline at end of file From cc4a96f4b49e5cf735c5999e8409a4923233134f Mon Sep 17 00:00:00 2001 From: apaul42 Date: Wed, 12 Nov 2025 19:37:08 +0530 Subject: [PATCH 39/50] missing profile picture fixed --- website/blog/templates/blog/blog_list.html | 2 +- .../forum/static/image/profile_pic/default.png | Bin 0 -> 147813 bytes .../interview_exp/templates/exp_detail.html | 6 +++++- website/interview_exp/templates/exp_list.html | 2 +- .../templates/profile/profile.html | 11 ++++++++--- 5 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 website/forum/static/image/profile_pic/default.png diff --git a/website/blog/templates/blog/blog_list.html b/website/blog/templates/blog/blog_list.html index 9a5d2829..4e7ca7d3 100644 --- a/website/blog/templates/blog/blog_list.html +++ b/website/blog/templates/blog/blog_list.html @@ -12,7 +12,7 @@ {% if p.image %} img {% else %} - default + default {% endif %} {% endif %} {% endfor %} diff --git a/website/forum/static/image/profile_pic/default.png b/website/forum/static/image/profile_pic/default.png new file mode 100644 index 0000000000000000000000000000000000000000..77d3ae0d3e00a62123215b10717cc857c64fa02c GIT binary patch literal 147813 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 V2V8hRd{{ experience.formatted_markdown | safe }}

    - img + {% if profile and profile.image %} + img + {% else %} + default profile + {% endif %}   {{experience.user}} diff --git a/website/interview_exp/templates/exp_list.html b/website/interview_exp/templates/exp_list.html index 6f7c65b1..f0024f48 100644 --- a/website/interview_exp/templates/exp_list.html +++ b/website/interview_exp/templates/exp_list.html @@ -8,7 +8,7 @@ {% if p.image %} img {% else %} - default + default {% endif %} {% endif %} {% endfor %} diff --git a/website/user_profile/templates/profile/profile.html b/website/user_profile/templates/profile/profile.html index 7836cfb2..96f4c370 100644 --- a/website/user_profile/templates/profile/profile.html +++ b/website/user_profile/templates/profile/profile.html @@ -7,9 +7,14 @@

    - {% if profile.image %} -
    Profile Picture
    - {% endif %} + {% load static %} +
    + {% if profile and profile.image %} + Profile Picture + {% else %} + Profile Picture + {% endif %} +

    {{ profile.name }}

    @{{ profile.user }}
    From 9788dccf96a9f1cd6e90852d26daa9e5b63efc8e Mon Sep 17 00:00:00 2001 From: apaul42 Date: Sun, 30 Nov 2025 02:23:58 +0530 Subject: [PATCH 40/50] content filter and rate limiting --- .gitignore | 3 +- new_requirements2.txt | 40 -------- new_requirements.txt => old_requirements.txt | Bin requirements.txt | Bin 1708 -> 893 bytes website/blog/views.py | 8 ++ website/forum/views.py | 8 ++ website/interview_exp/views.py | 11 ++ website/utils/__init__.py | 0 website/utils/content_filter.py | 100 +++++++++++++++++++ website/website/settings.py | 8 +- 10 files changed, 133 insertions(+), 45 deletions(-) delete mode 100644 new_requirements2.txt rename new_requirements.txt => old_requirements.txt (100%) create mode 100644 website/utils/__init__.py create mode 100644 website/utils/content_filter.py diff --git a/.gitignore b/.gitignore index 7b49a20a..4c99b8a7 100644 --- a/.gitignore +++ b/.gitignore @@ -86,4 +86,5 @@ website/migrations/ # Media files media/ -staticfiles/ \ No newline at end of file +staticfiles/ +.agent/ \ No newline at end of file diff --git a/new_requirements2.txt b/new_requirements2.txt deleted file mode 100644 index e352f6df..00000000 --- a/new_requirements2.txt +++ /dev/null @@ -1,40 +0,0 @@ -asgiref==3.8.1 -beautifulsoup4==4.11.1 -certifi==2023.11.17 -cffi==1.16.0 -charset-normalizer==2.1.1 -cryptography==41.0.7 -defusedxml==0.7.1 -dj-database-url==0.5.0 -Django==3.2.25 -django-cors-headers==3.13.0 -django-filter==22.1 -django-markdownx==3.0.1 -django-prometheus==2.2.0 -django-widget-tweaks==1.4.12 -djangorestframework==3.14.0 -djangorestframework-simplejwt==5.2.2 -html2markdown==0.1.7 -idna==3.4 -Markdown==3.4.1 -mysqlclient==2.2.6 -oauthlib==3.2.2 -Pillow==10.1.0 -prometheus-client==0.15.0 -pycparser==2.21 -PyJWT==2.10.1 -python-dateutil==2.8.2 -python-decouple==3.6 -python-dotenv==1.0.1 -python3-openid==3.2.0 -pytz==2023.3.post1 -requests==2.31.0 -requests-oauthlib==1.3.1 -six==1.16.0 -social-auth-app-django==5.4.2 -social-auth-core==4.5.4 -soupsieve==2.3.2.post1 -sqlparse==0.4.4 -typing_extensions==4.12.2 -tzdata==2024.2 -urllib3==2.1.0 diff --git a/new_requirements.txt b/old_requirements.txt similarity index 100% rename from new_requirements.txt rename to old_requirements.txt diff --git a/requirements.txt b/requirements.txt index 9aa1660dc544a77802bbeb2a6beab467b907a664..e25aaf989d446765fa461495a242604e6ea2e463 100644 GIT binary patch literal 893 zcmY*XJ+Ip^4BYiA40LG+3Gqkrx_IkUpg@Ke9Sg;#Vo1K`}Ljc9ruA|*MxFlcLT6ws?s6it|Ya>t;3KZ7q<8}%ky z>ZVBTb+C1ff3&qH+3KVZI+C2i0&l_l?WD(}V|9fpw&>zt*|-p8Pr9WLxt5n`U2C1O zX_=1k4{Xrk+`7wn<>t)`_rVR6dYZrj{ujx`v`1`{E~L+>w4)nqh-w{*Isy56#W(Wk){4>7!X4lhhQYNn~gk5%UK2bIGd-)&fkU?ct9c#devrO3s8{{H*tk`epimrLOU2=T-H0@qMH zVQO#n)a@aho&32_-0DwT}-(M^ghAB#qg3PKLGh!Ek}1Q zqCs5ic}3S4Z6z-+wfFL#HQQQ2=D$^(26R>m72uPPhCTokpe$AiFmG95;y5jc;<(t zf4?)yB$17*C6`zH7Sc$G-%2KOk7pv6a)VDO7jiChpz9W!Ydn%MVz-J2@g2!si(H^? zEh#eAii{B*<1^7X-?LwX-&DS2iO%dt#FZl1?@``SWhHOm#At*FyM!&Dv=)3b4xYp9Yhcb^F`(>bk1OHOYrl+~H&XTA(|f5aBn&7L0S> zV?=Xik38I+a}g)K>9A|Kp#b|CYI_9zWEygpE6l_g-`d8P$eKNJKC$|xSYzS#^lM2LndC;Ax%9i z-o@md$+lO?{JBAO-X-*PEDv25-aL`+eTx`x?2VpVZe!cBxmJvuU7@%|&*{pb?j wYp{_5PGC3Iac7eioJTW66LmaqUf%7zSLf|5$C`Er^8}y9Qtbwn91qL+2QVlDjsO4v diff --git a/website/blog/views.py b/website/blog/views.py index 26477dd4..1cd6b755 100644 --- a/website/blog/views.py +++ b/website/blog/views.py @@ -1,4 +1,6 @@ from django.shortcuts import render, redirect,get_object_or_404, get_list_or_404 +from utils.content_filter import check_content_safety +from django_ratelimit.decorators import ratelimit from .models import * from user_profile.models import * from events.models import * @@ -78,6 +80,7 @@ def valid_url_extension(url, extension_list=VALID_IMAGE_EXTENSIONS): return type @login_required +@ratelimit(key='user', rate='1/10m', method='POST', block=True) def add_blog(request): form = Postform(request.POST or None) @@ -85,6 +88,11 @@ def add_blog(request): if request.method=='POST': form2 = Tagform(request.POST) if form.is_valid() and form2.is_valid(): + description = form.cleaned_data.get('description') + title = form.cleaned_data.get('title') + if not check_content_safety(description, title): + form.add_error('description', "Your post contains irrelevant/inappropriate content and cannot be published.") + return render(request, 'blog/blog_form.html', {'form': form,'form2':form2,}) f = form.save(commit=False) f.user_id = request.user form.save() diff --git a/website/forum/views.py b/website/forum/views.py index d9d4c92d..1905ab86 100644 --- a/website/forum/views.py +++ b/website/forum/views.py @@ -1,4 +1,6 @@ from django.shortcuts import render, redirect,get_object_or_404, get_list_or_404 +from utils.content_filter import check_content_safety +from django_ratelimit.decorators import ratelimit from .models import * from user_profile.models import * from events.models import * @@ -119,6 +121,7 @@ def valid_url_extension(url, extension_list=VALID_IMAGE_EXTENSIONS): return type @login_required +@ratelimit(key='user', rate='1/10m', method='POST', block=True) def add_question(request): form = Questionform(request.POST or None) @@ -126,6 +129,11 @@ def add_question(request): if request.method=='POST': form2 = Tagform(request.POST) if form.is_valid() and form2.is_valid(): + description = form.cleaned_data.get('description') + title = form.cleaned_data.get('title') + if not check_content_safety(description, title): + form.add_error('description', "Your post contains irrelevant/inappropriate content and cannot be published.") + return render(request, 'forum/questions-form.html', {'form': form,'form2':form2,}) f = form.save(commit=False) f.user_id = request.user form.save() diff --git a/website/interview_exp/views.py b/website/interview_exp/views.py index a5f2f5b6..ed7fb8c1 100644 --- a/website/interview_exp/views.py +++ b/website/interview_exp/views.py @@ -1,4 +1,5 @@ from django.shortcuts import render, redirect,get_object_or_404, get_list_or_404 +from utils.content_filter import check_content_safety from .models import * from .forms import * from django.http import HttpResponse, HttpResponseRedirect @@ -13,10 +14,20 @@ from django.core.mail import send_mass_mail +from django_ratelimit.decorators import ratelimit + @login_required +@ratelimit(key='user', rate='1/10m', method='POST', block=True) def add_experience(request): form = ExperienceForm(request.POST or None) if form.is_valid(): + interview_Questions = form.cleaned_data.get('interview_Questions') + company = form.cleaned_data.get('company') + job_profile = form.cleaned_data.get('job_Profile') + title = f"{company} - {job_profile}" + if not check_content_safety(interview_Questions, title): + form.add_error('interview_Questions', "Your post contains irrelevant/inappropriate content and cannot be published.") + return render(request, 'experience-form.html', {'form': form}) f = form.save(commit=False) f.user = request.user f.save() diff --git a/website/utils/__init__.py b/website/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/utils/content_filter.py b/website/utils/content_filter.py new file mode 100644 index 00000000..2c39b408 --- /dev/null +++ b/website/utils/content_filter.py @@ -0,0 +1,100 @@ +import os +import requests +import json + +def check_content_safety(text, title=None): + """ + Checks if the provided text (and optional title) is safe using the Gemini API. + Returns: + - True: if content is safe + - False: if content is unsafe + """ + api_key = os.environ.get("GEMINI_API_KEY") + api_url = os.environ.get("GEMINI_API_URL") + + if not api_key or not api_url: + print("Gemini API credentials missing in .env") + return True # Fail open if not configured + + headers = { + "Content-Type": "application/json" + } + + # Append key to URL if not present (standard Gemini API pattern) + if "?key=" not in api_url and "&key=" not in api_url: + final_url = f"{api_url}?key={api_key}" + else: + final_url = api_url + + content_to_analyze = f"Title: {title}\n" if title else "" + content_to_analyze += f"Body: {text}" + + prompt = f""" + You are an AI Content Moderator for a college technical club website. Your goal is to filter user submissions for a Blog, Ask Section, Interview Experience Section. The environment must remain professional, academic, and safe. + + Analyze the input text (Title and Body) based on the following criteria: + + 1. **SPAM & ADVERTISING (Strict Ban):** + - Flag any promotion of pharmaceuticals, supplements, "useless medicines," weight loss pills, or male enhancement products. + - Flag any commercial advertising, affiliate links, or "get rich quick" schemes. + - Flag any text that looks like SEO spam (repetitive keywords without context). + + 2. **SAFETY & TOXICITY (Strict Ban):** + - Flag any sexual content, NSFW material, or innuendo. + - Flag hate speech, harassment, or bullying. + - Flag content encouraging self-harm or violence. + + 3. **RELEVANCE (Allow):** + - Allow questions about coding, engineering, science, and technology. + - Allow general curiosity questions suitable for an academic environment. + - Allow constructive discussions about college life or club activities. + + 4. **TONE:** + - The content must be professional or semi-professional. + - Reject gibberish or incoherent text. + + Respond ONLY with a JSON object in the following format: + {{ + "is_safe": true/false, + "reason": "brief explanation if unsafe" + }} + + Text to analyze: + {content_to_analyze} + """ + + data = { + "contents": [{ + "parts": [{"text": prompt}] + }] + } + + try: + response = requests.post(final_url, headers=headers, json=data, timeout=10) + response.raise_for_status() + result = response.json() + + # Extract text response + try: + content_text = result['candidates'][0]['content']['parts'][0]['text'] + + # Clean potential markdown formatting + content_text = content_text.strip() + if content_text.startswith("```json"): + content_text = content_text[7:] + if content_text.startswith("```"): + content_text = content_text[3:] + if content_text.endswith("```"): + content_text = content_text[:-3] + + parsed = json.loads(content_text) + return parsed.get("is_safe", True) + + except (KeyError, IndexError, json.JSONDecodeError) as e: + print(f"Error parsing Gemini response: {e}") + print(f"Response body: {result}") + return True # Fail open on parse error + + except Exception as e: + print(f"Error checking content safety: {e}") + return True # Fail open on network/API error diff --git a/website/website/settings.py b/website/website/settings.py index d5f4a1a9..47425702 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -10,7 +10,7 @@ import os from dotenv import load_dotenv load_dotenv() -from decouple import config +from decouple import config, Csv from datetime import timedelta import dj_database_url @@ -28,9 +28,9 @@ # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = config('DEBUG', default=True, cast=bool) -ALLOWED_HOSTS = ["127.0.0.1", "localhost"] +ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1,localhost', cast=Csv()) CORS_ALLOW_ALL_ORIGINS = True @@ -236,7 +236,7 @@ EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend') # Sender addresses -DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='recursion2026@gmail.com') +DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL') SERVER_EMAIL = config('SERVER_EMAIL', default=DEFAULT_FROM_EMAIL) # SMTP settings (used when EMAIL_BACKEND is set to SMTP) From 2008def547b195951883a31b11ff2f076cd91488 Mon Sep 17 00:00:00 2001 From: apaul42 Date: Thu, 11 Dec 2025 20:56:53 +0530 Subject: [PATCH 41/50] fixed team member icon hover and layout issue and some other change --- website/forum/static/css/team.css | 4 +- website/team/templates/team/team.html | 297 ++++++++++++++------------ website/team/views.py | 2 +- website/website/settings.py | 19 +- 4 files changed, 173 insertions(+), 149 deletions(-) diff --git a/website/forum/static/css/team.css b/website/forum/static/css/team.css index 9e837e01..e0088a4a 100644 --- a/website/forum/static/css/team.css +++ b/website/forum/static/css/team.css @@ -27,7 +27,9 @@ .pic img { width: 100%; - height: auto; + aspect-ratio: 1 / 1; + height: 100%; + object-fit: cover; } .our-team .social-links { diff --git a/website/team/templates/team/team.html b/website/team/templates/team/team.html index a19a6e0c..39bdfd3d 100644 --- a/website/team/templates/team/team.html +++ b/website/team/templates/team/team.html @@ -9,149 +9,170 @@ -

    Meet The Team

    - -
    -
    - {% for p in presi %} -
    -
    -
    - - -
    -

    {{ p.name }}

    - {{ p.designation }} -
    -
    - {% endfor %} - - {% for c in convener %} -
    -
    -
    - - -
    -

    {{ c.name }}

    - {{ c.designation }} -
    -
    - {% endfor %} - - {% for t in treasurer %} -
    -
    -
    - - -
    -

    {{ t.name }}

    - {{ t.designation }} -
    -
    - {% endfor %} - - {% for v in vice_presi %} -
    -
    -
    - - -
    -

    {{ v.name }}

    - {{ v.designation }} -
    -
    - {% endfor %} - - {% for g in gen_sec %} -
    -
    -
    - - -
    -

    {{ g.name }}

    - {{ g.designation }} -
    -
    - {% endfor %} - +
    +

    Meet The Team

    +
    + +
    +
    + {% for p in presi %} +
    +
    +
    + +
    -

    +

    {{ p.name }}

    + {{ p.designation }} +
    +
    + {% endfor %} + + {% for v in vice_presi %} +
    +
    +
    + + +
    +

    {{ v.name }}

    + {{ v.designation }} +
    +
    + {% endfor %} + + {% for t in treasurer %} +
    +
    +
    + + +
    +

    {{ t.name }}

    + {{ t.designation }} +
    +
    + {% endfor %} + +
    +
    + + {% for c in convener %} +
    +
    +
    + + +
    +

    {{ c.name }}

    + {{ c.designation }} +
    +
    + {% endfor %} + + {% for g in gen_sec %} +
    +
    +
    + + +
    +

    {{ g.name }}

    + {{ g.designation }} +
    +
    + {% endfor %} + +
    +

    + + + +
    +
    + + {% for member in members %} + {% if member.designation != "President" and member.designation != "Convenor" and member.designation != "Treasurer" and member.designation != "Vice President" and member.designation != "General Secretary" %} +
    +
    +
    + + +
    +

    {{ member.name }}

    + {{ member.designation }} +
    +
    + {% endif %} + {% endfor %} +
    +
    +
    - -
    -
    + +
    +

    Meet our Alumni

    +
    +
    +
    - {% for member in members %} - {% if member.designation != "President" and member.designation != "Convener" and member.designation != "Treasurer" and member.designation != "Vice President" and member.designation != "General Secretary" %} -
    -
    -
    - {{ member.name }}
    - {{ member.designation }} -
    -
    - {% endif %} - {% endfor %} + {% for year in year_set %} -
    -

    - - -

    Meet our Alumni

    -
    -
    - - {% for year in year_set %} - -

    Batch of {{ year }}

    - - {% for a in alumni %} - {% if a.batch_year == year %} - -
    -
    -
    -
    - -
    -
    - {{ a.name }}
    - B.Tech. in {{ a.branch }}
    - LinkedIn Profile -
    -
    -
    -
    - - {% endif %} - {% endfor %} - {% endfor %} +
    +

    Batch of {{ year }}

    +
    + + {% for a in alumni %} + {% if a.batch_year == year %} +
    +
    +
    +
    + +
    +
    + {{ a.name }}
    + B.Tech. in {{ a.branch }}
    + LinkedIn + Profile +
    +
    -{% endblock %} + + {% endif %} + {% endfor %} + {% endfor %} + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/website/team/views.py b/website/team/views.py index c142869d..bf271e29 100644 --- a/website/team/views.py +++ b/website/team/views.py @@ -22,7 +22,7 @@ def team_page(request): year_set.append(a.batch_year) presi = Members.objects.filter(batch_year = curr_batch_year, designation = "President") - convener = Members.objects.filter(batch_year=curr_batch_year, designation="Convener") + convener = Members.objects.filter(batch_year=curr_batch_year, designation="Convenor") treasurer = Members.objects.filter(batch_year=curr_batch_year, designation="Treasurer") vice_presi = Members.objects.filter(batch_year=curr_batch_year, designation="Vice President") gen_sec = Members.objects.filter(batch_year=curr_batch_year, designation="General Secretary") diff --git a/website/website/settings.py b/website/website/settings.py index 47425702..58e599a5 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -59,6 +59,7 @@ 'django_prometheus', 'events_calendar', 'corsheaders', + 'rest_framework_simplejwt.token_blacklist' ] MIDDLEWARE = [ @@ -185,18 +186,18 @@ ] SOCIAL_AUTH_PIPELINE = ( - 'social.pipeline.social_auth.social_details', - 'social.pipeline.social_auth.social_uid', - 'social.pipeline.social_auth.auth_allowed', - 'social.pipeline.social_auth.social_user', + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.pipeline.social_auth.social_user', 'website.utils.associate_by_email', - 'social.pipeline.user.get_username', - 'social.pipeline.user.create_user', - 'social.pipeline.social_auth.associate_user', - 'social.pipeline.social_auth.load_extra_data', - 'social.pipeline.user.user_details', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details', 'website.utils.set_image_for_new_users' ) From 48f70d1b238d0a3dc64eaadef960ddaac4984260 Mon Sep 17 00:00:00 2001 From: AayushAnand39 Date: Sun, 21 Dec 2025 13:21:18 +0530 Subject: [PATCH 42/50] Made a few backend changes to facilitate registration and user creation --- .gitignore | 1 + website/user_profile/api/serializers.py | 64 +++++++++++++++++++------ website/user_profile/views.py | 2 +- website/website/settings.py | 8 +++- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 4c99b8a7..427d9337 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ website/db.sqlit .venv env/ myEnv/ +myenv/ venv/ ENV/ env.bak/ diff --git a/website/user_profile/api/serializers.py b/website/user_profile/api/serializers.py index 698ba00b..b3c74d7b 100644 --- a/website/user_profile/api/serializers.py +++ b/website/user_profile/api/serializers.py @@ -6,12 +6,43 @@ from user_profile.utils import LowerEmailField +# class RegistrationSerializer(serializers.ModelSerializer): +# password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) +# email = LowerEmailField( +# required=True, +# allow_blank=False, +# label='Email address', +# max_length=30, +# validators=[UniqueValidator(queryset=User.objects.all())], +# ) + +# class Meta: +# model = User +# fields = ['username', 'email', 'password', 'password2'] +# extra_kwargs = { +# 'password': {'write_only': True} +# } + +# def save(self): +# password = self.validated_data['password'] +# password2 = self.validated_data['password2'] +# if password != password2: +# raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) +# account = User( +# username=self.validated_data['username'], +# email=self.validated_data['email'].lower(), +# is_active=False # TO BE CHANGED TO FALSE +# ) +# account.set_password(password) +# account.save() +# return account + class RegistrationSerializer(serializers.ModelSerializer): - password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) + password2 = serializers.CharField(write_only=True) + email = LowerEmailField( required=True, allow_blank=False, - label='Email address', max_length=30, validators=[UniqueValidator(queryset=User.objects.all())], ) @@ -23,19 +54,24 @@ class Meta: 'password': {'write_only': True} } - def save(self): - password = self.validated_data['password'] - password2 = self.validated_data['password2'] - if password != password2: - raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) - account = User( - username=self.validated_data['username'], - email=self.validated_data['email'].lower(), - is_active=False # TO BE CHANGED TO FALSE + def validate(self, attrs): + if attrs['password'] != attrs['password2']: + raise serializers.ValidationError({ + 'password2': 'Passwords must match!' + }) + return attrs + + def create(self, validated_data): + validated_data.pop('password2') + + user = User.objects.create_user( + username=validated_data['username'], + email=validated_data['email'].lower(), + password=validated_data['password'], + is_active=False ) - account.set_password(password) - account.save() - return account + return user + class UserSerializer(serializers.ModelSerializer): diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 6f3f0487..8a6a1e23 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -177,7 +177,7 @@ def activate(request, uidb64, token, backend='django.contrib.auth.backends.Model profile.save() if profile.user == request.user: - return redirect('user_profile:edit_profile') + return redirect('http://localhost:3000/login') return redirect('user_profile:edit_profile') else: return render(request, 'account_activation_invalid.html') diff --git a/website/website/settings.py b/website/website/settings.py index 58e599a5..6bc31d51 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -32,7 +32,13 @@ ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1,localhost', cast=Csv()) -CORS_ALLOW_ALL_ORIGINS = True +# CORS_ALLOW_ALL_ORIGINS = True + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000" +] + +CORS_ALLOW_CREDENTIALS = True # Application definition From 1f3d8fb01cc8521276b8a1440d00fcf5a211f7f9 Mon Sep 17 00:00:00 2001 From: ThunderEmperors Date: Wed, 24 Dec 2025 23:56:36 +0530 Subject: [PATCH 43/50] Password reset api fixed --- .gitignore | 1 + website/user_profile/api/serializers.py | 66 ++++++++++++++----- .../registration/password_reset_email.html | 2 +- website/user_profile/urls.py | 2 +- website/user_profile/views.py | 11 +++- website/website/settings.py | 10 ++- 6 files changed, 70 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 4c99b8a7..427d9337 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ website/db.sqlit .venv env/ myEnv/ +myenv/ venv/ ENV/ env.bak/ diff --git a/website/user_profile/api/serializers.py b/website/user_profile/api/serializers.py index 698ba00b..ccaa0f74 100644 --- a/website/user_profile/api/serializers.py +++ b/website/user_profile/api/serializers.py @@ -6,12 +6,43 @@ from user_profile.utils import LowerEmailField +# class RegistrationSerializer(serializers.ModelSerializer): +# password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) +# email = LowerEmailField( +# required=True, +# allow_blank=False, +# label='Email address', +# max_length=30, +# validators=[UniqueValidator(queryset=User.objects.all())], +# ) + +# class Meta: +# model = User +# fields = ['username', 'email', 'password', 'password2'] +# extra_kwargs = { +# 'password': {'write_only': True} +# } + +# def save(self): +# password = self.validated_data['password'] +# password2 = self.validated_data['password2'] +# if password != password2: +# raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) +# account = User( +# username=self.validated_data['username'], +# email=self.validated_data['email'].lower(), +# is_active=False # TO BE CHANGED TO FALSE +# ) +# account.set_password(password) +# account.save() +# return account + class RegistrationSerializer(serializers.ModelSerializer): - password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) + password2 = serializers.CharField(write_only=True) + email = LowerEmailField( required=True, allow_blank=False, - label='Email address', max_length=30, validators=[UniqueValidator(queryset=User.objects.all())], ) @@ -23,19 +54,24 @@ class Meta: 'password': {'write_only': True} } - def save(self): - password = self.validated_data['password'] - password2 = self.validated_data['password2'] - if password != password2: - raise serializers.ValidationError({'confirm_password': 'Passwords must match!'}) - account = User( - username=self.validated_data['username'], - email=self.validated_data['email'].lower(), - is_active=False # TO BE CHANGED TO FALSE + def validate(self, attrs): + if attrs['password'] != attrs['password2']: + raise serializers.ValidationError({ + 'password2': 'Passwords must match!' + }) + return attrs + + def create(self, validated_data): + validated_data.pop('password2') + + user = User.objects.create_user( + username=validated_data['username'], + email=validated_data['email'].lower(), + password=validated_data['password'], + is_active=False ) - account.set_password(password) - account.save() - return account + return user + class UserSerializer(serializers.ModelSerializer): @@ -54,4 +90,4 @@ class ProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile exclude = ('id',) - read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) + read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) \ No newline at end of file diff --git a/website/user_profile/templates/registration/password_reset_email.html b/website/user_profile/templates/registration/password_reset_email.html index 2cdbb383..aa7be301 100644 --- a/website/user_profile/templates/registration/password_reset_email.html +++ b/website/user_profile/templates/registration/password_reset_email.html @@ -2,7 +2,7 @@ To initiate the password reset process for your {{ user.get_username }} RECursion Account, click the link below: -{{ protocol }}http://{{ domain }}{% url 'user_profile:password_reset_confirm' uidb64=uid token=token %} +{{ protocol }}http://localhost:3000{% url 'user_profile:password_reset_confirm' uidb64=uid token=token %} If clicking the link above doesn't work, please copy and paste the URL in a new browser window instead. diff --git a/website/user_profile/urls.py b/website/user_profile/urls.py index 4a11f503..355357d7 100644 --- a/website/user_profile/urls.py +++ b/website/user_profile/urls.py @@ -12,7 +12,7 @@ path('change_password/', change_password, name='change_password'), path('password_reset/', password_reset, name='password_reset'), path('password_reset/done/', password_reset_done, name='password_reset_done'), - path('reset/([0-9A-Za-z_\-]+)/([0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/', + path('reset///', password_reset_confirm, name='password_reset_confirm'), path('reset/done/', password_reset_complete, name='password_reset_complete'), path('viewprofile//', view_profile, name='view_profile'), diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 6f3f0487..800e24b5 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -177,7 +177,7 @@ def activate(request, uidb64, token, backend='django.contrib.auth.backends.Model profile.save() if profile.user == request.user: - return redirect('user_profile:edit_profile') + return redirect('http://localhost:3000/login') return redirect('user_profile:edit_profile') else: return render(request, 'account_activation_invalid.html') @@ -260,16 +260,21 @@ def password_reset_confirm(request, uidb64, token, backend='django.contrib.auth. except (TypeError, ValueError, OverflowError, User.DoesNotExist): user = None + # Use password_reset_token (was incorrectly using account_activation_token) if user is not None and password_reset_token.check_token(user, token): if request.method == 'POST': - form = SetPasswordForm(user, request.POST) + data = { + 'new_password1': request.POST.get('password'), + 'new_password2': request.POST.get('confirmPassword'), + } + form = SetPasswordForm(user, data=data) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) # Important! user.is_active = True user.save() - return redirect('login') + return HttpResponse("Changed.") # if invalid, re-render with errors return render(request, 'registration/password_reset_confirm.html', {'form': form}) else: diff --git a/website/website/settings.py b/website/website/settings.py index 58e599a5..7b0de6dd 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -32,7 +32,13 @@ ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1,localhost', cast=Csv()) -CORS_ALLOW_ALL_ORIGINS = True +# CORS_ALLOW_ALL_ORIGINS = True + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000" +] + +CORS_ALLOW_CREDENTIALS = True # Application definition @@ -258,4 +264,4 @@ # PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) # STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') # MEDIA_URL='/media/' -# MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') +# MEDIA_ROOT=os.path.join(BASE_DIR,'members/media') \ No newline at end of file From e7ab379af5d068381707cb73be79970331227a0a Mon Sep 17 00:00:00 2001 From: ThunderEmperors Date: Fri, 26 Dec 2025 00:33:16 +0530 Subject: [PATCH 44/50] fixed response for invalid password in password reset --- website/user_profile/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 800e24b5..1e69c570 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -276,7 +276,7 @@ def password_reset_confirm(request, uidb64, token, backend='django.contrib.auth. user.save() return HttpResponse("Changed.") # if invalid, re-render with errors - return render(request, 'registration/password_reset_confirm.html', {'form': form}) + return HttpResponse(form) else: form = SetPasswordForm(user) return render(request, 'registration/password_reset_confirm.html', {'form': form}) From d4626a6c87f18e6e490d21346dd2fce64e112d16 Mon Sep 17 00:00:00 2001 From: ThunderEmperors Date: Fri, 26 Dec 2025 02:08:59 +0530 Subject: [PATCH 45/50] fixed events_calendar api to send events in order --- website/events_calendar/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/events_calendar/api/views.py b/website/events_calendar/api/views.py index 9e473db1..667ad07d 100644 --- a/website/events_calendar/api/views.py +++ b/website/events_calendar/api/views.py @@ -21,7 +21,7 @@ class EventsListCreateView(ListCreateAPIView): ordering_fields = ['start_time', 'end_time', 'updated_at', 'duration'] def get_queryset(self): - return Events_Calendar.objects.select_related('user').all() + return Events_Calendar.objects.all().order_by('-start_time') def perform_create(self, serializer): start_time, end_time = serializer.validated_data.get('start_time'), serializer.validated_data.get('end_time') From 4ee0fdf47ad11eda4a08bb2783e88f419d0f711d Mon Sep 17 00:00:00 2001 From: apaul42 Date: Tue, 6 Jan 2026 22:49:15 +0530 Subject: [PATCH 46/50] made some changes to interview experience mailing --- website/interview_exp/api/serializers.py | 4 ++-- website/interview_exp/api/views.py | 17 ++++++++--------- .../templates/changes_requested_email.html | 2 ++ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/website/interview_exp/api/serializers.py b/website/interview_exp/api/serializers.py index dc9f0bcf..3e5369b6 100644 --- a/website/interview_exp/api/serializers.py +++ b/website/interview_exp/api/serializers.py @@ -17,8 +17,8 @@ class Meta: class RevisionSerializer(serializers.ModelSerializer): - experience = IESerializer() - reviewer = UserSerializer() + experience = IESerializer(read_only=True) + reviewer = UserSerializer(read_only=True) # experience = serializers.HyperlinkedRelatedField(view_name='experiences_api:ie_detail', # lookup_field='id', diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index 0ea536cc..a7c463a5 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -34,8 +34,6 @@ ) # email only for trial purposes -# TRIAL_REC_MAIL = 'jiwegaw290@randrai.com' -TRIAL_REC_MAIL = settings.TRIAL_REC_MAIL class IEListView(ListCreateAPIView): @@ -74,7 +72,7 @@ def perform_create(self, serializer): # 'domain': current_site.domain, # 'experience': Experiences.objects.get(pk=f.id), # }) - # msg = (subject, message, 'webmaster@localhost', [TRIAL_REC_MAIL,]) + # msg = (subject, message, 'webmaster@localhost', ["example@gmail.com",]) # if msg not in messages: # messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -95,8 +93,8 @@ def perform_create(self, serializer): 'experience': exp, } ), - to=[TRIAL_REC_MAIL, ], - + # to=[user.email, ], + to=[user.email, ], ) for user in member_users ] @@ -147,7 +145,7 @@ def perform_update(self, serializer): 'experience': exp, } ), - to=[TRIAL_REC_MAIL, ], # to=[reviewer.email, ] + to=[reviewer.email, ], # to=[reviewer.email, ] ) ] # uncomment below to mail @@ -168,7 +166,7 @@ def perform_update(self, serializer): 'experience': exp, } ), - to=[TRIAL_REC_MAIL, ], # to=[reviewer.email, ] + to=[user.email, ], # to=[reviewer.email, ] ) for user in member_users ] @@ -219,7 +217,7 @@ def perform_create(self, serializer): review_code = self.kwargs.get('review_code', '') print(self.kwargs) if review_code == 'acc': # Accepted - exp.verification_Status = 'Accepted' + exp.verification_Status = 'Approved' exp.verifier = curr_user # TODO # Send mail to the author about publication @@ -243,9 +241,10 @@ def perform_create(self, serializer): 'user': exp.user, 'domain': domain, 'experience': exp, + 'message': msg, } ), - to=[TRIAL_REC_MAIL, ], # exp.user.email + to=[exp.user.email], # exp.user.email ) ] diff --git a/website/interview_exp/templates/changes_requested_email.html b/website/interview_exp/templates/changes_requested_email.html index 2d2e5a36..15f1f08e 100644 --- a/website/interview_exp/templates/changes_requested_email.html +++ b/website/interview_exp/templates/changes_requested_email.html @@ -2,6 +2,8 @@ Hi {{ user.username }}, Changes were requested in your Interview Experience by some Member/Admin. +They Said: +"{{ message }}" Kindly, make them by clicking on the link below: http://{{ domain }}{% url 'interview_exp:detail_experiences' experience.id %} From 3c5766ee1f4796f195e39a06a3b01dc3a4481eb1 Mon Sep 17 00:00:00 2001 From: apaul42 Date: Tue, 6 Jan 2026 23:06:26 +0530 Subject: [PATCH 47/50] deployment changes --- website/blog/views.py | 16 +++++----- website/forum/views.py | 30 +++++++++---------- website/interview_exp/views.py | 10 +++---- .../registration/password_reset_email.html | 2 +- website/user_profile/views.py | 3 +- website/website/settings.py | 4 ++- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/website/blog/views.py b/website/blog/views.py index 1cd6b755..0ac1bfe1 100644 --- a/website/blog/views.py +++ b/website/blog/views.py @@ -120,7 +120,7 @@ def add_blog(request): 'domain': current_site.domain, 'post' : Posts.objects.get(pk=f.id), }) - msg=(subject, message, 'webmaster@localhost', [user.email]) + msg=(subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) result = send_mass_mail(messages, fail_silently=False) return redirect('blog:list_blogs') @@ -271,7 +271,7 @@ def update_blogs(request, id): 'domain': current_site.domain, 'post': post, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -332,7 +332,7 @@ def add_reply(request, id): 'domain': current_site.domain, 'post': post, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -394,7 +394,7 @@ def update_reply(request, id): 'domain': current_site.domain, 'post': post, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -444,7 +444,7 @@ def add_comment(request, id): 'domain': current_site.domain, 'post': post, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -496,7 +496,7 @@ def update_comment(request, id): 'domain': current_site.domain, 'post': post, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -758,7 +758,7 @@ def add_comment_reply(request, id): 'domain': current_site.domain, 'post': reply.post_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user @@ -809,7 +809,7 @@ def update_comment_reply(request, id): 'domain': current_site.domain, 'post': reply.post_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) '''for follow in follows: user = follow.user diff --git a/website/forum/views.py b/website/forum/views.py index 1905ab86..d49753ee 100644 --- a/website/forum/views.py +++ b/website/forum/views.py @@ -163,7 +163,7 @@ def add_question(request): 'domain': current_site.domain, 'question' : Questions.objects.get(pk=f.id), }) - msg=(subject, message, 'webmaster@localhost', [user.email]) + msg=(subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) return redirect('forum:list_questions') @@ -320,7 +320,7 @@ def update_questions(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -331,7 +331,7 @@ def update_questions(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -386,7 +386,7 @@ def add_answer(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -397,7 +397,7 @@ def add_answer(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -448,7 +448,7 @@ def update_answer(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -459,7 +459,7 @@ def update_answer(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -526,7 +526,7 @@ def add_comment(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -537,7 +537,7 @@ def add_comment(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -579,7 +579,7 @@ def update_comment(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -590,7 +590,7 @@ def update_comment(request, id): 'domain': current_site.domain, 'question': question, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -723,7 +723,7 @@ def add_comment_answer(request, id): 'domain': current_site.domain, 'question': answer.question_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -734,7 +734,7 @@ def add_comment_answer(request, id): 'domain': current_site.domain, 'question': answer.question_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) # result = send_mass_mail(messages, fail_silently=False) @@ -775,7 +775,7 @@ def update_comment_answer(request, id): 'domain': current_site.domain, 'question': answer.question_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) messages += (msg,) for follow in follows: user = follow.user @@ -786,7 +786,7 @@ def update_comment_answer(request, id): 'domain': current_site.domain, 'question': answer.question_id, }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) #result = send_mass_mail(messages, fail_silently=False) diff --git a/website/interview_exp/views.py b/website/interview_exp/views.py index ed7fb8c1..fc3cd8ff 100644 --- a/website/interview_exp/views.py +++ b/website/interview_exp/views.py @@ -43,7 +43,7 @@ def add_experience(request): 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=f.id), }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) result = send_mass_mail(messages, fail_silently=False) @@ -88,7 +88,7 @@ def update_experience(request, id): 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) result = send_mass_mail(messages, fail_silently=False) @@ -105,7 +105,7 @@ def update_experience(request, id): 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) result = send_mass_mail(messages, fail_silently=False) @@ -302,7 +302,7 @@ def revise_experience(request, id, action): 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) result = send_mass_mail(messages, fail_silently=False) @@ -329,7 +329,7 @@ def revise_experience(request, id, action): 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), }) - msg = (subject, message, 'webmaster@localhost', [user.email]) + msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: messages += (msg,) result = send_mass_mail(messages, fail_silently=False) diff --git a/website/user_profile/templates/registration/password_reset_email.html b/website/user_profile/templates/registration/password_reset_email.html index aa7be301..77863354 100644 --- a/website/user_profile/templates/registration/password_reset_email.html +++ b/website/user_profile/templates/registration/password_reset_email.html @@ -2,7 +2,7 @@ To initiate the password reset process for your {{ user.get_username }} RECursion Account, click the link below: -{{ protocol }}http://localhost:3000{% url 'user_profile:password_reset_confirm' uidb64=uid token=token %} +{{ protocol }}{{ frontend_base_url }}{% url 'user_profile:password_reset_confirm' uidb64=uid token=token %} If clicking the link above doesn't work, please copy and paste the URL in a new browser window instead. diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 1e69c570..0b1848c1 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -177,7 +177,7 @@ def activate(request, uidb64, token, backend='django.contrib.auth.backends.Model profile.save() if profile.user == request.user: - return redirect('http://localhost:3000/login') + return redirect(settings.FRONTEND_BASE_URL + '/login') return redirect('user_profile:edit_profile') else: return render(request, 'account_activation_invalid.html') @@ -240,6 +240,7 @@ def password_reset(request): 'domain': current_site.domain, 'uid': uid, 'token': password_reset_token.make_token(user), + 'frontend_base_url': settings.FRONTEND_BASE_URL, }) user.email_user(subject, message) return HttpResponse("We've emailed you instructions for setting your password, if an account exists with the email you entered! You should receive them shortly." diff --git a/website/website/settings.py b/website/website/settings.py index 7b0de6dd..384d038e 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -32,10 +32,12 @@ ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1,localhost', cast=Csv()) +FRONTEND_BASE_URL = config('FRONTEND_BASE_URL', default='http://localhost:3000') + # CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOWED_ORIGINS = [ - "http://localhost:3000" + FRONTEND_BASE_URL ] CORS_ALLOW_CREDENTIALS = True From e57d5323f48d16faa2dd5cf8a84e8e4bde5351b2 Mon Sep 17 00:00:00 2001 From: apaul42 Date: Fri, 9 Jan 2026 23:58:05 +0530 Subject: [PATCH 48/50] fixed google login --- website/user_profile/api/views.py | 46 +++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index 2be95f1c..aca5780a 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -23,6 +23,7 @@ import requests from rest_framework_simplejwt.tokens import RefreshToken from django.conf import settings +from django.core.files.base import ContentFile from .serializers import ( @@ -64,34 +65,57 @@ def generate_token(self,user): def post(self, request): access_token = request.data.get('token') GOOGLE_CLIENT_ID = getattr(settings, "GOOGLE_CLIENT_ID", None) + if not GOOGLE_CLIENT_ID: + GOOGLE_CLIENT_ID = getattr(settings, "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY", None) + try: - TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+access_token + TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token=" + access_token headers = { - "Authorization": "Bearer {access_token}", + "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } - token_info = requests.get(TOKEN_INFO_URL ,data = {} ,headers = headers).json() + token_info = requests.get(TOKEN_INFO_URL, data={}, headers=headers).json() + + if 'error' in token_info: + print(f"Google API Error: {token_info}") + return Response(data={'response': 'Invalid token from Google'}, status=400) + if token_info['issued_to'] == GOOGLE_CLIENT_ID: try: - USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?access_token="+access_token - user_info = requests.get(USER_INFO_URL ,data = {} ,headers = headers).json() + USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + access_token + user_info = requests.get(USER_INFO_URL, data={}, headers=headers).json() user, created = User.objects.get_or_create( - username = user_info['email'].split('@')[0], - defaults = { + username=user_info['email'].split('@')[0], + defaults={ 'first_name': user_info.get('name', ''), 'last_name': user_info.get('given_name', ''), 'email': user_info['email'] } ) + + if created: + try: + profile = user.profile + profile.name = user_info.get('name', '') + picture_url = user_info.get('picture') + if picture_url: + image_response = requests.get(picture_url) + if image_response.status_code == 200: + profile.image.save(f"{user.username}_google.jpg", ContentFile(image_response.content), save=False) + profile.save() + except Exception as e: + print(f"Error saving user profile data from Google: {e}") + tokens = self.generate_token(user) - return Response(data={'access': tokens['access'],'refresh':tokens['refresh'], 'response':'valid'}, status=200) + return Response(data={'access': tokens['access'], 'refresh': tokens['refresh'], 'response': 'valid', 'is_new_user': created}, status=200) except Exception as e: - print(e) + print(f"User creation/retrieval error: {e}") return Response(data={'response': 'Unauthorized'}, status=401) else: - return Response(data={'response': 'Unauthorized'}, status=401) + print(f"Client ID mismatch. Expected: {GOOGLE_CLIENT_ID}, Got: {token_info.get('issued_to')}") + return Response(data={'response': 'Unauthorized - Client ID mismatch'}, status=401) except Exception as e: - print(e) + print(f"Top level error in LoginWithGoogleView: {e}") return Response(data={'response': 'Invalid token'}, status=400) From 460d98be1c45b0bbc17111cbf3615e70bcbaecbb Mon Sep 17 00:00:00 2001 From: apaul42 Date: Sun, 18 Jan 2026 23:24:24 +0530 Subject: [PATCH 49/50] finalised certain login/signup flow APIs, fixed password reset issue, fixed email link using previous domain name, blocked the old UI --- website/interview_exp/api/views.py | 4 ++ .../templates/changes_requested_email.html | 2 +- .../templates/new_experience_entry_email.html | 2 +- .../update_Experience_to_all_email.html | 2 +- .../templates/update_experience_email.html | 2 +- website/interview_exp/views.py | 6 ++ website/user_profile/api/serializers.py | 32 ++++++++- website/user_profile/api/urls.py | 16 ++++- website/user_profile/api/views.py | 72 +++++++++++++++++++ website/user_profile/models.py | 2 +- .../templates/account_activation_email.html | 2 +- website/user_profile/tokens.py | 2 +- website/user_profile/urls.py | 2 +- website/user_profile/utils.py | 12 +--- website/user_profile/views.py | 5 +- website/website/middleware.py | 62 ++++++++++++++++ website/website/settings.py | 5 ++ 17 files changed, 206 insertions(+), 24 deletions(-) create mode 100644 website/website/middleware.py diff --git a/website/interview_exp/api/views.py b/website/interview_exp/api/views.py index a7c463a5..08edf7cc 100644 --- a/website/interview_exp/api/views.py +++ b/website/interview_exp/api/views.py @@ -91,6 +91,7 @@ def perform_create(self, serializer): 'user': user, 'domain': domain, 'experience': exp, + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, } ), # to=[user.email, ], @@ -143,6 +144,7 @@ def perform_update(self, serializer): 'user': reviewer, 'domain': domain, 'experience': exp, + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, } ), to=[reviewer.email, ], # to=[reviewer.email, ] @@ -164,6 +166,7 @@ def perform_update(self, serializer): 'user': user, 'domain': domain, 'experience': exp, + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, } ), to=[user.email, ], # to=[reviewer.email, ] @@ -242,6 +245,7 @@ def perform_create(self, serializer): 'domain': domain, 'experience': exp, 'message': msg, + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, } ), to=[exp.user.email], # exp.user.email diff --git a/website/interview_exp/templates/changes_requested_email.html b/website/interview_exp/templates/changes_requested_email.html index 15f1f08e..7f06a1a1 100644 --- a/website/interview_exp/templates/changes_requested_email.html +++ b/website/interview_exp/templates/changes_requested_email.html @@ -6,7 +6,7 @@ "{{ message }}" Kindly, make them by clicking on the link below: -http://{{ domain }}{% url 'interview_exp:detail_experiences' experience.id %} +{{ FRONTEND_BASE_URL }}{% url 'interview_exp:detail_experiences' experience.id %} Sincerely, RECursion Team diff --git a/website/interview_exp/templates/new_experience_entry_email.html b/website/interview_exp/templates/new_experience_entry_email.html index cbca3274..9e224339 100644 --- a/website/interview_exp/templates/new_experience_entry_email.html +++ b/website/interview_exp/templates/new_experience_entry_email.html @@ -4,7 +4,7 @@ A new Interview Experience has been Added in Interview Experiences Section. Please, verify it by clicking on the link below: -http://{{ domain }}{% url 'interview_exp:detail_experiences' experience.id %} +{{ FRONTEND_BASE_URL }}{% url 'interview_exp:detail_experiences' experience.id %} Sincerely, RECursion Team diff --git a/website/interview_exp/templates/update_Experience_to_all_email.html b/website/interview_exp/templates/update_Experience_to_all_email.html index 60e8081b..eeccfcdf 100644 --- a/website/interview_exp/templates/update_Experience_to_all_email.html +++ b/website/interview_exp/templates/update_Experience_to_all_email.html @@ -4,7 +4,7 @@ An Interview Experience has been updated in Interview Experiences Section. Please, verify it by clicking on the link below: -http://{{ domain }}{% url 'interview_exp:detail_experiences' experience.id %} +{{ FRONTEND_BASE_URL }}{% url 'interview_exp:detail_experiences' experience.id %} Sincerely, RECursion Team diff --git a/website/interview_exp/templates/update_experience_email.html b/website/interview_exp/templates/update_experience_email.html index c6cee9af..2b67b5c7 100644 --- a/website/interview_exp/templates/update_experience_email.html +++ b/website/interview_exp/templates/update_experience_email.html @@ -4,7 +4,7 @@ An Interview Experience you reviewed has been updated in Interview Experience Section. Please, verify it by clicking on the link below: -http://{{ domain }}{% url 'interview_exp:detail_experiences' experience.id %} +{{ FRONTEND_BASE_URL }}{% url 'interview_exp:detail_experiences' experience.id %} Sincerely, RECursion Team diff --git a/website/interview_exp/views.py b/website/interview_exp/views.py index fc3cd8ff..60145078 100644 --- a/website/interview_exp/views.py +++ b/website/interview_exp/views.py @@ -12,6 +12,7 @@ from django.contrib.sites.shortcuts import get_current_site from django.template.loader import render_to_string from django.core.mail import send_mass_mail +from django.conf import settings from django_ratelimit.decorators import ratelimit @@ -42,6 +43,7 @@ def add_experience(request): 'user': user, 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=f.id), + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, }) msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: @@ -87,6 +89,7 @@ def update_experience(request, id): 'user': user, 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, }) msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: @@ -104,6 +107,7 @@ def update_experience(request, id): 'user': user, 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, }) msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: @@ -301,6 +305,7 @@ def revise_experience(request, id, action): 'user': user, 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, }) msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: @@ -328,6 +333,7 @@ def revise_experience(request, id, action): 'user': user, 'domain': current_site.domain, 'experience': Experiences.objects.get(pk=experience.id), + 'FRONTEND_BASE_URL': settings.FRONTEND_BASE_URL, }) msg = (subject, message, settings.SERVER_EMAIL, [user.email]) if msg not in messages: diff --git a/website/user_profile/api/serializers.py b/website/user_profile/api/serializers.py index ccaa0f74..d15a6656 100644 --- a/website/user_profile/api/serializers.py +++ b/website/user_profile/api/serializers.py @@ -39,6 +39,7 @@ class RegistrationSerializer(serializers.ModelSerializer): password2 = serializers.CharField(write_only=True) + dept = serializers.CharField(max_length=70, required=False, allow_blank=True) email = LowerEmailField( required=True, @@ -49,7 +50,7 @@ class RegistrationSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['username', 'email', 'password', 'password2'] + fields = ['username', 'email', 'password', 'password2', 'dept'] extra_kwargs = { 'password': {'write_only': True} } @@ -63,6 +64,7 @@ def validate(self, attrs): def create(self, validated_data): validated_data.pop('password2') + dept = validated_data.pop('dept', None) user = User.objects.create_user( username=validated_data['username'], @@ -70,6 +72,11 @@ def create(self, validated_data): password=validated_data['password'], is_active=False ) + + if dept: + user.profile.dept = dept + user.profile.save() + return user @@ -90,4 +97,25 @@ class ProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile exclude = ('id',) - read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) \ No newline at end of file + read_only_fields = ('user', 'role', 'email_confirmed', 'created_at', 'updated_at',) + + +class PasswordResetRequestSerializer(serializers.Serializer): + email = serializers.EmailField() + + def validate_email(self, value): + if not User.objects.filter(email=value.lower()).exists(): + raise serializers.ValidationError("No user with that Email exists.") + return value.lower() + + +class PasswordResetConfirmSerializer(serializers.Serializer): + uidb64 = serializers.CharField() + token = serializers.CharField() + password = serializers.CharField(write_only=True) + confirm_password = serializers.CharField(write_only=True) + + def validate(self, attrs): + if attrs['password'] != attrs['confirm_password']: + raise serializers.ValidationError({"confirm_password": "Passwords must match."}) + return attrs diff --git a/website/user_profile/api/urls.py b/website/user_profile/api/urls.py index 4a1474ce..1bc0e76e 100644 --- a/website/user_profile/api/urls.py +++ b/website/user_profile/api/urls.py @@ -8,7 +8,11 @@ UserSearchView, username_existence_check, email_existence_check, - GetProfileRoleView + username_existence_check, + email_existence_check, + GetProfileRoleView, + RequestPasswordResetAPI, + ResetPasswordConfirmAPI, ) from user_profile.views import activate @@ -19,12 +23,20 @@ path('register/', RegistrationView.as_view(), name='user_registration'), path('', ListProfileView.as_view(), name='all_users_list'), # path('resend-user-activation/', resendVerificationView, name='resend-user-activation'), - path('/', RetrieveUpdateProfileView.as_view(), name='user_detail'), + + # Password Reset + path('password-reset/', RequestPasswordResetAPI.as_view(), name='password_reset_api'), + path('password-reset-confirm/', ResetPasswordConfirmAPI.as_view(), name='password_reset_confirm_api'), + + # Specific Checks and Views path('search', UserSearchView.as_view(), name='user_search'), path('username-check', username_existence_check, name='username_exists'), path('email-check', email_existence_check, name='email_exists'), path('roles', GetProfileRoleView.as_view(), name='roles'), + # Account Activation path('activate///', activate, name='activate'), + # Profile Detail (Catch-all) + path('/', RetrieveUpdateProfileView.as_view(), name='user_detail'), ] diff --git a/website/user_profile/api/views.py b/website/user_profile/api/views.py index aca5780a..4f242e72 100644 --- a/website/user_profile/api/views.py +++ b/website/user_profile/api/views.py @@ -14,6 +14,7 @@ from django.contrib.sites.shortcuts import get_current_site from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny from user_profile.models import Profile from user_profile.utils import send_verification_mail @@ -29,8 +30,16 @@ from .serializers import ( RegistrationSerializer, UserSerializer, + UserSerializer, ProfileSerializer, + PasswordResetRequestSerializer, + PasswordResetConfirmSerializer, ) +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from user_profile.tokens import password_reset_token +from django.template.loader import render_to_string + # TODO @@ -193,3 +202,66 @@ def email_existence_check(request): if (not search_query) or (not User.objects.filter(email=search_query.lower()).exists()): return Response(data={'response': 'Email ID is not used yet!', 'exists': 0}) return Response(data={'response': 'An account is registered with the email address!', 'exists': 1}) + + +class RequestPasswordResetAPI(APIView): + permission_classes = [AllowAny] + authentication_classes = [] + def post(self, request): + serializer = PasswordResetRequestSerializer(data=request.data) + if serializer.is_valid(): + email = serializer.validated_data['email'] + user = User.objects.get(email=email) + + # Logic from existing view + current_site = get_current_site(request) + subject = 'Reset Your RECursion Account Password' + uid = urlsafe_base64_encode(force_bytes(user.pk)) + token = password_reset_token.make_token(user) + + # Using specific frontend URL construction + frontend_base = settings.FRONTEND_BASE_URL + + # Send email + # We reuse the template but ensure it uses the frontend link + # Or we can just send the link directly if the template allows + # The existing template likely uses 'uid' and 'token' to build a link. + # We should check if the template uses 'frontend_base_url' correctly. + + message = render_to_string('registration/password_reset_email.html', { + 'user': user, + 'domain': current_site.domain, + 'uid': uid, + 'token': token, + 'frontend_base_url': frontend_base, + }) + user.email_user(subject, message) + + return Response({"success": True, "message": "Password reset email sent."}) + return Response(serializer.errors, status=400) + + +class ResetPasswordConfirmAPI(APIView): + permission_classes = [AllowAny] + authentication_classes = [] + def post(self, request): + serializer = PasswordResetConfirmSerializer(data=request.data) + if serializer.is_valid(): + uidb64 = serializer.validated_data['uidb64'] + token = serializer.validated_data['token'] + password = serializer.validated_data['password'] + + try: + uid = urlsafe_base64_decode(uidb64).decode() + user = User.objects.get(pk=uid) + except (TypeError, ValueError, OverflowError, User.DoesNotExist): + return Response({"error": "Invalid UID"}, status=400) + + if user is not None and password_reset_token.check_token(user, token): + user.set_password(password) + user.save() + return Response({"success": True, "message": "Password has been reset."}) + else: + return Response({"error": "Invalid or expired token"}, status=400) + + return Response(serializer.errors, status=400) diff --git a/website/user_profile/models.py b/website/user_profile/models.py index 2b7706b4..f769e67c 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -36,7 +36,7 @@ class Profile(ExportModelOperationsMixin('profile'), models.Model): ('3', 'User') ) role = models.CharField(max_length=50, choices=role_choices, default='3') - dept = models.CharField(max_length=20, blank=True, null=True) + dept = models.CharField(max_length=70, blank=True, null=True) url_CodeChef = models.URLField(blank=True, null=True) url_Codeforces = models.URLField(blank=True, null=True) url_SPOJ = models.URLField(blank=True, null=True) diff --git a/website/user_profile/templates/account_activation_email.html b/website/user_profile/templates/account_activation_email.html index 389d7a1f..7965b506 100644 --- a/website/user_profile/templates/account_activation_email.html +++ b/website/user_profile/templates/account_activation_email.html @@ -3,7 +3,7 @@ Please click on the link below to confirm your registration: -http://{{ domain }}{% url 'user_profile:activate' uidb64=uid token=token %} +http://{{ domain }}{% url 'user_profile_api:activate' uidb64=uid token=token %} Sincerely, RECursion Team diff --git a/website/user_profile/tokens.py b/website/user_profile/tokens.py index 90e7faa7..9fb533c9 100644 --- a/website/user_profile/tokens.py +++ b/website/user_profile/tokens.py @@ -9,4 +9,4 @@ def _make_hash_value(self, user, timestamp): ) account_activation_token = AccountActivationTokenGenerator() -password_reset_token = AccountActivationTokenGenerator() \ No newline at end of file +password_reset_token = PasswordResetTokenGenerator() \ No newline at end of file diff --git a/website/user_profile/urls.py b/website/user_profile/urls.py index 355357d7..9c3e38fb 100644 --- a/website/user_profile/urls.py +++ b/website/user_profile/urls.py @@ -7,7 +7,7 @@ app_name="profile" urlpatterns = [ path('account_activation_sent/', account_activation_sent, name='account_activation_sent'), - path('activate/([0-9A-Za-z_\-]+)/([0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/', + path('activate///', activate, name='activate'), path('change_password/', change_password, name='change_password'), path('password_reset/', password_reset, name='password_reset'), diff --git a/website/user_profile/utils.py b/website/user_profile/utils.py index c0d28dc6..7bc78440 100644 --- a/website/user_profile/utils.py +++ b/website/user_profile/utils.py @@ -40,15 +40,7 @@ def __str__(self): return 'ProfileMatcher object with query="{}", ratio_threshold={} .'.format(self.query, self.ratio_threshold) -class TokenGenerator(PasswordResetTokenGenerator): - def _make_hash_value(self, user, timestamp): - return ( - six.text_type(user.pk) + six.text_type(timestamp) + - six.text_type(user.is_active) - ) - - -account_activation_token = TokenGenerator() +from .tokens import account_activation_token class LowerEmailField(serializers.EmailField): @@ -90,7 +82,7 @@ def send_verification_mail(domain, user, *args, **kwargs): 'uid': urlsafe_base64_encode(force_bytes(user.id)), 'token': account_activation_token.make_token(user), }) - to_email = settings.TRIAL_REC_MAIL + to_email = user.email mail = EmailMessage( mail_subject, message, diff --git a/website/user_profile/views.py b/website/user_profile/views.py index 0b1848c1..afda95e6 100644 --- a/website/user_profile/views.py +++ b/website/user_profile/views.py @@ -28,6 +28,7 @@ from forum.models import * from blog.models import * from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from rest_framework_simplejwt.tokens import RefreshToken def view_profile(request, id=None): @@ -177,8 +178,8 @@ def activate(request, uidb64, token, backend='django.contrib.auth.backends.Model profile.save() if profile.user == request.user: - return redirect(settings.FRONTEND_BASE_URL + '/login') - return redirect('user_profile:edit_profile') + return redirect(settings.FRONTEND_BASE_URL + '/profile/edit?activated=true') + return redirect(settings.FRONTEND_BASE_URL + '/profile/edit?activated=true') else: return render(request, 'account_activation_invalid.html') diff --git a/website/website/middleware.py b/website/website/middleware.py new file mode 100644 index 00000000..6ee6b4d2 --- /dev/null +++ b/website/website/middleware.py @@ -0,0 +1,62 @@ +from django.conf import settings +from django.http import HttpResponse, HttpResponseForbidden +from django.shortcuts import render + +class APIModeMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # 1. Check if strict API mode is enabled + if not getattr(settings, 'API_ONLY_MODE', False): + return self.get_response(request) + + # 2. Check if the path is allowed + path = request.path + + # Always allow these prefixes + allowed_prefixes = [ + '/api/', + '/admin/', + '/static/', + '/media/', + '/oauth/', + ] + + # Add whitelisted paths from settings + whitelist = getattr(settings, 'API_MODE_WHITELIST', []) + + # Helper to check permissions + is_allowed = False + + # Check standard prefixes + for prefix in allowed_prefixes: + if path.startswith(prefix): + is_allowed = True + break + + # Check whitelist (can be exact match or prefix) + if not is_allowed: + for item in whitelist: + # If item ends with slash, treat as prefix? + # Ideally, simple startswith check is flexible enough + if path.startswith(item): + is_allowed = True + break + + if is_allowed: + return self.get_response(request) + + # 3. Block access + # Return a simple unauthorized HTML page + content = """ + + Unauthorized + +
    +

    APIs are routed through this! (●'◡'●)

    +
    + + + """ + return HttpResponse(content, status=200) # Using 200 so it renders nicely in browser, or could use 403 diff --git a/website/website/settings.py b/website/website/settings.py index 384d038e..795b9ea6 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -32,6 +32,10 @@ ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1,localhost', cast=Csv()) + +API_ONLY_MODE = config('API_ONLY_MODE', default=False, cast=bool) +API_MODE_WHITELIST = [] + FRONTEND_BASE_URL = config('FRONTEND_BASE_URL', default='http://localhost:3000') # CORS_ALLOW_ALL_ORIGINS = True @@ -81,6 +85,7 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware', + 'website.middleware.APIModeMiddleware', ] AUTHENTICATION_BACKENDS = ( From 008e5480c166157849da283b206ad1b6e8f11af7 Mon Sep 17 00:00:00 2001 From: Shubham15986 Date: Sun, 3 May 2026 23:33:07 +0530 Subject: [PATCH 50/50] Fix duplicate profile images --- website/user_profile/models.py | 35 ++++++++++++++++++++++++++-------- website/website/settings.py | 15 ++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/website/user_profile/models.py b/website/user_profile/models.py index f769e67c..0d8fd3d2 100644 --- a/website/user_profile/models.py +++ b/website/user_profile/models.py @@ -14,11 +14,10 @@ import os from PIL import Image from io import BytesIO -from django.core.files.uploadedfile import InMemoryUploadedFile +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage from django_prometheus.models import ExportModelOperationsMixin -import sys - def content_file_name(instance, filename): ext = "png" @@ -51,15 +50,36 @@ def __str__(self): return self.user.username def save(self, *args, **kwargs): - if self.image: + previous_image_name = None + if self.pk: + previous = Profile.objects.filter(pk=self.pk).only('image').first() + if previous and previous.image: + previous_image_name = previous.image.name + + # Only process when a new image file is uploaded. + if self.image and not getattr(self.image, '_committed', True): img = Image.open(self.image) + img = img.convert('RGB') output = BytesIO() img = img.resize((100, 100)) img.save(output, format='PNG', quality=100) output.seek(0) - self.image = InMemoryUploadedFile(output, 'ImageField', ".png", 'image/png', - sys.getsizeof(output), None) - super(Profile, self).save() + + canonical_name = content_file_name(self, self.image.name or '') + if default_storage.exists(canonical_name): + default_storage.delete(canonical_name) + + self.image.save(canonical_name, ContentFile(output.getvalue()), save=False) + + super(Profile, self).save(*args, **kwargs) + + new_image_name = self.image.name if self.image else None + if ( + previous_image_name + and previous_image_name != new_image_name + and default_storage.exists(previous_image_name) + ): + default_storage.delete(previous_image_name) def get_absolute_url(self): return reverse('user_profile_api:user_detail', kwargs={'username': self.user.username}) @@ -72,4 +92,3 @@ class Meta: def update_user_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) - instance.profile.save() diff --git a/website/website/settings.py b/website/website/settings.py index 795b9ea6..f6e86f4f 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -9,7 +9,8 @@ import os from dotenv import load_dotenv -load_dotenv() +# Always load .env from the Django project root, regardless of current cwd. +load_dotenv(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env')) from decouple import config, Csv from datetime import timedelta import dj_database_url @@ -38,11 +39,15 @@ FRONTEND_BASE_URL = config('FRONTEND_BASE_URL', default='http://localhost:3000') -# CORS_ALLOW_ALL_ORIGINS = True +# Add explicit local dev origins by default while keeping env-driven control. +CORS_ALLOWED_ORIGINS = config( + 'CORS_ALLOWED_ORIGINS', + default='http://localhost:3000,http://127.0.0.1:3000,https://www.recursionnitd.in', + cast=Csv(), +) -CORS_ALLOWED_ORIGINS = [ - FRONTEND_BASE_URL -] +if FRONTEND_BASE_URL and FRONTEND_BASE_URL not in CORS_ALLOWED_ORIGINS: + CORS_ALLOWED_ORIGINS.append(FRONTEND_BASE_URL) CORS_ALLOW_CREDENTIALS = True