def edit_user_preferences(request, template_name="dashboard/edit_user_preferences.html"): if not request.user.is_siae_staff: raise PermissionDenied current_siae_pk = request.session.get(settings.ITOU_SESSION_CURRENT_SIAE_KEY) siae = get_object_or_404(Siae, pk=current_siae_pk) membership = request.user.siaemembership_set.get(siae=siae) new_job_app_notification_form = EditNewJobAppEmployersNotificationForm( recipient=membership, siae=siae, data=request.POST or None ) dashboard_url = reverse_lazy("dashboard:index") back_url = get_safe_url(request, "back_url", fallback_url=dashboard_url) if request.method == "POST" and new_job_app_notification_form.is_valid(): new_job_app_notification_form.save() messages.success(request, "Vos préférences ont été modifiées.") success_url = get_safe_url(request, "success_url", fallback_url=dashboard_url) return HttpResponseRedirect(success_url) context = { "new_job_app_notification_form": new_job_app_notification_form, "back_url": back_url, } return render(request, template_name, context)
def test_get_safe_url(self): """Test `urls.get_safe_url()`.""" request = RequestFactory().get( "/?next=/siae/search%3Fdistance%3D100%26city%3Dstrasbourg-67" ) url = get_safe_url(request, "next") expected = "/siae/search?distance=100&city=strasbourg-67" self.assertEqual(url, expected) request = RequestFactory().post( "/", data={"next": "/siae/search?distance=100&city=strasbourg-67"} ) url = get_safe_url(request, "next") expected = "/siae/search?distance=100&city=strasbourg-67" self.assertEqual(url, expected) request = RequestFactory().get("/?next=https://evil.com/siae/search") url = get_safe_url(request, "next") expected = None self.assertEqual(url, expected) request = RequestFactory().post("/", data={"next": "https://evil.com"}) url = get_safe_url(request, "next", fallback_url="/fallback") expected = "/fallback" self.assertEqual(url, expected)
def suspension_update(request, suspension_id, template_name="approvals/suspension_update.html"): """ Edit the given suspension. """ siae = get_current_siae_or_404(request) suspension = get_object_or_404(Suspension, pk=suspension_id) if not suspension.can_be_handled_by_siae(siae): raise PermissionDenied() back_url = get_safe_url(request, "back_url", fallback_url=reverse("dashboard:index")) form = SuspensionForm(approval=suspension.approval, siae=siae, instance=suspension, data=request.POST or None) if request.method == "POST" and form.is_valid(): suspension = form.save(commit=False) suspension.updated_by = request.user suspension.save() messages.success(request, "Modification de suspension effectuée.") return HttpResponseRedirect(back_url) context = { "suspension": suspension, "back_url": back_url, "form": form, } return render(request, template_name, context)
def job_seeker_nir(request, template_name="signup/job_seeker_nir.html", redirect_field_name=REDIRECT_FIELD_NAME): form = forms.JobSeekerNirForm(data=request.POST or None) if request.method == "POST": next_url = reverse("signup:job_seeker") if form.is_valid(): request.session[ settings.ITOU_SESSION_NIR_KEY] = form.cleaned_data["nir"] # forward next page if redirect_field_name in form.data: next_url = f"{next_url}?{redirect_field_name}={form.data[redirect_field_name]}" return HttpResponseRedirect(next_url) if form.data.get("skip"): return HttpResponseRedirect(next_url) context = { "form": form, "redirect_field_name": redirect_field_name, "redirect_field_value": get_safe_url(request, redirect_field_name), } return render(request, template_name, context)
def job_seeker_situation(request, template_name="signup/job_seeker_situation.html", redirect_field_name=REDIRECT_FIELD_NAME): """ Second step of the signup process for jobseeker. The user is asked to choose at least one eligibility criterion to continue the signup process. """ form = forms.JobSeekerSituationForm(data=request.POST or None) if request.method == "POST" and form.is_valid(): next_url = reverse("signup:job_seeker_situation_not_eligible") # If at least one of the eligibility choices is selected, go to the signup form. if any(choice in forms.JobSeekerSituationForm.ELIGIBLE_SITUATION for choice in form.cleaned_data["situation"]): next_url = reverse("signup:job_seeker_nir") # forward next page if redirect_field_name in form.data: next_url = f"{next_url}?{redirect_field_name}={form.data[redirect_field_name]}" return HttpResponseRedirect(next_url) context = { "form": form, "redirect_field_name": redirect_field_name, "redirect_field_value": get_safe_url(request, redirect_field_name), } return render(request, template_name, context)
def card(request, org_id, template_name="prescribers/card.html"): """ Prescriber organization's card (or "Fiche" in French). """ prescriber_org = get_object_or_404(PrescriberOrganization, pk=org_id, is_authorized=True) back_url = get_safe_url(request, "back_url") context = {"prescriber_org": prescriber_org, "back_url": back_url} return render(request, template_name, context)
def get_email_confirmation_redirect_url(self, request): """ Redirection performed after a user confirmed its email address. """ next_url = request.POST.get("next") or request.GET.get("next") url = super().get_email_confirmation_redirect_url(request) if next_url: url = get_safe_url(request, "next") return url
def new_user(request, invitation_type, invitation_id, template_name="invitations_views/new_user.html"): invitation_type = InvitationAbstract.get_model_from_string(invitation_type) invitation = get_object_or_404(invitation_type, pk=invitation_id) context = {"invitation": invitation} next_step = None if request.user.is_authenticated: if not request.user.email == invitation.email: message = ( "Un utilisateur est déjà connecté.<br>" "Merci de déconnecter ce compte en cliquant sur le bouton ci-dessous. " "La page d'accueil se chargera automatiquement, n'en tenez pas compte.<br>" "Retournez dans votre boite mail et cliquez de nouveau sur le lien " "reçu pour accepter l'invitation.") message = safestring.mark_safe(message) messages.error(request, message) return redirect("account_logout") if invitation.can_be_accepted: user = User.objects.filter(email__iexact=invitation.email) if user: # The user exists but he should log in first next_step_url = "{url}?account_type={account_type}&next={redirect_to}".format( url=reverse("account_login"), account_type=invitation.SIGNIN_ACCOUNT_TYPE, redirect_to=get_safe_url(request, "redirect_to"), ) next_step = redirect(next_step_url) else: # A new user should be created before joining form = NewUserInvitationForm(data=request.POST or None, invitation=invitation) context["form"] = form if form.is_valid(): user = form.save(request) get_adapter().login(request, user) next_step = redirect(get_safe_url(request, "redirect_to")) else: messages.error(request, "Cette invitation n'est plus valide.") return next_step or render(request, template_name, context=context)
def card(request, siae_id, template_name="siaes/card.html"): """ SIAE's card (or "Fiche" in French). """ queryset = Siae.active_objects.prefetch_job_description_through( is_active=True) siae = get_object_or_404(queryset, pk=siae_id) back_url = get_safe_url(request, "back_url") context = {"siae": siae, "back_url": back_url} return render(request, template_name, context)
def signup(request, template_name="signup/signup.html", redirect_field_name="next"): """ Override allauth `account_signup` URL (the route is defined in config.urls). """ context = { "redirect_field_name": redirect_field_name, "redirect_field_value": get_safe_url(request, redirect_field_name), } return render(request, template_name, context)
def edit_user_info(request, template_name="dashboard/edit_user_info.html"): """ Edit a user. """ dashboard_url = reverse_lazy("dashboard:index") prev_url = get_safe_url(request, "prev_url", fallback_url=dashboard_url) form = EditUserInfoForm(instance=request.user, data=request.POST or None) if request.method == "POST" and form.is_valid(): form.save() messages.success(request, _("Mise à jour de vos informations effectuée !")) success_url = get_safe_url(request, "success_url", fallback_url=dashboard_url) return HttpResponseRedirect(success_url) context = {"form": form, "prev_url": prev_url} return render(request, template_name, context)
def card(request, siae_id, template_name="siaes/card.html"): """ SIAE's card (or "Fiche" in French). # COVID-19 "Operation ETTI". Public view (previously private, made public during COVID-19). """ queryset = Siae.objects.prefetch_job_description_through(is_active=True) siae = get_object_or_404(queryset, pk=siae_id) back_url = get_safe_url(request, "back_url") context = {"siae": siae, "back_url": back_url} return render(request, template_name, context)
def new_user(request, invitation_type, invitation_id, template_name="invitations_views/new_user.html"): invitation_type = InvitationAbstract.get_model_from_string(invitation_type) invitation = get_object_or_404(invitation_type, pk=invitation_id) context = {"invitation": invitation} next_step = None if request.user.is_authenticated: if not request.user.email == invitation.email: message = ( "Un utilisateur précédent est déjà connecté.<br>" "Veuillez déconnecter ce compte en cliquant sur le bouton ci-dessous " "(ne tenez pas compte de la page d'accueil qui se chargera automatiquement) " "puis cliquez de nouveau sur le lien envoyé par e-mail pour accepter votre invitation." ) message = safestring.mark_safe(message) messages.error(request, _(message)) return redirect("account_logout") if invitation.can_be_accepted: user = get_user_model().objects.filter(email=invitation.email) if not user: form = NewUserForm(data=request.POST or None, invitation=invitation) context["form"] = form if form.is_valid(): user = form.save(request) DefaultAccountAdapter().login(request, user) next_step = redirect(get_safe_url(request, "redirect_to")) else: next_step = "{}?account_type={}&next={}".format( reverse("account_login"), invitation.SIGNIN_ACCOUNT_TYPE, get_safe_url(request, "redirect_to")) next_step = redirect(next_step) return next_step or render(request, template_name, context=context)
def pe_approval_search_user(request, pe_approval_id, template_name="approvals/pe_approval_search_user.html"): """ 2nd step of the PoleEmploiApproval's conversion process. Search for a given user by email address. """ pe_approval = get_object_or_404(PoleEmploiApproval, pk=pe_approval_id) back_url = get_safe_url(request, "back_url", fallback_url=reverse("dashboard:index")) form = UserExistsForm(data=None) context = {"back_url": back_url, "form": form, "pe_approval": pe_approval} return render(request, template_name, context)
def job_description_card(request, job_description_id, template_name="siaes/job_description_card.html"): """ SIAE's job description card (or "Fiche" in French). """ job_description = get_object_or_404(SiaeJobDescription, pk=job_description_id) back_url = get_safe_url(request, "back_url") context = { "job": job_description, "siae": job_description.siae, "back_url": back_url, } return render(request, template_name, context)
def siae_select(request, template_name="signup/siae_select.html"): """ Entry point of the signup process for SIAEs which consists of 2 steps. The user is asked to select an SIAE based on a selection that match a given SIREN number. """ siaes_without_members = None siaes_with_members = None next_url = get_safe_url(request, "next") siren_form = forms.SiaeSearchBySirenForm(data=request.GET or None) siae_select_form = None # The SIREN, when available, is always passed in the querystring. if request.method in ["GET", "POST"] and siren_form.is_valid(): # Make sure to look only for active structures. siaes_for_siren = Siae.objects.active().filter( siret__startswith=siren_form.cleaned_data["siren"]) # A user cannot join structures that already have members. # Show these structures in the template to make that clear. siaes_with_members = siaes_for_siren.exclude(members=None) siaes_without_members = siaes_for_siren.filter(members=None) siae_select_form = forms.SiaeSelectForm(data=request.POST or None, siaes=siaes_without_members) if request.method == "POST" and siae_select_form and siae_select_form.is_valid( ): siae_selected = siae_select_form.cleaned_data["siaes"] siae_selected.new_signup_activation_email_to_official_contact( request).send() message = ( f"Nous venons d'envoyer un e-mail à l'adresse {siae_selected.obfuscated_auth_email} " f"pour continuer votre inscription. Veuillez consulter votre boite " f"de réception.") messages.success(request, message) return HttpResponseRedirect(next_url or "/") context = { "next_url": next_url, "siaes_without_members": siaes_without_members, "siaes_with_members": siaes_with_members, "siae_select_form": siae_select_form, "siren_form": siren_form, } return render(request, template_name, context)
def details_for_siae(request, job_application_id, template_name="apply/process_details_siae.html"): """ Detail of an application for an SIAE with the ability: - to update start date of a contract (provided given date is in the future), - to give an answer. """ queryset = ( JobApplication.objects.siae_member_required(request.user) .not_archived() .select_related( "job_seeker", "eligibility_diagnosis", "sender", "sender_siae", "sender_prescriber_organization", "to_siae", "approval", ) .prefetch_related("selected_jobs__appellation") ) job_application = get_object_or_404(queryset, id=job_application_id) transition_logs = job_application.logs.select_related("user").all().order_by("timestamp") approval_can_be_suspended_by_siae = job_application.approval and job_application.approval.can_be_suspended_by_siae( job_application.to_siae ) approval_can_be_prolonged_by_siae = job_application.approval and job_application.approval.can_be_prolonged_by_siae( job_application.to_siae ) expired_eligibility_diagnosis = EligibilityDiagnosis.objects.last_expired( job_seeker=job_application.job_seeker, for_siae=job_application.to_siae ) back_url = get_safe_url(request, "back_url", fallback_url=reverse_lazy("apply:list_for_siae")) context = { "approvals_wrapper": job_application.job_seeker.approvals_wrapper, "approval_can_be_suspended_by_siae": approval_can_be_suspended_by_siae, "approval_can_be_prolonged_by_siae": approval_can_be_prolonged_by_siae, "eligibility_diagnosis": job_application.get_eligibility_diagnosis(), "expired_eligibility_diagnosis": expired_eligibility_diagnosis, "job_application": job_application, "transition_logs": transition_logs, "back_url": back_url, } return render(request, template_name, context)
def declare_prolongation(request, approval_id, template_name="approvals/declare_prolongation.html"): """ Declare a prolongation for the given approval. """ siae = get_current_siae_or_404(request) approval = get_object_or_404(Approval, pk=approval_id) if not approval.can_be_prolonged_by_siae(siae): raise PermissionDenied() back_url = get_safe_url(request, "back_url", fallback_url=reverse("dashboard:index")) preview = False form = DeclareProlongationForm(approval=approval, siae=siae, data=request.POST or None) if request.method == "POST" and form.is_valid(): prolongation = form.save(commit=False) prolongation.created_by = request.user prolongation.declared_by = request.user prolongation.declared_by_siae = form.siae prolongation.validated_by = form.validated_by if request.POST.get("edit"): preview = False if request.POST.get("preview"): preview = True elif request.POST.get("save"): prolongation.save() if form.cleaned_data.get("email"): # Send an email w/o DB changes prolongation.notify_authorized_prescriber() messages.success(request, "Déclaration de prolongation enregistrée.") return HttpResponseRedirect(back_url) context = { "approval": approval, "back_url": back_url, "form": form, "preview": preview, } return render(request, template_name, context)
def step_application_sent( request, siae_pk, template_name="apply/submit_step_application_sent.html"): if request.user.is_siae_staff: dashboard_url = reverse("apply:list_for_siae") messages.success(request, "Candidature bien envoyée !") return HttpResponseRedirect(dashboard_url) session_data = request.session[settings.ITOU_SESSION_JOB_APPLICATION_KEY] back_url = get_safe_url(request=request, url=session_data["back_url"]) job_seeker = get_object_or_404(User, pk=session_data["job_seeker_pk"]) siae = get_object_or_404(Siae, pk=session_data["to_siae_pk"]) context = { "back_url": back_url, "job_seeker": job_seeker, "siae": siae, } return render(request, template_name, context)
def details_for_prescriber(request, job_application_id, template_name="apply/process_details_prescriber.html"): """ Detail of an application for an SIAE with the ability: - to update start date of a contract (provided given date is in the future), - to give an answer. """ job_applications = get_all_available_job_applications_as_prescriber(request) queryset = job_applications.select_related( "job_seeker", "eligibility_diagnosis", "sender", "sender_siae", "sender_prescriber_organization", "to_siae", "approval", ).prefetch_related("selected_jobs__appellation") job_application = get_object_or_404(queryset, id=job_application_id) transition_logs = job_application.logs.select_related("user").all().order_by("timestamp") # We are looking for the most plausible availability date for eligibility criterions before_date = job_application.hiring_end_at if before_date is None and job_application.approval and job_application.approval.end_at is not None: before_date = job_application.approval.end_at else: before_date = datetime.datetime.now() back_url = get_safe_url(request, "back_url", fallback_url=reverse_lazy("apply:list_for_prescriber")) context = { "approvals_wrapper": job_application.job_seeker.approvals_wrapper, "eligibility_diagnosis": job_application.get_eligibility_diagnosis(), "job_application": job_application, "transition_logs": transition_logs, "back_url": back_url, } return render(request, template_name, context)
def job_description_card(request, job_description_id, template_name="siaes/job_description_card.html"): """ SIAE's job description card (or "Fiche" in French). Public view. """ job_description = get_object_or_404(SiaeJobDescription, pk=job_description_id) back_url = get_safe_url(request, "back_url") siae = job_description.siae others_active_jobs = ( SiaeJobDescription.objects.select_related("appellation").filter( is_active=True, siae=siae).exclude(id=job_description_id).order_by( "-updated_at", "-created_at")) context = { "job": job_description, "siae": siae, "others_active_jobs": others_active_jobs, "back_url": back_url, } return render(request, template_name, context)
def suspension_delete(request, suspension_id, template_name="approvals/suspension_delete.html"): """ Delete the given suspension. """ siae = get_current_siae_or_404(request) suspension = get_object_or_404(Suspension, pk=suspension_id) if not suspension.can_be_handled_by_siae(siae): raise PermissionDenied() back_url = get_safe_url(request, "back_url", fallback_url=reverse("dashboard:index")) if request.method == "POST" and request.POST.get("confirm") == "true": suspension.delete() messages.success(request, "Annulation de suspension effectuée.") return HttpResponseRedirect(back_url) context = { "suspension": suspension, "back_url": back_url, } return render(request, template_name, context)
def suspend(request, approval_id, template_name="approvals/suspend.html"): """ Suspend the given approval. """ siae = get_current_siae_or_404(request) approval = get_object_or_404(Approval, pk=approval_id) if not approval.can_be_suspended_by_siae(siae): raise PermissionDenied() back_url = get_safe_url(request, "back_url", fallback_url=reverse("dashboard:index")) preview = False form = SuspensionForm(approval=approval, siae=siae, data=request.POST or None) if request.method == "POST" and form.is_valid(): suspension = form.save(commit=False) suspension.created_by = request.user if request.POST.get("edit"): preview = False if request.POST.get("preview"): preview = True elif request.POST.get("save"): suspension.save() messages.success(request, "Suspension effectuée.") return HttpResponseRedirect(back_url) context = { "approval": approval, "back_url": back_url, "form": form, "preview": preview, } return render(request, template_name, context)
def start(request, siae_pk): """ Entry point. """ siae = get_object_or_404(Siae, pk=siae_pk) if request.user.is_siae_staff and not siae.has_member(request.user): raise PermissionDenied( "Vous ne pouvez postuler pour un candidat que dans votre structure." ) # Refuse all applications except those issued by the SIAE if siae.block_job_applications and not siae.has_member(request.user): # Message only visible in DEBUG raise Http404( "Cette organisation n'accepte plus de candidatures pour le moment." ) back_url = get_safe_url(request, "back_url") # Start a fresh session. request.session[settings.ITOU_SESSION_JOB_APPLICATION_KEY] = { "back_url": back_url, "job_seeker_pk": None, "nir": None, "to_siae_pk": siae.pk, "sender_pk": None, "sender_kind": None, "sender_siae_pk": None, "sender_prescriber_organization_pk": None, "job_description_id": request.GET.get("job_description_id"), } next_url = reverse("apply:step_sender", kwargs={"siae_pk": siae.pk}) return HttpResponseRedirect(next_url)
def inject_context_into_response(self, response, params): if isinstance(response, TemplateResponse): account_type = params.get("account_type") signup_url = reverse( ItouLoginView.ACCOUNT_TYPE_TO_SIGNUP_URL.get( account_type, "account_signup")) show_sign_in_providers = account_type == "job_seeker" show_france_connect = settings.FRANCE_CONNECT_ENABLED signup_allowed = account_type != "institution" redirect_field_value = get_safe_url(self.request, REDIRECT_FIELD_NAME) context = { "account_type": account_type, "signup_url": signup_url, "show_sign_in_providers": show_sign_in_providers, "show_france_connect": show_france_connect, "redirect_field_name": REDIRECT_FIELD_NAME, "redirect_field_value": redirect_field_value, "signup_allowed": signup_allowed, } response.context_data.update(context) return response
def edit_job_seeker_info(request, job_application_id, template_name="dashboard/edit_job_seeker_info.html"): job_application = get_object_or_404(JobApplication.objects.select_related("job_seeker"), pk=job_application_id) current_siae_pk = request.session.get(settings.ITOU_SESSION_CURRENT_SIAE_KEY) if not user_can_edit_job_seeker_info(request.user, job_application, current_siae_pk): raise PermissionDenied dashboard_url = reverse_lazy("dashboard:index") back_url = get_safe_url(request, "back_url", fallback_url=dashboard_url) form = EditUserInfoForm( request=request, instance=job_application.job_seeker, editor=request.user, data=request.POST or None ) if request.method == "POST" and form.is_valid(): form.save() messages.success(request, "Les informations du candidat ont été mises à jour.") return HttpResponseRedirect(back_url) context = { "form": form, "job_application": job_application, "prev_url": back_url, } return render(request, template_name, context)
def prescriber_check_already_exists( request, template_name="signup/prescriber_check_already_exists.html"): """ Entry point of the signup process for prescribers/orienteurs. The signup process consists of several steps during which the user answers a series of questions to determine the `kind` of his organization if any. Answers are kept in session. At the end of the process a user will be created and he will be: - added to the members of a pre-existing Pôle emploi agency ("prescripteur habilité") - added to the members of a new authorized organization ("prescripteur habilité") - added to the members of a new unauthorized organization ("orienteur") - without any organization ("orienteur") Step 1: makes it possible to avoid duplicates of prescriber's organizations. As 80% of prescribers on Itou are Pôle emploi members, a link is dedicated for users who work for PE. """ # Start a fresh session that will be used during the signup process. # Since we can go back-and-forth, or someone always has the option # of using a direct link, its state must be kept clean in each step. request.session[settings.ITOU_SESSION_PRESCRIBER_SIGNUP_KEY] = { "authorization_status": None, "kind": None, "prescriber_org_data": None, "pole_emploi_org_pk": None, "safir_code": None, "url_history": [request.path], "next": get_safe_url(request, "next"), } prescriber_orgs_with_members_same_siret = None prescriber_orgs_with_members_same_siren = None form = forms.PrescriberCheckAlreadyExistsForm(data=request.POST or None) if request.method == "POST" and form.is_valid(): # Puts the data from API entreprise and geocoding in session for the last creation step session_data = request.session[ settings.ITOU_SESSION_PRESCRIBER_SIGNUP_KEY] session_data["prescriber_org_data"] = form.org_data request.session.modified = True # Get organizations with members with precisely the same SIRET prescriber_orgs_with_members_same_siret = PrescriberOrganization.objects.prefetch_active_memberships( ).filter(siret=form.cleaned_data["siret"]) # Get organizations with members with same SIREN but not the same SIRET prescriber_orgs_with_members_same_siren = ( PrescriberOrganization.objects.prefetch_active_memberships( ).filter(siret__startswith=form.cleaned_data["siret"][:9], department=form.cleaned_data["department"]). exclude(members=None).exclude( pk__in=[p.pk for p in prescriber_orgs_with_members_same_siret])) # Redirect to creation steps if no organization with member is found, # else, displays the same form with the list of organizations with first member # to indicate which person to request an invitation from if not prescriber_orgs_with_members_same_siret and not prescriber_orgs_with_members_same_siren: return HttpResponseRedirect( reverse("signup:prescriber_choose_org")) context = { "prescriber_orgs_with_members_same_siret": prescriber_orgs_with_members_same_siret, "prescriber_orgs_with_members_same_siren": prescriber_orgs_with_members_same_siren, "form": form, } return render(request, template_name, context)