Converts the data held on the submission into an editable format and knows how to save
that back to the object. Shortcuts the normal update view save approach
defrender_preview(self,request:HttpRequest,form:BaseModelForm)->HttpResponse:"""Gets a rendered preview of a form Creates a new revision on the `ApplicationSubmission`, removes the forms temporary files Args: request: Request used to trigger the preview to be used in the render form: Form to be rendered Returns: An `HttpResponse` containing a preview of the given form """self.object.create_revision(draft=True,by=request.user)messages.success(self.request,_("Draft saved"))# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()context=self.get_context_data()returnrender(request,"funds/application_preview.html",context)
defbuttons(self,)->Generator[Tuple[str,str,str],Tuple[str,str,str],Tuple[str,str,str]]:"""The buttons to be presented to the in the EditView Returns: A generator returning a tuple strings in the format of: (<button type>, <button styling>, <button label>) """ifsettings.SUBMISSION_PREVIEW_REQUIRED:yield("preview","primary",_("Preview and submit"))yield("save","white",_("Save draft"))else:yield("submit","primary",_("Submit"))yield("save","white",_("Save draft"))yield("preview","white",_("Preview"))
defget_on_submit_transition(self,user):"""Gets the transition that should be triggered when a form is submitted. Checks all available status transitions for the current user and returns the first one that has trigger_on_submit=True in its custom settings. Returns: dict: The transition configuration dictionary with trigger_on_submit=True, or None if no matching transition is found. """returnnext((tfortinself.object.get_available_user_status_transitions(user)ift.custom.get("trigger_on_submit",False)),None,)
defform_valid(self,form:BaseModelForm)->HttpResponse:"""Handle the form returned from a `SubmissionEditView`. Determine whether to return a form preview, draft the new edits, or submit and transition the `ApplicationSubmission` object Args: form: The valid form Returns: An `HttpResponse` depending on the actions taken in the edit view """self.object.form_data=form.cleaned_datais_draft=self.object.status==DRAFT_STATE# Handle a preview or a save (aka a draft)if"preview"inself.request.POST:returnself.render_preview(self.request,form)if"save"inself.request.POST:returnself.save_draft_and_refresh_page(form=form)# Handle an application being submitted from a DRAFT_STATE. This includes updating submit_timeifis_draftand"submit"inself.request.POST:self.object.submit_time=timezone.now()ifself.object.round:current_round=self.get_object_fund_current_round()ifcurrent_round:self.object.round=current_roundself.object.save(update_fields=["submit_time","round"])revision=self.object.create_revision(by=self.request.user)submitting_proposal=self.object.phase.nameinSTAGE_CHANGE_ACTIONSifsubmitting_proposal:messenger(MESSAGES.PROPOSAL_SUBMITTED,request=self.request,user=self.request.user,source=self.object,)elifrevisionandnotself.object.status==DRAFT_STATE:messenger(MESSAGES.APPLICANT_EDIT,request=self.request,user=self.request.user,source=self.object,related=revision,)if"submit"inself.request.POST:iftransition:=self.get_on_submit_transition(self.request.user):notify=(not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,)self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=notify,# Use the other notification)# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()returnHttpResponseRedirect(self.get_success_url())
Returns the keyword arguments for instantiating the form.
This method is called by the form mixin during form instantiation.
It returns a dictionary of keyword arguments that will be passed to
the form's constructor.
Returns:
dict –
A dictionary of keyword arguments for the form constructor.
Source code in hypha/apply/funds/views/submission_edit.py
defget_form_kwargs(self):""" Returns the keyword arguments for instantiating the form. This method is called by the form mixin during form instantiation. It returns a dictionary of keyword arguments that will be passed to the form's constructor. Returns: dict: A dictionary of keyword arguments for the form constructor. """kwargs=super().get_form_kwargs()instance=kwargs.pop("instance").from_draft()initial=instance.raw_dataforfield_idininstance.file_field_ids:initial.pop(field_id+"-uploads",False)initial[field_id]=self.get_placeholder_file(instance.raw_data.get(field_id))kwargs["initial"]=initialreturnkwargs
defget_form_class(self):""" Returns the form class for the view. This method is called by the view during form instantiation. It returns the form class that will be used to render the form. When trying to save as draft, this method will return a version of form class that doesn't validate required fields while saving. The method also disables any group toggle fields in the form, as they are not supported on edit forms. Returns: class: The form class for the view. """is_draft=Trueif"save"inself.request.POSTelseFalseform_fields=self.object.get_form_fields(draft=is_draft,form_data=self.object.raw_data,user=self.request.user)field_blocks=self.object.get_defined_fields()forfield_blockinfield_blocks:ifisinstance(field_block.block,GroupToggleBlock):# Disable group toggle field as it is not supported on edit forms.form_fields[field_block.id].disabled=Truereturntype("WagtailStreamForm",(self.object.submission_form_class,),form_fields)
defrender_preview(self,request:HttpRequest,form:BaseModelForm)->HttpResponse:"""Gets a rendered preview of a form Creates a new revision on the `ApplicationSubmission`, removes the forms temporary files Args: request: Request used to trigger the preview to be used in the render form: Form to be rendered Returns: An `HttpResponse` containing a preview of the given form """self.object.create_revision(draft=True,by=request.user)messages.success(self.request,_("Draft saved"))# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()context=self.get_context_data()returnrender(request,"funds/application_preview.html",context)
defget_on_submit_transition(self,user):"""Gets the transition that should be triggered when a form is submitted. Checks all available status transitions for the current user and returns the first one that has trigger_on_submit=True in its custom settings. Returns: dict: The transition configuration dictionary with trigger_on_submit=True, or None if no matching transition is found. """returnnext((tfortinself.object.get_available_user_status_transitions(user)ift.custom.get("trigger_on_submit",False)),None,)
defform_valid(self,form:BaseModelForm)->HttpResponse:"""Handle the form returned from a `SubmissionEditView`. Determine whether to return a form preview, draft the new edits, or submit and transition the `ApplicationSubmission` object Args: form: The valid form Returns: An `HttpResponse` depending on the actions taken in the edit view """self.object.form_data=form.cleaned_datais_draft=self.object.status==DRAFT_STATE# Handle a preview or a save (aka a draft)if"preview"inself.request.POST:returnself.render_preview(self.request,form)if"save"inself.request.POST:returnself.save_draft_and_refresh_page(form=form)# Handle an application being submitted from a DRAFT_STATE. This includes updating submit_timeifis_draftand"submit"inself.request.POST:self.object.submit_time=timezone.now()ifself.object.round:current_round=self.get_object_fund_current_round()ifcurrent_round:self.object.round=current_roundself.object.save(update_fields=["submit_time","round"])revision=self.object.create_revision(by=self.request.user)submitting_proposal=self.object.phase.nameinSTAGE_CHANGE_ACTIONSifsubmitting_proposal:messenger(MESSAGES.PROPOSAL_SUBMITTED,request=self.request,user=self.request.user,source=self.object,)elifrevisionandnotself.object.status==DRAFT_STATE:messenger(MESSAGES.APPLICANT_EDIT,request=self.request,user=self.request.user,source=self.object,related=revision,)if"submit"inself.request.POST:iftransition:=self.get_on_submit_transition(self.request.user):notify=(not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,)self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=notify,# Use the other notification)# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()returnHttpResponseRedirect(self.get_success_url())
Returns the keyword arguments for instantiating the form.
This method is called by the form mixin during form instantiation.
It returns a dictionary of keyword arguments that will be passed to
the form's constructor.
Returns:
dict –
A dictionary of keyword arguments for the form constructor.
Source code in hypha/apply/funds/views/submission_edit.py
defget_form_kwargs(self):""" Returns the keyword arguments for instantiating the form. This method is called by the form mixin during form instantiation. It returns a dictionary of keyword arguments that will be passed to the form's constructor. Returns: dict: A dictionary of keyword arguments for the form constructor. """kwargs=super().get_form_kwargs()instance=kwargs.pop("instance").from_draft()initial=instance.raw_dataforfield_idininstance.file_field_ids:initial.pop(field_id+"-uploads",False)initial[field_id]=self.get_placeholder_file(instance.raw_data.get(field_id))kwargs["initial"]=initialreturnkwargs
defget_form_class(self):""" Returns the form class for the view. This method is called by the view during form instantiation. It returns the form class that will be used to render the form. When trying to save as draft, this method will return a version of form class that doesn't validate required fields while saving. The method also disables any group toggle fields in the form, as they are not supported on edit forms. Returns: class: The form class for the view. """is_draft=Trueif"save"inself.request.POSTelseFalseform_fields=self.object.get_form_fields(draft=is_draft,form_data=self.object.raw_data,user=self.request.user)field_blocks=self.object.get_defined_fields()forfield_blockinfield_blocks:ifisinstance(field_block.block,GroupToggleBlock):# Disable group toggle field as it is not supported on edit forms.form_fields[field_block.id].disabled=Truereturntype("WagtailStreamForm",(self.object.submission_form_class,),form_fields)
defbuttons(self,)->Generator[Tuple[str,str,str],Tuple[str,str,str],Tuple[str,str,str]]:"""The buttons to be presented in the `AdminSubmissionEditView` Admins shouldn't be required to preview, but should have the option. Returns: A generator returning a tuple strings in the format of: (<button type>, <button styling>, <button label>) """yield("submit","primary",_("Submit"))yield("save","white",_("Save draft"))yield("preview","white",_("Preview"))
defrender_preview(self,request:HttpRequest,form:BaseModelForm)->HttpResponse:"""Gets a rendered preview of a form Creates a new revision on the `ApplicationSubmission`, removes the forms temporary files Args: request: Request used to trigger the preview to be used in the render form: Form to be rendered Returns: An `HttpResponse` containing a preview of the given form """self.object.create_revision(draft=True,by=request.user)messages.success(self.request,_("Draft saved"))# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()context=self.get_context_data()returnrender(request,"funds/application_preview.html",context)
defbuttons(self,)->Generator[Tuple[str,str,str],Tuple[str,str,str],Tuple[str,str,str]]:"""The buttons to be presented to the in the EditView Returns: A generator returning a tuple strings in the format of: (<button type>, <button styling>, <button label>) """ifsettings.SUBMISSION_PREVIEW_REQUIRED:yield("preview","primary",_("Preview and submit"))yield("save","white",_("Save draft"))else:yield("submit","primary",_("Submit"))yield("save","white",_("Save draft"))yield("preview","white",_("Preview"))
defget_on_submit_transition(self,user):"""Gets the transition that should be triggered when a form is submitted. Checks all available status transitions for the current user and returns the first one that has trigger_on_submit=True in its custom settings. Returns: dict: The transition configuration dictionary with trigger_on_submit=True, or None if no matching transition is found. """returnnext((tfortinself.object.get_available_user_status_transitions(user)ift.custom.get("trigger_on_submit",False)),None,)
defform_valid(self,form:BaseModelForm)->HttpResponse:"""Handle the form returned from a `SubmissionEditView`. Determine whether to return a form preview, draft the new edits, or submit and transition the `ApplicationSubmission` object Args: form: The valid form Returns: An `HttpResponse` depending on the actions taken in the edit view """self.object.form_data=form.cleaned_datais_draft=self.object.status==DRAFT_STATE# Handle a preview or a save (aka a draft)if"preview"inself.request.POST:returnself.render_preview(self.request,form)if"save"inself.request.POST:returnself.save_draft_and_refresh_page(form=form)# Handle an application being submitted from a DRAFT_STATE. This includes updating submit_timeifis_draftand"submit"inself.request.POST:self.object.submit_time=timezone.now()ifself.object.round:current_round=self.get_object_fund_current_round()ifcurrent_round:self.object.round=current_roundself.object.save(update_fields=["submit_time","round"])revision=self.object.create_revision(by=self.request.user)submitting_proposal=self.object.phase.nameinSTAGE_CHANGE_ACTIONSifsubmitting_proposal:messenger(MESSAGES.PROPOSAL_SUBMITTED,request=self.request,user=self.request.user,source=self.object,)elifrevisionandnotself.object.status==DRAFT_STATE:messenger(MESSAGES.APPLICANT_EDIT,request=self.request,user=self.request.user,source=self.object,related=revision,)if"submit"inself.request.POST:iftransition:=self.get_on_submit_transition(self.request.user):notify=(not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,)self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=notify,# Use the other notification)# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()returnHttpResponseRedirect(self.get_success_url())
Returns the keyword arguments for instantiating the form.
This method is called by the form mixin during form instantiation.
It returns a dictionary of keyword arguments that will be passed to
the form's constructor.
Returns:
dict –
A dictionary of keyword arguments for the form constructor.
Source code in hypha/apply/funds/views/submission_edit.py
defget_form_kwargs(self):""" Returns the keyword arguments for instantiating the form. This method is called by the form mixin during form instantiation. It returns a dictionary of keyword arguments that will be passed to the form's constructor. Returns: dict: A dictionary of keyword arguments for the form constructor. """kwargs=super().get_form_kwargs()instance=kwargs.pop("instance").from_draft()initial=instance.raw_dataforfield_idininstance.file_field_ids:initial.pop(field_id+"-uploads",False)initial[field_id]=self.get_placeholder_file(instance.raw_data.get(field_id))kwargs["initial"]=initialreturnkwargs
defget_form_class(self):""" Returns the form class for the view. This method is called by the view during form instantiation. It returns the form class that will be used to render the form. When trying to save as draft, this method will return a version of form class that doesn't validate required fields while saving. The method also disables any group toggle fields in the form, as they are not supported on edit forms. Returns: class: The form class for the view. """is_draft=Trueif"save"inself.request.POSTelseFalseform_fields=self.object.get_form_fields(draft=is_draft,form_data=self.object.raw_data,user=self.request.user)field_blocks=self.object.get_defined_fields()forfield_blockinfield_blocks:ifisinstance(field_block.block,GroupToggleBlock):# Disable group toggle field as it is not supported on edit forms.form_fields[field_block.id].disabled=Truereturntype("WagtailStreamForm",(self.object.submission_form_class,),form_fields)
defrender_preview(self,request:HttpRequest,form:BaseModelForm)->HttpResponse:"""Gets a rendered preview of a form Creates a new revision on the `ApplicationSubmission`, removes the forms temporary files Args: request: Request used to trigger the preview to be used in the render form: Form to be rendered Returns: An `HttpResponse` containing a preview of the given form """self.object.create_revision(draft=True,by=request.user)messages.success(self.request,_("Draft saved"))# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()context=self.get_context_data()returnrender(request,"funds/application_preview.html",context)
defbuttons(self,)->Generator[Tuple[str,str,str],Tuple[str,str,str],Tuple[str,str,str]]:"""The buttons to be presented to the in the EditView Returns: A generator returning a tuple strings in the format of: (<button type>, <button styling>, <button label>) """ifsettings.SUBMISSION_PREVIEW_REQUIRED:yield("preview","primary",_("Preview and submit"))yield("save","white",_("Save draft"))else:yield("submit","primary",_("Submit"))yield("save","white",_("Save draft"))yield("preview","white",_("Preview"))
defget_on_submit_transition(self,user):"""Gets the transition that should be triggered when a form is submitted. Checks all available status transitions for the current user and returns the first one that has trigger_on_submit=True in its custom settings. Returns: dict: The transition configuration dictionary with trigger_on_submit=True, or None if no matching transition is found. """returnnext((tfortinself.object.get_available_user_status_transitions(user)ift.custom.get("trigger_on_submit",False)),None,)
defform_valid(self,form:BaseModelForm)->HttpResponse:"""Handle the form returned from a `SubmissionEditView`. Determine whether to return a form preview, draft the new edits, or submit and transition the `ApplicationSubmission` object Args: form: The valid form Returns: An `HttpResponse` depending on the actions taken in the edit view """self.object.form_data=form.cleaned_datais_draft=self.object.status==DRAFT_STATE# Handle a preview or a save (aka a draft)if"preview"inself.request.POST:returnself.render_preview(self.request,form)if"save"inself.request.POST:returnself.save_draft_and_refresh_page(form=form)# Handle an application being submitted from a DRAFT_STATE. This includes updating submit_timeifis_draftand"submit"inself.request.POST:self.object.submit_time=timezone.now()ifself.object.round:current_round=self.get_object_fund_current_round()ifcurrent_round:self.object.round=current_roundself.object.save(update_fields=["submit_time","round"])revision=self.object.create_revision(by=self.request.user)submitting_proposal=self.object.phase.nameinSTAGE_CHANGE_ACTIONSifsubmitting_proposal:messenger(MESSAGES.PROPOSAL_SUBMITTED,request=self.request,user=self.request.user,source=self.object,)elifrevisionandnotself.object.status==DRAFT_STATE:messenger(MESSAGES.APPLICANT_EDIT,request=self.request,user=self.request.user,source=self.object,related=revision,)if"submit"inself.request.POST:iftransition:=self.get_on_submit_transition(self.request.user):notify=(not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,)self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=notify,# Use the other notification)# Required for django-file-form: delete temporary files for the new files# uploaded while edit.form.delete_temporary_files()returnHttpResponseRedirect(self.get_success_url())
Returns the keyword arguments for instantiating the form.
This method is called by the form mixin during form instantiation.
It returns a dictionary of keyword arguments that will be passed to
the form's constructor.
Returns:
dict –
A dictionary of keyword arguments for the form constructor.
Source code in hypha/apply/funds/views/submission_edit.py
defget_form_kwargs(self):""" Returns the keyword arguments for instantiating the form. This method is called by the form mixin during form instantiation. It returns a dictionary of keyword arguments that will be passed to the form's constructor. Returns: dict: A dictionary of keyword arguments for the form constructor. """kwargs=super().get_form_kwargs()instance=kwargs.pop("instance").from_draft()initial=instance.raw_dataforfield_idininstance.file_field_ids:initial.pop(field_id+"-uploads",False)initial[field_id]=self.get_placeholder_file(instance.raw_data.get(field_id))kwargs["initial"]=initialreturnkwargs
defget_form_class(self):""" Returns the form class for the view. This method is called by the view during form instantiation. It returns the form class that will be used to render the form. When trying to save as draft, this method will return a version of form class that doesn't validate required fields while saving. The method also disables any group toggle fields in the form, as they are not supported on edit forms. Returns: class: The form class for the view. """is_draft=Trueif"save"inself.request.POSTelseFalseform_fields=self.object.get_form_fields(draft=is_draft,form_data=self.object.raw_data,user=self.request.user)field_blocks=self.object.get_defined_fields()forfield_blockinfield_blocks:ifisinstance(field_block.block,GroupToggleBlock):# Disable group toggle field as it is not supported on edit forms.form_fields[field_block.id].disabled=Truereturntype("WagtailStreamForm",(self.object.submission_form_class,),form_fields)
defdispatch(self,request,*args,**kwargs):submission=self.get_object()# If the requesting user submitted the application, return the Applicant view.# Partners may somtimes be applicants as well.partner_has_access=submission.partners.filter(pk=request.user.pk).exists()ifnotpartner_has_accessandsubmission.user!=request.user:raisePermissionDeniedreturnsuper(ApplicantSubmissionEditView,self).dispatch(request,*args,**kwargs)
defpost(self,*args,**kwargs):form=ProjectCreateForm(self.request.POST,instance=self.submission)ifform.is_valid():project=form.save()# Record activitymessenger(MESSAGES.CREATED_PROJECT,request=self.request,user=self.request.user,source=project,related=project.submission,)# add task for staff to add PAF to the projectadd_task_to_user(code=PROJECT_WAITING_PF,user=project.lead,related_obj=project,)ifself.submission.page.specific.sow_forms.first():# Add SOW task if one exists on the parentadd_task_to_user(code=PROJECT_WAITING_SOW,user=project.lead,related_obj=project,)returnHttpResponseClientRedirect(project.get_absolute_url())returnrender(self.request,"funds/includes/create_project_form.html",context={"form":form,"value":_("Confirm"),"object":self.object},status=400,)
defpost(self,*args,**kwargs):form=UpdateSubmissionLeadForm(self.request.POST,instance=self.object)old_lead=copy(self.object.lead)ifform.is_valid():form.save()messenger(MESSAGES.UPDATE_LEAD,request=self.request,user=self.request.user,source=form.instance,related=old_lead,)returnHttpResponse(status=204,headers={"HX-Trigger":json.dumps({"leadUpdated":None,"showMessage":"Submission Lead updated."}),},)returnrender(self.request,self.template,context={"form":form,"value":_("Update"),"object":self.object},status=400,)
defpost(self,*args,**kwargs):form=UpdateReviewersForm(self.request.POST,user=self.request.user,instance=self.submission)old_reviewers={copy(reviewer)forreviewerinform.instance.assigned.all()}ifform.is_valid():form.save()new_reviewers=set(form.instance.assigned.all())added=new_reviewers-old_reviewersremoved=old_reviewers-new_reviewersmessenger(MESSAGES.REVIEWERS_UPDATED,request=self.request,user=self.request.user,source=self.submission,added=added,removed=removed,)# Update submission status if needed.services.set_status_after_reviewers_assigned(submission=form.instance,updated_by=self.request.user,request=self.request,)returnHttpResponse(status=204,headers={"HX-Trigger":json.dumps({"reviewerUpdated":None,"showMessage":"Reviewers updated."}),},)returnrender(self.request,"funds/includes/update_reviewer_form.html",context={"form":form,"value":_("Update"),"object":self.submission},)