Skip to content

Commit

Permalink
wip to reuse wizardview
Browse files Browse the repository at this point in the history
  • Loading branch information
xavfernandez committed Dec 18, 2024
1 parent 42a33dd commit 81e4c07
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 24 deletions.
5 changes: 5 additions & 0 deletions itou/www/apply/views/batch_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from itou.utils.perms.company import get_current_company_or_404
from itou.utils.urls import get_safe_url
from itou.www.apply.forms import BatchPostponeForm
from itou.www.apply.views.process_views import JobApplicationRefuseView


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -166,3 +167,7 @@ def transfer(request):
",".join(str(app_uid) for app_uid in transfered_ids),
)
return HttpResponseRedirect(next_url)

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.


class BatchRefuseView(JobApplicationRefuseView):
pass
136 changes: 135 additions & 1 deletion itou/www/apply/views/common.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.forms import ValidationError
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import get_object_or_404, render
from django.template import loader
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils import timezone
from django.utils.safestring import mark_safe
from django_htmx.http import HttpResponseClientRedirect
from django_xworkflows import models as xwf_models
from formtools.wizard.views import NamedUrlSessionWizardView

from itou.common_apps.address.forms import JobSeekerAddressForm
from itou.companies.enums import CompanyKind, ContractType
from itou.eligibility.models import EligibilityDiagnosis
from itou.eligibility.models.geiq import GEIQEligibilityDiagnosis
from itou.eligibility.utils import geiq_allowance_amount
from itou.job_applications import enums as job_applications_enums
from itou.job_applications.models import JobApplication, JobApplicationWorkflow
from itou.users.enums import UserKind
from itou.utils import constants as global_constants
from itou.utils.htmx import hx_trigger_modal_control
Expand All @@ -25,6 +30,9 @@
AcceptForm,
CertifiedCriteriaInfoRequiredForm,
CheckJobSeekerGEIQEligibilityForm,
JobApplicationRefusalJobSeekerAnswerForm,
JobApplicationRefusalPrescriberAnswerForm,
JobApplicationRefusalReasonForm,
JobSeekerPersonalDataForm,
)
from itou.www.eligibility_views.forms import AdministrativeCriteriaForm
Expand Down Expand Up @@ -291,3 +299,129 @@ def _geiq_eligibility_criteria(
context["job_seeker"] = job_seeker

return render(request, template_name, context)


def _show_prescriber_answer_form(wizard):
return wizard.job_application.sender_kind == job_applications_enums.SenderKind.PRESCRIBER


class RefuseSessionWizardView(LoginRequiredMixin, NamedUrlSessionWizardView):
STEP_REASON = "reason"
STEP_JOB_SEEKER_ANSWER = "job-seeker-answer"
STEP_PRESCRIBER_ANSWER = "prescriber-answer"

template_name = "apply/process_refuse.html"
form_list = [
(STEP_REASON, JobApplicationRefusalReasonForm),
(STEP_JOB_SEEKER_ANSWER, JobApplicationRefusalJobSeekerAnswerForm),
(STEP_PRESCRIBER_ANSWER, JobApplicationRefusalPrescriberAnswerForm),
]
condition_dict = {
STEP_PRESCRIBER_ANSWER: _show_prescriber_answer_form,
}

def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)

if request.user.is_authenticated:
self.job_application = get_object_or_404(
JobApplication.objects.is_active_company_member(request.user).select_related("job_seeker"),
pk=kwargs["job_application_id"],
)

def check_wizard_state(self, *args, **kwargs):
# Redirect to job application details if the state is not refusable
if self.job_application.state not in JobApplicationWorkflow.CAN_BE_REFUSED_STATES:
message = "Action déjà effectuée." if self.job_application.state.is_refused else "Action impossible."
messages.error(self.request, message, extra_tags="toast")
return HttpResponseRedirect(
reverse("apply:details_for_company", kwargs={"job_application_id": self.job_application.pk})
)

# Redirect to first step if form data is not retrieved in session (eg. direct url access)
if kwargs.get("step") in [
self.STEP_JOB_SEEKER_ANSWER,
self.STEP_PRESCRIBER_ANSWER,
] and not self.get_cleaned_data_for_step(self.STEP_REASON):
return HttpResponseRedirect(self.get_step_url(self.STEP_REASON))

def get(self, *args, **kwargs):
if check_response := self.check_wizard_state(*args, **kwargs):
return check_response
return super().get(*args, **kwargs)

def post(self, *args, **kwargs):
if check_response := self.check_wizard_state(*args, **kwargs):
return check_response
return super().post(*args, **kwargs)

