Merge branch 'main' into Abhishek28112002-patch-1

This commit is contained in:
karthik mv 2023-05-30 15:03:39 +05:30 committed by GitHub
commit b06455cf5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 995 additions and 131 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored
View File

@ -139,3 +139,4 @@ dmypy.json
/CDC_Backend/Storage/
.idea
*.pyc
dev.env

BIN
CDC_Backend.zip Normal file

Binary file not shown.

BIN
CDC_Backend/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -4,10 +4,50 @@ from django.shortcuts import resolve_url
from django.utils.html import format_html
from django.utils.safestring import SafeText
from simple_history.admin import SimpleHistoryAdmin
from import_export.admin import ImportExportMixin, ExportMixin
from import_export import resources
from .models import *
admin.site.register(User)
admin.site.register(Admin)
class ArrayFieldListFilter(admin.SimpleListFilter):
"""This is a list filter based on the values
from a model's `keywords` ArrayField. """
title = 'Roles'
parameter_name = 'user_type'
def lookups(self, request, model_admin):
# Very similar to our code above, but this method must return a
# list of tuples: (lookup_value, human-readable value). These
# appear in the admin's right sidebar
keywords = User.objects.values_list("user_type", flat=True)
keywords = [(kw, kw) for sublist in keywords for kw in sublist if kw]
keywords = sorted(set(keywords))
return keywords
def queryset(self, request, queryset):
# when a user clicks on a filter, this method gets called. The
# provided queryset with be a queryset of Items, so we need to
# filter that based on the clicked keyword.
lookup_value = self.value() # The clicked keyword. It can be None!
if lookup_value:
# the __contains lookup expects a list, so...
queryset = queryset.filter(user_type__contains=[lookup_value])
return queryset
class UserAdmin(ImportExportMixin, SimpleHistoryAdmin):
list_display = ('email', 'user_type', 'last_login_time')
list_filter = (ArrayFieldListFilter, 'last_login_time')
search_fields = ('email', 'user_type')
ordering = ('email', 'user_type')
admin.site.register(User, UserAdmin)
admin.site.site_header = "CDC Recruitment Portal"
@ -17,8 +57,12 @@ def model_admin_url(obj, name=None) -> str:
return format_html('<a href="{}">{}</a>', url, name or str(obj))
class StudentAdmin(ImportExportMixin, SimpleHistoryAdmin):
pass
@admin.register(Student)
class Student(admin.ModelAdmin):
class Student(StudentAdmin):
list_display = ("roll_no", "name", "batch", "branch", "phone_number", 'can_apply')
search_fields = ("roll_no", "name", "phone_number")
ordering = ("roll_no", "name", "batch", "branch", "phone_number")
@ -35,17 +79,57 @@ class Student(admin.ModelAdmin):
queryset.update(can_apply=True)
self.message_user(request, "Registered the users")
class PlacementResources(resources.ModelResource):
class Meta:
model = Placement
exclude = ('id','changed_by', 'is_company_details_pdf', 'is_description_pdf',
'is_compensation_details_pdf', 'is_selection_procedure_details_pdf')
class AdminAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PlacementResources
class PlacementResources(resources.ModelResource):
class Meta:
model = Placement
exclude = ('id', 'changed_by', 'is_company_details_pdf', 'is_description_pdf',
'is_compensation_details_pdf', 'is_selection_procedure_details_pdf')
class AdminAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PlacementResources
class PlacementResources(resources.ModelResource):
class Meta:
model = Placement
exclude = ('id', 'changed_by', 'is_company_details_pdf', 'is_description_pdf',
'is_compensation_details_pdf', 'is_selection_procedure_details_pdf')
class AdminAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PlacementResources
@admin.register(Placement)
class Placement(admin.ModelAdmin):
class Placement(AdminAdmin):
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',)
class PlacementApplicationResources(resources.ModelResource):
class Meta:
model = PlacementApplication
exclude = ('id', 'changed_by')
class PlacementAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PlacementApplicationResources
@admin.register(PlacementApplication)
class PlacementApplication(admin.ModelAdmin):
class PlacementApplication(PlacementAdmin):
list_display = ('id', 'Placement', 'Student', 'selected')
search_fields = ('id',)
ordering = ('id',)
@ -58,8 +142,18 @@ class PlacementApplication(admin.ModelAdmin):
return model_admin_url(obj.student)
class PrePlacementResources(resources.ModelResource):
class Meta:
model = PrePlacementOffer
exclude = ('id', 'changed_by')
class PrePlacementOfferAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PrePlacementResources
@admin.register(PrePlacementOffer)
class PrePlacementOffer(admin.ModelAdmin):
class PrePlacementOffer(PrePlacementOfferAdmin):
list_display = ('company', 'Student', 'accepted')
search_fields = ('company',)
ordering = ('company',)

View File

@ -8,10 +8,12 @@ urlpatterns = [
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('deleteAdditionalInfo/', adminViews.deleteAdditionalInfo, name="Delete Additional Info"),
path('addAdditionalInfo/', adminViews.addAdditionalInfo, name="Add Additional Info"),
path('getApplications/', adminViews.getApplications, name="Get Applications"),
path("submitApplication/", adminViews.submitApplication, name="Submit Application"),
path('generateCSV/', adminViews.generateCSV, name="Generate CSV"),
path('addPPO/', adminViews.addPPO, name="Add PPO"),
path('getStudentApplication/', adminViews.getStudentApplication, name="Get student application"),
path('getStats/', adminViews.getStats, name="Get Stats"),
]

