[ADD] ONBOARDING: Added templates as attachment feature

This commit is contained in:
Horilla
2023-12-21 16:37:23 +05:30
parent 97fd49f852
commit b95b46da76
3 changed files with 207 additions and 129 deletions

View File

@@ -19,38 +19,17 @@
<h1 class="oh-main__titlebar-title fw-bold mb-0">
{% trans "Hired Candidates" %}
</h1>
<a
class="oh-main__titlebar-search-toggle"
role="button"
aria-label="Toggle Search"
@click="searchShow = !searchShow"
>
<ion-icon
name="search-outline"
class="oh-main__titlebar-serach-icon"
></ion-icon>
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
@click="searchShow = !searchShow">
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
</a>
</div>
<div class="oh-main__titlebar oh-main__titlebar--right">
{% if candidates %}
<div
class="oh-input-group oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''"
>
<ion-icon
name="search-outline"
class="oh-input-group__icon oh-input-group__icon--left"
></ion-icon>
<input
type="text"
class="oh-input oh-input__icon"
aria-label="Search Input"
placeholder="{% trans 'Search' %}"
name="name"
hx-get="{% url 'candidate-filter' %}"
hx-trigger="keyup"
hx-target="#candidates"
/>
<div class="oh-input-group oh-input__search-group" :class="searchShow ? 'oh-input__search-group--show' : ''">
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
<input type="text" class="oh-input oh-input__icon" aria-label="Search Input" placeholder="{% trans 'Search' %}"
name="name" hx-get="{% url 'candidate-filter' %}" hx-trigger="keyup" hx-target="#candidates" />
</div>
<div class="oh-main__titlebar-button-container">
<div class="oh-dropdown" x-data="{open: false}">
@@ -61,25 +40,15 @@
</div>
<div class="oh-btn-group ml-2">
<button
class="oh-btn oh-btn--primary oh-btn--shadow"
id="trigger-onboarding"
>
{% trans "Trigger Onboarding" %}
<button class="oh-btn oh-btn--info oh-btn--shadow" data-target="#addAttachments" data-toggle="oh-modal-toggle">
{% trans "Send Portal" %}
</button>
<input
type="hidden"
name="csrfmiddlewaretoken"
value="{{ csrf_token }}"
/>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}" />
</div>
{% endif %}
<div class="oh-btn-group ml-2">
<a
href="{% url 'candidate-create' %}?onboarding=True"
class="oh-btn oh-btn--secondary oh-btn--shadow"
method="get"
>
<a href="{% url 'candidate-create' %}?onboarding=True" class="oh-btn oh-btn--secondary oh-btn--shadow"
method="get">
<ion-icon name="add-outline" class="me-1"></ion-icon>
{% trans "Create" %}
</a>
@@ -88,77 +57,105 @@
</div>
</section>
<div class="d-flex flex-row-reverse oh-wrapper">
<span class="m-3" onclick="$('[name=joining_set]').val('true'); $('[name=joining_set]').first().change(); $('.filterButton').click()" style="cursor: pointer">
<span class="m-3"
onclick="$('[name=joining_set]').val('true'); $('[name=joining_set]').first().change(); $('.filterButton').click()"
style="cursor: pointer">
<span class="oh-dot oh-dot--small me-1" style="background-color:yellow"></span>
Joining Set
</span>
<span class="m-3" onclick="$('[name=joining_set]').val('false'); $('[name=joining_set]').first().change(); $('.filterButton').click()" style="cursor: pointer">
<span class="m-3"
onclick="$('[name=joining_set]').val('false'); $('[name=joining_set]').first().change(); $('.filterButton').click()"
style="cursor: pointer">
<span class="oh-dot oh-dot--small me-1" style="background-color:burlywood"></span>
Joining Not-Set
</span>
<span class="m-3" onclick="$('[name=portal_sent]').val('false'); $('[name=portal_sent]').first().change(); $('.filterButton').click()" style="cursor: pointer">
<span class="m-3"
onclick="$('[name=portal_sent]').val('false'); $('[name=portal_sent]').first().change(); $('.filterButton').click()"
style="cursor: pointer">
<span class="oh-dot oh-dot--small me-1" style="background-color:rgba(128, 128, 128, 0.482)"></span>
Portal Not-Sent
</span>
<span class="m-3" onclick="$('[name=portal_sent]').val('true'); $('[name=portal_sent]').first().change(); $('.filterButton').click()" style="cursor: pointer">
<span class="m-3"
onclick="$('[name=portal_sent]').val('true'); $('[name=portal_sent]').first().change(); $('.filterButton').click()"
style="cursor: pointer">
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
Portal Sent
</span>
</div>
<div id="messages" class="oh-alert-container">
</div>
<div class="oh-wrapper" id="candidates">
{% if candidates %}
{% include 'onboarding/candidates.html' %}
{% else %}
<div style="height: 70vh; display:flex;align-items: center;justify-content: center;" class="">
<div style="" class="">
<img style="width: 190px;height: 190px; margin:0 auto;" src="{% static 'images/ui/candidate.png' %}" class="oh-404__image d-block mb-4" alt="Page not found. 404."/>
<h5 class="oh-404__subtitle">{% trans "At present, There are no Candidates onboarding." %}</h5>
</div>
</div>
<div style="height: 70vh; display:flex;align-items: center;justify-content: center;" class="">
<div style="" class="">
<img style="width: 190px;height: 190px; margin:0 auto;" src="{% static 'images/ui/candidate.png' %}"
class="oh-404__image d-block mb-4" alt="Page not found. 404." />
<h5 class="oh-404__subtitle">{% trans "At present, There are no Candidates onboarding." %}</h5>
</div>
</div>
{% endif %}
</div>
<div class="oh-modal" id="addAttachments" role="dialog" aria-labelledby="addAttachments" aria-hidden="true">
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<span class="oh-modal__dialog-title" id="addEmployeeModalLabel">Attachments <span
style="color: #80808080;font-size: 14px;">(Optional)</span></span>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<form hx-post="{% url 'email-send' %}" hx-target="#addAttachmentsBody" hx-encoding="multipart/form-data">
<div class="oh-modal__dialog-body" id="addAttachmentsBody">
<section>
<div id="keyResultsContainer">
<div class="my-3" id="keyResultCard">
<div class="oh-card oh-card--no-shadow oh-card__body">
<div class="oh-input__group">
<select name="ids" id="hired_candidates" multiple class="w-100" hidden>
{% for candidate in hired_candidates %}
<option value="{{candidate.id}}">{{candidate.name}}</option>
{% endfor %}
</select>
</div>
<div class="oh-input__group">
<label class="oh-input__label mt-0" for="keyTitle">Template as Attachments</label>
<select name="template_attachment_ids" id="template_attachment_ids" multiple
class="oh-select oh-select-2 w-100">
{% for template in mail_templates %}
<option value="{{template.id}}">{{template.title}}</option>
{% endfor %}
</select>
</div>
<div class="oh-input__group">
<label class="oh-input__label" for="keyDesc">Other Attachments</label>
<input type="file" multiple name="other_attachments" id="other_attachments">
</div>
</div>
</div>
</div>
</section>
</div>
<div class="oh-modal__dialog-footer">
<button type="submit" onclick="$('.oh-modal#addAttachments').removeClass('oh-modal--show');$(`#messages`).html(
$(`
<div class='oh-alert oh-alert--animated oh-alert--info'>
Processing...
</div>
`)
)"
class="oh-btn oh-btn--secondary oh-btn--shadow" id="trigger-onboarding">
Send Portal Link
</button>
</div>
</form>
</div>
</div>
<script>
$("#trigger-onboarding").on("click", function () {
var array = [];
$(".checkboxAll:checked").each(function () {
array.push($(this).attr("id"));
});
if (array.length) {
$("#message").empty();
$("#message").append(`
<div class="oh-alert oh-alert--animated oh-alert--success">
Processing
</div>`);
$.ajax({
url: "{% url 'email-send' %}",
data: {
ids: array,
},
traditional: true,
success: function (response) {
$("#message").empty();
$("#message").append(`
<div class="oh-alert oh-alert--animated oh-alert--success">
Candidate portal link sent.
</div>`);
if (response.tags == "success") {
setTimeout(function () {
// Code to be executed after the delay
location.reload();
}, 2000);
}
},
});
} else {
$("#message").append(`
<div class="oh-alert oh-alert--animated oh-alert--info">
Please select candidates
</div>`);
}
});
function checkRow(element){
function checkRow(element) {
if (element.checked) {
$(".checkboxAll").each(function () {
$(".checkboxAll").prop("checked", true);
@@ -169,8 +166,14 @@
});
}
}
$("#selectAll").on("click", function () {
$("#selectAll").click("click", function () {
checkRow(this)
var checkedIds = []
$.each($(".checkboxAll:checked"), function (indexInArray, valueOfElement) {
checkedIds.push($(valueOfElement).attr("id"))
});
$("#hired_candidates[name=ids]").val(checkedIds);
$("select[name=ids]").change()
});
$("#delete-link").on("click", function (event) {
@@ -184,4 +187,4 @@
}
});
</script>
{% endblock content %}
{% endblock content %}

View File

@@ -13,6 +13,7 @@ provide the main entry points for interacting with the application's functionali
from urllib.parse import parse_qs
import json, contextlib, random, secrets
from django import template
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.core.mail import send_mail
@@ -30,8 +31,8 @@ from notifications.signals import notify
from horilla import settings
from horilla.decorators import login_required, hx_request_required
from horilla.decorators import permission_required
from base.methods import get_key_instances
from recruitment.models import Candidate, Recruitment
from base.methods import generate_pdf, get_key_instances
from recruitment.models import Candidate, Recruitment, RecruitmentMailTemplate
from recruitment.filters import CandidateFilter
from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails
from django.db.models import ProtectedError
@@ -481,6 +482,7 @@ def candidates_view(request):
previous_data = request.GET.urlencode()
page_number = request.GET.get("page")
page_obj = paginator_qry(queryset, page_number)
mail_templates = RecruitmentMailTemplate.objects.all()
return render(
request,
"onboarding/candidates_view.html",
@@ -488,6 +490,8 @@ def candidates_view(request):
"candidates": page_obj,
"form": candidate_filter_obj.form,
"pd": previous_data,
"mail_templates": mail_templates,
"hired_candidates": queryset,
},
)
@@ -556,9 +560,44 @@ def email_send(request):
"""
host = request.get_host()
protocol = "https" if request.is_secure() else "http"
candidates = request.GET.getlist("ids")
candidates = request.POST.getlist("ids")
other_attachments = request.FILES.getlist("other_attachments")
template_attachment_ids = request.POST.getlist("template_attachment_ids")
print(candidates)
if not candidates:
messages.info(request, "Please choose chandidates")
return HttpResponse("<script>window.location.reload()</script>")
bodys = list(
RecruitmentMailTemplate.objects.filter(
id__in=template_attachment_ids
).values_list("body", flat=True)
)
if not candidates:
messages.info(request, "Please choose candidates")
attachments_other = []
for file in other_attachments:
attachments_other.append((file.name, file.read(), file.content_type))
file.close()
for cand_id in candidates:
attachments = list(set(attachments_other) | set([]))
candidate = Candidate.objects.get(id=cand_id)
for html in bodys:
# due to not having solid template we first need to pass the context
template_bdy = template.Template(html)
context = template.Context(
{"instance": candidate, "self": request.user.employee_get}
)
render_bdy = template_bdy.render(context)
attachments.append(
(
"Document",
generate_pdf(render_bdy, {}, path=False, title="Document").content,
"application/pdf",
)
)
token = secrets.token_hex(15)
existing_portal = OnboardingPortal.objects.filter(candidate_id=candidate)
if existing_portal.exists():
@@ -584,16 +623,16 @@ def email_send(request):
[candidate.email],
)
email.content_subtype = "html"
email.attachments = attachments
try:
email.send()
# to check ajax or not
if not request.headers.get("x-requested-with") == "XMLHttpRequest":
messages.success(request, "Portal link sent to the candidate")
messages.success(request, "Portal link sent to the candidate")
except:
messages.error(request, f"Mail not send to {candidate.name}")
candidate.start_onboard = True
candidate.save()
return redirect(candidates_view)
return HttpResponse("<script>window.location.reload()</script>")
@login_required

View File

@@ -11,7 +11,8 @@ This module is part of the recruitment project and is intended to
provide the main entry points for interacting with the application's functionality.
"""
from datetime import date, datetime
from django import template
from django.core.mail import EmailMessage
import os
import json
import contextlib
@@ -21,18 +22,22 @@ from django.db.models import Q
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.core import serializers
import pandas as pd
from base.models import JobPosition
from django.contrib import messages
from django.views.decorators.http import require_http_methods
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _
from notifications.signals import notify
from horilla import settings
from horilla.decorators import permission_required, login_required, hx_request_required
from base.methods import export_data, get_key_instances
from base.methods import export_data, generate_pdf, get_key_instances
from recruitment.views.paginator_qry import paginator_qry
from recruitment.models import Recruitment, Candidate, Stage, StageNote
from recruitment.models import (
RecruitmentMailTemplate,
Recruitment,
Candidate,
Stage,
StageNote,
)
from recruitment.filters import (
CandidateFilter,
CandidateReGroup,
@@ -998,8 +1003,11 @@ def form_send_mail(request, cand_id):
This method is used to render the bootstrap modal content body form
"""
candidate_obj = Candidate.objects.get(id=cand_id)
templates = RecruitmentMailTemplate.objects.all()
return render(
request, "pipeline/pipeline_components/send_mail.html", {"cand": candidate_obj}
request,
"pipeline/pipeline_components/send_mail.html",
{"cand": candidate_obj, "templates": templates},
)
@@ -1009,29 +1017,57 @@ def send_acknowledgement(request):
"""
This method is used to send acknowledgement mail to the candidate
"""
with contextlib.suppress(Exception):
send_to = request.POST.get("to")
subject = request.POST.get("subject")
bdy = request.POST.get("body")
res = send_mail(
subject, bdy, settings.EMAIL_HOST_USER, [send_to], fail_silently=False
)
if res == 1:
return HttpResponse(
"""
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated oh-alert--success"> Mail sent.</div>
</div>
"""
)
return HttpResponse(
"""
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated oh-alert--danger">Sorry,\
Something went wrong.</div>
</div>
"""
candidate_id = request.POST["id"]
subject = request.POST.get("subject")
bdy = request.POST.get("body")
other_attachments = request.FILES.getlist("other_attachments")
attachments = [
(file.name, file.read(), file.content_type) for file in other_attachments
]
host = settings.EMAIL_HOST_USER
candidate_obj = Candidate.objects.get(id=candidate_id)
template_attachment_ids = request.POST.getlist("template_attachments")
bodys = list(
RecruitmentMailTemplate.objects.filter(
id__in=template_attachment_ids
).values_list("body", flat=True)
)
for html in bodys:
# due to not having solid template we first need to pass the context
template_bdy = template.Template(html)
context = template.Context(
{"instance": candidate_obj, "self": request.user.employee_get}
)
render_bdy = template_bdy.render(context)
attachments.append(
(
"Document",
generate_pdf(render_bdy, {}, path=False, title="Document").content,
"application/pdf",
)
)
template_bdy = template.Template(bdy)
context = template.Context(
{"instance": candidate_obj, "self": request.user.employee_get}
)
render_bdy = template_bdy.render(context)
email = EmailMessage(
subject,
render_bdy,
host,
[candidate_obj.email],
)
email.content_subtype = "html"
email.attachments = attachments
try:
email.send()
messages.success(request, "Mail sent to candidate")
except Exception as e:
messages.error(request, "Something went wrong")
return HttpResponse("<script>window.location.reload()</script>")
@login_required