def summary(request, employee_record_id, template_name="employee_record/summary.html"): """ Display the summary of a given employee record (no update possible) """ siae = get_current_siae_or_404(request) if not siae.can_use_employee_record: raise PermissionDenied query_base = EmployeeRecord.objects.full_fetch() employee_record = get_object_or_404(query_base, pk=employee_record_id) job_application = employee_record.job_application if not siae_is_allowed(job_application, siae): raise PermissionDenied status = request.GET.get("status") context = { "employee_record": employee_record, "status": status, } return render(request, template_name, context)
def can_create_employee_record(request, job_application_id=None): """ Check if conditions / permissions are set to use the employee record views If a valid job_application_id is given, return a full-fledged JobApplication object reusable in view (skip one extra DB query) """ # SIAEs only siae = get_current_siae_or_404(request) # SIAE is eligible to employee record ? if not siae.can_use_employee_record: raise PermissionDenied if job_application_id: # We want to reuse a job application in view, but first check that all is ok job_application = get_object_or_404( JobApplication.objects.select_related( "approval", "to_siae", "job_seeker", "job_seeker__jobseeker_profile", ), pk=job_application_id, ) if not (siae_is_allowed(job_application, siae) and tunnel_step_is_allowed(job_application)): raise PermissionDenied # Finally, the reusable Holy Grail return job_application # All checks performed, without asking for a job application object return None
def list_for_siae(request, template_name="apply/list_for_siae.html"): """ List of applications for an SIAE. """ siae = get_current_siae_or_404(request) job_applications = siae.job_applications_received filters_form = SiaeFilterJobApplicationsForm(job_applications, request.GET or None) filters = None job_applications = job_applications.not_archived().with_list_related_data() if filters_form.is_valid(): job_applications = job_applications.filter( *filters_form.get_qs_filters()) filters = filters_form.humanize_filters() job_applications_page = pager(job_applications, request.GET.get("page"), items_per_page=10) context = { "siae": siae, "job_applications_page": job_applications_page, "filters_form": filters_form, "filters": filters, } return render(request, template_name, context)
def approval_as_pdf(request, job_application_id, template_name="approvals/approval_as_pdf.html"): """ Displays the approval in pdf format """ siae = get_current_siae_or_404(request) queryset = JobApplication.objects.select_related("job_seeker", "eligibility_diagnosis", "approval", "to_siae") job_application = get_object_or_404(queryset, pk=job_application_id, to_siae=siae) if not job_application.can_download_approval_as_pdf: # Message only visible in DEBUG raise Http404("Nous sommes au regret de vous informer que vous ne pouvez pas télécharger cet agrément.") diagnosis = job_application.get_eligibility_diagnosis() diagnosis_author = None diagnosis_author_org = None diagnosis_author_org_name = None if diagnosis: diagnosis_author = diagnosis.author.get_full_name() diagnosis_author_org = diagnosis.author_prescriber_organization or diagnosis.author_siae if diagnosis_author_org: diagnosis_author_org_name = diagnosis_author_org.display_name if not diagnosis and job_application.approval and job_application.approval.originates_from_itou: # On November 30th, 2021, AI were delivered a PASS IAE # without a diagnosis for all of their employees. if not job_application.approval.is_from_ai_stock: # Keep track of job applications without a proper eligibility diagnosis because # it shouldn't happen. # If this occurs too much we may have to change `can_download_approval_as_pdf()` # and investigate a lot more about what's going on. # See also migration `0035_link_diagnoses.py`. raise ObjectDoesNotExist("Job application %s has no eligibility diagnosis." % job_application.pk) # The PDFShift API can load styles only if it has the full URL. base_url = request.build_absolute_uri("/")[:-1] if settings.DEBUG: # Use staging or production styles when working locally # as PDF shift can't access local files. base_url = f"{settings.ITOU_PROTOCOL}://{settings.ITOU_STAGING_DN}" context = { "approval": job_application.approval, "base_url": base_url, "assistance_url": settings.ITOU_ASSISTANCE_URL, "diagnosis_author": diagnosis_author, "diagnosis_author_org_name": diagnosis_author_org_name, "siae": job_application.to_siae, "job_seeker": job_application.job_seeker, } html = SimpleTemplateResponse(template=template_name, context=context).rendered_content full_name_slug = slugify(job_application.job_seeker.get_full_name()) filename = f"{full_name_slug}-pass-iae.pdf" with HtmlToPdf(html, autoclose=False) as transformer: return FileResponse(transformer.file, as_attachment=True, filename=filename)
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 create_siae(request, template_name="siaes/create_siae.html"): """ Create a new SIAE (Antenne in French). """ current_siae = get_current_siae_or_404(request) if not request.user.can_create_siae_antenna(parent_siae=current_siae): raise PermissionDenied form = CreateSiaeForm( current_siae=current_siae, current_user=request.user, data=request.POST or None, initial={ "siret": current_siae.siret, "kind": current_siae.kind, "department": current_siae.department }, ) if request.method == "POST" and form.is_valid(): with transaction.atomic(): # The form creates multiple objects siae = form.save(request) request.session[settings.ITOU_SESSION_CURRENT_SIAE_KEY] = siae.pk return HttpResponseRedirect(reverse("dashboard:index")) context = {"form": form} return render(request, template_name, context)
def dashboard(request, template_name="dashboard/dashboard.html"): job_applications_counter = 0 prescriber_authorization_status_not_set = None prescriber_is_orienter = False if request.user.is_siae_staff: siae = get_current_siae_or_404(request) job_applications_counter = siae.job_applications_received.filter( state=JobApplicationWorkflow.STATE_NEW).count() # See template for display message while authorized organization is being validated (prescriber path) if request.user.is_prescriber_with_org: prescriber_organization = get_current_org_or_404(request) prescriber_authorization_status_not_set = ( prescriber_organization.authorization_status == PrescriberOrganization.AuthorizationStatus.NOT_SET) # This is to hide the "secret code", except for orienter orgs prescriber_is_orienter = ( prescriber_organization.authorization_status == PrescriberOrganization.AuthorizationStatus.NOT_REQUIRED) context = { "job_applications_counter": job_applications_counter, "prescriber_authorization_status_not_set": prescriber_authorization_status_not_set, "prescriber_is_orienter": prescriber_is_orienter, } return render(request, template_name, context)
def approval_as_pdf(request, job_application_id, template_name="approvals/approval_as_pdf.html"): siae = get_current_siae_or_404(request) queryset = JobApplication.objects.select_related("job_seeker", "approval", "to_siae") job_application = get_object_or_404(queryset, pk=job_application_id, to_siae=siae) if not job_application.can_download_approval_as_pdf: raise Http404( _( """ Nous sommes au regret de vous informer que vous ne pouvez pas télécharger cet agrément.""" ) ) job_seeker = job_application.job_seeker user_name = job_seeker.get_full_name() diagnosis = None diagnosis_author = None diagnosis_author_org = None diagnosis_author_org_name = None # If an approval has been delivered by Pole Emploi, a diagnosis might # exist in the real world but not in our database. # Raise an error only if the diagnosis does not exist for an Itou approval. if job_application.approval.originates_from_itou: diagnosis = job_seeker.get_eligibility_diagnosis() diagnosis_author = diagnosis.author.get_full_name() diagnosis_author_org = diagnosis.author_prescriber_organization or diagnosis.author_siae if diagnosis_author_org: diagnosis_author_org_name = diagnosis_author_org.display_name # The PDFShift API can load styles only if it has # the full URL. base_url = request.build_absolute_uri("/")[:-1] if settings.DEBUG: # Use staging or production styles when working locally # as PDF shift can't access local files. base_url = f"{settings.ITOU_PROTOCOL}://{settings.ITOU_STAGING_DN}" context = { "approval": job_application.approval, "base_url": base_url, "contact_email": settings.ITOU_EMAIL_CONTACT, "diagnosis_author": diagnosis_author, "diagnosis_author_org_name": diagnosis_author_org_name, "siae": job_application.to_siae, "user_name": user_name, } html = SimpleTemplateResponse(template=template_name, context=context).rendered_content filename = f"{slugify(user_name)}-pass-iae.pdf" with HtmlToPdf(html, autoclose=False) as transformer: return FileResponse(transformer.file, as_attachment=True, filename=filename)
def configure_jobs(request, template_name="siaes/configure_jobs.html"): """ Configure an SIAE's jobs. """ siae = get_current_siae_or_404(request) if request.method == "POST": current_codes = set( siae.job_description_through.values_list("appellation__code", flat=True)) submitted_codes = set(request.POST.getlist("code")) codes_to_create = submitted_codes - current_codes # It is assumed that the codes to delete are not submitted (they must # be removed from the DOM via JavaScript). Instead, they are deducted. codes_to_delete = current_codes - submitted_codes codes_to_update = current_codes - codes_to_delete if codes_to_create or codes_to_delete or codes_to_update: # Create. for code in codes_to_create: appellation = Appellation.objects.get(code=code) through_defaults = { "custom_name": request.POST.get(f"custom-name-{code}", ""), "description": request.POST.get(f"description-{code}", ""), "is_active": bool(request.POST.get(f"is_active-{code}")), } siae.jobs.add(appellation, through_defaults=through_defaults) # Delete. if codes_to_delete: appellations = Appellation.objects.filter( code__in=codes_to_delete) siae.jobs.remove(*appellations) # Update. for job_through in siae.job_description_through.filter( appellation__code__in=codes_to_update): code = job_through.appellation.code new_custom_name = request.POST.get(f"custom-name-{code}", "") new_description = request.POST.get(f"description-{code}", "") new_is_active = bool(request.POST.get(f"is_active-{code}")) if (job_through.custom_name != new_custom_name or job_through.description != new_description or job_through.is_active != new_is_active): job_through.custom_name = new_custom_name job_through.description = new_description job_through.is_active = new_is_active job_through.save() messages.success(request, _("Mise à jour effectuée !")) return HttpResponseRedirect(reverse_lazy("dashboard:index")) context = {"siae": siae} return render(request, template_name, context)
def configure_jobs(request, template_name="siaes/configure_jobs.html"): """ Configure an SIAE's jobs. Time was limited during the prototyping phase and this view is based on JavaScript to generate a dynamic form. No proper Django form is used. """ siae = get_current_siae_or_404(request, with_job_app_score=True, with_job_descriptions=True) job_descriptions = (siae.job_description_through.select_related( "appellation__rome").all().order_by("-updated_at", "-created_at")) errors = {} if request.method == "POST": # Validate data for Siae block_job_applications form_siae_block_job_applications = BlockJobApplicationsForm( instance=siae, data=request.POST or None) if form_siae_block_job_applications.is_valid(): form_siae_block_job_applications.save() refreshed_cards = refresh_card_list(request=request, siae=siae) if not refreshed_cards["errors"]: with transaction.atomic(): if refreshed_cards["jobs"]["create"]: SiaeJobDescription.objects.bulk_create( refreshed_cards["jobs"]["create"]) if refreshed_cards["jobs"]["update"]: SiaeJobDescription.objects.bulk_update( refreshed_cards["jobs"]["update"], [ "custom_name", "description", "is_active", "updated_at" ]) if refreshed_cards["jobs"]["delete"]: siae.jobs.remove(*refreshed_cards["jobs"]["delete"]) messages.success(request, "Mise à jour effectuée !") return HttpResponseRedirect(reverse("dashboard:index")) else: errors = refreshed_cards["errors"] context = { "errors": errors, "job_descriptions": job_descriptions, "siae": siae } return render(request, template_name, context)
def pe_approval_search(request, template_name="approvals/pe_approval_search.html"): """ Entry point of the `PoleEmploiApproval`'s conversion process which consists of 3 steps and allows to convert a `PoleEmploiApproval` into an `Approval`. This process is required following the end of the software allowing Pôle emploi to manage their approvals. Search for a PoleEmploiApproval by number. Redirects to the existing Pass if it exists. If not, it will ask you to search for an user in order to import the "agrément" as a "PASS IAE". """ approval = None form = PoleEmploiApprovalSearchForm(request.GET or None) number = None siae = get_current_siae_or_404(request) back_url = reverse("approvals:pe_approval_search") if form.is_valid(): number = form.cleaned_data["number"] approvals_wrapper = ApprovalsWrapper(number=number) approval = approvals_wrapper.latest_approval if approval: if approval.is_pass_iae: job_application = approval.user.last_accepted_job_application if job_application and job_application.to_siae == siae: # Suspensions and prolongations links are available in the job application details page. application_details_url = reverse( "apply:details_for_siae", kwargs={"job_application_id": job_application.pk} ) return HttpResponseRedirect(application_details_url) # The employer cannot handle the PASS as it's already used by another one. # Suggest him to make a self-prescription. A link is offered in the template. context = { "approval": approval, "back_url": back_url, "form": form, "number": number, "siae": siae, } return render(request, "approvals/pe_approval_search_found.html", context) context = { "form": form, "number": number, "siae": siae, } return render(request, template_name, context)
def test_accept_existing_user_is_employer(self): self.user = SiaeWith2MembershipsFactory().members.first() self.invitation = SiaeSentInvitationFactory( sender=self.sender, siae=self.siae, first_name=self.user.first_name, last_name=self.user.last_name, email=self.user.email, ) self.client.login(email=self.user.email, password=DEFAULT_PASSWORD) self.response = self.client.get(self.invitation.acceptance_link, follow=True) current_siae = get_current_siae_or_404(self.response.wsgi_request) self.assertEqual(self.invitation.siae.pk, current_siae.pk)
def edit_siae(request, template_name="siaes/edit_siae.html"): """ Edit an SIAE. """ siae = get_current_siae_or_404(request) form = EditSiaeForm(instance=siae, data=request.POST or None) if request.method == "POST" and form.is_valid(): form.save() messages.success(request, _("Mise à jour effectuée !")) return HttpResponseRedirect(reverse_lazy("dashboard:index")) context = {"form": form, "siae": siae} return render(request, template_name, context)
def card_search_preview(request, template_name="siaes/includes/_card_siae.html"): """ SIAE's card (or "Fiche" in French) search preview. Return only html of card search preview without global template (header, footer, ...) Need to recount active jobs to avoid supress and updated count active jobs """ siae = get_current_siae_or_404(request, with_job_app_score=True, with_job_descriptions=True) form_siae_block_job_applications = BlockJobApplicationsForm( instance=siae, data=request.POST or None) if form_siae_block_job_applications.is_valid(): siae = form_siae_block_job_applications.instance refreshed_cards = refresh_card_list(request=request, siae=siae) if not refreshed_cards["errors"]: # sort all the jobs buy updated_at and created_at desc list_jobs_descriptions = sorted( refreshed_cards["jobs"]["create"] + refreshed_cards["jobs"]["update"] + refreshed_cards["jobs"]["unmodified"], key=lambda x: x.updated_at if x.updated_at else x.created_at, reverse=True, ) # count the number of active jobs count_active_job_descriptions = 0 for job in list_jobs_descriptions: # int(True) = 1, int(False) = 0 count_active_job_descriptions += int(job.is_active) siae.count_active_job_descriptions = count_active_job_descriptions context = { "siae": siae, "jobs_descriptions": list_jobs_descriptions, } html = render_to_string(template_name, context) else: context = {"errors": refreshed_cards["errors"]} template_name_errors = "siaes/includes/_alert_configure_job.html" html = render_to_string(template_name_errors, context) return HttpResponse(html)
def show_financial_annexes(request, template_name="siaes/show_financial_annexes.html"): """ Show a summary of the financial annexes of the convention to the siae admin user. Financial annexes are grouped by suffix and only the most relevant one (active if any, or most recent if not) is shown for each suffix. """ current_siae = get_current_siae_or_404(request) if not current_siae.convention_can_be_accessed_by(request.user): raise PermissionDenied financial_annexes = [] if current_siae.convention: financial_annexes = current_siae.convention.financial_annexes.all() # For each group of AFs sharing the same number prefix, show only the most relevant AF # (active if any, or most recent if not). We do this to avoid showing too many AFs and confusing the user. prefix_to_af = {} for af in financial_annexes: prefix = af.number_prefix if prefix not in prefix_to_af or af.is_active: # Always show an active AF when there is one. prefix_to_af[prefix] = af continue old_suffix = prefix_to_af[prefix].number_suffix new_suffix = af.number_suffix if not prefix_to_af[prefix].is_active and new_suffix > old_suffix: # Show the AF with the latest suffix when there is no active one. prefix_to_af[prefix] = af continue financial_annexes = list(prefix_to_af.values()) financial_annexes.sort(key=lambda af: af.number, reverse=True) context = { "siae": current_siae, "convention": current_siae.convention, "financial_annexes": financial_annexes, "can_select_af": current_siae.convention_can_be_changed_by(request.user), "current_siae_is_asp": current_siae.source == Siae.SOURCE_ASP, "current_siae_is_user_created": current_siae.source == Siae.SOURCE_USER_CREATED, } return render(request, template_name, context)
def deactivate_member(request, user_id, template_name="siaes/deactivate_member.html"): siae = get_current_siae_or_404(request) target_member = User.objects.get(pk=user_id) if deactivate_org_member(request=request, target_member=target_member, organization=siae): return HttpResponseRedirect(reverse("siaes_views:members")) context = { "structure": siae, "target_member": target_member, } return render(request, template_name, context)
def members(request, template_name="siaes/members.html"): """ List members of an SIAE. """ siae = get_current_siae_or_404(request) members = siae.siaemembership_set.select_related("user").all().order_by( "joined_at") pending_invitations = siae.invitations.filter( accepted=False).all().order_by("sent_at") context = { "siae": siae, "members": members, "pending_invitations": pending_invitations } 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 list_for_siae_exports(request, template_name="apply/list_of_available_exports.html" ): """ List of applications for a SIAE, sorted by month, displaying the count of applications per month with the possibiliy to download those applications as a CSV file. """ siae = get_current_siae_or_404(request) job_applications = siae.job_applications_received.not_archived() job_applications_by_month = job_applications.with_monthly_counts() context = { "job_applications_by_month": job_applications_by_month, "siae": siae, "export_for": "siae" } return render(request, template_name, context)
def stats_siae(request, template_name=_STATS_HTML_TEMPLATE): """ SIAE stats shown to their own members. They can only view data for their own SIAE. """ current_org = get_current_siae_or_404(request) if not request.user.can_view_stats_siae(current_org=current_org): raise PermissionDenied params = {SIAE_FILTER_KEY: current_org.convention.asp_id} context = { "iframeurl": metabase_embedded_url(settings.SIAE_STATS_DASHBOARD_ID, params=params), "page_title": "Données de ma structure", "stats_base_url": settings.METABASE_SITE_URL, } return render(request, template_name, context)
def block_job_applications(request, template_name="siaes/block_job_applications.html"): """ Settings: block job applications for given SIAE """ siae = get_current_siae_or_404(request) form = BlockJobApplicationsForm(instance=siae, data=request.POST or None) if request.method == "POST" and form.is_valid(): form.save() messages.success( request, _("Mise à jour du blocage des candidatures effectuée !")) return HttpResponseRedirect(reverse_lazy("dashboard:index")) context = {"siae": siae, "form": form} return render(request, template_name, context)
def members(request, template_name="siaes/members.html"): """ List members of an SIAE. """ siae = get_current_siae_or_404(request) if not siae.is_active: raise PermissionDenied members = siae.siaemembership_set.active().select_related( "user").all().order_by("joined_at") pending_invitations = siae.invitations.pending() context = { "siae": siae, "members": members, "pending_invitations": pending_invitations, } return render(request, template_name, context)
def invite_siae_staff(request, template_name="invitations_views/create.html"): siae = get_current_siae_or_404(request) form_kwargs = {"sender": request.user, "siae": siae} formset = NewSiaeStaffInvitationFormSet(data=request.POST or None, form_kwargs=form_kwargs) if request.POST: if formset.is_valid(): invitations = formset.save() count = len(formset.forms) message_singular = ( "Votre invitation a été envoyée par e-mail.<br>" "Pour rejoindre votre organisation, l'invité(e) peut désormais cliquer " "sur le lien de validation reçu dans le courriel.<br>") message_plural = ( "Vos invitations ont été envoyées par e-mail.<br>" "Pour rejoindre votre organisation, vos invités peuvent désormais " "cliquer sur le lien de validation reçu dans l'e-mail.<br>") message = __(message_singular, message_plural, count) % { "count": count } expiration_date = formats.date_format( invitations[0].expiration_date) message += _( f"Le lien de validation est valable jusqu'au {expiration_date}." ) message = safestring.mark_safe(message) messages.success(request, message) formset = NewSiaeStaffInvitationFormSet(form_kwargs=form_kwargs) return redirect(request.path) form_post_url = reverse("invitations_views:invite_siae_staff") back_url = reverse("siaes_views:members") context = { "back_url": back_url, "form_post_url": form_post_url, "formset": formset } return render(request, template_name, context)
def list_for_siae_exports_download(request, month_identifier): """ List of applications for a SIAE for a given month identifier (YYYY-mm), exported as a CSV file with immediate download """ year, month = month_identifier.split("-") siae = get_current_siae_or_404(request) job_applications = siae.job_applications_received.not_archived() job_applications = job_applications.created_on_given_year_and_month( year, month).with_list_related_data() filename = f"candidatures-{slugify(siae.display_name)}-{month_identifier}.csv" response = HttpResponse(content_type="text/csv", charset="utf-8") response["Content-Disposition"] = 'attachment; filename="{}"'.format( filename) generate_csv_export(job_applications, response) return response
def update_admin_role(request, action, user_id, template_name="siaes/update_admins.html"): siae = get_current_siae_or_404(request) target_member = User.objects.get(pk=user_id) if update_org_admin_role(request=request, organization=siae, target_member=target_member, action=action): return HttpResponseRedirect(reverse("siaes_views:members")) context = { "action": action, "structure": siae, "target_member": target_member, } return render(request, template_name, context)
def create_siae(request, template_name="siaes/create_siae.html"): """ Create a new SIAE (Agence / Etablissement in French). """ current_siae = get_current_siae_or_404(request) form = CreateSiaeForm( current_siae=current_siae, current_user=request.user, data=request.POST or None, initial={"siret": current_siae.siret}, ) if request.method == "POST" and form.is_valid(): siae = form.save(request) request.session[settings.ITOU_SESSION_CURRENT_SIAE_KEY] = siae.pk messages.success(request, _(f"Vous travaillez sur {siae.display_name}")) return HttpResponseRedirect(reverse_lazy("dashboard:index")) context = {"form": form} return render(request, template_name, context)
def select_financial_annex(request, template_name="siaes/select_financial_annex.html"): """ Let siae admin user select a new convention via a financial annex number. """ current_siae = get_current_siae_or_404(request) if not current_siae.convention_can_be_changed_by(request.user): raise PermissionDenied # We only allow the user to select an AF under the same SIREN as the current siae. financial_annexes = ( SiaeFinancialAnnex.objects.select_related("convention").filter( convention__kind=current_siae.kind, convention__siret_signature__startswith=current_siae.siren). order_by("-number")) # Show only one AF for each AF number prefix to significantly reduce the length of the dropdown when there are # many AFs in the same SIREN. prefix_to_af = {af.number_prefix: af for af in financial_annexes.all()} # The form expects a queryset and not a list. financial_annexes = financial_annexes.filter( pk__in=[af.pk for af in prefix_to_af.values()]) select_form = FinancialAnnexSelectForm(data=request.POST or None, financial_annexes=financial_annexes) if request.method == "POST" and select_form.is_valid(): financial_annex = select_form.cleaned_data["financial_annexes"] current_siae.convention = financial_annex.convention current_siae.save() message = ( f"Nous avons bien attaché votre structure à l'annexe financière" f" {financial_annex.number_prefix_with_spaces}.") messages.success(request, message) return HttpResponseRedirect( reverse("siaes_views:show_financial_annexes")) context = {"select_form": select_form} 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 test_accept_existing_user_already_member_of_inactive_siae(self): """ An inactive SIAE user (i.e. attached to a single inactive SIAE) can only be ressucitated by being invited to a new SIAE. We test here that this is indeed possible. """ siae = SiaeWithMembershipFactory() sender = siae.members.first() user = SiaeWithMembershipFactory( convention__is_active=False).members.first() invitation = SentSiaeStaffInvitationFactory( sender=sender, siae=siae, first_name=user.first_name, last_name=user.last_name, email=user.email, ) self.client.login(email=user.email, password=DEFAULT_PASSWORD) response = self.client.get(invitation.acceptance_link, follow=True) self.assertRedirects(response, reverse("dashboard:index")) current_siae = get_current_siae_or_404(response.wsgi_request) self.assertEqual(siae.pk, current_siae.pk) self.assert_accepted_invitation(invitation, user)