Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# (c) 2018-2020 

2# MPIB <https://www.mpib-berlin.mpg.de/>, 

3# MPI-CBS <https://www.cbs.mpg.de/>, 

4# MPIP <http://www.psych.mpg.de/> 

5# 

6# This file is part of Castellum. 

7# 

8# Castellum is free software; you can redistribute it and/or modify it 

9# under the terms of the GNU Affero General Public License as published 

10# by the Free Software Foundation; either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# Castellum is distributed in the hope that it will be useful, but 

14# WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

16# Affero General Public License for more details. 

17# 

18# You should have received a copy of the GNU Affero General Public 

19# License along with Castellum. If not, see 

20# <http://www.gnu.org/licenses/>. 

21 

22from django.conf import settings 

23from django.contrib import messages 

24from django.forms import modelformset_factory 

25from django.http import HttpResponseBadRequest 

26from django.shortcuts import get_object_or_404 

27from django.shortcuts import redirect 

28from django.template.response import TemplateResponse 

29from django.urls import reverse 

30from django.utils.translation import gettext_lazy as _ 

31from django.views.generic import DeleteView 

32from django.views.generic import ListView 

33from django.views.generic import UpdateView 

34from django.views.generic import View 

35 

36from castellum.castellum_auth.mixins import PermissionRequiredMixin 

37from castellum.recruitment import filter_queries 

38from castellum.recruitment.forms import SubjectFilterAddForm 

39from castellum.recruitment.forms import SubjectFilterForm 

40from castellum.recruitment.forms import SubjectFilterFormSet 

41from castellum.recruitment.models import AttributeDescription 

42from castellum.recruitment.models import SubjectFilter 

43from castellum.recruitment.models import SubjectFilterGroup 

44from castellum.subjects.models import Subject 

45from castellum.utils.views import ReadonlyMixin 

46 

47from ..mixins import StudyMixin 

48from ..models import Study 

49 

50 

51class CustomFilterMixin: 

52 """Should be called *after* PermissionRequiredMixin.""" 

53 

54 def dispatch(self, request, *args, **kwargs): 

55 if self.study.custom_filter: 55 ↛ 56line 55 didn't jump to line 56, because the condition on line 55 was never true

56 return TemplateResponse( 

57 request=self.request, 

58 template='studies/filtergroup_blocked.html', 

59 context={ 

60 'study': self.study, 

61 'total_count': Subject.objects.count(), 

62 'count': ( 

63 Subject.objects.filter( 

64 filter_queries.study(self.object), 

65 filter_queries.has_consent(), 

66 ).count() 

67 ), 

68 }, 

69 ) 

70 return super().dispatch(request, *args, **kwargs) 

71 

72 

73class FilterMixin(StudyMixin, PermissionRequiredMixin, CustomFilterMixin): 

74 tab = 'recruitmentsettings' 

75 subtab = 'filters' 

76 

77 def get_permission_required(self): 

78 permission_required = set(['studies.change_study']) 

79 permission_required.update(super().get_permission_required()) 

80 

81 max_privacy_level = self.study.get_filter_max_privacy_level() 

82 if max_privacy_level: 

83 permission_required.add('castellum_auth.privacy_level_{}'.format(max_privacy_level)) 

84 

85 return permission_required 

86 

87 

88class FilterGroupListView(FilterMixin, ListView): 

89 model = SubjectFilterGroup 

90 template_name = 'studies/filtergroup_advanced.html' 

91 permission_required = 'recruitment.view_subjectfilter' 

92 

93 def get_queryset(self): 

94 qs = super().get_queryset() 

95 return qs.filter(study=self.study) 

96 

97 def get_context_data(self, **kwargs): 

98 context = super().get_context_data(**kwargs) 

99 context['total_count'] = Subject.objects.count() 

100 context['count'] = ( 

101 Subject.objects.filter( 

102 filter_queries.study(self.study), 

103 filter_queries.has_consent() 

104 ).count() 

105 ) 

106 context['expected_subject_factor'] = settings.CASTELLUM_EXPECTED_SUBJECT_FACTOR 

107 context['expected_subject_count'] = ( 

108 self.study.min_subject_count * settings.CASTELLUM_EXPECTED_SUBJECT_FACTOR 

109 ) 

110 return context 

111 

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

113 if self.study.advanced_filtering: 

114 self.object_list = self.get_queryset() 

115 context = self.get_context_data() 

116 return self.render_to_response(context) 

117 else: 

118 group, __ = SubjectFilterGroup.objects.get_or_create(study=self.study) 

119 return redirect(group.get_absolute_url()) 

120 

121 

122class FilterGroupCreateView(FilterMixin, View): 

123 permission_required = 'recruitment.change_subjectfilter' 

124 study_status = [Study.EDIT] 