View File

@ -39,6 +39,7 @@ def markStatus(request, id, email, user_type):
sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_SELECTED_TEMPLATE)
else:
sendEmail(email, subject, data, STUDENT_APPLICATION_STATUS_NOT_SELECTED_TEMPLATE)
application.chaged_by = get_object_or_404(User, id=id)
application.save()
else:
raise ValueError("Student - " + i[STUDENT_ID] + " didn't apply for this opening")
@ -89,6 +90,7 @@ def updateDeadline(request, id, email, user_type):
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
# Updating deadline date with correct format in datetime field
opening.deadline_datetime = datetime.datetime.strptime(data[DEADLINE_DATETIME], '%Y-%m-%d %H:%M:%S %z')
opening.changed_by = get_object_or_404(User, id=id)
opening.save()
return Response({'action': "Update Deadline", 'message': "Deadline Updated"},
status=status.HTTP_200_OK)
@ -102,21 +104,30 @@ def updateDeadline(request, id, email, user_type):
@api_view(['POST'])
@isAuthorized([ADMIN])
@isAuthorized([SUPER_ADMIN])
@precheck([OPENING_ID, OFFER_ACCEPTED])
def updateOfferAccepted(request, id, email, user_type):
try:
data = request.data
print(data)
offer_accepted = data[OFFER_ACCEPTED]
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
opening.offer_accepted = True if data[OFFER_ACCEPTED] == True else False
print(opening.offer_accepted)
opening.save()
if opening.offer_accepted is None:
opening.offer_accepted = offer_accepted == "true"
opening.changed_by = get_object_or_404(User, id=id)
opening.save()
if opening.offer_accepted:
send_opening_notifications(opening.id)
else:
raise ValueError("Offer Status already updated")
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 ValueError as e:
return Response({'action': "Update Offer Accepted", 'message': str(e)},
status=status.HTTP_400_BAD_REQUEST)
except:
logger.warning("Update Offer Accepted: " + str(sys.exc_info()))
return Response({'action': "Update Offer Accepted", 'message': "Something went wrong"},
@ -131,6 +142,7 @@ def updateEmailVerified(request, id, email, user_type):
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.changed_by = get_object_or_404(User, id=id)
opening.save()
return Response({'action': "Update Email Verified", 'message': "Email Verified Updated"},
status=status.HTTP_200_OK)
@ -145,29 +157,55 @@ def updateEmailVerified(request, id, email, user_type):
@api_view(['POST'])
@isAuthorized([ADMIN])
@precheck([OPENING_ID, ADDITIONAL_INFO])
def updateAdditionalInfo(request, id, email, user_type):
@precheck([OPENING_ID, FIELD])
def deleteAdditionalInfo(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]
if data[FIELD] in opening.additional_info:
opening.additional_info.remove(data[FIELD])
opening.changed_by = get_object_or_404(User, id=id)
opening.save()
return Response({'action': "Delete Additional Info", 'message': "Additional Info Deleted"},
status=status.HTTP_200_OK)
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)
raise ValueError("Additional Info Not Found")
except Http404:
return Response({'action': "Update Additional Info", 'message': 'Opening Not Found'},
return Response({'action': "Delete 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"},
return Response({'action': "Delete Additional Info", 'message': "Additional Info not found"},
status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.warning("Delete Additional Info: " + str(e))
return Response({'action': "Delete Additional Info", 'message': "Something went wrong"},
status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@isAuthorized([ADMIN])
@precheck([OPENING_ID, FIELD])
def addAdditionalInfo(request, id, email, user_type):
try:
data = request.data
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
if data[FIELD] not in opening.additional_info:
opening.additional_info.append(data[FIELD])
opening.save()
return Response({'action': "Add Additional Info", 'message': "Additional Info Added"},
status=status.HTTP_200_OK)
else:
raise ValueError("Additional Info Found")
except Http404:
return Response({'action': "Add Additional Info", 'message': 'Opening Not Found'},
status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response({'action': "Add Additional Info", 'message': "Additional Info already found"},
status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.warning("Update Additional Info: " + str(e))
return Response({'action': "Update Additional Info", 'message': "Something went wrong"},
logger.warning("Add Additional Info: " + str(e))
return Response({'action': "Add Additional Info", 'message': "Something went wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -199,7 +237,7 @@ def submitApplication(request, id, email, user_type):
data = request.data
student = get_object_or_404(Student, pk=data[STUDENT_ID])
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
student_user = get_object_or_404(User, id=student.id)
if data[APPLICATION_ID] == "":
application = PlacementApplication()
application.id = generateRandomString()
@ -216,7 +254,16 @@ def submitApplication(request, id, email, user_type):
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": "Placement",
"additional_info": dict(json.loads(application.additional_info)),
}
subject = STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT.format(company_name=opening.company_name)
application.changed_by = get_object_or_404(User, id=id)
application.save()
sendEmail(student_user.email, subject, data, STUDENT_APPLICATION_SUBMITTED_TEMPLATE)
return Response({'action': "Add Student Application", 'message': "Application added"},
status=status.HTTP_200_OK)
else:
@ -235,7 +282,17 @@ def submitApplication(request, id, email, user_type):
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": "Placement",
"resume": application.resume[16:],
"additional_info_items": dict(json.loads(application.additional_info)),
}
subject = STUDENT_APPLICATION_UPDATED_TEMPLATE_SUBJECT.format(company_name=opening.company_name)
application.changed_by = get_object_or_404(User, id=id)
application.save()
sendEmail(student_user.email, subject, data, STUDENT_APPLICATION_UPDATED_TEMPLATE)
return Response({'action': "Add Student Application", 'message': "Application updated"},
status=status.HTTP_200_OK)
else:
@ -300,7 +357,6 @@ def generateCSV(request, id, email, user_type):
status=status.HTTP_200_OK)
except:
logger.warning("Create csv: " + str(sys.exc_info()))
print(sys.exc_info())
return Response({'action': "Create csv", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -313,7 +369,7 @@ def addPPO(request, id, email, user_type):
data = request.data
PPO = PrePlacementOffer()
PPO.company = data[COMPANY_NAME]
PPO.compensation = data[COMPENSATION_GROSS]
PPO.compensation = int(data[COMPENSATION_GROSS])
if data[OFFER_ACCEPTED] == "true":
PPO.accepted = True
elif data[OFFER_ACCEPTED] == "false":
@ -322,15 +378,15 @@ def addPPO(request, id, email, user_type):
PPO.accepted = None
PPO.student = get_object_or_404(Student, id=data[STUDENT_ID])
PPO.designation = data[DESIGNATION]
PPO.tier = data[TIER]
PPO.tier = int(data[TIER])
if COMPENSATION_DETAILS in data:
PPO.compensation_details = data[COMPENSATION_DETAILS]
PPO.changed_by = get_object_or_404(User, id=id)
PPO.save()
return Response({'action': "Add PPO", 'message': "PPO added"},
status=status.HTTP_200_OK)
except:
logger.warning("Add PPO: " + str(sys.exc_info()))
print(sys.exc_info())
return Response({'action': "Add PPO", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -373,3 +429,205 @@ def getStudentApplication(request, id, email, user_type):
logger.warning("Get Student Application: " + str(sys.exc_info()))
return Response({'action': "Get Student Application", 'message': "Something Went Wrong"},
status.HTTP_400_BAD_REQUEST)
@api_view(['GET'])
@isAuthorized(allowed_users=[ADMIN])
def getStats(request, id, email, user_type):
try:
stats = []
placement_ids = {}
tier_count = {
"CSE": {
"1":0,
"2":0,
"3":0,
"4":0,
"5":0,
"6":0,
"7":0,
"psu":0,
},
"EE": {
"1":0,
"2":0,
"3":0,
"4":0,
"5":0,
"6":0,
"7":0,
"psu":0,
},
"MMAE": {
"1":0,
"2":0,
"3":0,
"4":0,
"5":0,
"6":0,
"7":0,
"psu":0,
},
"Total": {
"1":0,
"2":0,
"3":0,
"4":0,
"5":0,
"6":0,
"7":0,
"psu":0,
},
}
number_of_students_placed = {
"CSE": 0,
"EE": 0,
"MMAE": 0,
"Total": 0,
}
number_of_students_with_multiple_offers = 0
number_of_students_with_no_offers = {
"CSE": 0,
"EE": 0,
"MMAE": 0,
"Total": 0,
}
max_CTC = {
"CSE": 0,
"EE": 0,
"MMAE": 0
}
average_CTC = {
"CSE": 0,
"EE": 0,
"MMAE": 0
}
count = {
"CSE": 0,
"EE": 0,
"MMAE": 0
}
students = Student.objects.all().order_by("roll_no")
for student in students.iterator():
applications = PlacementApplication.objects.filter(student=student, selected=True)
ppos = PrePlacementOffer.objects.filter(student=student, accepted=True)
first_offer_data = None
second_offer_data = None
# get the first and second offer
offers = []
offers.extend(applications)
offers.extend(ppos)
if len(offers) == 0:
number_of_students_with_no_offers[student.branch] += 1
number_of_students_with_no_offers["Total"] += 1
else:
number_of_students_placed[student.branch] += 1
number_of_students_placed["Total"] += 1
if len(offers) > 1:
number_of_students_with_multiple_offers += 1
for offer in offers:
if type(offer) == PrePlacementOffer:
if first_offer_data is None:
first_offer_data = {
"id": offer.id,
"company": offer.company,
"compensation": offer.compensation,
"tier": offer.tier,
"type": "PPO",
}
elif second_offer_data is None:
second_offer_data = {
"id": offer.id,
"company": offer.company,
"compensation": offer.compensation,
"tier": offer.tier,
"type": "PPO",
}
elif type(offer) == PlacementApplication:
if first_offer_data is None:
first_offer_data = {
"id": offer.placement.id,
"company": offer.placement.company_name,
"compensation": offer.placement.compensation_CTC,
"tier": offer.placement.tier,
"type": "Placement",
}
elif second_offer_data is None:
second_offer_data = {
"id": offer.placement.id,
"company": offer.placement.company_name,
"compensation": offer.placement.compensation_CTC,
"tier": offer.placement.tier,
"type": "Placement",
}
data = {
"id": student.id,
"name": student.name,
"roll_no": student.roll_no,
"batch": student.batch,
"branch": student.branch,
"cpi": student.cpi,
"first_offer": first_offer_data['company'] if first_offer_data is not None else None,
"first_offer_tier": first_offer_data['tier'] if first_offer_data is not None else None,
"first_offer_compensation": first_offer_data['compensation'] if first_offer_data is not None else None,
"second_offer": second_offer_data['company'] if second_offer_data is not None else None,
"second_offer_tier": second_offer_data['tier'] if second_offer_data is not None else None,
"second_offer_compensation": second_offer_data['compensation'] if second_offer_data is not None else None,
}
if first_offer_data is not None:
tier_count[student.branch][first_offer_data['tier']] += 1
tier_count['Total'][first_offer_data['tier']] += 1
max_CTC[student.branch] = max(max_CTC[student.branch], first_offer_data['compensation'])
average_CTC[student.branch] += first_offer_data['compensation']
count[student.branch] += 1
if first_offer_data['type'] == "Placement":
placement_ids[first_offer_data['company']] = first_offer_data['id']
if second_offer_data is not None:
tier_count[student.branch][second_offer_data['tier']] += 1
tier_count['Total'][second_offer_data['tier']] += 1
max_CTC[student.branch] = max(max_CTC[student.branch], second_offer_data['compensation'])
average_CTC[student.branch] += second_offer_data['compensation']
count[student.branch] += 1
if second_offer_data['type'] == "Placement":
placement_ids[second_offer_data['company']] = second_offer_data['id']
stats.append(data)
for branch in average_CTC:
if count[branch] > 0:
average_CTC[branch] /= count[branch]
# round off to 2 decimal places
average_CTC[branch] = round(average_CTC[branch], 2)
else:
average_CTC[branch] = 0
return Response({'action': "Get Stats", 'message': "Stats fetched", 'stats': stats, 'placement_ids': placement_ids,
"tier_count": {br: tier_count[br].values() for br in tier_count},
"number_of_students_placed": number_of_students_placed,
"number_of_students_with_multiple_offers": number_of_students_with_multiple_offers,
"number_of_students_with_no_offers": number_of_students_with_no_offers,
"max_CTC": max_CTC,
"average_CTC": average_CTC,
},
status=status.HTTP_200_OK)
except:
logger.warning("Get Stats: " + str(sys.exc_info()))
print(sys.exc_info())
return Response({'action': "Get Stats", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)

View File

@ -194,7 +194,7 @@ def addPlacement(request):
'%d-%m-%Y').date()
# Only Allowing Fourth Year for Placement
opening.allowed_batch = [FOURTH_YEAR, ]
opening.allowed_batch = [2017, 2018, 2019, 2020, 2021]
# Check if allowed_branch are valid
if data[ALLOWED_BRANCH] is None:
raise ValueError('Allowed Branch cannot be empty')
@ -230,9 +230,14 @@ def addPlacement(request):
status=status.HTTP_200_OK)
except ValueError as e:
store_all_files(request)
exception_email(data)
logger.info("ValueError in addPlacement: " + str(e))
return Response({'action': "Add Placement", 'message': str(e)},
status=status.HTTP_400_BAD_REQUEST)
except:
store_all_files(request)
exception_email(data)
logger.warning("Add New Placement: " + str(sys.exc_info()))
return Response({'action': "Add Placement", 'message': "Something went wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -276,11 +281,9 @@ def verifyEmail(request):
data = {
"designation": opening.designation,
"opening_type": PLACEMENT,
"opening_link": PLACEMENT_OPENING_URL.format(id=opening.id), # Some Changes here too
"company_name": opening.company_name,
}
json_data = json.dumps(data, default=str)
sendEmail(opening.email, COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT.format(id=opening.id), json_data,
sendEmail([opening.email, CDC_MAIl_ADDRESS], COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT.format(id=opening.id), data,
COMPANY_OPENING_SUBMITTED_TEMPLATE, attachment_jnf_respone)
return Response({'action': "Verify Email", 'message': "Email Verified Successfully"},

View File

@ -1,20 +1,24 @@
import os
BRANCH_CHOICES = [
["CSE", "CSE"],
["EE", "EE"],
["ME", "ME"],
['MMAE', 'MMAE'],
['EP', 'EP'],
]
BRANCHES = [
"CSE",
"EE",
"ME",
"MMAE",
"EP"
]
BATCH_CHOICES = [
["2021", "2021"],
["2020", "2020"],
["2019", "2019"],
["2018", "2018"]
["2018", "2018"],
["2017", "2017"],
]
OFFER_CITY_TYPE = [
@ -29,31 +33,42 @@ TIERS = [
['3', 'Tier 3'],
['4', 'Tier 4'],
['5', 'Tier 5'],
['6', 'Tier 6']
['6', 'Tier 6'],
['7', 'Tier 7'],
]
DEGREE_CHOICES = [
['bTech', 'B.Tech'],
['ms/phd', 'MS/ PhD'],
]
TOTAL_BRANCHES = 4 # Total No of Branches
TOTAL_BATCHES = 4 # Total No of Batches
TOTAL_BATCHES = 5 # Total No of Batches
CDC_MAIl_ADDRESS = 'cdc@iitdh.ac.in'
# To be Configured Properly
CLIENT_ID = "956830229554-290mirc16pdhd5j7ph7v7ukibo4t1qcp.apps.googleusercontent.com" # Google Login Client ID
CLIENT_ID = os.environ.get('GOOGLE_OAUTH_CLIENT_ID') # Google Login Client ID
# To be Configured Properly
PLACEMENT_OPENING_URL = "https://www.googleapis.com/auth/adwords/{id}" # On frontend, this is the URL to be opened
LINK_TO_STORAGE_COMPANY_ATTACHMENT = "http://localhost/storage/Company_Attachments/"
LINK_TO_STORAGE_RESUME = "http://localhost/storage/Resumes/"
LINK_TO_APPLICATIONS_CSV = "http://localhost/storage/Application_CSV/"
LINK_TO_EMAIl_VERIFICATION_API = "http://localhost:3000/company/verifyEmail?token={token}"
PLACEMENT_OPENING_URL = "https://cdc.iitdh.ac.in/portal/student/dashboard/placements/{id}" # On frontend, this is the URL to be opened
LINK_TO_STORAGE_COMPANY_ATTACHMENT = "https://cdc.iitdh.ac.in/storage/Company_Attachments/"
LINK_TO_STORAGE_RESUME = "https://cdc.iitdh.ac.in/storage/Resumes/"
LINK_TO_APPLICATIONS_CSV = "https://cdc.iitdh.ac.in/storage/Application_CSV/"
LINK_TO_EMAIl_VERIFICATION_API = "https://cdc.iitdh.ac.in/portal/company/verifyEmail?token={token}"
PDF_FILES_SERVING_ENDPOINT = 'https://cdc.iitdh.ac.in/storage/Company_Attachments/' # TODO: Change this to actual URL
EMAIL = "email"
STUDENT = 'student'
ADMIN = 'admin'
COMPANY = ''
SUPER_ADMIN = 's_admin'
COMPANY = 'company'
TIER = 'tier'
# To be Configured Properly
FOURTH_YEAR = '2019'
MAX_OFFERS_PER_STUDENT = 2
MAX_RESUMES_PER_STUDENT = 3
EMAIL_VERIFICATION_TOKEN_TTL = 48 # in hours
JNF_TEXT_MAX_CHARACTER_COUNT = 100
JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT = 200
@ -70,6 +85,7 @@ RESUME_FILE_NAME = 'resume_file_name'
APPLICATION_ID = "application_id"
OPENING_ID = "opening_id"
ADDITIONAL_INFO = "additional_info"
FIELD = "field"
STATUS_ACCEPTING_APPLICATIONS = "Accepting Applications"
@ -133,21 +149,25 @@ EXCLUDE_IN_PDF = ['id', 'is_company_details_pdf', 'offer_accepted', 'is_descript
'email_verified', 'created_at']
SPECIAL_FORMAT_IN_PDF = ['website', 'company_details_pdf_names', 'description_pdf_names',
'compensation_details_pdf_names',
'selection_procedure_pdf_names']
'selection_procedure_details_pdf_names']
COMPANY_OPENING_ERROR_TEMPLATE = "Alert! Error submitting opening for {company_name}."
COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT = "Notification Submitted - {id} - Career Development Cell, IIT Dharwad"
STUDENT_APPLICATION_STATUS_TEMPLATE_SUBJECT = 'Application Status : {company_name} - {id}'
STUDENT_APPLICATION_STATUS_TEMPLATE_SUBJECT = 'Application Status - {company_name} - {id}'
STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT = 'CDC - Application Submitted - {company_name}'
STUDENT_APPLICATION_UPDATED_TEMPLATE_SUBJECT = 'CDC - Application Updated - {company_name}'
COMPANY_EMAIl_VERIFICATION_TEMPLATE_SUBJECT = 'Email Verification - Career Development Cell, IIT Dharwad'
NOTIFY_STUDENTS_OPENING_TEMPLATE_SUBJECT = 'Placement Opportunity at {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'
STUDENT_APPLICATION_UPDATED_TEMPLATE = 'student_application_updated.html'
COMPANY_EMAIL_VERIFICATION_TEMPLATE = 'company_email_verification.html'
COMPANY_JNF_RESPONSE_TEMPLATE = 'company_jnf_response.html'
NOTIFY_STUDENTS_OPENING_TEMPLATE = 'notify_students_new_opening.html'
APPLICATION_CSV_COL_NAMES = ['Applied At', 'Roll No.', 'Name', 'Email', 'Phone Number', 'Branch', 'Batch', 'CPI',
'Resume', 'Selected', ]
PDF_FILES_SERVING_ENDPOINT = 'http://localhost/storage/Company_Attachments/' # TODO: Change this to actual URL

View File

@ -1,6 +1,7 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils import timezone
from simple_history.models import HistoricalRecords
from .constants import *
@ -10,9 +11,10 @@ from .constants import *
class User(models.Model):
email = models.EmailField(primary_key=True, blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
id = models.CharField(blank=False, max_length=25)
id = models.CharField(blank=False, max_length=25, db_index=True)
user_type = ArrayField(models.CharField(blank=False, max_length=10), size=4, default=list, blank=False)
last_login_time = models.DateTimeField(default=timezone.now)
history = HistoricalRecords()
class Meta:
verbose_name_plural = "User"
@ -30,18 +32,46 @@ class Student(models.Model):
default=list, blank=True)
cpi = models.DecimalField(decimal_places=2, max_digits=4)
can_apply = models.BooleanField(default=True, verbose_name='Registered')
changed_by = models.ForeignKey(User, blank=True, on_delete=models.RESTRICT, default=None, null=True)
degree = models.CharField(choices=DEGREE_CHOICES, blank=False, max_length=10, default=DEGREE_CHOICES[0][0])
history = HistoricalRecords(user_model=User)
def __str__(self):
return str(self.roll_no)
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
if isinstance(value, User):
self.changed_by = value
else:
self.changed_by = None
class Admin(models.Model):
id = models.CharField(blank=False, max_length=15, primary_key=True)
name = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
changed_by = models.ForeignKey(User, blank=True, on_delete=models.RESTRICT, default=None, null=True)
history = HistoricalRecords(user_model=User)
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
if isinstance(value, User):
self.changed_by = value
else:
self.changed_by = None
def two_day_after_today():
return timezone.now() + timezone.timedelta(days=2)
# round off to nearest day
return timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + timezone.timedelta(days=2)
class Placement(models.Model):
@ -53,7 +83,7 @@ class Placement(models.Model):
nature_of_business = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
type_of_organisation = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="", blank=False)
website = models.CharField(blank=True, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
company_details = models.CharField(max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT, default=None, null=True)
company_details = models.CharField(max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT, default=None, null=True, blank=True)
company_details_pdf_names = ArrayField(
models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT), size=5,
default=list, blank=True)
@ -114,6 +144,8 @@ class Placement(models.Model):
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)
updated_at = models.DateTimeField(blank=False, default=None, null=True)
changed_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
history = HistoricalRecords(user_model=User)
def format(self):
if self.company_name is not None:
@ -156,7 +188,19 @@ class Placement(models.Model):
if self.other_requirements is not None:
self.other_requirements = self.other_requirements.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
if self.additional_info is not None:
self.additional_info = [info.strip()[:JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT] for info in list(self.additional_info)]
self.additional_info = [info.strip()[:JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT] for info in
list(self.additional_info)]
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
if isinstance(value, User):
self.changed_by = value
else:
self.changed_by = None
def save(self, *args, **kwargs):
''' On save, add timestamps '''
@ -179,6 +223,8 @@ class PlacementApplication(models.Model):
selected = models.BooleanField(null=True, default=None, blank=True)
applied_at = models.DateTimeField(blank=False, default=None, null=True)
updated_at = models.DateTimeField(blank=False, default=None, null=True)
changed_by = models.ForeignKey(User, blank=False, on_delete=models.RESTRICT, default=None, null=True)
history = HistoricalRecords(user_model=User)
def save(self, *args, **kwargs):
''' On save, add timestamps '''
@ -188,6 +234,17 @@ class PlacementApplication(models.Model):
return super(PlacementApplication, self).save(*args, **kwargs)
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
if isinstance(value, User):
self.changed_by = value
else:
self.changed_by = None
class Meta:
verbose_name_plural = "Placement Applications"
unique_together = ('placement_id', 'student_id')
@ -204,5 +261,31 @@ class PrePlacementOffer(models.Model):
compensation = models.IntegerField(blank=False) # Job - Per Year
compensation_details = models.CharField(blank=True, max_length=200)
tier = models.CharField(blank=False, choices=TIERS, max_length=10)
designation = models.CharField(blank=False, max_length=25, default=None, null=True)
designation = models.CharField(blank=False, max_length=100, default=None, null=True)
accepted = models.BooleanField(default=None, null=True)
changed_by = models.ForeignKey(User, blank=False, on_delete=models.RESTRICT, default=None, null=True)
history = HistoricalRecords(user_model=User)
@property
def _history_user(self):
return self.changed_by
@_history_user.setter
def _history_user(self, value):
if isinstance(value, User):
self.changed_by = value
else:
self.changed_by = None
class Contributor(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, blank=False, default="")
email = models.EmailField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, blank=False, default="", unique=True)
github_id = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, blank=False, default="", unique=True)
linkedin = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, unique=True, null=True)
commits = models.IntegerField(blank=False, default=0)
image = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, blank=False, default="", null=True)
def __str__(self):
return self.name

View File

@ -162,7 +162,8 @@ class PlacementApplicationSerializer(serializers.ModelSerializer):
return data
def get_resume_link(self, obj):
ele = {'link': LINK_TO_STORAGE_RESUME + urllib.parse.quote(obj.id + "/" + obj.resume), 'name': obj.resume}
ele = {'link': LINK_TO_STORAGE_RESUME + urllib.parse.quote(str(obj.student.roll_no) + "/" + obj.resume),
'name': obj.resume}
return ele
class Meta:
@ -185,3 +186,9 @@ class PlacementApplicationSerializerForAdmin(serializers.ModelSerializer):
class Meta:
model = PlacementApplication
exclude = ['placement', 'resume']
class ContributorSerializer(serializers.ModelSerializer):
class Meta:
model = Contributor
fields = '__all__'

View File

@ -9,4 +9,6 @@ urlpatterns = [
path("addResume/", studentViews.addResume, name="Upload Resume"),
path("deleteResume/", studentViews.deleteResume, name="Upload Resume"),
path("submitApplication/", studentViews.submitApplication, name="Submit Application"),
path("deleteApplication/", studentViews.deleteApplication, name="Delete Application"),
path("getContributorStats/", studentViews.getContributorStats, name="Get Contributor Stats"),
]

View File

@ -40,17 +40,23 @@ def addResume(request, id, email, user_type):
student = get_object_or_404(Student, id=id)
files = request.FILES
if len(student.resumes) >= MAX_RESUMES_PER_STUDENT:
raise PermissionError('Max Number of Resumes limit reached')
file = files['file']
destination_path = STORAGE_DESTINATION_RESUMES + str(student.roll_no) + "/"
file_name = saveFile(file, destination_path)
student.resumes.append(file_name)
student.changed_by = get_object_or_404(User, id=id)
student.save()
return Response({'action': "Upload Resume", 'message': "Resume Added"},
status=status.HTTP_200_OK)
except Http404:
return Response({'action': "Upload Resume", 'message': 'Student Not Found'},
status=status.HTTP_404_NOT_FOUND)
except PermissionError:
return Response({'action': "Upload Resume", 'message': 'Max Number of Resumes limit reached'},
status=status.HTTP_400_BAD_REQUEST)
except:
if path.exists(destination_path):
logger.error("Upload Resume: Error in Saving Resume")
@ -71,7 +77,10 @@ def getDashboard(request, id, email, user_type):
allowed_branch__contains=[studentDetails.branch],
deadline_datetime__gte=datetime.datetime.now(),
offer_accepted=True, email_verified=True).order_by('deadline_datetime')
placementsdata = PlacementSerializerForStudent(placements, many=True).data
filtered_placements = placement_eligibility_filters(studentDetails, placements)
placementsdata = PlacementSerializerForStudent(filtered_placements, many=True).data
placementApplications = PlacementApplication.objects.filter(student_id=id)
placementApplications = PlacementApplicationSerializer(placementApplications, many=True).data
return Response(
@ -83,7 +92,6 @@ def getDashboard(request, id, email, user_type):
status=status.HTTP_404_NOT_FOUND)
except:
logger.warning("Get Dashboard -Student: " + str(sys.exc_info()))
print(sys.exc_info())
return Response({'action': "Get Dashboard - Student", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -104,6 +112,7 @@ def deleteResume(request, id, email, user_type):
if path.exists(destination_path):
# remove(destination_path)
student.resumes.remove(file_name)
student.changed_by = get_object_or_404(User, id=id)
student.save()
return Response({'action': "Delete Resume", 'message': "Resume Deleted"},
status=status.HTTP_200_OK)
@ -177,7 +186,7 @@ def submitApplication(request, id, email, user_type):
}
subject = STUDENT_APPLICATION_SUBMITTED_TEMPLATE_SUBJECT.format(company_name=opening.company_name)
sendEmail(email, subject, data, STUDENT_APPLICATION_SUBMITTED_TEMPLATE)
application.changed_by = get_object_or_404(User, id=id)
application.save()
return Response({'action': "Submit Application", 'message': "Application Submitted"},
status=status.HTTP_200_OK)
@ -192,8 +201,49 @@ def submitApplication(request, id, email, user_type):
status=status.HTTP_404_NOT_FOUND)
except:
logger.warning("Submit Application: " + str(sys.exc_info()))
print(traceback.format_exc())
print(sys.exc_info())
return Response({'action': "Submit Application", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@isAuthorized(allowed_users=[STUDENT])
@precheck(required_data=[APPLICATION_ID])
def deleteApplication(request, id, email, user_type):
try:
data = request.data
application = get_object_or_404(PlacementApplication, id=data[APPLICATION_ID],
student_id=id)
if application.placement.deadline_datetime < timezone.now():
raise PermissionError("Deadline Passed")
application.delete()
return Response({'action': "Delete Application", 'message': "Application Deleted"},
status=status.HTTP_200_OK)
except Http404 as e:
return Response({'action': "Delete Application", 'message': str(e)},
status=status.HTTP_404_NOT_FOUND)
except PermissionError as e:
return Response({'action': "Delete Application", 'message': str(e)},
status=status.HTTP_403_FORBIDDEN)
except:
logger.warning("Delete Application: " + str(sys.exc_info()))
return Response({'action': "Delete Application", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET'])
@isAuthorized(allowed_users='*')
def getContributorStats(request, id, email, user_type):
try:
contributors = Contributor.objects.all()
serialized_data = ContributorSerializer(contributors, many=True).data
return Response({'action': "Get Contributor Stats", 'message': "Contributor Stats Fetched",
'data': serialized_data},
status=status.HTTP_200_OK)
except:
logger.warning("Get Contributor Stats: " + str(sys.exc_info()))
return Response({'action': "Get Contributor Stats", 'message': "Something Went Wrong"},
status=status.HTTP_400_BAD_REQUEST)

View File

@ -12,6 +12,7 @@ from os import path, remove
import background_task
import jwt
import pdfkit
import pytz
import requests as rq
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
@ -27,7 +28,7 @@ from rest_framework import status
from rest_framework.response import Response
from .constants import *
from .models import User, PrePlacementOffer, PlacementApplication, Placement
from .models import User, PrePlacementOffer, PlacementApplication, Placement, Student
logger = logging.getLogger('db')
@ -57,6 +58,7 @@ def precheck(required_data=None):
return view_func(request, *args, **kwargs)
except:
logger.warning("Pre check: " + str(sys.exc_info()))
return Response({'action': "Pre check", 'message': "Something went wrong"},
status=status.HTTP_400_BAD_REQUEST)
@ -77,7 +79,6 @@ def isAuthorized(allowed_users=None):
token_id = headers['HTTP_AUTHORIZATION'][7:]
idinfo = id_token.verify_oauth2_token(token_id, requests.Request(), CLIENT_ID)
email = idinfo[EMAIL]
print(email)
user = get_object_or_404(User, email=email)
if user:
user.last_login_time = timezone.now()
@ -138,7 +139,7 @@ def saveFile(file, location):
return file_name
@background_task.background(schedule=5)
@background_task.background(schedule=2)
def sendEmail(email_to, subject, data, template, attachment_jnf_response=None):
try:
if not isinstance(data, dict):
@ -147,12 +148,15 @@ def sendEmail(email_to, subject, data, template, attachment_jnf_response=None):
text_content = strip_tags(html_content)
email_from = settings.EMAIL_HOST_USER
recipient_list = [str(email_to), ]
if type(email_to) is list:
recipient_list = [str(email) for email in email_to]
else:
recipient_list = [str(email_to), ]
msg = EmailMultiAlternatives(subject, text_content, email_from, recipient_list)
msg.attach_alternative(html_content, "text/html")
if attachment_jnf_response:
logger.info(attachment_jnf_response)
# logger.info(attachment_jnf_response)
pdf = pdfkit.from_string(attachment_jnf_response['html'], False,
options={"--enable-local-file-access": "", '--dpi': '96'})
msg.attach(attachment_jnf_response['name'], pdf, 'application/pdf')
@ -160,7 +164,6 @@ def sendEmail(email_to, subject, data, template, attachment_jnf_response=None):
return True
except:
logger.error("Send Email: " + str(sys.exc_info()))
print(str(sys.exc_info()[1]))
return False
@ -169,31 +172,36 @@ def PlacementApplicationConditions(student, placement):
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(student=student, accepted=True)
# find lenght of PPO
print(PPO)
print(len(PPO), "ere")
PPO_PSU = [i for i in PPO if i.tier == 'psu']
# find length of PPO
if len(selected_companies) + len(PPO) >= MAX_OFFERS_PER_STUDENT:
raise PermissionError("Max Applications Reached for the Season")
if len(selected_companies_PSU) > 0:
raise PermissionError('Selected for PSU Can\'t apply anymore')
if len(PPO_PSU) > 0:
raise PermissionError('Selected for PSU Can\'t apply anymore')
if placement.tier == 'psu':
return True, "Conditions Satisfied"
for i in selected_companies:
print(int(i.placement.tier) < int(placement.tier), int(i.placement.tier), int(placement.tier))
if int(i.placement.tier) < int(placement.tier):
return False, "Can't apply for this tier"
for i in PPO:
if int(i.tier) < int(placement.tier):
return False, "Can't apply for this tier"
if student.degree != 'bTech' and not placement.rs_eligible:
raise PermissionError("Can't apply for this placement")
return True, "Conditions Satisfied"
except PermissionError as e:
return False, e
except:
print(sys.exc_info())
print(traceback.format_exc())
logger.warning("Utils - PlacementApplicationConditions: " + str(sys.exc_info()))
return False, "_"
@ -231,7 +239,6 @@ def getTier(compensation_gross, is_psu=False):
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, "_"
@ -248,36 +255,35 @@ def generateOneTimeVerificationLink(email, opening_id, opening_type):
link = LINK_TO_EMAIl_VERIFICATION_API.format(token=token)
return True, link
except:
print(sys.exc_info())
logger.warning("Utils - generateOneTimeVerificationLink: " + str(sys.exc_info()))
return False, "_"
def verify_recaptcha(request):
try:
print(settings.RECAPTCHA_SECRET_KEY)
data = {
'secret': settings.RECAPTCHA_SECRET_KEY,
'response': request
}
print(data)
r = rq.post('https://www.google.com/recaptcha/api/siteverify', data=data)
result = r.json()
# logger.info("Recaptcha Response: " + str(result)+"request: "+str(data))
print(result, "Result")
if not result['success']:
logger.warning("Utils - verify_recaptcha: " + str(result))
return result['success']
except:
# get exception line number
print(sys.exc_info())
print(traceback.format_exc())
logger.warning("Utils - verify_recaptcha: " + str(sys.exc_info()))
return False, "_"
def opening_description_table_html(opening):
details = model_to_dict(opening, fields=[field.name for field in Placement._meta.fields],
exclude=EXCLUDE_IN_PDF)
# check typing of opening
if isinstance(opening, Placement):
details = model_to_dict(opening, fields=[field.name for field in Placement._meta.fields],
exclude=EXCLUDE_IN_PDF)
# check typing of opening is query dict
else: # if isinstance(opening, QueryDict):
details = opening
keys = list(details.keys())
newdetails = {}
for key in keys:
@ -287,7 +293,7 @@ def opening_description_table_html(opening):
if key ==