* Prod Changes (#135)

* bug fixes

* merge conlicts resolve

* minor bug fixes

* Send email to cdc on new opening.

* added email option while  jnf submission error

* Send alert email to cdc when error while submitting jnf by company

* fixing debug issue in setting

* setting offerAccepted to only superAdmins

* remove dev.env from git

* changed role to su

-cant give a person 3 roles (student,super_admin,admin) because of varchar(10) given earlier

* changing to s_admin

* added admin view for User modal

Co-authored-by: karthik mv <karthik.murakonda14@gmail.com>
This commit is contained in:
Gowtham Sai 2022-09-12 11:24:50 +05:30 committed by GitHub
parent 004773f3ce
commit c4e33e5eeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 194 additions and 45 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
CDC_Backend.zip Normal file

Binary file not shown.

BIN
CDC_Backend/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -10,11 +10,44 @@ from import_export import resources
from .models import *
class UserAdmin(ImportExportMixin, SimpleHistoryAdmin):
pass
admin.site.register(User,UserAdmin)
admin.site.register(Admin, SimpleHistoryAdmin)
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"
@ -54,6 +87,17 @@ 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(AdminAdmin):
list_display = (COMPANY_NAME, CONTACT_PERSON_NAME, PHONE_NUMBER, 'tier', 'compensation_CTC')
@ -70,6 +114,7 @@ class PlacementApplicationResources(resources.ModelResource):
class PlacementAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PlacementApplicationResources
@admin.register(PlacementApplication)
class PlacementApplication(PlacementAdmin):
list_display = ('id', 'Placement', 'Student', 'selected')
@ -92,6 +137,7 @@ class PrePlacementResources(resources.ModelResource):
class PrePlacementOfferAdmin(ExportMixin, SimpleHistoryAdmin):
resource_class = PrePlacementResources
@admin.register(PrePlacementOffer)
class PrePlacementOffer(PrePlacementOfferAdmin):
list_display = ('company', 'Student', 'accepted')

View File

@ -104,22 +104,29 @@ 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
offer_accepted = data[OFFER_ACCEPTED]
opening = get_object_or_404(Placement, pk=data[OPENING_ID])
if not opening.offer_accepted:
opening.offer_accepted = True
if opening.offer_accepted is None:
opening.offer_accepted = offer_accepted == "true"
opening.changed_by = get_object_or_404(User, id=id)
opening.save()
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"},

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)
@ -278,7 +283,7 @@ def verifyEmail(request):
"opening_type": PLACEMENT,
"company_name": opening.company_name,
}
sendEmail(opening.email, COMPANY_OPENING_SUBMITTED_TEMPLATE_SUBJECT.format(id=opening.id), 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,23 +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 = [
@ -32,32 +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 = os.environ.get("GOOGLE_OAUTH_CLIENT_ID") # "client_id"
CLIENT_ID = os.environ.get('GOOGLE_OAUTH_CLIENT_ID') # Google Login Client ID
# To be Configured Properly
PLACEMENT_OPENING_URL = "http://localhost:3000/student/dashboard/placements/{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
MAX_RESUMES_PER_STUDENT = 3
EMAIL_VERIFICATION_TOKEN_TTL = 48 # in hours
JNF_TEXT_MAX_CHARACTER_COUNT = 100
JNF_TEXTMEDIUM_MAX_CHARACTER_COUNT = 200
@ -140,6 +151,7 @@ SPECIAL_FORMAT_IN_PDF = ['website', 'company_details_pdf_names', 'description_pd
'compensation_details_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_SUBMITTED_TEMPLATE_SUBJECT = 'CDC - Application Submitted - {company_name}'
@ -159,4 +171,3 @@ 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

@ -33,6 +33,7 @@ class Student(models.Model):
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):
@ -82,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)

View File

@ -57,6 +57,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)
@ -146,7 +147,10 @@ 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")
@ -180,6 +184,9 @@ def PlacementApplicationConditions(student, placement):
if int(i.placement.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:
@ -250,6 +257,8 @@ def verify_recaptcha(request):
}
r = rq.post('https://www.google.com/recaptcha/api/siteverify', data=data)
result = r.json()
if not result['success']:
logger.warning("Utils - verify_recaptcha: " + str(result))
return result['success']
except:
# get exception line number
@ -258,8 +267,13 @@ def verify_recaptcha(request):
def opening_description_table_html(opening):
details = model_to_dict(opening, fields=[field.name for field in Placement._meta.fields],
# 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:
@ -269,7 +283,7 @@ def opening_description_table_html(opening):
if key == 'website':
details[key] = {"details": details[key], "type": ["link"]}
else:
details[key] = {"details": [item[16:] for item in details[key]["details"]], "type": ["list", "link"],
details[key] = {"details": [item for item in details[key]["details"]], "type": ["list", "link"],
"link": PDF_FILES_SERVING_ENDPOINT + opening.id + "/"}
new_key = key.replace('_', ' ')
if new_key.endswith(' names'):
@ -326,3 +340,41 @@ def send_opening_notifications(placement_id):
logger.warning('Utils - send_opening_notifications: ' + str(sys.exc_info()))
return False
def exception_email(opening):
opening = opening.dict()
data = {
"designation": opening["designation"],
"opening_type": PLACEMENT,
"company_name": opening["company_name"],
}
pdfhtml = opening_description_table_html(opening)
name = opening["company_name"]+'_jnf_response.pdf'
attachment_jnf_respone = {
"name": name,
"html": pdfhtml,
}
sendEmail(CDC_MAIl_ADDRESS, COMPANY_OPENING_ERROR_TEMPLATE.format(company_name=opening["company_name"]), data,
COMPANY_OPENING_SUBMITTED_TEMPLATE, attachment_jnf_respone)
def store_all_files(request):
files = request.FILES
data = request.data
# save all the files
if files:
# company details pdf
for file in files.getlist(COMPANY_DETAILS_PDF):
file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + "temp" + '/'
saveFile(file, file_location)
# compensation details pdf
for file in files.getlist(COMPENSATION_DETAILS_PDF):
file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + "temp" + '/'
saveFile(file, file_location)
# selection procedure details pdf
for file in files.getlist(SELECTION_PROCEDURE_DETAILS_PDF):
file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + "temp" + '/'
saveFile(file, file_location)
# description pdf
for file in files.getlist(DESCRIPTION_PDF):
file_location = STORAGE_DESTINATION_COMPANY_ATTACHMENTS + "temp" + '/'
saveFile(file, file_location)

View File

@ -15,7 +15,6 @@ 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, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -24,12 +23,12 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("GOOGLE_OAUTH_SECRET_KEY")
SECRET_KEY = os.environ.get("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.environ.get('DEBUG') == "True"
ALLOWED_HOSTS = ['cdc-iitdh.herokuapp.com/', 'localhost', '192.168.29.199', '127.0.0.1']
ALLOWED_HOSTS = ['cdc.iitdh.ac.in', 'localhost']
# Application definition
@ -56,6 +55,7 @@ MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'corsheaders.middleware.CorsPostCsrfMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
@ -146,13 +146,14 @@ STATICFILES_DIR = (
os.path.join(BASE_DIR, 'static'),
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = [
"https://cdc.iitdh.ac.in",
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8000",
"http://127.0.0.1:8000"
"https://localhost:3000"
]
CORS_REPLACE_HTTPS_REFERER = True
CSRF_TRUSTED_ORIGINS = [ "https://cdc.iitdh.ac.in", "http://cdc.iitdh.ac.in"]
# EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = './emails'

View File

@ -6,7 +6,6 @@ It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application

1
CDC_Backend/run_prod.sh Normal file
View File

@ -0,0 +1 @@
gunicorn --certfile=/home/cdc/Desktop/1f9476e3959ebe60.crt --keyfile=/home/cdc/Desktop/star_iitdh_key.key --bind localhost:8000 CDC_Backend.wsgi --access-logfile access.log --error-logfile error.log &

View File

@ -44,7 +44,7 @@
{% for item in value.details %}
<li>
{% if 'link' in value.type and value.link %}
<a href="{{ value.link|add:item}}">{{ item }}</a>
<a href="{{ value.link|add:item}}">{{ item|slice:"16:" }}</a>
{% elif 'link' in value.type %}
<a href="{{ item }}">{{ item }}</a>
{% else %}

13
Email_service_README.md Normal file
View File

@ -0,0 +1,13 @@
we have defined a service for running the `start_email_service.sh` file continusouly on the server. Use the following commands for doing anything to the email
service.
## Enable the Service
`sudo systemctl enable cdc-email-sender.service`
## Start the Service
`sudo systemctl start cdc-email-sender.service`
## Check Status of the Service
`sudo systemctl status cdc-email-sender.service`
Any Doubts contact Gowtham Sai - 190010036

View File

@ -34,21 +34,27 @@ http {
server {
listen 80;
server_name localhost;
server_name cdc.iitdh.ac.in;
# listen 443 ssl;
listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
ssl_certificate /home/cdc/Desktop/1f9476e3959ebe60.pem;
ssl_certificate_key /home/cdc/Desktop/star_iitdh_key.key;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /portal;
}
location /portal {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /portal/index.html;
}
# Server static files /storage
@ -65,14 +71,14 @@ http {
}
location /api/ {
proxy_pass http://localhost:8000;
proxy_pass https://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location /admin/ {
proxy_pass http://localhost:8000;
proxy_pass https://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

7
start_email_service.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
source /home/cdc/Desktop/CDC_Web_Portal_Backend/cdc-placement-website-backend/venv/bin/activate
cd /home/cdc/Desktop/CDC_Web_Portal_Backend/cdc-placement-website-backend/CDC_Backend
python manage.py process_tasks