defget_context_data(self,**kwargs):submissions=ApplicationSubmission.objects.exclude_draft()# Getting values is an expensive operation. If there's no valid filters# then `count_values` & `total_value` will be encapsulating all submissions# and should be used rather than recaluclating these values.ifnot(filter:=kwargs.get("filter"))ornotis_filter_empty(filter):submission_count=kwargs.get("count_values")submission_sum=kwargs.get("total_value")else:submission_count=submissions.count()submission_value=submissions.current().value()submission_sum=format_submission_sum_value(submission_value)submission_undetermined_count=submissions.undetermined().count()review_my_count=submissions.reviewed_by(self.request.user).count()submission_accepted=submissions.current_accepted()submission_accepted_value=submission_accepted.value()submission_accepted_sum=format_submission_sum_value(submission_accepted_value)submission_accepted_count=submission_accepted.count()reviews=Review.objects.submitted()review_count=reviews.count()review_my_score=reviews.by_user(self.request.user).score()returnsuper().get_context_data(submission_undetermined_count=submission_undetermined_count,review_my_count=review_my_count,submission_sum=submission_sum,submission_count=submission_count,submission_accepted_count=submission_accepted_count,submission_accepted_sum=submission_accepted_sum,review_count=review_count,review_my_score=review_my_score,**kwargs,)
defget_object(self):# Make sure the form instance, bound at the parent class level, is the same as the# value we work with on the class.# If we don't have self.object, bind the parent instance to it. This value will then# be used by the form. Any further calls to get_object will get a new instance of the objectifnothasattr(self,"object"):parent_object=self.get_parent_object()ifisinstance(parent_object,self.model):returnparent_objectreturnsuper().get_object()
defget_context_data(self,**kwargs):# Use the previous context but override the validated formform=kwargs.pop("form")kwargs.update(self.kwargs["context"])kwargs.update(**{self.context_name:form})returnsuper().get_context_data(**kwargs)
defcontribute_form(self,parent):self.parent=parent# We do not want to bind any forms generated this way# pretend we are doing a get request to avoid passing data to formsold_method=Noneifself.request.methodin("POST","PUT"):old_method=self.request.methodself.request.method="GET"form=self.get_form()ifold_method:self.request.method=old_methodreturnself.context_name,form
defform_invalid(self,form):messages.error(self.request,mark_safe(_("Sorry something went wrong")+form.errors.as_ul()),)returnsuper().form_invalid(form)
defget_object(self):# Make sure the form instance, bound at the parent class level, is the same as the# value we work with on the class.# If we don't have self.object, bind the parent instance to it. This value will then# be used by the form. Any further calls to get_object will get a new instance of the objectifnothasattr(self,"object"):parent_object=self.get_parent_object()ifisinstance(parent_object,self.model):returnparent_objectreturnsuper().get_object()
defget_context_data(self,**kwargs):# Use the previous context but override the validated formform=kwargs.pop("form")kwargs.update(self.kwargs["context"])kwargs.update(**{self.context_name:form})returnsuper().get_context_data(**kwargs)
defcontribute_form(self,parent):self.parent=parent# We do not want to bind any forms generated this way# pretend we are doing a get request to avoid passing data to formsold_method=Noneifself.request.methodin("POST","PUT"):old_method=self.request.methodself.request.method="GET"form=self.get_form()ifold_method:self.request.method=old_methodreturnself.context_name,form
defform_invalid(self,form):messages.error(self.request,mark_safe(_("Sorry something went wrong")+form.errors.as_ul()),)returnsuper().form_invalid(form)
defget_object(self):# Make sure the form instance, bound at the parent class level, is the same as the# value we work with on the class.# If we don't have self.object, bind the parent instance to it. This value will then# be used by the form. Any further calls to get_object will get a new instance of the objectifnothasattr(self,"object"):parent_object=self.get_parent_object()ifisinstance(parent_object,self.model):returnparent_objectreturnsuper().get_object()
defget_context_data(self,**kwargs):# Use the previous context but override the validated formform=kwargs.pop("form")kwargs.update(self.kwargs["context"])kwargs.update(**{self.context_name:form})returnsuper().get_context_data(**kwargs)
defcontribute_form(self,parent):self.parent=parent# We do not want to bind any forms generated this way# pretend we are doing a get request to avoid passing data to formsold_method=Noneifself.request.methodin("POST","PUT"):old_method=self.request.methodself.request.method="GET"form=self.get_form()ifold_method:self.request.method=old_methodreturnself.context_name,form
defform_invalid(self,form):messages.error(self.request,mark_safe(_("Sorry something went wrong")+form.errors.as_ul()),)returnsuper().form_invalid(form)
defget_object(self):# Make sure the form instance, bound at the parent class level, is the same as the# value we work with on the class.# If we don't have self.object, bind the parent instance to it. This value will then# be used by the form. Any further calls to get_object will get a new instance of the objectifnothasattr(self,"object"):parent_object=self.get_parent_object()ifisinstance(parent_object,self.model):returnparent_objectreturnsuper().get_object()
defget_context_data(self,**kwargs):# Use the previous context but override the validated formform=kwargs.pop("form")kwargs.update(self.kwargs["context"])kwargs.update(**{self.context_name:form})returnsuper().get_context_data(**kwargs)
defcontribute_form(self,parent):self.parent=parent# We do not want to bind any forms generated this way# pretend we are doing a get request to avoid passing data to formsold_method=Noneifself.request.methodin("POST","PUT"):old_method=self.request.methodself.request.method="GET"form=self.get_form()ifold_method:self.request.method=old_methodreturnself.context_name,form
defform_valid(self,form):# If a user without archive edit access is somehow able to access batch archive submissions# (ie. they were looking at the submission list when permissions changed) "refresh" the pageifnotcan_alter_archived_submissions(self.request.user):returnHttpResponseRedirect(self.request.path)submissions=form.cleaned_data["submissions"]services.bulk_archive_submissions(submissions=submissions,user=self.request.user,request=self.request,)returnsuper().form_valid(form)
defform_invalid(self,form):messages.error(self.request,mark_safe(_("Sorry something went wrong")+form.errors.as_ul()),)returnsuper().form_invalid(form)
defget_object(self):# Make sure the form instance, bound at the parent class level, is the same as the# value we work with on the class.# If we don't have self.object, bind the parent instance to it. This value will then# be used by the form. Any further calls to get_object will get a new instance of the objectifnothasattr(self,"object"):parent_object=self.get_parent_object()ifisinstance(parent_object,self.model):returnparent_objectreturnsuper().get_object()
defget_context_data(self,**kwargs):# Use the previous context but override the validated formform=kwargs.pop("form")kwargs.update(self.kwargs["context"])kwargs.update(**{self.context_name:form})returnsuper().get_context_data(**kwargs)
defcontribute_form(self,parent):self.parent=parent# We do not want to bind any forms generated this way# pretend we are doing a get request to avoid passing data to formsold_method=Noneifself.request.methodin("POST","PUT"):old_method=self.request.methodself.request.method="GET"form=self.get_form()ifold_method:self.request.method=old_methodreturnself.context_name,form
defform_valid(self,form):submissions=form.cleaned_data["submissions"]transitions=form.cleaned_data.get("action")try:redirect=BatchDeterminationCreateView.should_redirect(self.request,submissions,transitions)exceptValueErrorase:messages.warning(self.request,"Could not determine: "+str(e))returnself.form_invalid(form)else:ifredirect:returnredirectfailed=[]phase_changes={}forsubmissioninsubmissions:valid_actions={actionforaction,_insubmission.get_actions_for_user(self.request.user)}old_phase=submission.phasetry:transition=(valid_actions&set(transitions)).pop()submission.perform_transition(transition,self.request.user,request=self.request,notify=False,)except(PermissionDenied,KeyError):failed.append(submission)else:phase_changes[submission.id]=old_phaseiffailed:messages.warning(self.request,_("Failed to update: ")+", ".join(str(submission)forsubmissioninfailed),)succeeded_submissions=submissions.exclude(id__in=[submission.idforsubmissioninfailed])messenger(MESSAGES.BATCH_TRANSITION,user=self.request.user,request=self.request,sources=succeeded_submissions,related=phase_changes,)ready_for_review=[phaseforphaseintransitionsifphaseinreview_statuses]ifready_for_review:messenger(MESSAGES.BATCH_READY_FOR_REVIEW,user=self.request.user,request=self.request,sources=succeeded_submissions.filter(status__in=ready_for_review),)returnsuper().form_valid(form)
defget_queryset(self):""" If use_settings variable is set for ReviewerSettings use settings parameters to filter submissions or return only reviewed_by as it was by default. """reviewer_settings=ReviewerSettings.for_request(self.request)ifreviewer_settings.use_settings:return(super().get_queryset().for_reviewer_settings(self.request.user,reviewer_settings).order_by("-submit_time"))returnsuper().get_queryset().reviewed_by(self.request.user)
defget_queryset(self):ifself.request.GET.get("archived"):# if archived is in param, let archived filter handle the queryset as per its value.submissions=(self.filterset_class._meta.model.objects.include_archive().for_table(self.request.user))else:submissions=self.filterset_class._meta.model.objects.current().for_table(self.request.user)ifnotcan_access_drafts(self.request.user):submissions=submissions.exclude_draft()returnsubmissions
defget_queryset(self):""" If use_settings variable is set for ReviewerSettings use settings parameters to filter submissions or return only reviewed_by as it was by default. """reviewer_settings=ReviewerSettings.for_request(self.request)ifreviewer_settings.use_settings:return(super().get_queryset().for_reviewer_settings(self.request.user,reviewer_settings).order_by("-submit_time"))returnsuper().get_queryset().reviewed_by(self.request.user)
defdispatch(self,request,*args,**kwargs):self.status=kwargs.get("status")try:status_data=self.status_mapping[self.status]exceptKeyError:raiseHttp404(_("No statuses match the requested value"))fromNoneself.status_name=status_data["name"]self.statuses=status_data["statuses"]returnsuper().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_PAF,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},)
defdispatch(self,request,*args,**kwargs):self.submission=get_object_or_404(ApplicationSubmission,id=kwargs.get("pk"))ifnotrequest.user.is_org_faculty:messages.warning(self.request,"User attempted to translate submission but is not org faculty",)returnHttpResponseRedirect(self.submission.get_absolute_url())returnsuper(TranslateSubmissionView,self).dispatch(request,*args,**kwargs)
defget(self,request:HttpRequest,*args,**kwargs)->HttpResponse:self.object=self.get_object()extra_context={}# Check for language params - if they exist and are valid then update the contextiflang_params:=get_translation_params(request=request):from_lang,to_lang=lang_paramstry:self.object.form_data=translate_application_form_data(self.object,from_lang,to_lang)extra_context.update({"from_lang_name":get_lang_name(from_lang),"to_lang_name":get_lang_name(to_lang),})exceptValueError:# Language package isn't valid or installed, redirect to the submission w/o paramsreturnredirect(self.object.get_absolute_url())context=self.get_context_data(object=self.object,**extra_context)returnself.render_to_response(context)
defget_context_data(self,**kwargs):extra={# Do not prefetch on the related_object__author as the models# are not homogeneous and this will fail"comments":services.get_related_comments_for_user(self.object,self.request.user),"comments_count":services.get_comment_count(self.object,self.request.user),}returnsuper().get_context_data(**extra,**kwargs)
defdispatch(self,request,*args,**kwargs):submission=self.get_object()# If the requesting user submitted the application, return the Applicant view.# Reviewers may sometimes be applicants as well.ifsubmission.user==request.user:returnApplicantSubmissionDetailView.as_view()(request,*args,**kwargs)ifsubmission.status==DRAFT_STATE:raiseHttp404permission,_=has_permission("submission_view",request.user,object=submission,raise_exception=True)reviewer_settings=ReviewerSettings.for_request(request)ifreviewer_settings.use_settings:queryset=ApplicationSubmission.objects.for_reviewer_settings(request.user,reviewer_settings)# Reviewer can't view submission which is not listed in ReviewerSubmissionsTableifnotqueryset.filter(id=submission.id).exists():raisePermissionDeniedreturnsuper().dispatch(request,*args,**kwargs)
defget_context_data(self,**kwargs):extra={# Do not prefetch on the related_object__author as the models# are not homogeneous and this will fail"comments":services.get_related_comments_for_user(self.object,self.request.user),"comments_count":services.get_comment_count(self.object,self.request.user),}returnsuper().get_context_data(**extra,**kwargs)
defdispatch(self,request,*args,**kwargs):submission=self.get_object()permission,_=has_permission("submission_view",request.user,object=submission,raise_exception=True)# If the requesting user submitted the application, return the Applicant view.# Partners may sometimes be applicants as well.ifsubmission.user==request.user:returnApplicantSubmissionDetailView.as_view()(request,*args,**kwargs)# Only allow partners in the submission they are added as partnerspartner_has_access=submission.partners.filter(pk=request.user.pk).exists()ifnotpartner_has_access:raisePermissionDeniedifsubmission.status==DRAFT_STATE:raiseHttp404returnsuper().dispatch(request,*args,**kwargs)
defget_context_data(self,**kwargs):extra={# Do not prefetch on the related_object__author as the models# are not homogeneous and this will fail"comments":services.get_related_comments_for_user(self.object,self.request.user),"comments_count":services.get_comment_count(self.object,self.request.user),}returnsuper().get_context_data(**extra,**kwargs)
defdispatch(self,request,*args,**kwargs):submission=self.get_object()permission,_=has_permission("submission_view",request.user,object=submission,raise_exception=True)# If the requesting user submitted the application, return the Applicant view.# Reviewers may sometimes be applicants as well.ifsubmission.user==request.user:returnApplicantSubmissionDetailView.as_view()(request,*args,**kwargs)# Only allow community reviewers in submission with a community review state.ifnotsubmission.community_review:raisePermissionDeniedifsubmission.status==DRAFT_STATE:raiseHttp404returnsuper().dispatch(request,*args,**kwargs)
defget_context_data(self,**kwargs):extra={# Do not prefetch on the related_object__author as the models# are not homogeneous and this will fail"comments":services.get_related_comments_for_user(self.object,self.request.user),"comments_count":services.get_comment_count(self.object,self.request.user),}returnsuper().get_context_data(**extra,**kwargs)
defdispatch(self,request,*args,**kwargs):submission=self.get_object()permission,_=has_permission("submission_view",request.user,object=submission,raise_exception=True)# This view is only for applicants.ifsubmission.user!=request.user:raisePermissionDeniedreturnsuper().dispatch(request,*args,**kwargs)
defpeeked(self,submission):messenger(MESSAGES.OPENED_SEALED,request=self.request,user=self.request.user,source=submission,)self.request.session.setdefault("peeked",{})[str(submission.id)]=True# Dictionary updates do not trigger session saves. Force updateself.request.session.modified=True
@classmethoddefround_is_sealed(cls,submission):try:returnsubmission.round.specific.is_sealedexceptAttributeError:# Its a lab - cant be sealedreturnFalse
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"))
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,)action=set(self.request.POST.keys())&set(self.transitions.keys())try:transition=self.transitions[action.pop()]exceptKeyError:passelse:self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,# 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.
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)
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,)action=set(self.request.POST.keys())&set(self.transitions.keys())try:transition=self.transitions[action.pop()]exceptKeyError:passelse:self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,# 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.
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"))
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,)action=set(self.request.POST.keys())&set(self.transitions.keys())try:transition=self.transitions[action.pop()]exceptKeyError:passelse:self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,# 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.
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"))
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,)action=set(self.request.POST.keys())&set(self.transitions.keys())try:transition=self.transitions[action.pop()]exceptKeyError:passelse:self.object.perform_transition(transition.target,self.request.user,request=self.request,notify=not(revisionorsubmitting_proposal)orself.object.status==DRAFT_STATE,# 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.
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)
defget_queryset(self):"""Get a queryset of all valid `ApplicationRevision`s that can be compared for the current submission This excludes draft & preview revisions unless draft(s) are the only existing revisions, in which the last draft will be returned in a QuerySet Returns: An [`ApplicationRevision`][hypha.apply.funds.models.ApplicationRevision] QuerySet """self.submission=get_object_or_404(ApplicationSubmission,id=self.kwargs["submission_pk"])revisions=self.model.objects.filter(submission=self.submission).exclude(draft__isnull=False,live__isnull=True)filtered_revisions=revisions.filter(is_draft=False)# An edge case for when an instance has `SUBMISSIONS_DRAFT_ACCESS_STAFF=True`# and a staff member tries to view the revisions of the draft.iflen(filtered_revisions)<1:self.queryset=self.model.objects.filter(id=revisions.last().id)else:self.queryset=filtered_revisionsreturnsuper().get_queryset()
View for deleting submissions with confirmation modal.
After successful deletion:
- Redirects applicants to their dashboard
- Redirects staff to the submissions list
- Creates delete notification unless author deleting own draft
defform_valid(self,form):submission=self.get_object()# Notify unless author delete own draft.ifsubmission.status!=DRAFT_STATEandsubmission.user!=self.request.user:messenger(MESSAGES.DELETE_SUBMISSION,user=self.request.user,request=self.request,source=submission,)# Delete NEW_SUBMISSION event for this particular submission, if any.# Otherwise, the submission deletion will fail.Event.objects.filter(type=MESSAGES.NEW_SUBMISSION,object_id=submission.id).delete()# delete submission and redirect to success urlreturnsuper().form_valid(form)
defget_table_data(self):table_data=super().get_table_data()reviewer_roles=ReviewerRole.objects.all().order_by("order")fordataintable_data:fori,_roleinenumerate(reviewer_roles):# Only setting column name with dummy value 0.# Actual value will be set in RoleColumn render method.setattr(data,f"role{i}",0)returntable_data