Coverage for src/mesh/views/views_base.py: 33%

46 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-04-28 07:45 +0000

1from typing import Any 

2 

3from django.contrib import messages 

4from django.http import HttpRequest, HttpResponse, HttpResponseRedirect 

5from django.utils import timezone 

6from django.utils.translation import gettext_lazy as _ 

7from ptf.url_utils import add_query_parameters_to_url 

8 

9from mesh.views.forms.base_forms import ( 

10 SUBMIT_QUERY_PARAMETER, 

11 FormAction, 

12 SubmittableModelForm, 

13) 

14 

15 

16class SubmittableModelFormMixin: 

17 """ 

18 View mixin for submittable model forms. 

19 To be used together with: 

20 - RoleMixin 

21 - TemplateResponseMixin 

22 - FormMixin - The form class must inherit SubmittableModelForm 

23 

24 This uses the same view for saving the form (and model), submitting the form 

25 and confirming the submission of the form. 

26 The status is known through the booleans `_submit` and `_submit_confirm`. 

27 The `form_pre_save` and `form_post_save` hooks enable additional processing. 

28 

29 Normal workflow: 

30 1. The user saves the form any number of times (draft). 

31 2. When ready, the user clicks the "Submit" button 

32 3. The user is redirected to a recap of the form to be submitted (default: same 

33 route & view with the disabled form). 

34 4. The user confirms the submission of the form. 

35 5. The underlying model gets updated: `submitted=True` & `date_submitted=now` 

36 

37 Steps 4 and 5 have a simple implementation in this view mixin. 

38 These steps can be externalized to a distinct view. In that case, don't forget 

39 to update the model's `submitted` and `date_submitted` fields ! 

40 """ 

41 

42 # Whether the request (POST) is the submission of the form/model. 

43 _submit = False 

44 # Whether the request (GET or POST) is the confirmation of the submission 

45 # of the form/model. 

46 _submit_confirm = False 

47 # Whether to add a message when redirecting to the confirmation URL. 

48 add_confirm_message = True 

49 # Only for typing. This attribute should be set somewhere else (usually by the 

50 # RoleMixin) 

51 request: HttpRequest 

52 

53 def submit_url(self) -> str: 

54 """ 

55 URL to redirect to to when the user submits the form. 

56 

57 The default is the current URL with an added query parameter indicating 

58 the confirmation request. 

59 """ 

60 return add_query_parameters_to_url( 

61 self.request.build_absolute_uri(), {SUBMIT_QUERY_PARAMETER: ["true"]} 

62 ) 

63 

64 def form_pre_save(self, form: SubmittableModelForm) -> None: 

65 """ 

66 Hook called before `form_valid` (wich saves the underlying model to DB) 

67 when posting the form. 

68 """ 

69 pass 

70 

71 def form_post_save( 

72 self, form: SubmittableModelForm, original_response: HttpResponse 

73 ) -> HttpResponse: 

74 """ 

75 Hook called after `form_valid` (wich saves the underlying model to DB) 

76 when posting the form. 

77 

78 This must return an HTTP response. 

79 """ 

80 return original_response 

81 

82 def form_valid(self, form: SubmittableModelForm): 

83 """ 

84 Checks whether the POST request is a simple save, a submit or 

85 a submit confirmation. 

86 """ 

87 if FormAction.SUBMIT.value in self.request.POST: 

88 self._submit = True 

89 elif ( 

90 FormAction.SUBMIT_CONFIRM.value in self.request.POST 

91 and form.instance.submitted is False 

92 ): 

93 self._submit_confirm = True 

94 form.instance.submitted = True 

95 if form.instance.date_submitted is None: 

96 form.instance.date_submitted = timezone.now() 

97 

98 self.form_pre_save(form) 

99 

100 resp = super().form_valid(form) # type:ignore 

101 

102 if self._submit: 

103 resp = HttpResponseRedirect(self.submit_url()) 

104 if self.add_confirm_message: 

105 messages.info(self.request, _("Please confirm the submission of the form.")) 

106 

107 resp = self.form_post_save(form, resp) 

108 

109 return resp 

110 

111 def get_context_data(self, *args, **kwargs): 

112 context = super().get_context_data(*args, **kwargs) # type:ignore 

113 context["submit_confirm"] = self._submit_confirm 

114 return context 

115 

116 def get(self, request, *args, **kwargs): 

117 """ 

118 Overloads the default get to catch whether it's a submit confirmation 

119 request. 

120 """ 

121 self._submit_confirm = self.request.GET.get(SUBMIT_QUERY_PARAMETER, None) == "true" 

122 return super().get(request, *args, **kwargs) # type:ignore 

123 

124 def get_form_kwargs(self) -> dict[str, Any]: 

125 kwargs = super().get_form_kwargs() # type:ignore 

126 if self.request.GET.get(SUBMIT_QUERY_PARAMETER, None) == "true": 

127 kwargs[SUBMIT_QUERY_PARAMETER] = True 

128 return kwargs