def done(self, form_list, *args, **kwargs):
try:
# After each successful transition, a save() is performed by django-xworkflows.
cleaned_data = self.get_all_cleaned_data()
self.job_application.refusal_reason = cleaned_data["refusal_reason"]
self.job_application.refusal_reason_shared_with_job_seeker = cleaned_data[
"refusal_reason_shared_with_job_seeker"
]
self.job_application.answer = cleaned_data["job_seeker_answer"]
self.job_application.answer_to_prescriber = cleaned_data.get("prescriber_answer", "")
self.job_application.refuse(user=self.request.user)
messages.success(
self.request,
f"La candidature de {self.job_application.job_seeker.get_full_name()} a bien été déclinée.",
extra_tags="toast",
)
except xwf_models.InvalidTransitionError:
messages.error(self.request, "Action déjà effectuée.", extra_tags="toast")

next_url = reverse("apply:details_for_company", kwargs={"job_application_id": self.job_application.pk})
return HttpResponseRedirect(next_url)

def get_prefix(self, request, *args, **kwargs):
"""
Ensure that each refuse session is bound to a job application.
Avoid session conflicts when using multiple tabs.
"""
return f"job_application_{self.job_application.pk}_refuse"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.steps.current != self.STEP_REASON:
cleaned_data = self.get_cleaned_data_for_step(self.STEP_REASON)
context["refusal_reason_label"] = job_applications_enums.RefusalReason(
cleaned_data["refusal_reason"]
).label
context["refusal_reason_shared_with_job_seeker"] = cleaned_data["refusal_reason_shared_with_job_seeker"]
return context | {
"job_application": self.job_application,
"can_view_personal_information": True, # SIAE members have access to personal info
"matomo_custom_title": "Candidature refusée",
"matomo_event_name": f"refuse-application-{self.steps.current}-submit",
"primary_button_label": "Suivant" if context["wizard"]["steps"].next else "Confirmer le refus",
"secondary_button_label": "Précédent" if context["wizard"]["steps"].prev else "Annuler",
}

def get_form_kwargs(self, step=None):
if step in (self.STEP_REASON, self.STEP_PRESCRIBER_ANSWER):
return {
"job_application": self.job_application,
}
return {}

def get_form_initial(self, step):
initial_data = self.initial_dict.get(step, {})
if step == self.STEP_JOB_SEEKER_ANSWER:
refusal_reason = self.get_cleaned_data_for_step(self.STEP_REASON).get("refusal_reason")
if refusal_reason:
initial_data["job_seeker_answer"] = loader.render_to_string(
f"apply/refusal_messages/{refusal_reason}.txt",
context={
"job_application": self.job_application,
},
request=self.request,
)

return initial_data

def get_step_url(self, step):
return reverse(f"apply:{self.url_name}", kwargs={"job_application_id": self.job_application.pk, "step": step})
24 changes: 1 addition & 23 deletions itou/www/apply/views/process_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from django.views.decorators.http import require_POST
from django.views.generic.base import TemplateView
from django_xworkflows import models as xwf_models
from formtools.wizard.views import NamedUrlSessionWizardView

from itou.companies.enums import CompanyKind, ContractType
from itou.companies.models import Company
Expand All @@ -35,9 +34,6 @@
from itou.www.apply.forms import (
AcceptForm,
AnswerForm,
JobApplicationRefusalJobSeekerAnswerForm,
JobApplicationRefusalPrescriberAnswerForm,
JobApplicationRefusalReasonForm,
PriorActionForm,
TransferJobApplicationForm,
)
Expand Down Expand Up @@ -371,25 +367,7 @@ def process(request, job_application_id):
return HttpResponseRedirect(next_url)


def _show_prescriber_answer_form(wizard):
return wizard.job_application.sender_kind == job_applications_enums.SenderKind.PRESCRIBER


class JobApplicationRefuseView(LoginRequiredMixin, NamedUrlSessionWizardView):
STEP_REASON = "reason"
STEP_JOB_SEEKER_ANSWER = "job-seeker-answer"
STEP_PRESCRIBER_ANSWER = "prescriber-answer"

template_name = "apply/process_refuse.html"
form_list = [
(STEP_REASON, JobApplicationRefusalReasonForm),
(STEP_JOB_SEEKER_ANSWER, JobApplicationRefusalJobSeekerAnswerForm),
(STEP_PRESCRIBER_ANSWER, JobApplicationRefusalPrescriberAnswerForm),
]
condition_dict = {
STEP_PRESCRIBER_ANSWER: _show_prescriber_answer_form,
}

class JobApplicationRefuseView(common_views.RefuseSessionWizardView):
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)

Expand Down

0 comments on commit 81e4c07

Please sign in to comment.