diff --git a/CDC_Backend/APIs/admin.py b/CDC_Backend/APIs/admin.py index 4c26bd9..eb4711c 100644 --- a/CDC_Backend/APIs/admin.py +++ b/CDC_Backend/APIs/admin.py @@ -1,9 +1,41 @@ from django.contrib import admin from .models import * +from django.contrib import admin +from django.contrib.admin.templatetags.admin_urls import admin_urlname +from django.shortcuts import resolve_url +from django.utils.html import format_html +from django.utils.safestring import SafeText +from .models import * admin.site.register(User) -admin.site.register(Student) +# admin.site.register(Student) admin.site.register(Admin) -admin.site.register(Placement) +# admin.site.register(Placement) admin.site.register(PlacementApplication) admin.site.register(PrePlacementOffer) + +admin.site.site_header = "CDC Recruitment Portal" + + +def model_admin_url(obj, name=None) -> str: + url = resolve_url(admin_urlname(obj._meta, SafeText("change")), obj.pk) + return format_html('{}', url, name or str(obj)) + + +@admin.register(Student) +class Student(admin.ModelAdmin): + list_display = ("roll_no", "name", "batch", "branch", "phone_number") + search_fields = ("roll_no", "name","phone_number") + ordering = ("roll_no", "name", "batch", "branch", "phone_number") + list_filter = ("batch", "branch") + + +@admin.register(Placement) +class Placement(admin.ModelAdmin): + list_display = (COMPANY_NAME, CONTACT_PERSON_NAME, PHONE_NUMBER, 'tier', 'compensation_CTC') + search_fields = (COMPANY_NAME, CONTACT_PERSON_NAME) + ordering = (COMPANY_NAME, CONTACT_PERSON_NAME, 'tier', 'compensation_CTC') + list_filter = ('tier',) + + + diff --git a/CDC_Backend/APIs/adminUrls.py b/CDC_Backend/APIs/adminUrls.py index 203c0dc..bd3d6e5 100644 --- a/CDC_Backend/APIs/adminUrls.py +++ b/CDC_Backend/APIs/adminUrls.py @@ -1,7 +1,15 @@ from django.urls import path -from . import companyViews +from . import adminViews urlpatterns = [ - + path('markStatus/', adminViews.markStatus, name="Mark Status"), + path('getDashboard/', adminViews.getDashboard, name="Get Dashboard"), + path('updateDeadline/', adminViews.updateDeadline, name="Update Deadline"), + path('updateOfferAccepted/', adminViews.updateOfferAccepted, name="Update Offer Accepted"), + path('updateEmailVerified', adminViews.updateEmailVerified, name="Update Email Verified"), + path('updateAdditionalInfo/', adminViews.updateAdditionalInfo, name="Update Additional Info"), + path('getApplications/', adminViews.getApplications, name="Get Applications"), + path("submitApplication/", adminViews.submitApplication, name="Submit Application"), + path('generateCSV/', adminViews.generateCSV, name="Generate CSV"), ] diff --git a/CDC_Backend/APIs/adminViews.py b/CDC_Backend/APIs/adminViews.py index e69de29..845ecef 100644 --- a/CDC_Backend/APIs/adminViews.py +++ b/CDC_Backend/APIs/adminViews.py @@ -0,0 +1,299 @@ +import json +from datetime import datetime + +from .utils import * +from rest_framework.decorators import api_view +import csv +from .serializers import * + + +@api_view(['POST']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID, STUDENT_LIST]) +def markStatus(request, id, email, user_type): + try: + data = request.data + # Getting all application from db for this opening + applications = PlacementApplication.objects.filter(placement_id=data[OPENING_ID]) + for i in data[STUDENT_LIST]: + application = applications.filter(student_id=i[STUDENT_ID]) # Filtering student's application + if len(application) > 0: + application = application[0] + application.selected = True if i[STUDENT_SELECTED] == "true" else False + + email = str(application.student.roll_no) + "@iitdh.ac.in" # Only allowing for IITDh emails + subject = STUDENT_APPLICATION_STATUS_TEMPLATE_SUBJECT.format(company_name=application.placement.company_name, + id=application.id) + data = { + "company_name": application.placement.company_name, + "designation": application.placement.designation, + "student_name": application.student.name + } + if application.selected: # Sending corresponding email to students + sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_SELECTED_TEMPLATE) + else: + sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_NOT_SELECTED_TEMPLATE) + application.save() + else: + raise ValueError("Student - " + i[STUDENT_ID] + " didn't apply for this opening") + return Response({'action': "Mark Status", 'message': "Marked Status"}, + status=status.HTTP_200_OK) + + except ValueError as e: + return Response({'action': "Mark Status", 'message': str(e)}, + status=status.HTTP_400_BAD_REQUEST) + except: + logger.warning("Mark Status: " + str(sys.exc_info())) + return Response({'action': "Mark Status", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@isAuthorized([ADMIN]) +def getDashboard(request, id, email, user_type): + try: + placements = Placement.objects.all().order_by('-created_at') + ongoing = placements.filter(deadline_datetime__gt=datetime.now()) + previous = placements.exclude(deadline_datetime__gt=datetime.now()) + ongoing = PlacementSerializerForAdmin(ongoing, many=True).data + previous = PlacementSerializerForAdmin(previous, many=True).data + + return Response( + {'action': "Get Dashboard - Admin", 'message': "Data Found", "ongoing": ongoing, "previous": previous}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Get Dashboard - Admin", 'message': 'Student Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Get Dashboard - Admin: " + str(sys.exc_info())) + return Response({'action': "Get Dashboard - Admin", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['POST']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID, DEADLINE_DATETIME]) +def updateDeadline(request, id, email, user_type): + try: + data = request.data + opening = get_object_or_404(Placement, pk=data[OPENING_ID]) + # Updating deadline date with correct format in datetime field + opening.deadline_datetime = datetime.strptime(data[DEADLINE_DATETIME], '%Y-%m-%d %H:%M:%S %z') + opening.save() + return Response({'action': "Update Deadline", 'message': "Deadline Updated"}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Update Deadline", 'message': 'Opening Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Update Deadline: " + str(sys.exc_info())) + return Response({'action': "Update Deadline", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID, OFFER_ACCEPTED]) +def updateOfferAccepted(request, id, email, user_type): + try: + data = request.data + opening = get_object_or_404(Placement, pk=data[OPENING_ID]) + opening.offer_accepted = True if data[OFFER_ACCEPTED] == "true" else False + opening.save() + return Response({'action': "Update Offer Accepted", 'message': "Offer Accepted Updated"}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Update Offer Accepted", 'message': 'Opening Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Update Offer Accepted: " + str(sys.exc_info())) + return Response({'action': "Update Offer Accepted", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID, EMAIL_VERIFIED]) +def updateEmailVerified(request, id, email, user_type): + try: + data = request.data + opening = get_object_or_404(Placement, pk=data[OPENING_ID]) + opening.email_verified = True if data[EMAIL_VERIFIED] == "true" else False + opening.save() + return Response({'action': "Update Email Verified", 'message': "Email Verified Updated"}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Update Email Verified", 'message': 'Opening Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Update Email Verified: " + str(sys.exc_info())) + return Response({'action': "Update Email Verified", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID, ADDITIONAL_INFO]) +def updateAdditionalInfo(request, id, email, user_type): + try: + data = request.data + opening = get_object_or_404(Placement, pk=data[OPENING_ID]) + if data[ADDITIONAL_INFO] == "": + opening.additional_info = [] + elif isinstance(data[ADDITIONAL_INFO], list): + opening.additional_info = data[ADDITIONAL_INFO] + else: + raise ValueError("Additional Info must be a list") + opening.save() + return Response({'action': "Update Additional Info", 'message': "Additional Info Updated"}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Update Additional Info", 'message': 'Opening Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except ValueError: + return Response({'action': "Update Additional Info", 'message': "Additional Info must be a list"}, + status=status.HTTP_400_BAD_REQUEST) + except: + logger.warning("Update Additional Info: " + str(sys.exc_info())) + return Response({'action': "Update Additional Info", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +@isAuthorized([ADMIN]) +@precheck([OPENING_ID]) +def getApplications(request, id, email, user_type): + try: + data = request.GET + opening = get_object_or_404(Placement, pk=data[OPENING_ID]) + applications = PlacementApplication.objects.filter(placement=opening) + serializer = PlacementApplicationSerializerForAdmin(applications, many=True) + return Response({'action': "Get Applications", 'message': 'Data Found', 'applications':serializer.data}, + status=status.HTTP_200_OK) + except Http404: + return Response({'action': "Get Applications", 'message': 'Opening Not Found'}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Get Applications: " + str(sys.exc_info())) + return Response({'action': "Get Applications", 'message': "Something went wrong"}, + status=status.HTTP_400_BAD_REQUEST) + + + +@api_view(['POST']) +@isAuthorized(allowed_users=[ADMIN]) +@precheck(required_data=[OPENING_TYPE, OPENING_ID, RESUME_FILE_NAME, + STUDENT_ID]) +def submitApplication(request, id, email, user_type): + try: + data = request.data + student = get_object_or_404(Student, roll_no=data[STUDENT_ID]) + # Only Allowing Applications for Placements + if data[OPENING_TYPE] == PLACEMENT: + if not len(PlacementApplication.objects.filter( + student_id=student.id, placement_id=data[OPENING_ID])): + application = PlacementApplication() + opening = get_object_or_404(Placement, id=data[OPENING_ID], + allowed_batch__contains=[student.batch], + allowed_branch__contains=[student.branch], + deadline_datetime__gte=datetime.now().date() + ) + if not opening.offer_accepted or not opening.email_verified: + raise PermissionError("Placement Not Approved") + + cond_stat, cond_msg = PlacementApplicationConditions(student, opening) + if not cond_stat: + raise PermissionError(cond_msg) + application.placement = opening + else: + raise PermissionError("Application is already Submitted") + else: + raise ValueError(OPENING_TYPE + " is Invalid") + + if data[RESUME_FILE_NAME] in student.resumes: + application.resume = data[RESUME_FILE_NAME] + else: + raise FileNotFoundError(RESUME_FILE_NAME + " Not Found") + + application.student = student + application.id = generateRandomString() + additional_info = {} + for i in opening.additional_info: + if i not in data[ADDITIONAL_INFO]: + raise AttributeError(i + " not found in Additional Info") + else: + additional_info[i] = data[ADDITIONAL_INFO][i] + + application.additional_info = json.dumps(additional_info) + data = { + "name": student.name, + "company_name": opening.company_name, + "application_type": data[OPENING_TYPE], + "additional_info": dict(json.loads(application.additional_info)), + } + subject = STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT.format(company_name=opening.company_name) + student_email = str(student.roll_no)+"@iitdh.ac.in" + sendEmail(student_email, subject, data, STUDENT_APPLICATION_SUBMITTED_TEMPLATE) + + application.save() + return Response({'action': "Submit Application", 'message': "Application Submitted"}, + status=status.HTTP_200_OK) + except Http404 as e: + return Response({'action': "Submit Application", 'message': str(e)}, + status=status.HTTP_404_NOT_FOUND) + except PermissionError as e: + return Response({'action': "Submit Application", 'message': str(e)}, + status=status.HTTP_403_FORBIDDEN) + except FileNotFoundError as e: + return Response({'action': "Submit Application", 'message': str(e)}, + status=status.HTTP_404_NOT_FOUND) + except: + logger.warning("Submit Application: " + str(sys.exc_info())) + return Response({'action': "Submit Application", 'message': "Something Went Wrong"}, + status=status.HTTP_400_BAD_REQUEST) + + + +@api_view(['POST']) +@isAuthorized(allowed_users=[ADMIN]) +@precheck(required_data=[OPENING_ID]) +def generateCSV(request, id, email, user_type): + try: + data = request.data + placement = get_object_or_404(Placement, id=data[OPENING_ID]) + applications = PlacementApplication.objects.filter(placement=placement) + filename = generateRandomString() + if not os.path.isdir(STORAGE_DESTINATION_APPLICATION_CSV): + os.mkdir(STORAGE_DESTINATION_APPLICATION_CSV) + destination_path = STORAGE_DESTINATION_APPLICATION_CSV + filename + ".csv" + f = open(destination_path, 'w') + writer = csv.writer(f) + header_row = APPLICATION_CSV_COL_NAMES.copy() + + header_row.extend(placement.additional_info) + writer.writerow(header_row) + for apl in applications: + row_details=[] + + row_details.append(apl.applied_at) + row_details.append(apl.student.roll_no) + row_details.append(apl.student.name) + row_details.append(str(apl.student.roll_no)+"@iitdh.ac.in") + row_details.append(apl.student.phone_number) + row_details.append(apl.student.branch) + row_details.append(apl.student.batch) + row_details.append(apl.student.cpi) + link = LINK_TO_STORAGE_RESUME + urllib.parse.quote_plus(apl.student.id + "/" + apl.resume) + row_details.append(link) + row_details.append(apl.selected) + + for i in placement.additional_info: + row_details.append(json.loads(apl.additional_info)[i]) + + writer.writerow(row_details) + f.close() + file_path = LINK_TO_APPLICATIONS_CSV + urllib.parse.quote_plus(filename+".csv") + return Response({'action': "Create csv", 'message': "CSV created", 'file': file_path}, + status=status.HTTP_200_OK) + except: + logger.warning("Create csv: " + str(sys.exc_info())) + print(sys.exc_info()) + return Response({'action': "Create csv", 'message': "Error Occurred"}, + status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/CDC_Backend/APIs/companyUrls.py b/CDC_Backend/APIs/companyUrls.py index f175144..5cda889 100644 --- a/CDC_Backend/APIs/companyUrls.py +++ b/CDC_Backend/APIs/companyUrls.py @@ -3,5 +3,5 @@ from . import companyViews urlpatterns = [ - path('addOpening/', companyViews.addOpening, name="Add Opening"), + path('addPlacement/', companyViews.addPlacement, name="Add Placement"), ] diff --git a/CDC_Backend/APIs/companyViews.py b/CDC_Backend/APIs/companyViews.py index 92c7eac..7d7d50b 100644 --- a/CDC_Backend/APIs/companyViews.py +++ b/CDC_Backend/APIs/companyViews.py @@ -1,120 +1,222 @@ import json from datetime import datetime -from django.utils.timezone import make_aware from rest_framework.decorators import api_view +from .models import * from .utils import * logger = logging.getLogger('db') @api_view(['POST']) -@precheck([DESIGNATION, DESCRIPTION, OPENING_TYPE, CITY, CITY_TYPE, - COMPENSATION, COMPENSATION_DETAILS, ALLOWED_BATCH, ALLOWED_BRANCH, - ROUNDS, CO_OP, START_DATE, ADDITIONAL_INFO, - DURATION, ROUND_DETAILS]) -def addOpening(request): +@precheck([COMPANY_NAME, ADDRESS, COMPANY_TYPE, NATURE_OF_BUSINESS, WEBSITE, COMPANY_DETAILS, IS_COMPANY_DETAILS_PDF, + CONTACT_PERSON_NAME, PHONE_NUMBER, EMAIL, CITY, STATE, COUNTRY, PINCODE, DESIGNATION, DESCRIPTION, + IS_DESCRIPTION_PDF, + COMPENSATION_CTC, COMPENSATION_GROSS, COMPENSATION_TAKE_HOME, COMPENSATION_BONUS, COMPENSATION_DETAILS, + IS_COMPENSATION_DETAILS_PDF, + ALLOWED_BRANCH, SELECTION_PROCEDURE_ROUNDS, SELECTION_PROCEDURE_DETAILS, IS_SELECTION_PROCEDURE_DETAILS_PDF, + TENTATIVE_DATE_OF_JOINING, + TENTATIVE_NO_OF_OFFERS, OTHER_REQUIREMENTS + ]) +def addPlacement(request): try: data = request.data - if data[OPENING_TYPE] == "Placement": - opening = Placement() - else: - raise ValueError("Invalid Opening Type") + files = request.FILES + opening = Placement() opening.id = generateRandomString() - # Create Company object here for every Opening - - - # Some new code above - - if data[DESIGNATION] != "": - opening.designation = data[DESIGNATION] + # Add a company details in the opening + opening.company_name = data[COMPANY_NAME] + opening.address = data[ADDRESS] + opening.company_type = data[COMPANY_TYPE] + opening.nature_of_business = data[NATURE_OF_BUSINESS] + opening.website = data[WEBSITE] + opening.company_details = data[COMPANY_DETAILS] + if data[IS_COMPANY_DETAILS_PDF] == "true": + opening.is_company_details_pdf = True + elif data[IS_COMPANY_DETAILS_PDF] == "false": + opening.is_company_details_pdf = False else: - raise ValueError(DESIGNATION + " Not Found") + raise ValueError('Invalid value for is_company_details_pdf') + if opening.is_company_details_pdf: + company_details_pdf = [] + for file in files.getlist(COMPANY_DETAILS_PDF): + file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + opening.id + '/' + company_details_pdf.append(saveFile(file, file_location)) + + opening.company_details_pdf_names = company_details_pdf + + # Add a contact person details in the opening + opening.contact_person_name = data[CONTACT_PERSON_NAME] + # Check if Phone number is Integer + if data[PHONE_NUMBER].isdigit(): + opening.phone_number = int(data[PHONE_NUMBER]) + else: + raise ValueError('Phone number should be integer') + + opening.email = data[EMAIL] + + # Add a company location in the opening + opening.city = data[CITY] + opening.state = data[STATE] + opening.country = data[COUNTRY] + + # Check if Pincode is Integer + if data[PINCODE].isdigit(): + opening.pin_code = int(data[PINCODE]) + else: + raise ValueError('Pincode should be integer') + + # If India then set city_type as Domestic else International + if opening.country == 'India': + opening.city_type = 'Domestic' + else: + opening.city_type = 'International' + + # Add a designation details in the opening + opening.designation = data[DESIGNATION] opening.description = data[DESCRIPTION] - if data[START_DATE] != "": - opening.description = data[START_DATE] + # Check if is_description_pdf is boolean + if data[IS_DESCRIPTION_PDF] == "true": + opening.is_description_pdf = True + elif data[IS_DESCRIPTION_PDF] == "false": + opening.is_description_pdf = False else: - raise ValueError(START_DATE + " Not Found") - if data[START_DATE] != "": - opening.start_date = datetime.strptime(data[START_DATE], '%d-%m-%Y') + raise ValueError('Invalid value for is_description_pdf') + + if opening.is_description_pdf: + description_pdf = [] + for file in files.getlist(DESCRIPTION_PDF): + file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + opening.id + '/' + description_pdf.append(saveFile(file, file_location)) + + opening.description_pdf_names = description_pdf + + # Add a compensation details in the opening + # Check if compensation_ctc is integer + if data[COMPENSATION_CTC].isdigit(): + opening.compensation_CTC = int(data[COMPENSATION_CTC]) + elif data[COMPENSATION_CTC] is None: + opening.compensation_CTC = None else: - raise ValueError(START_DATE + " Not Found") - if data[CITY] != "": - opening.city = data[CITY] + raise ValueError('Compensation CTC must be an integer') + + # Check if compensation_gross is integer + if data[COMPENSATION_GROSS].isdigit(): + opening.compensation_gross = int(data[COMPENSATION_GROSS]) + elif data[COMPENSATION_GROSS] is None: + opening.compensation_gross = None else: - raise ValueError(CITY + " Not Found") - if data[CITY_TYPE] != "": - opening.city_type = data[CITY_TYPE] + raise ValueError('Compensation Gross must be an integer') + + # Check if compensation_take_home is integer + if data[COMPENSATION_TAKE_HOME].isdigit(): + opening.compensation_take_home = int(data[COMPENSATION_TAKE_HOME]) + elif data[COMPENSATION_TAKE_HOME] is None: + opening.compensation_take_home = None else: - raise ValueError(CITY_TYPE + " Not Found") - if data[COMPENSATION] != "": - opening.compensation = data[COMPENSATION] + raise ValueError('Compensation Take Home must be an integer') + + # Check if compensation_bonus is integer + if data[COMPENSATION_BONUS].isdigit(): + opening.compensation_bonus = int(data[COMPENSATION_BONUS]) + elif data[COMPENSATION_BONUS] is None: + opening.compensation_bonus = None else: - raise ValueError(COMPENSATION + " Not Found") + raise ValueError('Compensation Bonus must be an integer') opening.compensation_details = data[COMPENSATION_DETAILS] - - if data[ALLOWED_BATCH] != "": - if set(json.loads(data[ALLOWED_BATCH])).issubset(BATCHES): - opening.allowed_batch = json.loads(data[ALLOWED_BATCH]) - else: - raise ValueError(ALLOWED_BATCH + " is Invalid") + # Check if is_compensation_details_pdf is boolean + if data[IS_COMPENSATION_DETAILS_PDF] == "true": + opening.is_compensation_details_pdf = True + elif data[IS_COMPENSATION_DETAILS_PDF] == "false": + opening.is_compensation_details_pdf = False else: - raise ValueError(ALLOWED_BATCH + " Not Found") - if data[ALLOWED_BRANCH] != "": - if set(json.loads(data[ALLOWED_BRANCH])).issubset(BRANCHES): - opening.allowed_branch = json.loads(data[ALLOWED_BRANCH]) - else: - raise ValueError(ALLOWED_BATCH + " is Invalid") + raise ValueError('Invalid value for is_compensation_details_pdf') + + if opening.is_compensation_details_pdf: + compensation_details_pdf = [] + for file in files.getlist(COMPENSATION_DETAILS_PDF): + file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + opening.id + '/' + compensation_details_pdf.append(saveFile(file, file_location)) + + opening.compensation_details_pdf_names = compensation_details_pdf + + opening.bond_details = data[BOND_DETAILS] + + # Check if selection_procedure_rounds is list + if data[SELECTION_PROCEDURE_ROUNDS] is None: + raise ValueError('Selection Procedure Rounds cannot be empty') else: - raise ValueError(ALLOWED_BRANCH + " Not Found") + try: + opening.selection_procedure_rounds = json.loads(data[SELECTION_PROCEDURE_ROUNDS]) + except: + raise ValueError('Selection Procedure Rounds must be a list') + opening.selection_procedure_details = data[SELECTION_PROCEDURE_DETAILS] + # Check if is_selection_procedure_details_pdf is boolean + if data[IS_SELECTION_PROCEDURE_DETAILS_PDF] == "true": + opening.is_selection_procedure_details_pdf = True + elif data[IS_SELECTION_PROCEDURE_DETAILS_PDF] == "false": + opening.is_selection_procedure_details_pdf = False + else: + raise ValueError('Invalid value for is_selection_procedure_pdf') - opening.rounds = json.loads(data[ROUNDS]) + if opening.is_selection_procedure_details_pdf: + selection_procedure_details_pdf = [] + for file in files.getlist(SELECTION_PROCEDURE_DETAILS_PDF): + file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + opening.id + '/' + selection_procedure_details_pdf.append(saveFile(file, file_location)) - opening.additional_info = json.loads(data[ADDITIONAL_INFO]) + opening.selection_procedure_details_pdf_names = selection_procedure_details_pdf - opening.status = STATUS_ACCEPTING_APPLICATIONS + stat, tier = getTier(opening.compensation_gross) + if stat: + opening.tier = tier + else: + raise ValueError('Invalid compensation gross') + # Convert to date object + opening.tentative_date_of_joining = datetime.strptime(data[TENTATIVE_DATE_OF_JOINING], '%d-%m-%Y').date() - opening.rounds_details = json.loads(data[ROUND_DETAILS]) + # Only Allowing Fourth Year for Placement + opening.allowed_batch = [FOURTH_YEAR, ] + # Check if allowed_branch are valid + if data[ALLOWED_BRANCH] is None: + raise ValueError('Allowed Branch cannot be empty') + elif set(json.loads(data[ALLOWED_BRANCH])).issubset(BRANCHES): + opening.allowed_branch = json.loads(data[ALLOWED_BRANCH]) + else: + raise ValueError('Allowed Branch must be a subset of ' + str(BRANCHES)) - opening.created_at = make_aware(datetime.now()) - files = request.FILES.getlist(ATTACHMENTS) - attachments = [] - for file in files: - attachments.append(saveFile(file, STORAGE_DESTINATION_COMPANY_ATTACHMENTS)) + # Check if tentative_no_of_offers is integer + if data[TENTATIVE_NO_OF_OFFERS].isdigit(): + opening.tentative_no_of_offers = int(data[TENTATIVE_NO_OF_OFFERS]) + else: + raise ValueError('Tentative No Of Offers must be an integer') + + opening.other_requirements = data[OTHER_REQUIREMENTS] - opening.attachments = attachments opening.save() + data = { "designation": opening.designation, - "opening_type": data[OPENING_TYPE], - "opening_link": "google.com", # Some Changes here too - "company_name": opening.company.name + "opening_type": PLACEMENT, + "opening_link": PLACEMENT_OPENING_URL.format(id=opening.id), # Some Changes here too + "company_name": opening.company_name } - # Needs some edits here + sendEmail(opening.email, COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT.format(id=opening.id), data, + COMPANY_OPENING_SUBMITTED_TEMPLATE) - email = 'This is temporary' - - # Delete the above var when done - - stat = sendEmail(email, COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT.format(id=opening.id), data, - COMPANY_OPENING_SUBMITTED_TEMPLATE) - if stat is not True: - logger.warning("Add New Opening: Unable to send email - " + stat) - - return Response({'action': "Add Opening", 'message': "Opening Added"}, + return Response({'action': "Add Placement", 'message': "Placement Added Successfully"}, status=status.HTTP_200_OK) except ValueError as e: - return Response({'action': "Add Opening", 'message': str(e)}, + return Response({'action': "Add Placement", 'message': str(e)}, status=status.HTTP_400_BAD_REQUEST) except: - logger.warning("Add New Opening: " + str(sys.exc_info())) - return Response({'action': "Add Opening", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, + logger.warning("Add New Placement: " + str(sys.exc_info())) + return Response({'action': "Add Placement", 'message': "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST) diff --git a/CDC_Backend/APIs/constants.py b/CDC_Backend/APIs/constants.py index 7a4b25b..4201ce9 100644 --- a/CDC_Backend/APIs/constants.py +++ b/CDC_Backend/APIs/constants.py @@ -1,14 +1,20 @@ BRANCH_CHOICES = [ ["CSE", "CSE"], ["EE", "EE"], - ["ME", "ME"] + ["ME", "ME"], + ['EP', 'EP'], +] +BRANCHES = [ + "CSE", + "EE", + "ME", + "EP" ] - BATCH_CHOICES = [ - ["FIRST", "First"], - ["SECOND", "Second"], - ["THIRD", "Third"], - ["FOURTH", "Fourth"] + ["2021", "2021"], + ["2020", "2020"], + ["2019", "2019"], + ["2018", "2018"] ] OFFER_CITY_TYPE = [ @@ -26,70 +32,105 @@ TIERS = [ ['6', 'Tier 6'] ] - -TOTAL_BRANCHES = 3 # Total No of Branches +TOTAL_BRANCHES = 4 # Total No of Branches TOTAL_BATCHES = 4 # Total No of Batches +# To be Configured Properly CLIENT_ID = "956830229554-290mirc16pdhd5j7ph7v7ukibo4t1qcp.apps.googleusercontent.com" # Google Login Client ID +# To be Configured Properly +PLACEMENT_OPENING_URL = "https://www.googleapis.com/auth/adwords/{id}" +LINK_TO_STORAGE_COMPANY_ATTACHMENT = "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/" +LINK_TO_STORAGE_RESUME = "https://storage.googleapis.com/cdc-backend-attachments/resume/" +LINK_TO_APPLICATIONS_CSV = "https://storage.googleapis.com/cdc-backend-attachments/applications-csv/" + + TOKEN = "token_id" EMAIL = "email" STUDENT = 'student' -ADMIN = 'Admin' +ADMIN = 'admin' COMPANY = '' -STORAGE_DESTINATION = "./Storage/Resumes/" +# To be Configured Properly +FOURTH_YEAR = '2018' +MAX_OFFERS_PER_STUDENT = 2 + + +STORAGE_DESTINATION_RESUMES = "./Storage/Resumes/" STORAGE_DESTINATION_COMPANY_ATTACHMENTS = './Storage/Company_Attachments/' +STORAGE_DESTINATION_APPLICATION_CSV = './Storage/Application_CSV/' + RESUME_FILE_NAME = 'resume_file_name' APPLICATION_ID = "application_id" OPENING_ID = "opening_id" -STUDENT_ID = "student_id" ADDITIONAL_INFO = "additional_info" STATUS_ACCEPTING_APPLICATIONS = "Accepting Applications" PLACEMENT = "Placement" -COMPANY_WEBSITE = 'website' -COMPANY_ADDRESS = 'address' +COMPANY_NAME = "company_name" +ADDRESS = "address" +COMPANY_TYPE = "company_type" +NATURE_OF_BUSINESS = "nature_of_business" +WEBSITE = 'website' +COMPANY_DETAILS = "company_details" +COMPANY_DETAILS_PDF = "company_details_pdf" +IS_COMPANY_DETAILS_PDF = "is_company_details_pdf" +COMPANY_DETAILS_PDF_NAMES = "company_details_pdf_names" PHONE_NUMBER = 'phone_number' CONTACT_PERSON_NAME = 'contact_person_name' +CITY = 'city' +STATE = 'state' +COUNTRY = 'country' +PINCODE = 'pincode' + DESIGNATION = 'designation' DESCRIPTION = 'description' +DESCRIPTION_PDF = 'description_pdf' +DESCRIPTION_PDF_NAMES = 'description_pdf_names' +IS_DESCRIPTION_PDF = 'is_description_pdf' OPENING_TYPE = 'opening_type' -CITY = 'city' -CITY_TYPE = 'city_type' -COMPENSATION = 'compensation' +COMPENSATION_CTC = 'compensation_ctc' +COMPENSATION_GROSS = 'compensation_gross' +COMPENSATION_TAKE_HOME = 'compensation_take_home' +COMPENSATION_BONUS = 'compensation_bonus' COMPENSATION_DETAILS = 'compensation_details' +COMPENSATION_DETAILS_PDF = 'compensation_details_pdf' +COMPENSATION_DETAILS_PDF_NAMES = 'compensation_details_pdf_names' +IS_COMPENSATION_DETAILS_PDF = 'is_compensation_details_pdf' ALLOWED_BATCH = 'allowed_batch' ALLOWED_BRANCH = 'allowed_branch' -ATTACHMENTS = 'attachments' -ROUNDS = 'rounds' -ROUND_DETAILS = 'round_details' -DURATION = 'duration' -CO_OP = 'co_op' -START_DATE = "start_date" +BOND_DETAILS = 'bond_details' +SELECTION_PROCEDURE_ROUNDS = 'selection_procedure_rounds' +SELECTION_PROCEDURE_DETAILS = 'selection_procedure_details' +SELECTION_PROCEDURE_DETAILS_PDF = 'selection_procedure_details_pdf' +SELECTION_PROCEDURE_DETAILS_PDF_NAMES = 'selection_procedure_details_pdf_names' +IS_SELECTION_PROCEDURE_DETAILS_PDF = 'is_selection_procedure_details_pdf' +TENTATIVE_DATE_OF_JOINING = 'tentative_date_of_joining' +TENTATIVE_NO_OF_OFFERS = 'tentative_no_of_offers' +OTHER_REQUIREMENTS = 'other_requirements' +DEADLINE_DATETIME = 'deadline_datetime' +OFFER_ACCEPTED = 'offer_accepted' +EMAIL_VERIFIED = 'email_verified' STUDENT_LIST = "student_list" -STUDENT_STATUS = "student_status" +STUDENT_ID = "student_id" +STUDENT_SELECTED = "student_selected" -BRANCHES = [ - "CSE", - "EE", - "ME" -] -BATCHES = [ - "FIRST", - "SECOND", - "THIRD", - "FOURTH" -] COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT = "Notification Submitted - {id} - CDC IIT Dharwad" +STUDENT_APPLICATION_STATUS_TEMPLATE_SUBJECT = 'Application Status : {company_name} - {id}' +STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT = 'CDC - Application Submitted - {company_name}' STUDENT_APPLICATION_SUBMITTED_TEMPLATE = 'student_application_submitted.html' COMPANY_OPENING_SUBMITTED_TEMPLATE = 'company_opening_submitted.html' +STUDENT_APPLICATION_STATUS_SELECTED_TEMPLATE = 'student_application_status_selected.html' +STUDENT_APPLICATION_STATUS_NOT_SELECTED_TEMPLATE = 'student_application_status_not_selected.html' + +APPLICATION_CSV_COL_NAMES = ['Applied At', 'Roll No.', 'Name', 'Email', 'Phone Number', 'Branch', 'Batch', 'CPI', + 'Resume', 'Selected', ] diff --git a/CDC_Backend/APIs/models.py b/CDC_Backend/APIs/models.py index 7203738..d8c9080 100644 --- a/CDC_Backend/APIs/models.py +++ b/CDC_Backend/APIs/models.py @@ -2,6 +2,8 @@ from django.contrib.postgres.fields import ArrayField from django.db import models from django.utils import timezone from .constants import * +# from .utils import * + class User(models.Model): @@ -26,58 +28,80 @@ class Admin(models.Model): name = models.CharField(blank=False, max_length=50) +def two_day_after_today(): + return timezone.now() + timezone.timedelta(days=2) + + class Placement(models.Model): id = models.CharField(blank=False, primary_key=True, max_length=15) - name = models.CharField(blank=False, max_length=50, default="") - address = models.CharField(blank=False, max_length=150, default="") - companyType = models.CharField(blank=False, max_length=50, default="") - website = models.CharField(blank=True, max_length=50, default="") - contact_person_name = models.CharField(blank=False, max_length=50, default="") - phone_number = models.PositiveBigIntegerField(blank=False, default=0) - designation = models.CharField(blank=False, max_length=25, default=None, null=True) - description = models.CharField(blank=False, max_length=200) - start_date = models.DateField(blank=False, verbose_name="Start Date") + # Company Details + company_name = models.CharField(blank=False, max_length=50) + address = models.CharField(blank=False, max_length=150) + company_type = models.CharField(blank=False, max_length=50) + nature_of_business = models.CharField(blank=False, max_length=50, default="") + website = models.CharField(blank=True, max_length=50) + company_details = models.CharField(blank=False, max_length=500, default=None, null=True) + company_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5, default=list, blank=True) + is_company_details_pdf = models.BooleanField(blank=False, default=False) + contact_person_name = models.CharField(blank=False, max_length=50) + phone_number = models.PositiveBigIntegerField(blank=False) + email = models.CharField(blank=False, max_length=50, default="") city = models.CharField(blank=False, max_length=100, default="") + state = models.CharField(blank=False, max_length=100, default="") + country = models.CharField(blank=False, max_length=100, default="") + pin_code = models.IntegerField(blank=False, default=None,null=True) city_type = models.CharField(blank=False, max_length=15, choices=OFFER_CITY_TYPE) - compensation = models.IntegerField(blank=False) # Job - Per Year - compensation_details = models.CharField(blank=True, max_length=200) + # Job Details + designation = models.CharField(blank=False, max_length=25, default=None, null=True) + description = models.CharField(blank=False, max_length=200, default=None, null=True) + description_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5, default=list, blank=True) + is_description_pdf = models.BooleanField(blank=False, default=False) + compensation_CTC = models.IntegerField(blank=False, default=None, null=True ) # Job - Per Year + compensation_gross = models.IntegerField(blank=False, default=None, null=True) + compensation_take_home = models.IntegerField(blank=False, default=None, null=True) + compensation_bonus = models.IntegerField(blank=True, default=None, null=True) + compensation_details = models.CharField(blank=True, max_length=200, default=None, null=True) + compensation_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5, default=list, blank=True) + is_compensation_details_pdf = models.BooleanField(blank=False, default=False) + bond_details = models.CharField(blank=True, max_length=200) + selection_procedure_rounds = ArrayField(models.CharField(null=True, default=None, max_length=100), size=10, default=list, blank=True) + selection_procedure_details = models.CharField(blank=True, max_length=200) + selection_procedure_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5, default=list, blank=True) + is_selection_procedure_details_pdf = models.BooleanField(blank=False, default=False) tier = models.CharField(blank=False, choices=TIERS, max_length=10, default=None, null=True) + tentative_date_of_joining = models.DateField(blank=False, verbose_name="Tentative Date", default=timezone.now) allowed_batch = ArrayField( models.CharField(max_length=10, choices=BATCH_CHOICES), size=TOTAL_BATCHES, default=list ) + allowed_branch = ArrayField( models.CharField(choices=BRANCH_CHOICES, blank=False, max_length=10), size=TOTAL_BRANCHES, default=list ) - attachments = ArrayField( - models.CharField(max_length=100, blank=True), - size=10, - blank=True - ) - rounds = ArrayField( - models.CharField(max_length=25, blank=True), - size=10, - ) - additional_info = ArrayField( - models.CharField(max_length=25, blank=True), - size=10, - blank=True - ) - status = models.CharField(max_length=50, blank=False) - rounds_details = models.JSONField(blank=True, default=dict) + tentative_no_of_offers = models.IntegerField(blank=False, default=1) + other_requirements = models.CharField(blank=True, max_length=200, default="") + additional_info = ArrayField(models.CharField(blank=True, max_length=200), size=15, default=list, blank=True) + email_verified = models.BooleanField(blank=False, default=False) + offer_accepted = models.BooleanField(blank=False, default=None, null=True) + deadline_datetime = models.DateTimeField(blank=False, verbose_name="Deadline Date", default=two_day_after_today) created_at = models.DateTimeField(blank=False, default=None, null=True) + def save(self, *args, **kwargs): + ''' On save, add timestamps ''' + if not self.created_at: + self.created_at = timezone.now() + + return super(Placement, self).save(*args, **kwargs) class PlacementApplication(models.Model): id = models.CharField(blank=False, primary_key=True, max_length=15) placement = models.ForeignKey(Placement, blank=False, on_delete=models.RESTRICT, default=None, null=True) student = models.ForeignKey(Student, blank=False, on_delete=models.CASCADE) resume = models.CharField(max_length=100, blank=False, null=True, default=None) - status = models.CharField(max_length=50, null=True, blank=True, default=None) - additional_info = models.JSONField(blank=True, default=None, null=True) + additional_info = models.JSONField(blank=True, null=True, default=None) selected = models.BooleanField(null=True, default=None, blank=True) applied_at = models.DateTimeField(blank=False, default=None, null=True) diff --git a/CDC_Backend/APIs/serializers.py b/CDC_Backend/APIs/serializers.py index 62f9ef7..a7c1228 100644 --- a/CDC_Backend/APIs/serializers.py +++ b/CDC_Backend/APIs/serializers.py @@ -1,56 +1,178 @@ +import urllib + from rest_framework import serializers from .models import * class StudentSerializer(serializers.ModelSerializer): + resume_list = serializers.SerializerMethodField() + offers = serializers.SerializerMethodField() + + def get_resume_list(self, obj): + links = [] + for i in obj.resumes: + ele = {} + ele['link'] = LINK_TO_STORAGE_RESUME + urllib.parse.quote_plus(obj.id + "/" + i) + ele['name'] = i + links.append(ele) + return links + + def get_offers(self, obj): + selected_companies = PlacementApplication.objects.filter(student_id=obj.id, selected=True) + companies = [] + + for i in selected_companies: + ele = {} + ele['designation'] = i.placement.designation + ele['company_name'] = i.placement.company_name + ele['application_id'] = i.id + companies.append(ele) + + return companies + class Meta: model = Student - fields = '__all__' - # exclude = ['id'] + exclude = ['resumes'] + +class PlacementSerializerForStudent(serializers.ModelSerializer): + company_details_pdf_links = serializers.SerializerMethodField() + description_pdf_links = serializers.SerializerMethodField() + compensation_pdf_links = serializers.SerializerMethodField() + selection_procedure_details_pdf_links = serializers.SerializerMethodField() -class PlacementSerializer(serializers.ModelSerializer): - company_details = serializers.SerializerMethodField() + def get_company_details_pdf_links(self, obj): + links =[] + for pdf_name in obj.company_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_description_pdf_links(self, obj): + links =[] + for pdf_name in obj.description_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_compensation_pdf_links(self, obj): + links =[] + for pdf_name in obj.compensation_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_selection_procedure_details_pdf_links(self, obj): + links =[] + for pdf_name in obj.selection_procedure_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links - def get_company_details(self, obj): - data = { - "id": obj.company.id, - "name": obj.company.name, - "address": obj.company.address, - "companyType": obj.company.companyType, - "website": obj.company.website, - } - return data class Meta: model = Placement - exclude=[COMPANY] + exclude = [CONTACT_PERSON_NAME, PHONE_NUMBER, EMAIL, COMPANY_DETAILS_PDF_NAMES, DESCRIPTION_PDF_NAMES, + COMPENSATION_DETAILS_PDF_NAMES, SELECTION_PROCEDURE_DETAILS_PDF_NAMES, OFFER_ACCEPTED, EMAIL_VERIFIED] + depth = 1 + +class PlacementSerializerForAdmin(serializers.ModelSerializer): + company_details_pdf_links = serializers.SerializerMethodField() + description_pdf_links = serializers.SerializerMethodField() + compensation_pdf_links = serializers.SerializerMethodField() + selection_procedure_details_pdf_links = serializers.SerializerMethodField() + + + def get_company_details_pdf_links(self, obj): + links =[] + for pdf_name in obj.company_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_description_pdf_links(self, obj): + links =[] + for pdf_name in obj.description_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_compensation_pdf_links(self, obj): + links =[] + for pdf_name in obj.compensation_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + def get_selection_procedure_details_pdf_links(self, obj): + links =[] + for pdf_name in obj.selection_procedure_details_pdf_names: + ele = {} + link = LINK_TO_STORAGE_COMPANY_ATTACHMENT + urllib.parse.quote_plus(obj.id + "/" + pdf_name) + ele['link'] = link + ele['name'] = pdf_name + links.append(ele) + return links + + + + class Meta: + model = Placement + exclude = [COMPANY_DETAILS_PDF_NAMES, DESCRIPTION_PDF_NAMES, + COMPENSATION_DETAILS_PDF_NAMES, SELECTION_PROCEDURE_DETAILS_PDF_NAMES] depth = 1 class PlacementApplicationSerializer(serializers.ModelSerializer): - application_status = serializers.SerializerMethodField() - company_details = serializers.SerializerMethodField() + placement = serializers.SerializerMethodField() + resume_link = serializers.SerializerMethodField() - - def get_application_status(self, obj): - if obj.status is None: - return obj.placement.status - else: - return obj.status - - - def get_company_details(self, obj): - data = { - "id": obj.placement.company.id, - "name": obj.placement.company.name, - "address": obj.placement.company.address, - "companyType": obj.placement.company.companyType, - "website": obj.placement.company.website, - } + def get_placement(self, obj): + data = PlacementSerializerForStudent(obj.placement).data return data + def get_resume_link(self, obj): + link = LINK_TO_STORAGE_RESUME + urllib.parse.quote_plus(obj.id + "/" + obj.resume) + return link + class Meta: model = PlacementApplication - exclude = ['status', 'student'] + exclude = [STUDENT, 'resume'] + +class PlacementApplicationSerializerForAdmin(serializers.ModelSerializer): + student_details = serializers.SerializerMethodField() + resume_link = serializers.SerializerMethodField() + + def get_student_details(self, obj): + data = StudentSerializer(obj.student).data + return data + + def get_resume_link(self, obj): + link = LINK_TO_STORAGE_RESUME + urllib.parse.quote_plus(obj.id + "/" + obj.resume) + return link + + class Meta: + model = PlacementApplication + exclude = ['placement', 'resume'] \ No newline at end of file diff --git a/CDC_Backend/APIs/studentViews.py b/CDC_Backend/APIs/studentViews.py index d709576..0ea1824 100644 --- a/CDC_Backend/APIs/studentViews.py +++ b/CDC_Backend/APIs/studentViews.py @@ -1,5 +1,5 @@ -import logging -from os import path, remove +import json +from datetime import datetime from rest_framework.decorators import api_view @@ -16,8 +16,7 @@ def login(request, id, email, user_type): return Response({'action': "Login", 'message': "Verified", "user_type": user_type}, status=status.HTTP_200_OK) except: - return Response({'action': "Login", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, + return Response({'action': "Login", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) @@ -31,7 +30,8 @@ def studentProfile(request, id, email, user_type): return Response({'action': "Student Profile", 'message': "Details Found", "details": data}, status=status.HTTP_200_OK) except: - return Response({'action': "Student Profile", 'message': "Error Occurred {0}".format(str(sys.exc_info()[1]))}, + logger.warning("Student Profile: " + str(sys.exc_info())) + return Response({'action': "Student Profile", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) @@ -41,20 +41,12 @@ def addResume(request, id, email, user_type): destination_path = "" try: student = get_object_or_404(Student, id=id) - prefix = generateRandomString() files = request.FILES - file_name = prefix + "_" + files['file'].name - print(file_name) - student.resumes.append(file_name) file = files['file'] - destination_path = STORAGE_DESTINATION + str(file_name) - if path.exists(destination_path): - remove(destination_path) - - with open(destination_path, 'wb+') as destination: - for chunk in file.chunks(): - destination.write(chunk) + destination_path = STORAGE_DESTINATION_RESUMES + id + "/" + file_name = saveFile(file, destination_path) + student.resumes.append(file_name) student.save() return Response({'action': "Upload Resume", 'message': "Resume Added"}, @@ -68,8 +60,7 @@ def addResume(request, id, email, user_type): remove(destination_path) else: logger.warning("Upload Resume: " + str(sys.exc_info())) - return Response({'action': "Upload Resume", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, + return Response({'action': "Upload Resume", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) @@ -81,23 +72,23 @@ def getDashboard(request, id, email, user_type): placements = Placement.objects.filter(allowed_batch__contains=[studentDetails.batch], allowed_branch__contains=[studentDetails.branch], - status=STATUS_ACCEPTING_APPLICATIONS) - placementsdata = PlacementSerializer(placements, many=True).data + deadline_datetime__gte=datetime.now(), + offer_accepted=True, email_verified=True).order_by('deadline_datetime') + placementsdata = PlacementSerializerForStudent(placements, many=True).data placementApplications = PlacementApplication.objects.filter(student_id=id) placementApplications = PlacementApplicationSerializer(placementApplications, many=True).data return Response( - {'action': "Placement and Internships", 'message': "Data Found", "placements": placementsdata, + {'action': "Get Dashboard - Student", 'message': "Data Found", "placements": placementsdata, 'placementApplication': placementApplications}, status=status.HTTP_200_OK) except Http404: - return Response({'action': "Placements and Internships", 'message': 'Student Not Found'}, + return Response({'action': "Get Dashboard - Student", 'message': 'Student Not Found'}, status=status.HTTP_404_NOT_FOUND) except: - logger.warning("Placements and Internships: " + str(sys.exc_info())) - return Response({'action': "Placements and Internships", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, + logger.warning("Get Dashboard -Student: " + str(sys.exc_info())) + return Response({'action': "Get Dashboard - Student", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) @@ -108,7 +99,7 @@ def deleteResume(request, id, email, user_type): try: student = get_object_or_404(Student, id=id) file_name = request.data[RESUME_FILE_NAME] - destination_path = STORAGE_DESTINATION + str(file_name) + destination_path = STORAGE_DESTINATION_RESUMES + id + "/" + str(file_name) if path.exists(destination_path): remove(destination_path) student.resumes.remove(file_name) @@ -125,28 +116,32 @@ def deleteResume(request, id, email, user_type): status=status.HTTP_404_NOT_FOUND) except: logger.warning("Delete Resume: " + str(sys.exc_info())) - return Response({'action': "Delete Resume", 'message': "Error Occurred {0}".format( - str(sys.exc_info()))}, + return Response({'action': "Delete Resume", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @isAuthorized(allowed_users=[STUDENT]) @precheck(required_data=[OPENING_TYPE, OPENING_ID, RESUME_FILE_NAME, - ADDITIONAL_INFO]) + ]) def submitApplication(request, id, email, user_type): try: data = request.data student = get_object_or_404(Student, id=id) - + # Only Allowing Applications for Placements if data[OPENING_TYPE] == PLACEMENT: if not len(PlacementApplication.objects.filter( student_id=id, placement_id=data[OPENING_ID])): application = PlacementApplication() opening = get_object_or_404(Placement, id=data[OPENING_ID], - status=STATUS_ACCEPTING_APPLICATIONS) + allowed_batch__contains=[student.batch], + allowed_branch__contains=[student.branch], + deadline_datetime__gte=datetime.now().date() + ) + if not opening.offer_accepted or not opening.email_verified: + raise PermissionError("Placement Not Approved") + cond_stat, cond_msg = PlacementApplicationConditions(student, opening) - print(cond_stat, cond_msg) if not cond_stat: raise PermissionError(cond_msg) application.placement = opening @@ -162,21 +157,29 @@ def submitApplication(request, id, email, user_type): application.student = student application.id = generateRandomString() + additional_info = {} for i in opening.additional_info: if i not in data[ADDITIONAL_INFO]: - print(i) raise AttributeError(i + " not found in Additional Info") + else: + additional_info[i] = data[ADDITIONAL_INFO][i] - application.additional_info = data[ADDITIONAL_INFO] - if not sendApplicationEmail(email, student.name, opening.company.name, data[OPENING_TYPE], - data[ADDITIONAL_INFO]): - logger.error("Submit Application: Unable to Send Email") - # raise RuntimeError("Unable to Send Email") + application.additional_info = json.dumps(additional_info) + data = { + "name": student.name, + "company_name": opening.company_name, + "application_type": data[OPENING_TYPE], + "additional_info": dict(json.loads(application.additional_info)), + } + subject = STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT.format(company_name=opening.company_name) + sendEmail(email, subject, data, STUDENT_APPLICATION_SUBMITTED_TEMPLATE) application.save() return Response({'action': "Submit Application", 'message': "Application Submitted"}, status=status.HTTP_200_OK) - + except Http404 as e: + return Response({'action': "Submit Application", 'message': str(e)}, + status=status.HTTP_404_NOT_FOUND) except PermissionError as e: return Response({'action': "Submit Application", 'message': str(e)}, status=status.HTTP_403_FORBIDDEN) @@ -185,6 +188,5 @@ def submitApplication(request, id, email, user_type): status=status.HTTP_404_NOT_FOUND) except: logger.warning("Submit Application: " + str(sys.exc_info())) - return Response({'action': "Submit Application", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, + return Response({'action': "Submit Application", 'message': "Something Went Wrong"}, status=status.HTTP_400_BAD_REQUEST) diff --git a/CDC_Backend/APIs/utils.py b/CDC_Backend/APIs/utils.py index 422cec1..496d52c 100644 --- a/CDC_Backend/APIs/utils.py +++ b/CDC_Backend/APIs/utils.py @@ -5,6 +5,7 @@ import string import sys from os import path, remove +import background_task from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.http import Http404 @@ -16,7 +17,8 @@ from google.oauth2 import id_token from rest_framework import status from rest_framework.response import Response -from .models import * +from .constants import * +from .models import User, PrePlacementOffer, PlacementApplication logger = logging.getLogger('db') @@ -78,11 +80,9 @@ def isAuthorized(allowed_users=None): raise PermissionError("Authorization Header Not Found") except PermissionError as e: - print(e) return Response({'action': "Is Authorized?", 'message': str(e)}, status=status.HTTP_401_UNAUTHORIZED) except Http404: - print('http404') return Response({'action': "Is Authorized?", 'message': "User Not Found. Contact CDC for more details"}, status=status.HTTP_404_NOT_FOUND) except ValueError as e: @@ -90,9 +90,10 @@ def isAuthorized(allowed_users=None): return Response({'action': "Is Authorized?", 'message': str(e)}, status=status.HTTP_401_UNAUTHORIZED) except: - return Response({'action': "Is Authorized?", 'message': "Error Occurred {0}".format( - str(sys.exc_info()[1]))}, - status=status.HTTP_400_BAD_REQUEST) + logger.warning("Is Authorized? " + str(sys.exc_info())) + return Response( + {'action': "Is Authorized?", 'message': "Something went wrong. Contact CDC for more details"}, + status=status.HTTP_400_BAD_REQUEST) return wrapper_func @@ -102,36 +103,12 @@ def isAuthorized(allowed_users=None): def generateRandomString(): try: N = 15 - res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N)) + res = ''.join(random.choices(string.ascii_uppercase +string.ascii_lowercase+ string.digits, k=N)) return res except: return False -def sendApplicationEmail(email, name, company_name, applicaton_type, additional_info): - try: - subject = 'CDC - Application Submitted - ' + str(company_name) - data = { - "name": name, - "company_name": company_name, - "applicaton_type": applicaton_type, - "additional_info": additional_info - } - - html_content = render_to_string('student_application_submited.html', data) # render with dynamic value - text_content = strip_tags(html_content) - - email_from = settings.EMAIL_HOST_USER - recipient_list = [str(email), ] - - msg = EmailMultiAlternatives(subject, text_content, email_from, recipient_list) - msg.attach_alternative(html_content, "text/html") - msg.send() - return True - except: - return False - - def saveFile(file, location): prefix = generateRandomString() file_name = prefix + "_" + file.name @@ -139,7 +116,7 @@ def saveFile(file, location): if not path.isdir(location): os.mkdir(location) - destination_path = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + str(file_name) + destination_path = location + str(file_name) if path.exists(destination_path): remove(destination_path) @@ -150,6 +127,7 @@ def saveFile(file, location): return file_name +@background_task.background(schedule=10) def sendEmail(email_to, subject, data, template): try: html_content = render_to_string(template, data) # render with dynamic value @@ -163,17 +141,18 @@ def sendEmail(email_to, subject, data, template): msg.send() return True except: + logger.error("Send Email: " + str(sys.exc_info())) print(str(sys.exc_info()[1])) - return str(sys.exc_info()[1]) + return False def PlacementApplicationConditions(student, placement): try: selected_companies = PlacementApplication.objects.filter(student=student, selected=True) selected_companies_PSU = [i for i in selected_companies if i.placement.tier == 'psu'] - PPO = PrePlacementOffer.objects.filter(internship_application__student=student, accepted=True) + PPO = PrePlacementOffer.objects.filter(student=student, accepted=True) - if len(selected_companies) + len(PPO) >= 2: + if len(selected_companies) + len(PPO) >= MAX_OFFERS_PER_STUDENT: raise PermissionError("Max Applications Reached for the Season") if len(selected_companies_PSU) > 0: @@ -195,3 +174,41 @@ def PlacementApplicationConditions(student, placement): print(sys.exc_info()) logger.warning("Utils - PlacementApplicationConditions: " + str(sys.exc_info())) return False, "_" + + +def getTier(compensation_gross, is_psu=False): + try: + if is_psu: + return True, 'psu' + if compensation_gross < 0: + raise ValueError("Negative Compensation") + elif compensation_gross < 600000: # Tier 7 If less than 600,000 + return True, "7" + # Tier 6 If less than 800,000 and greater than or equal to 600,000 + elif compensation_gross < 800000: + return True, "6" + # Tier 5 If less than 1,000,000 and greater than or equal to 800,000 + elif compensation_gross < 1000000: + return True, "5" + # Tier 4 If less than 1,200,000 and greater than or equal to 1,000,000 + elif compensation_gross < 1200000: + return True, "4" + # Tier 3 If less than 1,500,000 and greater than or equal to 1,200,000 + elif compensation_gross < 1500000: + return True, "3" + # Tier 2 If less than 1,800,000 and greater than or equal to 1,500,000 + elif compensation_gross < 1800000: + return True, "2" + # Tier 1 If greater than or equal to 1,800,000 + elif compensation_gross >= 1800000: + return True, "1" + else: + raise ValueError("Invalid Compensation") + + except ValueError as e: + logger.warning("Utils - getTier: " + str(sys.exc_info())) + return False, e + except: + print(sys.exc_info()) + logger.warning("Utils - getTier: " + str(sys.exc_info())) + return False, "_" diff --git a/CDC_Backend/CDC_Backend/settings.py b/CDC_Backend/CDC_Backend/settings.py index e2b812b..8219a0f 100644 --- a/CDC_Backend/CDC_Backend/settings.py +++ b/CDC_Backend/CDC_Backend/settings.py @@ -11,7 +11,9 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os +from dotenv import load_dotenv +load_dotenv("../dev.env") # import django_heroku # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -41,6 +43,7 @@ INSTALLED_APPS = [ 'rest_framework', 'corsheaders', 'django_db_logger', + 'background_task' ] MIDDLEWARE = [ @@ -80,19 +83,15 @@ WSGI_APPLICATION = 'CDC_Backend.wsgi.application' # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - # 'default': { - # 'ENGINE': 'django.db.backends.sqlite3', - # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - # } - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'cdc', - 'USER': 'postgres', - 'PASSWORD': 'root', - 'HOST': 'localhost', - 'PORT': '5432', + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.environ.get("DB_NAME"), + 'USER': os.environ.get("DB_USER"), + 'PASSWORD': os.environ.get("DB_PASSWORD"), + 'HOST': os.environ.get("DB_HOST"), + 'PORT': os.environ.get("DB_PORT"), }, + # 'default': { # 'ENGINE': 'django.db.backends.postgresql_psycopg2', # 'NAME': 'd84i5cbjig5rrf', @@ -159,8 +158,8 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.gmail.com' EMAIL_USE_TLS = True EMAIL_PORT = 587 -EMAIL_HOST_USER = 'saisurya3127@gmail.com'#'email here' -EMAIL_HOST_PASSWORD = 'ehwkqmryyqjiifcz'#'password here' +EMAIL_HOST_USER = os.environ.get("EMAIL") # 'email here' +EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_PASSWORD") # 'password here' LOGGING = { 'version': 1, @@ -178,6 +177,11 @@ LOGGING = { 'level': 'DEBUG', 'class': 'django_db_logger.db_log_handler.DatabaseLogHandler' }, + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True, + } }, 'loggers': { 'db': { @@ -187,5 +191,4 @@ LOGGING = { } } - # django_heroku.settings(locals()) diff --git a/CDC_Backend/README.md b/CDC_Backend/README.md index e186f4b..6a041f0 100644 --- a/CDC_Backend/README.md +++ b/CDC_Backend/README.md @@ -1,13 +1,23 @@ # API References + 1. [**Common APIs**](#common-apis) 1. [**api/login/**](#apilogin) 2. [**Student APIs**](#student-portal-apis) - 1. [**api/student/profile/**](#apistudentprofile) - 2. [**api/student/getDashboard/**](#apistudentgetdashboard) - 3. [**api/student/addResume/**](#apistudentaddresume) - 4. [**api/student/deleteResume/**](#apistudentdeleteresume) - 5. [**api/student/submitApplication/**](#apistudentsubmitapplication) -3. [**Common Errors**](#common-errors) + 1. [**api/student/profile/**](#apistudentprofile) + 2. [**api/student/getDashboard/**](#apistudentgetdashboard) + 3. [**api/student/addResume/**](#apistudentaddresume) + 4. [**api/student/deleteResume/**](#apistudentdeleteresume) + 5. [**api/student/submitApplication/**](#apistudentsubmitapplication) +3. [**Admin APIs**](#admin-portal-apis) + 1. [**api/admin/markStatus/**](#apiadminmarkstatus) + 2. [**api/admin/getDashboard/**](#apiadmingetdashboard) + 3. [**api/admin/updateDeadline/**](#apiadminupdatedeadline) + 4. [**api/admin/updateOfferAccepted**](#apiadminupdateofferaccepted) + 5. [**api/admin/updateEmailVerified**](#apiadminupdateemailverified) + 6. [**api/admin/updateAdditionalInfo**](#apiadminupdateadditionalinfo) +4. [**Company APIs**](#company-portal-apis) + 1. [**api/company/addPlacement/**](#apicompanyaddplacement) +5. [**Common Errors**](#common-errors) --- @@ -35,19 +45,19 @@ Response is a Json with these fields ```json { - "action": "Login", - "message": "Verified", - "user_type": [ - "student" - ] + "action": "Login", + "message": "Verified", + "user_type": [ + "student" + ] } ``` - action: Tells us about the message creator
- message: Tells us what happened with our Request. - user_type: Tells us about the role the user possess. Can have these values - - student - - Admin + - student + - Admin ### Status Codes @@ -88,16 +98,26 @@ Response is a Json with these fields "action": "Student Profile", "message": "Details Found", "details": { - "id": "fdgdb", + "id": "190010036", + "resume_list": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/resume/190010036%2F8KIOT3PW1JIS718_CSE-V-SEM.pdf", + "name": "8KIOT3PW1JIS718_CSE-V-SEM.pdf" + } + ], + "offers": [ + { + "designation": "Software Developer", + "company_name": "Make My Trip", + "application_id": "LLW4STE76GEJYOR" + } + ], "roll_no": 190010036, "name": "Gowtham Sai", - "batch": "THIRD", + "batch": "2019", "branch": "CSE", "phone_number": 9390291911, - "resumes": [ - "XB85F4RIGBF5VJN_Cv-Gowtham.pdf" - ], - "cpi": "9.02" + "cpi": "9.15" } } ``` @@ -113,7 +133,7 @@ The possible responses for this api request are as follows | Status Codes | Possible Messages | | --------------- | ------------------------ | | 200 OK | `Details Found` | -| 400 BAD_REQUEST | `Error Occurred {error}` | +| 400 BAD_REQUEST | `Something Went Wrong` | You may see some different errors which can be seen [here](#common-errors) @@ -121,11 +141,11 @@ You may see some different errors which can be seen [here](#common-errors) ## `api/student/getDashboard` -This Api is used to get all the placements applicable to the student. +This Api is used to get all the placements applicable to the student and his applications which he has already applied. ### How to Use? -Send a `GET` request to `api/student/Dashboard`
+Send a `GET` request to `api/student/getDashboard`
Request_Body: @@ -136,88 +156,215 @@ Request_Body: > Headers
> Authorization: "Bearer {tokenID}" +> Only users with `student` role can access this Api. + + ### Response Response is a Json with these fields ```json { - "action": "Placement and Internships", + "action": "Get Dashboard - Student", "message": "Data Found", "placements": [ { - "id": "fdgdb121", - "designation": "Software Developer", - "description": "nice job", - "start_date": "2021-06-17", + "id": "Q54IRZZMC3RP8F6", + "company_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FI5U4RDTV0OP0EM0_2019+Student+Details+-+Total%28State+Sort%29+-+Copy.pdf", + "name": "I5U4RDTV0OP0EM0_2019 Student Details - Total(State Sort) - Copy.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FW04JWWNNMIBX0JX_2019+Student+Details+-+Total%28State+Sort%29.pdf", + "name": "W04JWWNNMIBX0JX_2019 Student Details - Total(State Sort).pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FT1BXP98WBT9BHOR_AP0313017732021LL-Driving+Licence+-+Copy.pdf", + "name": "T1BXP98WBT9BHOR_AP0313017732021LL-Driving Licence - Copy.pdf" + } + ], + "description_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FC78TE2Z67BPZ41O_CSE-V-SEM.pdf", + "name": "C78TE2Z67BPZ41O_CSE-V-SEM.pdf" + } + ], + "compensation_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2F8D5OFQ46H43DD3S_module5And6Attendance.pdf", + "name": "8D5OFQ46H43DD3S_module5And6Attendance.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FG8OU2PE919PFKSR_Print+Application11.pdf", + "name": "G8OU2PE919PFKSR_Print Application11.pdf" + } + ], + "selection_procedure_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FDZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_", + "name": "DZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FN490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3", + "name": "N490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3" + } + ], + "company_name": "Make My Trip", + "address": "MakeMyTrip India Pvt. Ltd.5, Awagarh House, MG Road(next to Bachoomal collections)Agra (UP), - 282002India", + "company_type": "Private Sector", + "nature_of_business": "Technology", + "website": "www.makemytrip.com", + "company_details": "This s a very nice company", + "is_company_details_pdf": true, "city": "Mumbai", + "state": "Maharashtra", + "country": "India", + "pin_code": 530013, "city_type": "Domestic", - "compensation": 1200000, - "compensation_details": "", + "designation": "Software Developer", + "description": "very nice job", + "is_description_pdf": true, + "compensation_CTC": 1200000, + "compensation_gross": 1100000, + "compensation_take_home": 1000000, + "compensation_bonus": 10000, + "compensation_details": "very good compensation", + "is_compensation_details_pdf": true, + "bond_details": "nil", + "selection_procedure_rounds": [ + "Resume Shortlisting", + "Technical Interview", + "HR Interview" + ], + "selection_procedure_details": "All rounds are complusory", + "is_selection_procedure_details_pdf": true, + "tier": "4", + "tentative_date_of_joining": "2022-01-15", "allowed_batch": [ - "THIRD", - "FOURTH" + "2018", + "2019" ], "allowed_branch": [ "CSE", - "EE", - "ME" - ], - "attachments": [], - "rounds": [ - "Resume Shortlisting", - "Technical Test", - "Interview" + "EE" ], + "tentative_no_of_offers": 5, + "other_requirements": "above 8 cpi", "additional_info": [ - "school", - "place of study", - "language" + "School", + "Home Town" ], - "status": "Resume Shortlisting", - "rounds_details": { - "Interview": "One -to-One interview", - "Technical Test": "Online Technical test which will be monitored remotely", - "Resume Shortlisting": "Resume will be seen" - }, - "company_details": { - "id": "fdgdb", - "name": "Apple", - "address": "California", - "companyType": "Technology", - "website": "" - } + "deadline_date": "2021-12-04", + "created_at": "2021-12-02T20:12:21+05:30" } ], "placementApplication": [ { - "id": "dsdads", - "application_status": "Resume Shortlisting", - "resume": "XB85F4RIGBF5VJN_Cv-Gowtham.pdf", - "additional_info": { - "school": "Delhi Public School", - "language": "Telugu", - "place of study": "Visakhapatnam" + "id": "LLW4STE76GEJYOR", + "placement": { + "id": "Q54IRZZMC3RP8F6", + "company_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FI5U4RDTV0OP0EM0_2019+Student+Details+-+Total%28State+Sort%29+-+Copy.pdf", + "name": "I5U4RDTV0OP0EM0_2019 Student Details - Total(State Sort) - Copy.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FW04JWWNNMIBX0JX_2019+Student+Details+-+Total%28State+Sort%29.pdf", + "name": "W04JWWNNMIBX0JX_2019 Student Details - Total(State Sort).pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FT1BXP98WBT9BHOR_AP0313017732021LL-Driving+Licence+-+Copy.pdf", + "name": "T1BXP98WBT9BHOR_AP0313017732021LL-Driving Licence - Copy.pdf" + } + ], + "description_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FC78TE2Z67BPZ41O_CSE-V-SEM.pdf", + "name": "C78TE2Z67BPZ41O_CSE-V-SEM.pdf" + } + ], + "compensation_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2F8D5OFQ46H43DD3S_module5And6Attendance.pdf", + "name": "8D5OFQ46H43DD3S_module5And6Attendance.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FG8OU2PE919PFKSR_Print+Application11.pdf", + "name": "G8OU2PE919PFKSR_Print Application11.pdf" + } + ], + "selection_procedure_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FDZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_", + "name": "DZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FN490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3", + "name": "N490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3" + } + ], + "company_name": "Make My Trip", + "address": "MakeMyTrip India Pvt. Ltd.5, Awagarh House, MG Road(next to Bachoomal collections)Agra (UP), - 282002India", + "company_type": "Private Sector", + "nature_of_business": "Technology", + "website": "www.makemytrip.com", + "company_details": "This s a very nice company", + "is_company_details_pdf": true, + "city": "Mumbai", + "state": "Maharashtra", + "country": "India", + "pin_code": 530013, + "city_type": "Domestic", + "designation": "Software Developer", + "description": "very nice job", + "is_description_pdf": true, + "compensation_CTC": 1200000, + "compensation_gross": 1100000, + "compensation_take_home": 1000000, + "compensation_bonus": 10000, + "compensation_details": "very good compensation", + "is_compensation_details_pdf": true, + "bond_details": "nil", + "selection_procedure_rounds": [ + "Resume Shortlisting", + "Technical Interview", + "HR Interview" + ], + "selection_procedure_details": "All rounds are complusory", + "is_selection_procedure_details_pdf": true, + "tier": "4", + "tentative_date_of_joining": "2022-01-15", + "allowed_batch": [ + "2018", + "2019" + ], + "allowed_branch": [ + "CSE", + "EE" + ], + "tentative_no_of_offers": 5, + "other_requirements": "above 8 cpi", + "additional_info": [ + "School", + "Home Town" + ], + "deadline_date": "2021-12-04", + "created_at": "2021-12-02T20:12:21+05:30" }, + "resume_link": "https://storage.googleapis.com/cdc-backend-attachments/resume/LLW4STE76GEJYOR%2F8KIOT3PW1JIS718_CSE-V-SEM.pdf", + "additional_info": "{\"School\": \"Narayana English Medium High School\", \"Home Town\": \"Vizag\"}", "selected": null, - "placement": "fdgdb121" + "applied_at": "2021-12-02T21:58:18.032466+05:30" } - ] + ] } ``` - action: Tells us about the message creator
- message: Tells us what happened with our Request. -- placements: Has the placements data. -- internships: Has the internships data. -- application_status: Can have many names - - Accepting Applications - - One of the Round Names - - Completed -- selected: Can take three Values - - null: Student is still in the Selection process - - true: Student is Selected - - false: Student is not selected +- placements: Array of Placement Objects +- placementApplication: Array of Placement Application Objects ### Status Codes @@ -225,8 +372,9 @@ The possible responses for this api request are as follows | Status Codes | Possible Messages | | --------------- | ------------------------ | -| 200 OK | `Resume Added` | -| 400 BAD_REQUEST | `Error Occurred {error}` | +| 200 OK | `Data Found` | +| 404 Not Found | `Student Not Found` | +| 400 BAD_REQUEST | `Something Went Wrong` | You can see some common errors [here](#common-errors) @@ -331,7 +479,7 @@ You can see some common errors [here](#common-errors) ## `api/student/submitApplication/` -This Api is used to submit application to Internships/Placements. +This Api is used to submit application to Placements. ### How to Use? @@ -343,24 +491,23 @@ Request_Body: ```json { - "opening_type": "Placement", - "opening_id": "fgervsdgdsf", - "resume_file_name": "1FYE0PQZZ508HR6_Resume for Google STEP.pdf", - "additional_info": { - "school": "Narayana English Medium", - "place of study": "Vizag", - "language": "Telugu" - } + "opening_type": "Placement", + "opening_id": "Q54IRZZMC3RP8F6", + "resume_file_name": "8KIOT3PW1JIS718_CSE-V-SEM.pdf", + "additional_info": { + "School": "Narayana English Medium High School", + "Home Town": "Vizag" + } } ``` > Headers
> Authorization: "Bearer {tokenID}" -- opening_type: Can be Placement/Internship +- opening_type: Can be Placement - opening_id: Opening Id unique to each opening. -- additional_info: This is the info which the Internship/Placement demands besides the normal user data which has to - asked and sent. These fields can be found in the Internship Details. +- additional_info: This is the info which the Placement demands besides the normal user data which has to + asked and sent. These fields can be found in the Placement Details. ### Response @@ -384,12 +531,559 @@ The possible responses for this api request are as follows | ------------- | ---------------------------------- | | 200 OK | `Application Submitted` | | 403 FORBIDDEN | `Application is already Submitted` | +| 403 FORBIDDEN | `Placement Not Approved` | | 404 NOT FOUND | `RESUME_FILE_NAME Not Found` | You can see some common errors [here](#common-errors) --- +# Admin Portal APIs + +## `api/admin/markStatus` + +This Api is used to mark the status for application for a specific placement. + +### How to Use? + +Send a `POST` request to `api/admin/markStatus`
+Request_Body: + +```json +{ + "opening_id": "Q54IRZZMC3RP8F6", + "student_list": [ + { + "student_id":"190010036", + "student_selected":"true" + }, + { + "student_id":"190050022", + "student_selected":"false" + } + ] +} +``` + + + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Mark Status", + "message": "Marked Status" +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Marked Status` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You may see some different errors which can be seen [here](#common-errors) + +--- + +## `api/admin/getDashboard` + +This Api is used to get all the placements. + +### How to Use? + +Send a `GET` request to `api/admin/getdashboard`
+ +Request_Body: + +```json +{} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Get Dashboard - Admin", + "message": "Data Found", + "ongoing": [ + { + "id": "Q54IRZZMC3RP8F6", + "company_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FI5U4RDTV0OP0EM0_2019+Student+Details+-+Total%28State+Sort%29+-+Copy.pdf", + "name": "I5U4RDTV0OP0EM0_2019 Student Details - Total(State Sort) - Copy.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FW04JWWNNMIBX0JX_2019+Student+Details+-+Total%28State+Sort%29.pdf", + "name": "W04JWWNNMIBX0JX_2019 Student Details - Total(State Sort).pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FT1BXP98WBT9BHOR_AP0313017732021LL-Driving+Licence+-+Copy.pdf", + "name": "T1BXP98WBT9BHOR_AP0313017732021LL-Driving Licence - Copy.pdf" + } + ], + "description_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FC78TE2Z67BPZ41O_CSE-V-SEM.pdf", + "name": "C78TE2Z67BPZ41O_CSE-V-SEM.pdf" + } + ], + "compensation_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2F8D5OFQ46H43DD3S_module5And6Attendance.pdf", + "name": "8D5OFQ46H43DD3S_module5And6Attendance.pdf" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FG8OU2PE919PFKSR_Print+Application11.pdf", + "name": "G8OU2PE919PFKSR_Print Application11.pdf" + } + ], + "selection_procedure_details_pdf_links": [ + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FDZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_", + "name": "DZTQQ6YBGBQ47PY_screencapture-onlinesbi-sbi-sbicollect-fsssuccessresponseredirect-htm-2021-07-19-18_" + }, + { + "link": "https://storage.googleapis.com/cdc-backend-attachments/company_attachments/Q54IRZZMC3RP8F6%2FN490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3", + "name": "N490PUXJEEN4JZ9_screencapture-onlinesbi-sbi-sbicollect-payment-suvidhapayment-htm-2021-07-19-23_12_3" + } + ], + "company_name": "Make My Trip", + "address": "MakeMyTrip India Pvt. Ltd.5, Awagarh House, MG Road(next to Bachoomal collections)Agra (UP), - 282002India", + "company_type": "Private Sector", + "nature_of_business": "Technology", + "website": "www.makemytrip.com", + "company_details": "This s a very nice company", + "is_company_details_pdf": true, + "contact_person_name": "Gowtham", + "phone_number": 9390291911, + "email": "saisurya3127@gmail.com", + "city": "Mumbai", + "state": "Maharashtra", + "country": "India", + "pin_code": 530013, + "city_type": "Domestic", + "designation": "Software Developer", + "description": "very nice job", + "is_description_pdf": true, + "compensation_CTC": 1200000, + "compensation_gross": 1100000, + "compensation_take_home": 1000000, + "compensation_bonus": 10000, + "compensation_details": "very good compensation", + "is_compensation_details_pdf": true, + "bond_details": "nil", + "selection_procedure_rounds": [ + "Resume Shortlisting", + "Technical Interview", + "HR Interview" + ], + "selection_procedure_details": "All rounds are complusory", + "is_selection_procedure_details_pdf": true, + "tier": "4", + "tentative_date_of_joining": "2022-01-15", + "allowed_batch": [ + "2018", + "2019" + ], + "allowed_branch": [ + "CSE", + "EE" + ], + "tentative_no_of_offers": 5, + "other_requirements": "above 8 cpi", + "additional_info": [ + "School", + "Home Town" + ], + "email_verified": false, + "offer_accepted": null, + "deadline_date": "2021-12-04", + "created_at": "2021-12-02T20:12:21+05:30" + } + ], + "previous": [] +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. +- ongoing: Gives us the list of placements that are accepting applications. +- previous: Gives us the list of placements that stopped accepting applications. + + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Data Found` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You can see some common errors [here](#common-errors) + +--- + +## `api/admin/updateDeadline` + +This Api is used to update deadline for a specific placement. + +### How to Use? + +Send a `POST` request to `api/admin/updateDeadline`
+Request_Body: + +```json +{ + "opening_id": "Q54IRZZMC3RP8F6", + "deadline_datetime": "2021-12-06 16:28:18 +0530" +} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Update Deadline", + "message": "Deadline Updated" +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Deadline Updated` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You may see some different errors which can be seen [here](#common-errors) + +--- + +## `api/admin/updateOfferAccepted` + +This Api is used to update offer verification state for a specific placement. + +### How to Use? + +Send a `POST` request to `api/admin/updateOfferAccepted`
+Request_Body: + +```json +{ + "opening_id": "Q54IRZZMC3RP8F6", + "offer_accepted": "true" +} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Update Offer Accepted", + "message": "Offer Accepted Updated" +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Update Offer Accepted` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You may see some different errors which can be seen [here](#common-errors) + +--- + +## `api/admin/updateEmailVerified` + +This Api is used to update email verification state for a specific placement. + +### How to Use? + +Send a `POST` request to `api/admin/updateEmailVerified`
+Request_Body: + +```json +{ + "opening_id": "Q54IRZZMC3RP8F6", + "email_verified": "false" +} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Update Email Verified", + "message": "Email Verified Updated" +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Email Verified Updated` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You may see some different errors which can be seen [here](#common-errors) + +--- + +## `api/admin/updateAdditionalInfo` + +This Api is used to update additional_info for a specific placement. + +### How to Use? + +Send a `POST` request to `api/admin/updateAdditionalInfo`
+Request_Body: + +```json +{ + "opening_id": "Q54IRZZMC3RP8F6", + "additional_info": [ + "School", + "Place of Living", + "Research Interests" + ] +} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Update Additional Info", + "message": "Additional Info Updated" +} +``` + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| --------------- | ------------------------ | +| 200 OK | `Additional Info Updated` | +| 400 BAD_REQUEST | `Something Went Wrong` | + +You may see some different errors which can be seen [here](#common-errors) + +--- + +# Company Portal APIs + +## `api/company/addPlacement/` + +This Api is used to add placements. + +### How to Use? + +Send a `POST` request to `api/company/addPlacement`
+ +Request_Body: + +```json +{ + "company_name": [ + "Make My Trip" + ], + "address": [ + "MakeMyTrip India Pvt. Ltd.\n5, Awagarh House, MG Road\n(next to Bachoomal collections)\nAgra (UP), - 282002\nIndia" + ], + "company_type": [ + "Private Sector" + ], + "nature_of_business": [ + "Technology" + ], + "website": [ + "www.makemytrip.com" + ], + "company_details": [ + "This s a very nice company" + ], + "is_company_details_pdf": [ + "true" + ], + "contact_person_name": [ + "Gowtham" + ], + "phone_number": [ + "9390291911" + ], + "email": [ + "saisurya3127@gmail.com" + ], + "city": [ + "Mumbai" + ], + "state": [ + "Maharashtra" + ], + "country": [ + "India" + ], + "pincode": [ + "530013" + ], + "designation": [ + "Software Developer" + ], + "description": [ + "very nice job" + ], + "is_description_pdf": [ + "true" + ], + "compensation_ctc": [ + "1200000" + ], + "compensation_gross": [ + "1100000" + ], + "compensation_take_home": [ + "1000000" + ], + "compensation_bonus": [ + "10000" + ], + "compensation_details": [ + "very good compensation" + ], + "is_compensation_details_pdf": [ + "true" + ], + "bond_details": [ + "nil" + ], + "selection_procedure_rounds": [ + "['Resume Shortlisting', 'Technical Interview', 'HR Interview']" + ], + "selection_procedure_details": [ + "All rounds are complusory" + ], + "is_selection_procedure_details_pdf": [ + "true" + ], + "tentative_date_of_joining": [ + "15-01-2022" + ], + "allowed_branch": [ + "['CSE', 'EE']" + ], + "tentative_no_of_offers": [ + "5" + ], + "other_requirements": [ + "above 8 cpi" + ], + "company_details_pdf": [ + "__FILE_OBJECT__", + "__FILE_OBJECT__" + ], + "description_pdf": [ + "__FILE_OBJECT__" + ], + "compensation_details_pdf": [ + "__FILE_OBJECT__" + ], + "selection_procedure_details_pdf": [ + "__FILE_OBJECT__", + "__FILE_OBJECT__" + ] +} +``` + +### Response + +Response is a Json with these fields + +```json +{ + "action": "Add Placement", + "message": "Placement Added Successfully" +} +``` + +> Headers
+> Authorization: "Bearer {tokenID}" + +- action: Tells us about the message creator
+- message: Tells us what happened with our Request. + +### Status Codes + +The possible responses for this api request are as follows + +| Status Codes | Possible Messages | +| ------------- | ----------------- | +| 200 OK | `Placement Added Successfully` | +| 404 NOT FOUND | `Something went wrong` | + +You can see some common errors [here](#common-errors) + +--- + ## `Common Errors` Some common errors that you may see while accessing the Apis @@ -400,4 +1094,9 @@ Some common errors that you may see while accessing the Apis | 401 UNAUTHORIZED | `Access Denied. You are not allowed to use this service` | Your may not have required access to those access those Apis. | | 401 UNAUTHORIZED | `Token has wrong audience` | You may be using wrong credentials for Google OAuth2.0. | | 404 NOT FOUND | `User Not Found. Contact CDC for more details` | You may not be a user at CDC, IIT Dharwad. Please contact us to get your user account | -| 400 BAD_REQUEST | `Error Occurred {error}` | Any random Error which can be seen in the {error} string. | +| 400 BAD_REQUEST | `Error Occurred` | Any random Error which can be seen in the {error} string. | +| 400 BAD_REQUEST | `Something went wrong` | Any random Error which can be seen in the {error} string. | + + + + diff --git a/CDC_Backend/templates/company_opening_submitted.html b/CDC_Backend/templates/company_opening_submitted.html new file mode 100644 index 0000000..88147e0 --- /dev/null +++ b/CDC_Backend/templates/company_opening_submitted.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ +
+ + + + + +
+

Thank You for filling the form

+

+ We have received your {{ opening_type }} notification for a {{ designation }} offer at + {{ company_name }}. Click here to view your notification. +

+ +

+ We will keep you informed with the updates. If you have any queries, please + feel to + write to + cdc@iitdh.ac.in +

+
+
+ + + + + + + +
+

+ ® CDC,IIT Dharwad,2021
+

+
+
+
+ + \ No newline at end of file diff --git a/CDC_Backend/templates/student_application_status_not_selected.html b/CDC_Backend/templates/student_application_status_not_selected.html new file mode 100644 index 0000000..ee01642 --- /dev/null +++ b/CDC_Backend/templates/student_application_status_not_selected.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + + + +
+

Hey, {{ student_name }}

+

+ We regret to inform you that you have not been selected for {{ designation }} role at {{ company_name }}. + CDC will keep bringing more such opportunities for you in the future. +

+ + + + +
+
+
+
+ + + + +
+

+ ® CDC,IIT Dharwad,2021
+

+
+
+
+ + diff --git a/CDC_Backend/templates/student_application_status_selected.html b/CDC_Backend/templates/student_application_status_selected.html new file mode 100644 index 0000000..0cd439f --- /dev/null +++ b/CDC_Backend/templates/student_application_status_selected.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + + + +
+

Hey, {{ student_name }}

+

+ Congratulations, You have been selected for the {{ designation }} at {{ company_name }}.
+

+ + + + +
+
+
+
+ + + + +
+

+ ® CDC,IIT Dharwad,2021
+

+
+
+
+ + diff --git a/CDC_Backend/templates/student_application_submitted.html b/CDC_Backend/templates/student_application_submitted.html index 805edee..eaeb123 100644 --- a/CDC_Backend/templates/student_application_submitted.html +++ b/CDC_Backend/templates/student_application_submitted.html @@ -44,7 +44,7 @@

Hello there, {{ name }}

- We have received your application for a {{ applicaton_type }} offer at + We have received your application for a {{ application_type }} offer at {{ company_name }} . We received these additional details