new endpoints (#109)
* added getstudentapplication and editstudentapplication endpoints - for admin views * added student data to send * format placement added. * changed submitApplication * merge fixes * made refractorings * minor changes in placement model * Added updated at for applications * removed log Co-authored-by: gowtham <gowthamsai.workspace@gmail.com>
This commit is contained in:
parent
dd4321f2a3
commit
db025d0259
|
@ -13,4 +13,5 @@ urlpatterns = [
|
||||||
path("submitApplication/", adminViews.submitApplication, name="Submit Application"),
|
path("submitApplication/", adminViews.submitApplication, name="Submit Application"),
|
||||||
path('generateCSV/', adminViews.generateCSV, name="Generate CSV"),
|
path('generateCSV/', adminViews.generateCSV, name="Generate CSV"),
|
||||||
path('addPPO/', adminViews.addPPO, name="Add PPO"),
|
path('addPPO/', adminViews.addPPO, name="Add PPO"),
|
||||||
|
path('getStudentApplication/', adminViews.getStudentApplication, name="Get student application"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -59,8 +59,8 @@ def markStatus(request, id, email, user_type):
|
||||||
def getDashboard(request, id, email, user_type):
|
def getDashboard(request, id, email, user_type):
|
||||||
try:
|
try:
|
||||||
placements = Placement.objects.all().order_by('-created_at')
|
placements = Placement.objects.all().order_by('-created_at')
|
||||||
ongoing = placements.filter(deadline_datetime__gt=datetime.datetime.now(), offer_accepted=True, email_verified=True)
|
ongoing = placements.filter(deadline_datetime__gt=timezone.now(), offer_accepted=True, email_verified=True)
|
||||||
previous = placements.exclude(deadline_datetime__gt=datetime.datetime.now()).filter(
|
previous = placements.exclude(deadline_datetime__gt=timezone.now()).filter(
|
||||||
offer_accepted=True, email_verified=True)
|
offer_accepted=True, email_verified=True)
|
||||||
new = placements.filter(offer_accepted__isnull=True, email_verified=True)
|
new = placements.filter(offer_accepted__isnull=True, email_verified=True)
|
||||||
ongoing = PlacementSerializerForAdmin(ongoing, many=True).data
|
ongoing = PlacementSerializerForAdmin(ongoing, many=True).data
|
||||||
|
@ -193,41 +193,40 @@ def getApplications(request, id, email, user_type):
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
@isAuthorized(allowed_users=[ADMIN])
|
@isAuthorized(allowed_users=[ADMIN])
|
||||||
@precheck(required_data=[OPENING_TYPE, OPENING_ID, RESUME_FILE_NAME,
|
@precheck(required_data=[APPLICATION_ID, STUDENT_ID, OPENING_ID, ADDITIONAL_INFO, RESUME_FILE_NAME])
|
||||||
STUDENT_ID])
|
|
||||||
def submitApplication(request, id, email, user_type):
|
def submitApplication(request, id, email, user_type):
|
||||||
try:
|
try:
|
||||||
data = request.data
|
data = request.data
|
||||||
student = get_object_or_404(Student, roll_no=data[STUDENT_ID])
|
student = get_object_or_404(Student, pk=data[STUDENT_ID])
|
||||||
if data[OPENING_TYPE] != PLACEMENT:
|
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
|
||||||
raise ValueError(OPENING_TYPE + " is Invalid")
|
|
||||||
# Only Allowing Applications for Placements
|
if data[APPLICATION_ID] == "":
|
||||||
else:
|
|
||||||
if not len(PlacementApplication.objects.filter(
|
|
||||||
student_id=student.id, placement_id=data[OPENING_ID])):
|
|
||||||
application = PlacementApplication()
|
application = PlacementApplication()
|
||||||
opening = get_object_or_404(Placement, id=data[OPENING_ID],
|
application.id = generateRandomString()
|
||||||
allowed_batch__contains=[student.batch],
|
|
||||||
allowed_branch__contains=[student.branch],
|
|
||||||
deadline_datetime__gte=datetime.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
|
application.placement = opening
|
||||||
else:
|
application.student = student
|
||||||
raise PermissionError("Application is already Submitted")
|
|
||||||
|
|
||||||
if data[RESUME_FILE_NAME] in student.resumes:
|
if data[RESUME_FILE_NAME] in student.resumes:
|
||||||
application.resume = data[RESUME_FILE_NAME]
|
application.resume = data[RESUME_FILE_NAME]
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError(RESUME_FILE_NAME + " Not Found")
|
raise FileNotFoundError(RESUME_FILE_NAME + " Not Found")
|
||||||
|
additional_info = {}
|
||||||
application.student = student
|
for i in opening.additional_info:
|
||||||
application.id = generateRandomString()
|
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)
|
||||||
|
application.save()
|
||||||
|
return Response({'action': "Add Student Application", 'message': "Application added"},
|
||||||
|
status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
application = get_object_or_404(PlacementApplication, id=data[APPLICATION_ID])
|
||||||
|
if application:
|
||||||
|
if data[RESUME_FILE_NAME] in student.resumes:
|
||||||
|
application.resume = data[RESUME_FILE_NAME]
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(RESUME_FILE_NAME + " Not Found")
|
||||||
|
application.resume = data[RESUME_FILE_NAME]
|
||||||
additional_info = {}
|
additional_info = {}
|
||||||
for i in opening.additional_info:
|
for i in opening.additional_info:
|
||||||
if i not in data[ADDITIONAL_INFO]:
|
if i not in data[ADDITIONAL_INFO]:
|
||||||
|
@ -236,19 +235,13 @@ def submitApplication(request, id, email, user_type):
|
||||||
additional_info[i] = data[ADDITIONAL_INFO][i]
|
additional_info[i] = data[ADDITIONAL_INFO][i]
|
||||||
|
|
||||||
application.additional_info = json.dumps(additional_info)
|
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()
|
application.save()
|
||||||
return Response({'action': "Submit Application", 'message': "Application Submitted"},
|
return Response({'action': "Add Student Application", 'message': "Application updated"},
|
||||||
status=status.HTTP_200_OK)
|
status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response({'action': "Edit Student Application", 'message': "No Application Found"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
except Http404 as e:
|
except Http404 as e:
|
||||||
return Response({'action': "Submit Application", 'message': str(e)},
|
return Response({'action': "Submit Application", 'message': str(e)},
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
@ -308,7 +301,7 @@ def generateCSV(request, id, email, user_type):
|
||||||
except:
|
except:
|
||||||
logger.warning("Create csv: " + str(sys.exc_info()))
|
logger.warning("Create csv: " + str(sys.exc_info()))
|
||||||
print(sys.exc_info())
|
print(sys.exc_info())
|
||||||
return Response({'action': "Create csv", 'message': "Error Occurred"},
|
return Response({'action': "Create csv", 'message': "Something Went Wrong"},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
@ -338,5 +331,45 @@ def addPPO(request, id, email, user_type):
|
||||||
except:
|
except:
|
||||||
logger.warning("Add PPO: " + str(sys.exc_info()))
|
logger.warning("Add PPO: " + str(sys.exc_info()))
|
||||||
print(sys.exc_info())
|
print(sys.exc_info())
|
||||||
return Response({'action': "Add PPO", 'message': "Error Occurred"},
|
return Response({'action': "Add PPO", 'message': "Something Went Wrong"},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@isAuthorized(allowed_users=[ADMIN])
|
||||||
|
@precheck(required_data=[STUDENT_ID, OPENING_ID])
|
||||||
|
def getStudentApplication(request, id, email, user_type):
|
||||||
|
try:
|
||||||
|
data = request.data
|
||||||
|
student = get_object_or_404(Student, id=data[STUDENT_ID])
|
||||||
|
student_serializer = StudentSerializer(student)
|
||||||
|
student_details = {
|
||||||
|
"name": student_serializer.data['name'],
|
||||||
|
"batch": student.batch,
|
||||||
|
"branch": student.branch,
|
||||||
|
"resume_list": student_serializer.data['resume_list'],
|
||||||
|
}
|
||||||
|
# search for the application if there or not
|
||||||
|
application = PlacementApplication.objects.filter(student=student,
|
||||||
|
placement=get_object_or_404(Placement, id=data[OPENING_ID]))
|
||||||
|
if application:
|
||||||
|
serializer = PlacementApplicationSerializer(application[0])
|
||||||
|
application_info = {
|
||||||
|
"id": serializer.data['id'],
|
||||||
|
"additional_info": serializer.data['additional_info'],
|
||||||
|
"resume": serializer.data['resume_link'],
|
||||||
|
}
|
||||||
|
return Response(
|
||||||
|
{'action': "Get Student Application", 'application_found': "true", "application_info": application_info,
|
||||||
|
"student_details": student_details}, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{'action': "Get Student Application", 'application_found': "false", "student_details": student_details},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
except Http404:
|
||||||
|
return Response({'action': "Get Student Application", 'message': "Student not found."},
|
||||||
|
status.HTTP_404_NOT_FOUND)
|
||||||
|
except:
|
||||||
|
logger.warning("Get Student Application: " + str(sys.exc_info()))
|
||||||
|
return Response({'action': "Get Student Application", 'message': "Something Went Wrong"},
|
||||||
|
status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -6,14 +6,14 @@ logger = logging.getLogger('db')
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
@precheck([COMPANY_NAME, ADDRESS, COMPANY_TYPE, NATURE_OF_BUSINESS, WEBSITE, COMPANY_DETAILS, IS_COMPANY_DETAILS_PDF,
|
@precheck([COMPANY_NAME, ADDRESS, COMPANY_TYPE, NATURE_OF_BUSINESS, TYPE_OF_ORGANISATION, WEBSITE, COMPANY_DETAILS,
|
||||||
CONTACT_PERSON_NAME, PHONE_NUMBER, EMAIL, CITY, STATE, COUNTRY, PINCODE, DESIGNATION, DESCRIPTION,
|
IS_COMPANY_DETAILS_PDF, CONTACT_PERSON_NAME, PHONE_NUMBER, EMAIL, CITY, STATE, COUNTRY, PINCODE, DESIGNATION,
|
||||||
IS_DESCRIPTION_PDF,
|
DESCRIPTION,
|
||||||
COMPENSATION_CTC, COMPENSATION_GROSS, COMPENSATION_TAKE_HOME, COMPENSATION_BONUS, COMPENSATION_DETAILS,
|
IS_DESCRIPTION_PDF, COMPENSATION_CTC, COMPENSATION_GROSS, COMPENSATION_TAKE_HOME, COMPENSATION_BONUS,
|
||||||
IS_COMPENSATION_DETAILS_PDF,
|
IS_COMPENSATION_DETAILS_PDF, ALLOWED_BRANCH, RS_ELIGIBLE, SELECTION_PROCEDURE_ROUNDS,
|
||||||
ALLOWED_BRANCH, SELECTION_PROCEDURE_ROUNDS, SELECTION_PROCEDURE_DETAILS, IS_SELECTION_PROCEDURE_DETAILS_PDF,
|
SELECTION_PROCEDURE_DETAILS,
|
||||||
TENTATIVE_DATE_OF_JOINING,
|
IS_SELECTION_PROCEDURE_DETAILS_PDF, TENTATIVE_DATE_OF_JOINING, TENTATIVE_NO_OF_OFFERS, OTHER_REQUIREMENTS,
|
||||||
TENTATIVE_NO_OF_OFFERS, OTHER_REQUIREMENTS, RECAPTCHA_VALUE
|
RECAPTCHA_VALUE, JOB_LOCATION
|
||||||
])
|
])
|
||||||
def addPlacement(request):
|
def addPlacement(request):
|
||||||
try:
|
try:
|
||||||
|
@ -29,9 +29,14 @@ def addPlacement(request):
|
||||||
opening.address = data[ADDRESS]
|
opening.address = data[ADDRESS]
|
||||||
opening.company_type = data[COMPANY_TYPE]
|
opening.company_type = data[COMPANY_TYPE]
|
||||||
opening.nature_of_business = data[NATURE_OF_BUSINESS]
|
opening.nature_of_business = data[NATURE_OF_BUSINESS]
|
||||||
|
opening.type_of_organisation = data[TYPE_OF_ORGANISATION]
|
||||||
opening.website = data[WEBSITE]
|
opening.website = data[WEBSITE]
|
||||||
opening.company_details = data[COMPANY_DETAILS]
|
opening.company_details = data[COMPANY_DETAILS]
|
||||||
opening.is_company_details_pdf = data[IS_COMPANY_DETAILS_PDF]
|
opening.is_company_details_pdf = data[IS_COMPANY_DETAILS_PDF]
|
||||||
|
if data[RS_ELIGIBLE] == 'Yes':
|
||||||
|
opening.rs_eligible = True
|
||||||
|
else:
|
||||||
|
opening.rs_eligible = False
|
||||||
|
|
||||||
if opening.is_company_details_pdf:
|
if opening.is_company_details_pdf:
|
||||||
company_details_pdf = []
|
company_details_pdf = []
|
||||||
|
@ -70,7 +75,7 @@ def addPlacement(request):
|
||||||
raise ValueError('Pincode should be integer')
|
raise ValueError('Pincode should be integer')
|
||||||
|
|
||||||
# If India then set city_type as Domestic else International
|
# If India then set city_type as Domestic else International
|
||||||
if opening.country == 'India':
|
if opening.country.upper() == 'INDIA':
|
||||||
opening.city_type = 'Domestic'
|
opening.city_type = 'Domestic'
|
||||||
else:
|
else:
|
||||||
opening.city_type = 'International'
|
opening.city_type = 'International'
|
||||||
|
@ -78,6 +83,7 @@ def addPlacement(request):
|
||||||
# Add a designation details in the opening
|
# Add a designation details in the opening
|
||||||
opening.designation = data[DESIGNATION]
|
opening.designation = data[DESIGNATION]
|
||||||
opening.description = data[DESCRIPTION]
|
opening.description = data[DESCRIPTION]
|
||||||
|
opening.job_location = data[JOB_LOCATION]
|
||||||
opening.is_description_pdf = data[IS_DESCRIPTION_PDF]
|
opening.is_description_pdf = data[IS_DESCRIPTION_PDF]
|
||||||
|
|
||||||
if opening.is_description_pdf:
|
if opening.is_description_pdf:
|
||||||
|
@ -129,7 +135,6 @@ def addPlacement(request):
|
||||||
else:
|
else:
|
||||||
raise ValueError('Compensation Bonus must be an integer')
|
raise ValueError('Compensation Bonus must be an integer')
|
||||||
|
|
||||||
opening.compensation_details = data[COMPENSATION_DETAILS]
|
|
||||||
opening.is_compensation_details_pdf = data[IS_COMPENSATION_DETAILS_PDF]
|
opening.is_compensation_details_pdf = data[IS_COMPENSATION_DETAILS_PDF]
|
||||||
|
|
||||||
if opening.is_compensation_details_pdf:
|
if opening.is_compensation_details_pdf:
|
||||||
|
|
|
@ -55,6 +55,10 @@ TIER = 'tier'
|
||||||
FOURTH_YEAR = '2019'
|
FOURTH_YEAR = '2019'
|
||||||
MAX_OFFERS_PER_STUDENT = 2
|
MAX_OFFERS_PER_STUDENT = 2
|
||||||
EMAIL_VERIFICATION_TOKEN_TTL = 48 # in hours
|
EMAIL_VERIFICATION_TOKEN_TTL = 48 # in hours
|
||||||
|
JNF_TEXT_MAX_CHARACTER_COUNT = 100
|
||||||
|
JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT = 200
|
||||||
|
JNF_TEXTAREA_MAX_CHARACTER_COUNT = 1000
|
||||||
|
JNF_SMALLTEXT_MAX_CHARACTER_COUNT = 50
|
||||||
|
|
||||||
STORAGE_DESTINATION_RESUMES = "./Storage/Resumes/"
|
STORAGE_DESTINATION_RESUMES = "./Storage/Resumes/"
|
||||||
STORAGE_DESTINATION_COMPANY_ATTACHMENTS = './Storage/Company_Attachments/'
|
STORAGE_DESTINATION_COMPANY_ATTACHMENTS = './Storage/Company_Attachments/'
|
||||||
|
@ -75,6 +79,7 @@ COMPANY_NAME = "company_name"
|
||||||
ADDRESS = "address"
|
ADDRESS = "address"
|
||||||
COMPANY_TYPE = "company_type"
|
COMPANY_TYPE = "company_type"
|
||||||
NATURE_OF_BUSINESS = "nature_of_business"
|
NATURE_OF_BUSINESS = "nature_of_business"
|
||||||
|
TYPE_OF_ORGANISATION = "type_of_organisation"
|
||||||
WEBSITE = 'website'
|
WEBSITE = 'website'
|
||||||
COMPANY_DETAILS = "company_details"
|
COMPANY_DETAILS = "company_details"
|
||||||
COMPANY_DETAILS_PDF = "company_details_pdf"
|
COMPANY_DETAILS_PDF = "company_details_pdf"
|
||||||
|
@ -93,6 +98,7 @@ DESCRIPTION_PDF = 'description_pdf'
|
||||||
DESCRIPTION_PDF_NAMES = 'description_pdf_names'
|
DESCRIPTION_PDF_NAMES = 'description_pdf_names'
|
||||||
IS_DESCRIPTION_PDF = 'is_description_pdf'
|
IS_DESCRIPTION_PDF = 'is_description_pdf'
|
||||||
OPENING_TYPE = 'opening_type'
|
OPENING_TYPE = 'opening_type'
|
||||||
|
JOB_LOCATION = 'job_location'
|
||||||
COMPENSATION_CTC = 'compensation_ctc'
|
COMPENSATION_CTC = 'compensation_ctc'
|
||||||
COMPENSATION_GROSS = 'compensation_gross'
|
COMPENSATION_GROSS = 'compensation_gross'
|
||||||
COMPENSATION_TAKE_HOME = 'compensation_take_home'
|
COMPENSATION_TAKE_HOME = 'compensation_take_home'
|
||||||
|
@ -103,6 +109,7 @@ COMPENSATION_DETAILS_PDF_NAMES = 'compensation_details_pdf_names'
|
||||||
IS_COMPENSATION_DETAILS_PDF = 'is_compensation_details_pdf'
|
IS_COMPENSATION_DETAILS_PDF = 'is_compensation_details_pdf'
|
||||||
ALLOWED_BATCH = 'allowed_batch'
|
ALLOWED_BATCH = 'allowed_batch'
|
||||||
ALLOWED_BRANCH = 'allowed_branch'
|
ALLOWED_BRANCH = 'allowed_branch'
|
||||||
|
RS_ELIGIBLE = 'rs_eligible'
|
||||||
BOND_DETAILS = 'bond_details'
|
BOND_DETAILS = 'bond_details'
|
||||||
SELECTION_PROCEDURE_ROUNDS = 'selection_procedure_rounds'
|
SELECTION_PROCEDURE_ROUNDS = 'selection_procedure_rounds'
|
||||||
SELECTION_PROCEDURE_DETAILS = 'selection_procedure_details'
|
SELECTION_PROCEDURE_DETAILS = 'selection_procedure_details'
|
||||||
|
@ -124,7 +131,8 @@ STUDENT_SELECTED = "student_selected"
|
||||||
EXCLUDE_IN_PDF = ['id', 'is_company_details_pdf', 'offer_accepted', 'is_description_pdf',
|
EXCLUDE_IN_PDF = ['id', 'is_company_details_pdf', 'offer_accepted', 'is_description_pdf',
|
||||||
'is_compensation_details_pdf', 'is_selection_procedure_details_pdf',
|
'is_compensation_details_pdf', 'is_selection_procedure_details_pdf',
|
||||||
'email_verified', 'created_at']
|
'email_verified', 'created_at']
|
||||||
SPECIAL_FORMAT_IN_PDF = ['website', 'company_details_pdf_names', 'description_pdf_names', 'compensation_details_pdf_names',
|
SPECIAL_FORMAT_IN_PDF = ['website', 'company_details_pdf_names', 'description_pdf_names',
|
||||||
|
'compensation_details_pdf_names',
|
||||||
'selection_procedure_pdf_names']
|
'selection_procedure_pdf_names']
|
||||||
|
|
||||||
COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT = "Notification Submitted - {id} - Career Development Cell, IIT Dharwad"
|
COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT = "Notification Submitted - {id} - Career Development Cell, IIT Dharwad"
|
||||||
|
|
|
@ -9,20 +9,25 @@ from .constants import *
|
||||||
|
|
||||||
|
|
||||||
class User(models.Model):
|
class User(models.Model):
|
||||||
email = models.CharField(primary_key=True, blank=False, max_length=50)
|
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)
|
||||||
user_type = ArrayField(models.CharField(blank=False, max_length=10), size=4, default=list, blank=False)
|
user_type = ArrayField(models.CharField(blank=False, max_length=10), size=4, default=list, blank=False)
|
||||||
last_login_time = models.DateTimeField(default=timezone.now)
|
last_login_time = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "User"
|
||||||
|
unique_together = ('email', 'id')
|
||||||
|
|
||||||
|
|
||||||
class Student(models.Model):
|
class Student(models.Model):
|
||||||
id = models.CharField(blank=False, max_length=15, primary_key=True)
|
id = models.CharField(blank=False, max_length=15, primary_key=True)
|
||||||
roll_no = models.IntegerField(blank=False)
|
roll_no = models.IntegerField(blank=False)
|
||||||
name = models.CharField(blank=False, max_length=50)
|
name = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
|
||||||
batch = models.CharField(max_length=10, choices=BATCH_CHOICES, blank=False)
|
batch = models.CharField(max_length=10, choices=BATCH_CHOICES, blank=False)
|
||||||
branch = models.CharField(choices=BRANCH_CHOICES, blank=False, max_length=10)
|
branch = models.CharField(choices=BRANCH_CHOICES, blank=False, max_length=10)
|
||||||
phone_number = models.PositiveBigIntegerField(blank=True, default=None, null=True)
|
phone_number = models.PositiveBigIntegerField(blank=True, default=None, null=True)
|
||||||
resumes = ArrayField(models.CharField(null=True, default=None, max_length=100), size=10, default=list, blank=True)
|
resumes = ArrayField(models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT), size=10,
|
||||||
|
default=list, blank=True)
|
||||||
cpi = models.DecimalField(decimal_places=2, max_digits=4)
|
cpi = models.DecimalField(decimal_places=2, max_digits=4)
|
||||||
can_apply = models.BooleanField(default=True, verbose_name='Registered')
|
can_apply = models.BooleanField(default=True, verbose_name='Registered')
|
||||||
|
|
||||||
|
@ -32,7 +37,7 @@ class Student(models.Model):
|
||||||
|
|
||||||
class Admin(models.Model):
|
class Admin(models.Model):
|
||||||
id = models.CharField(blank=False, max_length=15, primary_key=True)
|
id = models.CharField(blank=False, max_length=15, primary_key=True)
|
||||||
name = models.CharField(blank=False, max_length=50)
|
name = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
|
||||||
|
|
||||||
|
|
||||||
def two_day_after_today():
|
def two_day_after_today():
|
||||||
|
@ -42,42 +47,48 @@ def two_day_after_today():
|
||||||
class Placement(models.Model):
|
class Placement(models.Model):
|
||||||
id = models.CharField(blank=False, primary_key=True, max_length=15)
|
id = models.CharField(blank=False, primary_key=True, max_length=15)
|
||||||
# Company Details
|
# Company Details
|
||||||
company_name = models.CharField(blank=False, max_length=50)
|
company_name = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT)
|
||||||
address = models.CharField(blank=False, max_length=500)
|
address = models.CharField(blank=False, max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT)
|
||||||
company_type = models.CharField(blank=False, max_length=50)
|
company_type = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT)
|
||||||
nature_of_business = models.CharField(blank=False, max_length=50, default="")
|
nature_of_business = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
website = models.CharField(blank=True, max_length=50)
|
type_of_organisation = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="", blank=False)
|
||||||
company_details = models.CharField(max_length=500, default=None, null=True)
|
website = models.CharField(blank=True, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
|
||||||
company_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5,
|
company_details = models.CharField(max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT, default=None, null=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)
|
default=list, blank=True)
|
||||||
is_company_details_pdf = models.BooleanField(blank=False, default=False)
|
is_company_details_pdf = models.BooleanField(blank=False, default=False)
|
||||||
contact_person_name = models.CharField(blank=False, max_length=50)
|
contact_person_name = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT)
|
||||||
phone_number = models.PositiveBigIntegerField(blank=False)
|
phone_number = models.PositiveBigIntegerField(blank=False)
|
||||||
email = models.CharField(blank=False, max_length=50, default="")
|
email = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
city = models.CharField(blank=False, max_length=100, default="")
|
city = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
state = models.CharField(blank=False, max_length=100, default="")
|
state = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
country = models.CharField(blank=False, max_length=100, default="")
|
country = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
pin_code = models.IntegerField(blank=False, default=None, null=True)
|
pin_code = models.IntegerField(blank=False, default=None, null=True)
|
||||||
city_type = models.CharField(blank=False, max_length=15, choices=OFFER_CITY_TYPE)
|
city_type = models.CharField(blank=False, max_length=15, choices=OFFER_CITY_TYPE)
|
||||||
# Job Details
|
# Job Details
|
||||||
designation = models.CharField(blank=False, max_length=50, default=None, null=True)
|
designation = models.CharField(blank=False, max_length=JNF_TEXT_MAX_CHARACTER_COUNT, default=None, null=True)
|
||||||
description = models.CharField(blank=False, max_length=500, default=None, null=True)
|
description = models.CharField(blank=False, max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT, default=None, null=True)
|
||||||
description_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5, default=list,
|
job_location = models.CharField(blank=False, max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, default="")
|
||||||
|
description_pdf_names = ArrayField(
|
||||||
|
models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT), size=5, default=list,
|
||||||
blank=True)
|
blank=True)
|
||||||
is_description_pdf = models.BooleanField(blank=False, default=False)
|
is_description_pdf = models.BooleanField(blank=False, default=False)
|
||||||
compensation_CTC = models.IntegerField(blank=False, default=None, null=True) # Job - Per Year
|
compensation_CTC = models.IntegerField(blank=False, default=None, null=True) # Job - Per Year
|
||||||
compensation_gross = models.IntegerField(blank=False, default=None, null=True)
|
compensation_gross = models.IntegerField(blank=False, default=None, null=True)
|
||||||
compensation_take_home = 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_bonus = models.IntegerField(blank=True, default=None, null=True)
|
||||||
compensation_details = models.CharField(blank=True, max_length=500, default=None, null=True)
|
compensation_details_pdf_names = ArrayField(
|
||||||
compensation_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100), size=5,
|
models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT), size=5,
|
||||||
default=list, blank=True)
|
default=list, blank=True)
|
||||||
is_compensation_details_pdf = models.BooleanField(blank=False, default=False)
|
is_compensation_details_pdf = models.BooleanField(blank=False, default=False)
|
||||||
bond_details = models.CharField(blank=True, max_length=500)
|
bond_details = models.CharField(blank=True, max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT)
|
||||||
selection_procedure_rounds = ArrayField(models.CharField(null=True, default=None, max_length=100), size=10,
|
selection_procedure_rounds = ArrayField(
|
||||||
|
models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT), size=10,
|
||||||
default=list, blank=True)
|
default=list, blank=True)
|
||||||
selection_procedure_details = models.CharField(blank=True, max_length=500)
|
selection_procedure_details = models.CharField(blank=True, max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT)
|
||||||
selection_procedure_details_pdf_names = ArrayField(models.CharField(null=True, default=None, max_length=100),
|
selection_procedure_details_pdf_names = ArrayField(
|
||||||
|
models.CharField(null=True, default=None, max_length=JNF_TEXT_MAX_CHARACTER_COUNT),
|
||||||
size=5, default=list, blank=True)
|
size=5, default=list, blank=True)
|
||||||
is_selection_procedure_details_pdf = models.BooleanField(blank=False, default=False)
|
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)
|
tier = models.CharField(blank=False, choices=TIERS, max_length=10, default=None, null=True)
|
||||||
|
@ -94,18 +105,65 @@ class Placement(models.Model):
|
||||||
default=list
|
default=list
|
||||||
)
|
)
|
||||||
tentative_no_of_offers = models.IntegerField(blank=False, default=None, null=True)
|
tentative_no_of_offers = models.IntegerField(blank=False, default=None, null=True)
|
||||||
other_requirements = models.CharField(blank=True, max_length=200, default="")
|
rs_eligible = models.BooleanField(blank=False, default=False)
|
||||||
additional_info = ArrayField(models.CharField(blank=True, max_length=200), size=15, default=list, blank=True)
|
other_requirements = models.CharField(blank=True, max_length=JNF_TEXTAREA_MAX_CHARACTER_COUNT, default="")
|
||||||
|
additional_info = ArrayField(models.CharField(blank=True, max_length=JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT), size=15,
|
||||||
|
default=list, blank=True)
|
||||||
email_verified = models.BooleanField(blank=False, default=False)
|
email_verified = models.BooleanField(blank=False, default=False)
|
||||||
offer_accepted = models.BooleanField(blank=False, default=None, null=True)
|
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)
|
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)
|
created_at = models.DateTimeField(blank=False, default=None, null=True)
|
||||||
|
updated_at = models.DateTimeField(blank=False, default=None, null=True)
|
||||||
|
|
||||||
|
def format(self):
|
||||||
|
if self.company_name is not None:
|
||||||
|
self.company_name = self.company_name.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.company_type is not None:
|
||||||
|
self.company_type = self.company_type.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.company_details is not None:
|
||||||
|
self.company_details = self.company_details.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.address is not None:
|
||||||
|
self.address = self.address.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.nature_of_business is not None:
|
||||||
|
self.nature_of_business = self.nature_of_business.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.type_of_organisation is not None:
|
||||||
|
self.type_of_organisation = self.type_of_organisation.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.website is not None:
|
||||||
|
self.website = self.website.strip()[:JNF_TEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.contact_person_name is not None:
|
||||||
|
self.contact_person_name = self.contact_person_name.strip()[:JNF_TEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.email is not None:
|
||||||
|
self.email = self.email.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.city is not None:
|
||||||
|
self.city = self.city.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.state is not None:
|
||||||
|
self.state = self.state.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.country is not None:
|
||||||
|
self.country = self.country.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.city_type is not None:
|
||||||
|
self.city_type = self.city_type.strip()[:JNF_SMALLTEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.designation is not None:
|
||||||
|
self.designation = self.designation.strip()[:JNF_TEXT_MAX_CHARACTER_COUNT]
|
||||||
|
if self.description is not None:
|
||||||
|
self.description = self.description.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.job_location is not None:
|
||||||
|
self.job_location = self.job_location.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.selection_procedure_details is not None:
|
||||||
|
self.selection_procedure_details = self.selection_procedure_details.strip()[
|
||||||
|
:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
if self.bond_details is not None:
|
||||||
|
self.bond_details = self.bond_details.strip()[:JNF_TEXTAREA_MAX_CHARACTER_COUNT]
|
||||||
|
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)]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' On save, add timestamps '''
|
''' On save, add timestamps '''
|
||||||
if not self.created_at:
|
if not self.created_at:
|
||||||
self.created_at = timezone.now()
|
self.created_at = timezone.now()
|
||||||
|
self.format()
|
||||||
|
self.updated_at = timezone.now()
|
||||||
return super(Placement, self).save(*args, **kwargs)
|
return super(Placement, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -116,7 +174,7 @@ class PlacementApplication(models.Model):
|
||||||
id = models.CharField(blank=False, primary_key=True, max_length=15)
|
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)
|
placement = models.ForeignKey(Placement, blank=False, on_delete=models.RESTRICT, default=None, null=True)
|
||||||
student = models.ForeignKey(Student, blank=False, on_delete=models.CASCADE)
|
student = models.ForeignKey(Student, blank=False, on_delete=models.CASCADE)
|
||||||
resume = models.CharField(max_length=100, blank=False, null=True, default=None)
|
resume = models.CharField(max_length=JNF_TEXT_MAX_CHARACTER_COUNT, blank=False, null=True, default=None)
|
||||||
additional_info = models.JSONField(blank=True, null=True, default=None)
|
additional_info = models.JSONField(blank=True, null=True, default=None)
|
||||||
selected = models.BooleanField(null=True, default=None, blank=True)
|
selected = models.BooleanField(null=True, default=None, blank=True)
|
||||||
applied_at = models.DateTimeField(blank=False, default=None, null=True)
|
applied_at = models.DateTimeField(blank=False, default=None, null=True)
|
||||||
|
@ -141,7 +199,8 @@ class PlacementApplication(models.Model):
|
||||||
class PrePlacementOffer(models.Model):
|
class PrePlacementOffer(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
student = models.ForeignKey(Student, on_delete=models.CASCADE, blank=False)
|
student = models.ForeignKey(Student, on_delete=models.CASCADE, blank=False)
|
||||||
company = models.CharField(max_length=50, blank=False, default="", verbose_name="Company Name")
|
company = models.CharField(max_length=JNF_SMALLTEXT_MAX_CHARACTER_COUNT, blank=False, default="",
|
||||||
|
verbose_name="Company Name")
|
||||||
compensation = models.IntegerField(blank=False) # Job - Per Year
|
compensation = models.IntegerField(blank=False) # Job - Per Year
|
||||||
compensation_details = models.CharField(blank=True, max_length=200)
|
compensation_details = models.CharField(blank=True, max_length=200)
|
||||||
tier = models.CharField(blank=False, choices=TIERS, max_length=10)
|
tier = models.CharField(blank=False, choices=TIERS, max_length=10)
|
||||||
|
|
|
@ -162,9 +162,7 @@ class PlacementApplicationSerializer(serializers.ModelSerializer):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_resume_link(self, obj):
|
def get_resume_link(self, obj):
|
||||||
ele = {}
|
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(obj.id + "/" + obj.resume)
|
|
||||||
ele['name'] = obj.resume
|
|
||||||
return ele
|
return ele
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -181,8 +179,8 @@ class PlacementApplicationSerializerForAdmin(serializers.ModelSerializer):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_resume_link(self, obj):
|
def get_resume_link(self, obj):
|
||||||
link = LINK_TO_STORAGE_RESUME + urllib.parse.quote(obj.id + "/" + obj.resume)
|
ele = {'link': LINK_TO_STORAGE_RESUME + urllib.parse.quote(obj.id + "/" + obj.resume), 'name': obj.resume}
|
||||||
return link
|
return ele
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PlacementApplication
|
model = PlacementApplication
|
||||||
|
|
|
@ -20,6 +20,7 @@ from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
from django.utils import timezone
|
||||||
from google.auth.transport import requests
|
from google.auth.transport import requests
|
||||||
from google.oauth2 import id_token
|
from google.oauth2 import id_token
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
@ -79,7 +80,7 @@ def isAuthorized(allowed_users=None):
|
||||||
print(email)
|
print(email)
|
||||||
user = get_object_or_404(User, email=email)
|
user = get_object_or_404(User, email=email)
|
||||||
if user:
|
if user:
|
||||||
user.last_login_time = datetime.datetime.now()
|
user.last_login_time = timezone.now()
|
||||||
user.save()
|
user.save()
|
||||||
if len(set(user.user_type).intersection(set(allowed_users))) or allowed_users == '*':
|
if len(set(user.user_type).intersection(set(allowed_users))) or allowed_users == '*':
|
||||||
return view_func(request, user.id, user.email, user.user_type, *args, **kwargs)
|
return view_func(request, user.id, user.email, user.user_type, *args, **kwargs)
|
||||||
|
@ -120,7 +121,7 @@ def generateRandomString():
|
||||||
|
|
||||||
def saveFile(file, location):
|
def saveFile(file, location):
|
||||||
prefix = generateRandomString()
|
prefix = generateRandomString()
|
||||||
file_name = prefix + "_" + file.name
|
file_name = prefix + "_" + file.name.strip()
|
||||||
|
|
||||||
file_name = re.sub(r'[\\/:*?"<>|]', '_', file_name)
|
file_name = re.sub(r'[\\/:*?"<>|]', '_', file_name)
|
||||||
|
|
||||||
|
@ -137,8 +138,8 @@ def saveFile(file, location):
|
||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
|
|
||||||
@background_task.background(schedule=10)
|
@background_task.background(schedule=5)
|
||||||
def sendEmail(email_to, subject, data, template, attachment_jnf_respone=None):
|
def sendEmail(email_to, subject, data, template, attachment_jnf_response=None):
|
||||||
try:
|
try:
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
@ -150,11 +151,11 @@ def sendEmail(email_to, subject, data, template, attachment_jnf_respone=None):
|
||||||
|
|
||||||
msg = EmailMultiAlternatives(subject, text_content, email_from, recipient_list)
|
msg = EmailMultiAlternatives(subject, text_content, email_from, recipient_list)
|
||||||
msg.attach_alternative(html_content, "text/html")
|
msg.attach_alternative(html_content, "text/html")
|
||||||
if attachment_jnf_respone:
|
if attachment_jnf_response:
|
||||||
logger.info(attachment_jnf_respone)
|
logger.info(attachment_jnf_response)
|
||||||
pdf = pdfkit.from_string(attachment_jnf_respone['html'], False,
|
pdf = pdfkit.from_string(attachment_jnf_response['html'], False,
|
||||||
options={"--enable-local-file-access": "", '--dpi': '96'})
|
options={"--enable-local-file-access": "", '--dpi': '96'})
|
||||||
msg.attach(attachment_jnf_respone['name'], pdf, 'application/pdf')
|
msg.attach(attachment_jnf_response['name'], pdf, 'application/pdf')
|
||||||
msg.send()
|
msg.send()
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<td style="padding:0 0 36px 0;color:#153643;">
|
<td style="padding:0 0 36px 0;color:#153643;">
|
||||||
|
|
||||||
<p style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Roboto', sans-serif;">
|
<p style="margin:0 0 12px 0;font-size:16px;line-height:24px;font-family: 'Roboto', sans-serif;">
|
||||||
We have received your Job Notification. Kindly verify your email by clicking <a
|
We have received your Job Notification for {{ designation }}. Kindly verify your email by clicking <a
|
||||||
href="{{ one_time_link }}">here</a>.
|
href="{{ one_time_link }}">here</a>.
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</style>
|
</style>
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0;">
|
<body style="margin: 0;font-family: sans-serif;">
|
||||||
|
|
||||||
<header style="background-color: #334878;"><img style="height: 3cm; margin: auto; display: block; padding: 0.5cm;"
|
<header style="background-color: #334878;"><img style="height: 3cm; margin: auto; display: block; padding: 0.5cm;"
|
||||||
src='{{ imgpath }}' alt="cdc logo"></header>
|
src='{{ imgpath }}' alt="cdc logo"></header>
|
||||||
|
|
Loading…
Reference in New Issue