[ADD] ONBOARDING: Added templates as attachment feature
This commit is contained in:
@@ -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 %}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user