Files
ihrm/recruitment/views.py

1399 lines
54 KiB
Python

"""
views.py
This module contains the view functions for handling HTTP requests and rendering
responses in your application.
Each view function corresponds to a specific URL route and performs the necessary
actions to handle the request, process data, and generate a response.
This module is part of the recruitment project and is intended to
provide the main entry points for interacting with the application's functionality.
"""
import contextlib
import json
import logging
from django.contrib import messages
from django.contrib.auth.models import Permission
from django.core import serializers
from django.core.mail import send_mail
from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from base.backends import ConfiguredEmailBackend
from base.methods import sortby
from employee.models import Employee
from horilla import settings
from horilla.decorators import hx_request_required, login_required, permission_required
from notifications.signals import notify
from recruitment.decorators import manager_can_enter, recruitment_manager_can_enter
from recruitment.filters import CandidateFilter, RecruitmentFilter, StageFilter
from recruitment.forms import (
ApplicationForm,
CandidateCreationForm,
CandidateDropDownForm,
RecruitmentCreationForm,
RecruitmentDropDownForm,
StageCreationForm,
StageDropDownForm,
StageNoteForm,
)
from recruitment.methods import recruitment_manages
from recruitment.models import Candidate, Recruitment, Stage, StageNote
logger = logging.getLogger(__name__)
def is_stagemanager(request, stage_id=False):
"""
This method is used to identify the employee is a stage manager or
not, if stage_id is passed through args, method will
check the employee is manager to the corresponding stage, return
tuple with boolean and all stages that employee is manager.
if called this method without stage_id args it will return boolean
with all the stage that the employee is stage manager
Args:
request : django http request
stage_id : stage instance id
"""
user = request.user
employee = user.employee_get
if not stage_id:
return (
employee.stage_set.exists() or user.is_superuser,
employee.stage_set.all(),
)
stage_obj = Stage.objects.get(id=stage_id)
return (
employee in stage_obj.stage_managers.all()
or user.is_superuser
or is_recruitmentmanager(request, rec_id=stage_obj.recruitment_id.id)[0],
employee.stage_set.all(),
)
def is_recruitmentmanager(request, rec_id=False):
"""
This method is used to identify the employee is a recruitment
manager or not, if rec_id is passed through args, method will
check the employee is manager to the corresponding recruitment,
return tuple with boolean and all recruitment that employee is manager.
if called this method without recruitment args it will return
boolean with all the recruitment that the employee is recruitment manager
Args:
request : django http request
rec_id : recruitment instance id
"""
user = request.user
employee = user.employee_get
if not rec_id:
return (
employee.recruitment_set.exists() or user.is_superuser,
employee.recruitment_set.all(),
)
recruitment_obj = Recruitment.objects.get(id=rec_id)
return (
employee in recruitment_obj.recruitment_managers.all() or user.is_superuser,
employee.recruitment_set.all(),
)
def paginator_qry(qryset, page_number):
"""
This method is used to generate common paginator limit.
"""
paginator = Paginator(qryset, 50)
qryset = paginator.get_page(page_number)
return qryset
@login_required
@permission_required(perm="recruitment.add_recruitment")
def recruitment(request):
"""
This method is used to create recruitment, when create recruitment this method
add recruitment view,create candidate, change stage sequence and so on, some of
the permission is checking manually instead of using django permission permission
to the recruitment managers
"""
form = RecruitmentCreationForm()
if request.method == "POST":
form = RecruitmentCreationForm(request.POST)
if form.is_valid():
recruitment_obj = form.save()
messages.success(request, _("Recruitment added."))
with contextlib.suppress(Exception):
managers = recruitment_obj.recruitment_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb="You are chosen as one of recruitment manager",
verb_ar="تم اختيارك كأحد مديري التوظيف",
verb_de="Sie wurden als einer der Personalvermittler ausgewählt",
verb_es="Has sido elegido/a como uno de los gerentes de contratación",
verb_fr="Vous êtes choisi(e) comme l'un des responsables du recrutement",
icon="people-circle",
redirect=reverse("pipeline"),
)
response = render(
request, "recruitment/recruitment_form.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "recruitment/recruitment_form.html", {"form": form})
@login_required
@permission_required(perm="recruitment.change_recruitment")
@require_http_methods(["POST"])
def remove_recruitment_manager(request, mid, rid):
"""
This method is used to remove selected manager from the recruitment,
when remove the manager permissions also removed if the employee is not
exists in more stage manager or recruitment manager
Args:
mid : employee manager_id in the recruitment
rid : recruitment_id
"""
recruitment_obj = Recruitment.objects.get(id=rid)
manager = Employee.objects.get(id=mid)
recruitment_obj.recruitment_managers.remove(manager)
messages.success(request, _("Recruitment manager removed successfully."))
notify.send(
request.user.employee_get,
recipient=manager.employee_user_id,
verb=f"You are removed from recruitment manager from {recruitment_obj}",
verb_ar=f"تمت إزالتك من وظيفة مدير التوظيف في {recruitment_obj}",
verb_de=f"Sie wurden als Personalvermittler von {recruitment_obj} entfernt",
verb_es=f"Has sido eliminado/a como gerente de contratación de {recruitment_obj}",
verb_fr=f"Vous avez été supprimé(e) en tant que responsable\
du recrutement de {recruitment_obj}",
icon="person-remove",
redirect="",
)
recruitment_queryset = Recruitment.objects.all()
previous_data = request.environ["QUERY_STRING"]
return render(
request,
"recruitment/recruitment_component.html",
{
"data": paginator_qry(recruitment_queryset, request.GET.get("page")),
"pd": previous_data,
},
)
@login_required
@permission_required(perm="recruitment.view_recruitment")
def recruitment_view(request):
"""
This method is used to render all recruitment to view
"""
if not request.GET:
request.GET.copy().update({"is_active": "on"})
form = RecruitmentCreationForm()
filter_obj = RecruitmentFilter(request.GET, queryset=Recruitment.objects.all())
return render(
request,
"recruitment/recruitment_view.html",
{
"data": paginator_qry(filter_obj.qs, request.GET.get("page")),
"f": filter_obj,
"form": form,
},
)
@login_required
@permission_required(perm="recruitment.view_recruitment")
def recruitment_search(request):
"""
This method is used to search recruitment
"""
filter_obj = RecruitmentFilter(request.GET)
previous_data = request.environ["QUERY_STRING"]
recruitment_obj = sortby(request, filter_obj.qs, "orderby")
return render(
request,
"recruitment/recruitment_component.html",
{
"data": paginator_qry(recruitment_obj, request.GET.get("page")),
"pd": previous_data,
},
)
@login_required
@permission_required(perm="recruitment.change_recruitment")
@hx_request_required
def recruitment_update(request, rec_id):
"""
This method is used to update the recruitment, when updating the recruitment,
any changes in manager is exists then permissions also assigned to the manager
Args:
id : recruitment_id
"""
recruitment_obj = Recruitment.objects.get(id=rec_id)
form = RecruitmentCreationForm(instance=recruitment_obj)
if request.method == "POST":
form = RecruitmentCreationForm(request.POST, instance=recruitment_obj)
if form.is_valid():
recruitment_obj = form.save()
messages.success(request, _("Recruitment Updated."))
response = render(
request, "recruitment/recruitment_form.html", {"form": form}
)
with contextlib.suppress(Exception):
managers = recruitment_obj.recruitment_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"{recruitment_obj} is updated, You are chosen as one of the managers",
verb_ar=f"{recruitment_obj} تم تحديثه، تم اختيارك كأحد المديرين",
verb_de=f"{recruitment_obj} wurde aktualisiert. Sie wurden als\
einer der Manager ausgewählt",
verb_es=f"{recruitment_obj} ha sido actualizado/a. Has sido elegido\
a como uno de los gerentes",
verb_fr=f"{recruitment_obj} a été mis(e) à jour. Vous êtes choisi(e) comme l'un des responsables",
icon="people-circle",
redirect=reverse("pipeline"),
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "recruitment/recruitment_update_form.html", {"form": form})
@login_required
@permission_required(perm="recruitment.delete_recruitment")
@require_http_methods(["POST"])
def recruitment_delete(request, rec_id):
"""
This method is used to permanently delete the recruitment
Args:
id : recruitment_id
"""
recruitment_obj = Recruitment.objects.get(id=rec_id)
recruitment_mangers = recruitment_obj.recruitment_managers.all()
all_stage_permissions = Permission.objects.filter(
content_type__app_label="recruitment", content_type__model="stage"
)
all_candidate_permissions = Permission.objects.filter(
content_type__app_label="recruitment", content_type__model="candidate"
)
for manager in recruitment_mangers:
all_this_manger = manager.recruitment_set.all()
if len(all_this_manger) == 1:
for stage_permission in all_candidate_permissions:
manager.employee_user_id.user_permissions.remove(stage_permission.id)
for candidate_permission in all_stage_permissions:
manager.employee_user_id.user_permissions.remove(
candidate_permission.id
)
try:
recruitment_obj.delete()
messages.success(request, _("Recruitment deleted successfully."))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this recruitment"))
recruitment_obj = Recruitment.objects.all()
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@manager_can_enter(perm="recruitment.view_recruitment")
def recruitment_pipeline(request):
"""
This method is used to filter out candidate through pipeline structure
"""
view = request.GET.get("view")
template = "pipeline/pipeline.html"
if view == "card":
template = "pipeline/pipeline_card.html"
recruitment_form = RecruitmentDropDownForm()
stage_form = StageDropDownForm()
candidate_form = CandidateDropDownForm()
recruitment_obj = Recruitment.objects.filter(is_active=True, closed=False)
if request.method == "POST":
if request.POST.get(
"recruitment_managers"
) is not None and request.user.has_perm("add_recruitment"):
recruitment_form = RecruitmentDropDownForm(request.POST)
if recruitment_form.is_valid():
recruitment_obj = recruitment_form.save()
recruitment_form = RecruitmentDropDownForm()
messages.success(request, _("Recruitment added."))
with contextlib.suppress(Exception):
managers = recruitment_obj.recruitment_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"You are chosen as recruitment manager for\
the recruitment {recruitment_obj}",
verb_ar=f"تم اختيارك كمدير توظيف للتوظيف {recruitment_obj}",
verb_de=f"Sie wurden als Personalvermittler für die Rekrutierung\
{recruitment_obj} ausgewählt",
verb_es=f"Has sido elegido/a como gerente de contratación para la contratación {recruitment_obj}",
verb_fr=f"Vous êtes choisi(e) comme responsable du recrutement pour le recrutement {recruitment_obj}",
icon="people-circle",
redirect=reverse("pipeline"),
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
elif request.FILES.get("resume") is not None:
if request.user.has_perm("add_candidate") or is_stagemanager(
request,
):
candidate_form = CandidateDropDownForm(request.POST, request.FILES)
if candidate_form.is_valid():
candidate_obj = candidate_form.save()
candidate_form = CandidateDropDownForm()
with contextlib.suppress(Exception):
managers = candidate_obj.stage_id.stage_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"New candidate arrived on stage {candidate_obj.stage_id.stage}",
verb_ar=f"وصل مرشح جديد إلى المرحلة {candidate_obj.stage_id.stage}",
verb_de=f"Neuer Kandidat ist auf der Stufe {candidate_obj.stage_id.stage} angekommen",
verb_es=f"Nuevo candidato llegó a la etapa {candidate_obj.stage_id.stage}",
verb_fr=f"Nouveau candidat arrivé à l'étape {candidate_obj.stage_id.stage}",
icon="person-add",
redirect=reverse("pipeline"),
)
messages.success(request, _("Candidate added."))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
elif request.POST.get("stage_managers") and request.user.has_perm("add_stage"):
stage_form = StageDropDownForm(request.POST)
if stage_form.is_valid():
if recruitment_manages(
request, stage_form.instance.recruitment_id
) or request.user.has_perm("recruitment.add_stage"):
stage_obj = stage_form.save()
stage_form = StageDropDownForm()
messages.success(request, _("Stage added."))
with contextlib.suppress(Exception):
managers = stage_obj.stage_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"You are chosen as a stage manager on the stage {stage_obj.stage} in recruitment {stage_obj.recruitment_id}",
verb_ar=f"لقد تم اختيارك كمدير مرحلة في المرحلة {stage_obj.stage} في التوظيف {stage_obj.recruitment_id}",
verb_de=f"Sie wurden als Bühnenmanager für die Stufe {stage_obj.stage} in der Rekrutierung {stage_obj.recruitment_id} ausgewählt",
verb_es=f"Has sido elegido/a como gerente de etapa en la etapa {stage_obj.stage} en la contratación {stage_obj.recruitment_id}",
verb_fr=f"Vous avez été choisi(e) comme responsable de l'étape {stage_obj.stage} dans le recrutement {stage_obj.recruitment_id}",
icon="people-circle",
redirect=reverse("pipeline"),
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
messages.info(request, _("You dont have access"))
return render(
request,
template,
{
"recruitment": recruitment_obj,
"recruitment_form": recruitment_form,
"stage_form": stage_form,
"candidate_form": candidate_form,
},
)
@login_required
@permission_required(perm="recruitment.view_recruitment")
def recruitment_pipeline_card(request):
"""
This method is used to render pipeline card structure.
"""
search = request.GET.get("search")
search = search if search is not None else ""
recruitment_obj = Recruitment.objects.all()
candidates = Candidate.objects.filter(name__icontains=search, is_active=True)
stages = Stage.objects.all()
return render(
request,
"pipeline/pipeline_components/pipeline_card_view.html",
{"recruitment": recruitment_obj, "candidates": candidates, "stages": stages},
)
@login_required
@recruitment_manager_can_enter(perm="recruitment.change_stage")
def stage_update_pipeline(request, stage_id):
"""
This method is used to update stage from pipeline view
"""
stage_obj = Stage.objects.get(id=stage_id)
form = StageCreationForm(instance=stage_obj)
if request.POST:
form = StageCreationForm(request.POST, instance=stage_obj)
if form.is_valid():
stage_obj = form.save()
messages.success(request, _("Stage updated."))
with contextlib.suppress(Exception):
managers = stage_obj.stage_managers.select_related("employee_user_id")
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"{stage_obj.stage} stage in recruitment {stage_obj.recruitment_id}\
is updated, You are chosen as one of the managers",
verb_ar=f"تم تحديث مرحلة {stage_obj.stage} في التوظيف {stage_obj.recruitment_id}\
، تم اختيارك كأحد المديرين",
verb_de=f"Die Stufe {stage_obj.stage} in der Rekrutierung {stage_obj.recruitment_id}\
wurde aktualisiert. Sie wurden als einer der Manager ausgewählt",
verb_es=f"Se ha actualizado la etapa {stage_obj.stage} en la contratación {stage_obj.recruitment_id}.\
Has sido elegido/a como uno de los gerentes",
verb_fr=f"L'étape {stage_obj.stage} dans le recrutement {stage_obj.recruitment_id} a été mise à jour.\
Vous avez été choisi(e) comme l'un des responsables",
icon="people-circle",
redirect=reverse("pipeline"),
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
return render(request, "pipeline/form/stage_update.html", {"form": form})
@login_required
@recruitment_manager_can_enter(perm="recruitment.change_recruitment")
def recruitment_update_pipeline(request, rec_id):
"""
This method is used to update recruitment from pipeline view
"""
recruitment_obj = Recruitment.objects.get(id=rec_id)
form = RecruitmentCreationForm(instance=recruitment_obj)
if request.POST:
form = RecruitmentCreationForm(request.POST, instance=recruitment_obj)
if form.is_valid():
recruitment_obj = form.save()
messages.success(request, _("Recruitment updated."))
with contextlib.suppress(Exception):
managers = recruitment_obj.recruitment_managers.select_related(
"employee_user_id"
)
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"{recruitment_obj} is updated, You are chosen as one of the managers",
verb_ar=f"تم تحديث {recruitment_obj}، تم اختيارك كأحد المديرين",
verb_de=f"{recruitment_obj} wurde aktualisiert. Sie wurden als einer der Manager ausgewählt",
verb_es=f"{recruitment_obj} ha sido actualizado/a. Has sido elegido\
a como uno de los gerentes",
verb_fr=f"{recruitment_obj} a été mis(e) à jour. Vous avez été\
choisi(e) comme l'un des responsables",
icon="people-circle",
redirect=reverse("pipeline"),
)
response = render(
request, "pipeline/form/recruitment_update.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "pipeline/form/recruitment_update.html", {"form": form})
@login_required
@permission_required(perm="recruitment.delete_recruitment")
@require_http_methods(["POST"])
def recruitment_delete_pipeline(request, rec_id):
"""This method is used to delete the recruitment instance
Args:
id: recruitment instance id
Returns:
HttpResponseRedirect: Used to refresh the page
"""
recruitment_obj = Recruitment.objects.get(id=rec_id)
try:
recruitment_obj.delete()
messages.success(request, _("Recruitment deleted."))
except Exception as error:
messages.error(request, error)
messages.error(request, _("Recruitment already in use."))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@manager_can_enter(perm="recruitment.change_candidate")
def candidate_stage_update(request, cand_id):
"""
This method is a ajax method used to update candidate stage when drag and drop
the candidate from one stage to another on the pipeline template
Args:
id : candidate_id
"""
stage_id = request.POST["stageId"]
candidate_obj = Candidate.objects.get(id=cand_id)
history_queryset = candidate_obj.candidate_history.all().first()
stage_obj = Stage.objects.get(id=stage_id)
previous_stage = history_queryset.stage_id
if previous_stage == stage_obj:
return JsonResponse({"type": "info", "message": _("Sequence updated.")})
# Here set the last updated schedule date on this stage if schedule exists in history
history_queryset = candidate_obj.candidate_history.filter(stage_id=stage_obj)
schedule_date = None
if history_queryset.exists():
# this condition is executed when a candidate dropped back to any previous
# stage, if there any scheduled date then set it back
schedule_date = history_queryset.first().schedule_date
stage_manager_on_this_recruitment = (
is_stagemanager(request)[1]
.filter(recruitment_id=stage_obj.recruitment_id)
.exists()
)
if (
stage_manager_on_this_recruitment
or request.user.is_superuser
or is_recruitmentmanager(rec_id=stage_obj.recruitment_id.id)[0]
):
candidate_obj.stage_id = stage_obj
candidate_obj.schedule_date = None
candidate_obj.hired = stage_obj.stage_type == "hired"
candidate_obj.schedule_date = schedule_date
candidate_obj.start_onboard = False
candidate_obj.save()
with contextlib.suppress(Exception):
managers = stage_obj.stage_managers.select_related("employee_user_id")
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"New candidate arrived on stage {stage_obj.stage}",
verb_ar=f"وصل مرشح جديد إلى المرحلة {stage_obj.stage}",
verb_de=f"Neuer Kandidat ist auf der Stufe {stage_obj.stage} angekommen",
verb_es=f"Nuevo candidato llegó a la etapa {stage_obj.stage}",
verb_fr=f"Nouveau candidat arrivé à l'étape {stage_obj.stage}",
icon="person-add",
redirect=reverse("pipeline"),
)
return JsonResponse(
{"type": "success", "message": _("Candidate stage updated")}
)
return JsonResponse(
{"type": "danger", "message": _("Something went wrong, Try agian.")}
)
@login_required
@hx_request_required
@manager_can_enter(perm="recruitment.add_stagenote")
def add_note(request, cand_id=None):
"""
This method renders template component to add candidate remark
"""
form = StageNoteForm(initial={"candidate_id": cand_id})
if request.method == "POST":
form = StageNoteForm(
request.POST,
)
if form.is_valid():
note = form.save(commit=False)
note.stage_id = note.candidate_id.stage_id
note.updated_by = request.user.employee_get
note.save()
messages.success(request, _("Note added successfully.."))
response = render(
request, "pipeline/pipeline_components/add_note.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(
request,
"pipeline/pipeline_components/add_note.html",
{
"note_form": form,
},
)
@login_required
@hx_request_required
@manager_can_enter(perm="recruitment.view_stagenote")
def view_note(request, cand_id):
"""
This method renders a template components to view candidate remark or note
Args:
id : candidate instance id
"""
candidate_obj = Candidate.objects.get(id=cand_id)
return render(
request, "pipeline/pipeline_components/view_note.html", {"cand": candidate_obj}
)
@login_required
@permission_required(perm="recruitment.change_stagenote")
def note_update(request, note_id):
"""
This method is used to update the stage not
Args:
id : stage note instance id
"""
note = StageNote.objects.get(id=note_id)
form = StageNoteForm(instance=note)
if request.POST:
form = StageNoteForm(request.POST, instance=note)
if form.is_valid():
form.save()
messages.success(request, _("Note updated successfully..."))
response = render(
request, "pipeline/pipeline_components/update_note.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(
request, "pipeline/pipeline_components/update_note.html", {"form": form}
)
@login_required
@permission_required(perm="recruitment.delete_stagenote")
def note_delete(request, note_id):
"""
This method is used to delete the stage note
"""
note = StageNote.objects.get(id=note_id)
candidate_obj = note.candidate_id
try:
note.delete()
messages.success(request, _("Note deleted"))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this note."))
return render(
request, "pipeline/pipeline_components/view_note.html", {"cand": candidate_obj}
)
@login_required
@require_http_methods(["DELETE"])
@permission_required(perm="recruitment.delete_stagenote")
def candidate_remark_delete(request, note_id):
"""
This method is used to delete the candidate stage note
Args:
id : stage note instance id
"""
stage_note = StageNote.objects.get(id=note_id)
candidate_obj = stage_note.candidate_note_id.candidate_id
try:
stage_note.delete()
messages.success(request, _("Note deleted"))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this note."))
return render(
request,
"pipeline/pipeline_components/candidate_remark_view.html",
{"cand": candidate_obj},
)
@login_required
@permission_required(perm="recruitment.change_candidate")
def candidate_schedule_date_update(request):
"""
This is a an ajax method to update schedule date for a candidate
"""
candidate_id = request.POST["candidateId"]
schedule_date = request.POST["date"]
candidate_obj = Candidate.objects.get(id=candidate_id)
candidate_obj.schedule_date = schedule_date
candidate_obj.save()
return JsonResponse({"message": "congratulations"})
@login_required
@permission_required(perm="recruitment.add_stage")
def stage(request):
"""
This method is used to create stages, also several permission assigned to the stage managers
"""
form = StageCreationForm()
if request.method == "POST":
form = StageCreationForm(request.POST)
if form.is_valid():
stage_obj = form.save()
recruitment_obj = stage_obj.recruitment_id
rec_stages = (
Stage.objects.filter(recruitment_id=recruitment_obj, is_active=True)
.order_by("sequence")
.last()
)
if rec_stages.sequence is None:
stage_obj.sequence = 1
else:
stage_obj.sequence = rec_stages.sequence + 1
stage_obj.save()
messages.success(request, _("Stage added."))
with contextlib.suppress(Exception):
managers = stage_obj.stage_managers.select_related("employee_user_id")
users = [employee.employee_user_id for employee in managers]
notify.send(
request.user.employee_get,
recipient=users,
verb=f"Stage {stage_obj} is updated on recruitment {stage_obj.recruitment_id}, You are chosen as one of the managers",
verb_ar=f"تم تحديث المرحلة {stage_obj} في التوظيف {stage_obj.recruitment_id}، تم اختيارك كأحد المديرين",
verb_de=f"Stufe {stage_obj} wurde in der Rekrutierung {stage_obj.recruitment_id} aktualisiert. Sie wurden als einer der Manager ausgewählt",
verb_es=f"La etapa {stage_obj} ha sido actualizada en la contratación {stage_obj.recruitment_id}. Has sido elegido/a como uno de los gerentes",
verb_fr=f"L'étape {stage_obj} a été mise à jour dans le recrutement {stage_obj.recruitment_id}. Vous avez été choisi(e) comme l'un des responsables",
icon="people-circle",
redirect=reverse("pipeline"),
)
response = render(request, "stage/stage_form.html", {"form": form})
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "stage/stage_form.html", {"form": form})
@login_required
@permission_required(perm="recruitment.view_stage")
def stage_view(request):
"""
This method is used to render all stages to a template
"""
stages = Stage.objects.filter()
filter_obj = StageFilter()
form = StageCreationForm()
return render(
request,
"stage/stage_view.html",
{
"data": paginator_qry(stages, request.GET.get("page")),
"form": form,
"f": filter_obj,
},
)
@login_required
@permission_required(perm="recruitment.view_stage")
def stage_search(request):
"""
This method is used to search stage
"""
stages = StageFilter(request.GET).qs
previous_data = request.environ["QUERY_STRING"]
stages = sortby(request, stages, "orderby")
return render(
request,
"stage/stage_component.html",
{"data": paginator_qry(stages, request.GET.get("page")), "pd": previous_data},
)
@login_required
@permission_required(perm="recruitment.change_stage")
def remove_stage_manager(request, mid, sid):
"""
This method is used to remove selected stage manager and also removing the given
permission if the employee is not exists in more stage manager or recruitment manager
Args:
mid : manager_id in the stage
sid : stage_id
"""
stage_obj = Stage.objects.get(id=sid)
manager = Employee.objects.get(id=mid)
notify.send(
request.user.employee_get,
recipient=manager.employee_user_id,
verb=f"You are removed from stage managers from stage {stage_obj}",
verb_ar=f"تمت إزالتك من مديري المرحلة من المرحلة {stage_obj}",
verb_de=f"Sie wurden als Bühnenmanager von der Stufe {stage_obj} entfernt",
verb_es=f"Has sido eliminado/a de los gerentes de etapa de la etapa {stage_obj}",
verb_fr=f"Vous avez été supprimé(e) en tant que responsable de l'étape {stage_obj}",
icon="person-remove",
redirect="",
)
stage_obj.stage_managers.remove(manager)
messages.success(request, _("Stage manager removed successfully."))
stages = Stage.objects.all()
previous_data = request.environ["QUERY_STRING"]
return render(
request,
"stage/stage_component.html",
{"data": paginator_qry(stages, request.GET.get("page")), "pd": previous_data},
)
@login_required
@permission_required(perm="recruitment.change_stage")
@hx_request_required
def stage_update(request, stage_id):
"""
This method is used to update stage, if the managers changed then\
permission assigned to new managers also
Args:
id : stage_id
"""
stages = Stage.objects.get(id=stage_id)
form = StageCreationForm(instance=stages)
if request.method == "POST":
form = StageCreationForm(request.POST, instance=stages)
if form.is_valid():
form.save()
messages.success(request, _("Stage updated."))
response = render(
request, "recruitment/recruitment_form.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "stage/stage_update_form.html", {"form": form})
@login_required
@require_http_methods(["POST"])
@hx_request_required
def stage_name_update(request, stage_id):
"""
This method is used to update the name of recruitment stage
"""
stage_obj = Stage.objects.get(id=stage_id)
stage_obj.stage = request.POST["stage"]
stage_obj.save()
return HttpResponse({"message": "success"})
@login_required
@permission_required(perm="recruitment.delete_stage")
@require_http_methods(["POST", "DELETE"])
def stage_delete(request, stage_id):
"""
This method is used to delete stage permanently
Args:
id : stage_id
"""
stage_obj = Stage.objects.get(id=stage_id)
stage_managers = stage_obj.stage_managers.all()
for manager in stage_managers:
all_this_manger = manager.stage_set.all()
if len(all_this_manger) == 1:
view_recruitment = Permission.objects.get(codename="view_recruitment")
manager.employee_user_id.user_permissions.remove(view_recruitment.id)
initial_stage_manager = all_this_manger.filter(stage_type="initial")
if len(initial_stage_manager) == 1:
add_candidate = Permission.objects.get(codename="add_candidate")
change_candidate = Permission.objects.get(codename="change_candidate")
manager.employee_user_id.user_permissions.remove(add_candidate.id)
manager.employee_user_id.user_permissions.remove(change_candidate.id)
stage_obj.stage_managers.remove(manager)
try:
stage_obj.delete()
messages.success(request, _("Stage deleted successfully."))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this stage"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@permission_required(perm="recruitment.add_candidate")
def candidate(request):
"""
This method used to create candidate
"""
form = CandidateCreationForm()
open_recruitment = Recruitment.objects.filter(closed=False, is_active=True)
path = "/recruitment/candidate-view"
if request.method == "POST":
form = CandidateCreationForm(request.POST, request.FILES)
if form.is_valid():
candidate_obj = form.save(commit=False)
candidate_obj.start_onboard = False
if candidate_obj.stage_id is None:
candidate_obj.stage_id = Stage.objects.filter(
recruitment_id=candidate_obj.recruitment_id, stage_type="initial"
).first()
# when creating new candidate from onboarding view
if request.GET.get("onboarding") == "True":
candidate_obj.hired = True
path = "/onboarding/candidates-view"
candidate_obj.save()
messages.success(request, _("Candidate added."))
return redirect(path)
return render(
request,
"candidate/candidate_create_form.html",
{"form": form, "open_recruitment": open_recruitment},
)
@login_required
@permission_required(perm="recruitment.add_candidate")
def recruitment_stage_get(_, rec_id):
"""
This method returns all stages as json
Args:
id: recruitment_id
"""
recruitment_obj = Recruitment.objects.get(id=rec_id)
all_stages = recruitment_obj.stage_set.all()
all_stage_json = serializers.serialize("json", all_stages)
return JsonResponse({"stages": all_stage_json})
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_view(request):
"""
This method render all candidate to the template
"""
previous_data = request.environ["QUERY_STRING"]
candidates = Candidate.objects.filter(is_active=True)
filter_obj = CandidateFilter(queryset=candidates)
return render(
request,
"candidate/candidate_view.html",
{
"data": paginator_qry(filter_obj.qs, request.GET.get("page")),
"pd": previous_data,
"f": filter_obj,
},
)
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_filter_view(request):
"""
This method is used for filter,pagination and search candidate.
"""
previous_data = request.environ["QUERY_STRING"]
filter_obj = CandidateFilter(
request.GET, queryset=Candidate.objects.filter(is_active=True)
)
paginator = Paginator(filter_obj.qs, 24)
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
return render(
request,
"candidate/candidate_card.html",
{"data": page_obj, "pd": previous_data},
)
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_search(request):
"""
This method is used to search candidate model and return matching objects
"""
previous_data = request.environ["QUERY_STRING"]
search = request.GET.get("search")
if search is None:
search = ""
candidates = Candidate.objects.filter(name__icontains=search)
candidates = CandidateFilter(request.GET, queryset=candidates).qs
template = "candidate/candidate_card.html"
if request.GET.get("view") == "list":
template = "candidate/candidate_list.html"
candidates = sortby(request, candidates, "orderby")
candidates = paginator_qry(candidates, request.GET.get("page"))
return render(request, template, {"data": candidates, "pd": previous_data})
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_view_list(request):
"""
This method renders all candidate on candidate_list.html template
"""
previous_data = request.environ["QUERY_STRING"]
candidates = Candidate.objects.all()
if request.GET.get("is_active") is None:
candidates = candidates.filter(is_active=True)
candidates = CandidateFilter(request.GET, queryset=candidates).qs
return render(
request,
"candidate/candidate_list.html",
{
"data": paginator_qry(candidates, request.GET.get("page")),
"pd": previous_data,
},
)
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_view_card(request):
"""
This method renders all candidate on candidate_card.html template
"""
previous_data = request.environ["QUERY_STRING"]
candidates = Candidate.objects.all()
if request.GET.get("is_active") is None:
candidates = candidates.filter(is_active=True)
candidates = CandidateFilter(request.GET, queryset=candidates).qs
return render(
request,
"candidate/candidate_card.html",
{
"data": paginator_qry(candidates, request.GET.get("page")),
"pd": previous_data,
},
)
@login_required
@permission_required(perm="recruitment.view_candidate")
def candidate_view_individual(request, cand_id):
"""
This method is used to view profile of candidate.
"""
candidate_obj = Candidate.objects.get(id=cand_id)
return render(request, "candidate/individual.html", {"candidate": candidate_obj})
@login_required
@manager_can_enter(perm="recruitment.change_candidate")
def candidate_update(request, cand_id):
"""
Used to update or change the candidate
Args:
id : candidate_id
"""
candidate_obj = Candidate.objects.get(id=cand_id)
form = CandidateCreationForm(instance=candidate_obj)
path = "/recruitment/candidate-view"
if request.method == "POST":
form = CandidateCreationForm(
request.POST, request.FILES, instance=candidate_obj
)
if form.is_valid():
candidate_obj = form.save()
if candidate_obj.stage_id is None:
candidate_obj.stage_id = Stage.objects.filter(
recruitment_id=candidate_obj.recruitment_id, stage_type="initial"
).first()
if candidate_obj.stage_id is not None:
if (
candidate_obj.stage_id.recruitment_id
!= candidate_obj.recruitment_id
):
candidate_obj.stage_id = (
candidate_obj.recruitment_id.stage_set.filter(
stage_type="initial"
).first()
)
if request.GET.get("onboarding") == "True":
candidate_obj.hired = True
path = "/onboarding/candidates-view"
candidate_obj.save()
messages.success(request, _("Candidate Updated Successfully."))
return redirect(path)
return render(request, "candidate/candidate_create_form.html", {"form": form})
@login_required
@permission_required(perm="recruitment.delete_candidate")
@require_http_methods(["DELETE", "POST"])
def candidate_delete(request, cand_id):
"""
This method is used to delete candidate permanently
Args:
id : candidate_id
"""
try:
Candidate.objects.get(id=cand_id).delete()
messages.success(request, _("Candidate deleted successfully."))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this candidate"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@permission_required(perm="recruitment.delete_candidate")
def candidate_archive(request, cand_id):
"""
This method is used to archive or un-archive candidates
"""
candidate_obj = Candidate.objects.get(id=cand_id)
candidate_obj.is_active = not candidate_obj.is_active
candidate_obj.save()
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@permission_required(perm="recruitment.delete_candidate")
@require_http_methods(["POST"])
def candidate_bulk_delete(request):
"""
This method is used to bulk delete candidates
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for cand_id in ids:
candidate_obj = Candidate.objects.get(id=cand_id)
try:
candidate_obj.delete()
messages.success(
request, _("%(candidate_obj)s deleted.") % {"candidate": candidate_obj}
)
except Exception as error:
messages.error(
request,
_("You cannot delete %(candidate_obj)s") % {"candidate": candidate_obj},
)
messages.error(request, error)
return JsonResponse({"message": "Success"})
@login_required
@permission_required(perm="recruitment.delete_candidate")
@require_http_methods(["POST"])
def candidate_bulk_archive(request):
"""
This method is used to archive/un-archive bulk candidates
"""
ids = request.POST["ids"]
ids = json.loads(ids)
is_active = True
message = "un-archived"
if request.GET.get("is_active") == "False":
is_active = False
message = "archived"
for cand_id in ids:
candidate_obj = Candidate.objects.get(id=cand_id)
candidate_obj.is_active = is_active
candidate_obj.save()
messages.success(request, f"{candidate_obj} is {message}")
return JsonResponse({"message": "Success"})
@login_required
@permission_required(perm="recruitment.view_history")
def candidate_history(request, cand_id):
"""
This method is used to view candidate stage changes
Args:
id : candidate_id
"""
candidate_obj = Candidate.objects.get(id=cand_id)
candidate_history_queryset = candidate_obj.history.all()
return render(
request,
"candidate/candidate_history.html",
{"history": candidate_history_queryset},
)
def application_form(request):
"""
This method renders candidate form to create candidate
"""
form = ApplicationForm()
if request.POST:
form = ApplicationForm(request.POST, request.FILES)
if form.is_valid():
candidate_obj = form.save(commit=False)
recruitment_obj = candidate_obj.recruitment_id
stages = recruitment_obj.stage_set.all()
if stages.filter(stage_type="applied").exists():
candidate_obj.stage_id = stages.filter(stage_type="applied").first()
else:
candidate_obj.stage_id = stages.order_by("sequence").first()
candidate_obj.save()
messages.success(request, _("Application saved."))
return render(request, "candidate/success.html")
form.fields["job_position_id"].queryset = (
form.instance.recruitment_id.open_positions.all()
)
return render(request, "candidate/application_form.html", {"form": form})
@login_required
@hx_request_required
@manager_can_enter(perm="recruitment.change_candidate")
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)
return render(
request, "pipeline/pipeline_components/send_mail.html", {"cand": candidate_obj}
)
@login_required
@manager_can_enter(perm="recruitment.change_candidate")
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")
email_backend = ConfiguredEmailBackend()
display_email_name = email_backend.dynamic_from_email_with_display_name
if request:
try:
display_email_name = f"{request.user.employee_get.get_full_name()} <{request.user.employee_get.email}>"
except:
logger.error(Exception)
res = send_mail(
subject, bdy, display_email_name, [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>
"""
)
@login_required
@manager_can_enter(perm="recruitment.view_recruitment")
def dashboard(request):
"""
This method is used to render individual dashboard for recruitment module
"""
candidates = Candidate.objects.all()
hired_candidates = candidates.filter(hired=True)
total_candidates = len(candidates)
total_hired_candidates = len(hired_candidates)
hire_ratio = 0
if total_candidates != 0:
hire_ratio = f"{((total_hired_candidates / total_candidates) * 100):.1f}"
return render(
request,
"dashboard/dashboard.html",
{
"total_candidates": total_candidates,
"total_hired_candidates": total_hired_candidates,
"hire_ratio": hire_ratio,
"onboard_candidates": hired_candidates.filter(start_onboard=True),
},
)
def stage_type_candidate_count(rec, stage_type):
"""
This method is used find the count of candidate in recruitment
"""
candidates_count = 0
for stage_obj in rec.stage_set.filter(stage_type=stage_type):
candidates_count = candidates_count + len(
stage_obj.candidate_set.filter(is_active=True)
)
return candidates_count
@login_required
@manager_can_enter(perm="recruitment.view_recruitment")
def dashboard_pipeline(_):
"""
This method is used generate recruitment dataset for the dashboard
"""
recruitment_obj = Recruitment.objects.filter(closed=False)
data_set = []
labels = [type[1] for type in Stage.stage_types]
for rec in recruitment_obj:
data = [stage_type_candidate_count(rec, type[0]) for type in Stage.stage_types]
data_set.append(
{
"label": (
rec.title
if rec.title is not None
else f"""{rec.job_position_id}
{rec.start_date}"""
),
"data": data,
}
)
return JsonResponse({"dataSet": data_set, "labels": labels})
def get_open_position(request):
"""
This is an ajax method to render the open position to the recruitment
Returns:
obj: it returns the list of job positions
"""
rec_id = request.GET["recId"]
recruitment_obj = Recruitment.objects.get(id=rec_id)
queryset = recruitment_obj.open_positions.all()
job_info = serializers.serialize("json", queryset)
rec_info = serializers.serialize("json", [recruitment_obj])
return JsonResponse({"openPositions": job_info, "recruitmentInfo": rec_info})
@login_required
@manager_can_enter("recruitment.change_candidate")
def candidate_sequence_update(request):
"""
This method is used to update the sequence of candidate
"""
sequence_data = json.loads(request.POST["sequenceData"])
for cand_id, seq in sequence_data.items():
cand = Candidate.objects.get(id=cand_id)
cand.sequence = seq
cand.save()
return JsonResponse({"message": "Sequence updated", "type": "info"})
@login_required
@recruitment_manager_can_enter("recruitment.change_stage")
def stage_sequence_update(request):
"""
This method is used to update the sequence of the stages
"""
sequence_data = json.loads(request.POST["sequence"])
for stage_id, seq in sequence_data.items():
stage = Stage.objects.get(id=stage_id)
stage.sequence = seq
stage.save()
return JsonResponse({"type": "success", "message": "Stage sequence updated"})