125 

126 def post(self, *args, **kwargs): 

127 group = SubjectFilterGroup.objects.create(study=self.study) 

128 return redirect(group.get_absolute_url()) 

129 

130 

131class FilterGroupUpdateView(FilterMixin, ReadonlyMixin, UpdateView): 

132 model = SubjectFilterGroup 

133 fields = [] 

134 template_name = "studies/filtergroup.html" 

135 permission_required = 'recruitment.change_subjectfilter' 

136 

137 def get_object(self): 

138 return get_object_or_404(SubjectFilterGroup, study=self.study, pk=self.kwargs['pk']) 

139 

140 def post(self, request, *args, **kwargs): 

141 self.object = self.get_object() 

142 if self.get_readonly(): 142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true

143 return HttpResponseBadRequest() 

144 form = self.get_form() 

145 formset = self.get_formset() 

146 if form.is_valid() and formset.is_valid(): 

147 return self.form_valid(form, formset) 

148 else: 

149 return self.form_invalid(form, formset) 

150 

151 def form_invalid(self, form, formset): 

152 return self.render_to_response(self.get_context_data(form=form, formset=formset)) 

153 

154 def form_valid(self, form, formset): 

155 messages.success(self.request, _('Filter update successfull!')) 

156 formset.save() 

157 return super().form_valid(form) 

158 

159 def get_formset(self): 

160 formset_class = modelformset_factory( 

161 SubjectFilter, 

162 form=SubjectFilterForm, 

163 formset=SubjectFilterFormSet, 

164 extra=0, 

165 can_delete=True, 

166 ) 

167 return formset_class( 

168 queryset=self.object.subjectfilter_set.all(), 

169 data=self.get_form_kwargs().get('data'), 

170 ) 

171 

172 def get_readonly(self): 

173 return self.study.status != Study.EDIT 

174 

175 def get_inaccessible_attributedescriptions(self): 

176 min_privacy_level = 2 

177 for member in self.study.members.all(): 

178 if member.has_perm('recruitment.view_subjectfilter', obj=self.study): 178 ↛ 177line 178 didn't jump to line 177, because the condition on line 178 was never false

179 privacy_level = member.get_privacy_level(obj=self.study) 

180 if privacy_level < min_privacy_level: 180 ↛ 177line 180 didn't jump to line 177, because the condition on line 180 was never false

181 min_privacy_level = privacy_level 

182 

183 return AttributeDescription.objects.filter( 

184 subjectfilter__group__study=self.study, 

185 privacy_level_read__gt=min_privacy_level, 

186 ).distinct() 

187 

188 def get_context_data(self, **kwargs): 

189 context = super().get_context_data(**kwargs) 

190 

191 context['count'] = self.object.get_matches().count() 

192 

193 context['total_count'] = Subject.objects.count() 

194 context['expected_subject_factor'] = settings.CASTELLUM_EXPECTED_SUBJECT_FACTOR 

195 context['expected_subject_count'] = ( 

196 self.study.min_subject_count * settings.CASTELLUM_EXPECTED_SUBJECT_FACTOR 

197 ) 

198 

199 context['inaccessible'] = list(self.get_inaccessible_attributedescriptions()) 

200 

201 context['add_form'] = SubjectFilterAddForm(user=self.request.user) 

202 context['templates'] = {} 

203 for description in AttributeDescription.objects.all(): 

204 context['templates'][description.pk] = SubjectFilterForm( 

205 initial={'description': description}, prefix='{prefix}' 

206 ) 

207 

208 if 'formset' not in context: 

209 context['formset'] = self.get_formset() 

210 

211 return context 

212 

213 

214class FilterGroupDeleteView(FilterMixin, DeleteView): 

215 model = SubjectFilterGroup 

216 template_name = 'studies/filtergroup_confirm_delete.html' 

217 permission_required = 'recruitment.change_subjectfilter' 

218 study_status = [Study.EDIT] 

219 

220 def get_object(self): 

221 return get_object_or_404(SubjectFilterGroup, study=self.study, pk=self.kwargs['pk']) 

222 

223 def get_success_url(self): 

224 return reverse('studies:filtergroup-index', args=[self.study.pk]) 

225 

226 

227class FilterGroupDuplicateView(FilterMixin, View): 

228 permission_required = 'recruitment.change_subjectfilter' 

229 study_status = [Study.EDIT] 

230 

231 def post(self, request, study_pk, pk): 

232 original = get_object_or_404(SubjectFilterGroup, study=self.study, pk=pk) 

233 SubjectFilterGroup.objects.clone(original) 

234 messages.success(request, _( 

235 'The filters have been duplicated. You can now edit the duplicate.')) 

236 return redirect('studies:filtergroup-index', self.study.pk)