Coverage for datacite/forms.py: 97%

138 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 15:38 +0000

1from typing import Any 

2 

3from crispy_forms.helper import FormHelper # type: ignore[import-untyped] 

4from django import forms 

5from django.core.exceptions import ValidationError 

6from django.core.validators import MinLengthValidator 

7from extra_views import InlineFormSetFactory # type: ignore[import-untyped] 

8from extra_views.generic import ( # type: ignore[import-untyped] 

9 GenericInlineFormSetFactory, 

10) 

11 

12from datacite.models import ( 

13 LANGUAGES, 

14 Description, 

15 Format, 

16 Funding, 

17 Geolocation, 

18 Identifier, 

19 Metadata, 

20 MetadataContributor, 

21 Participant, 

22 ParticipantTypes, 

23 Rights, 

24 Title, 

25 TitleTypes, 

26) 

27 

28 

29class DalimaFormHelper(FormHelper): 

30 def __init__(self, *args: Any, **kwargs: Any) -> None: 

31 super().__init__(*args, **kwargs) 

32 

33 self.form_tag = False # rendering several forms 

34 self.form_class = "form-horizontal" 

35 self.label_class = "col-sm-4" 

36 self.field_class = "col-sm-8" 

37 

38 

39class MetadataFormHelper(FormHelper): 

40 def __init__(self, *args: Any, **kwargs: Any) -> None: 

41 super().__init__(*args, **kwargs) 

42 

43 self.form_tag = False # rendering several forms 

44 self.form_class = "form-horizontal" 

45 self.label_class = "col-md-2" 

46 self.field_class = "col-md-6" 

47 

48 

49class InlineFormSetHelper(FormHelper): 

50 def __init__(self, *args: Any, **kwargs: Any) -> None: 

51 super().__init__(*args, **kwargs) 

52 self.form_tag = False 

53 self.template = "bootstrap5/table_inline_formset.html" 

54 

55 

56class DalimaForm(forms.ModelForm): 

57 def __init__(self, *args: Any, **kwargs: Any) -> None: 

58 super().__init__(*args, **kwargs) 

59 self.helper = DalimaFormHelper() 

60 

61 

62class ContributorForm(DalimaForm): 

63 class Meta: 

64 model = MetadataContributor 

65 fields = ["contributor", "contributor_type"] 

66 

67 

68class DescriptionForm(DalimaForm): 

69 class Meta: 

70 model = Description 

71 fields = ["description", "description_type", "lang"] 

72 

73 

74class IdentifierForm(DalimaForm): 

75 identifier = forms.URLField() 

76 

77 class Meta: 

78 model = Identifier 

79 fields = [ 

80 "identifier", 

81 "scheme", 

82 ] 

83 labels = {"scheme": "Identifier scheme"} 

84 

85 

86class FormatForm(DalimaForm): 

87 class Meta: 

88 model = Format 

89 fields = ["format"] 

90 

91 

92class FundingForm(DalimaForm): 

93 class Meta: 

94 model = Funding 

95 fields = ["funder", "award_title", "award_number", "award_uri"] 

96 

97 

98class FunderForm(DalimaForm): 

99 lang = forms.CharField( 

100 validators=[MinLengthValidator(1)], 

101 widget=forms.Select(choices=LANGUAGES), 

102 ) 

103 name_type = forms.CharField( 

104 widget=forms.HiddenInput, 

105 initial=ParticipantTypes.ORGANIZATION, 

106 ) 

107 is_funder = forms.CharField( 

108 widget=forms.HiddenInput, 

109 initial=True, 

110 ) 

111 

112 class Meta: 

113 model = Participant 

114 fields = ["name_type", "name", "lang", "is_funder"] 

115 

116 

117class CreatorForm(DalimaForm): 

118 lang = forms.CharField( 

119 validators=[MinLengthValidator(1)], 

120 widget=forms.Select(choices=LANGUAGES), 

121 ) 

122 name_type = forms.CharField( 

123 validators=[MinLengthValidator(1)], 

124 widget=forms.Select(choices=ParticipantTypes), 

125 ) 

126 

127 class Meta: 

128 model = Participant 

129 fields = [ 

130 "name_type", 

131 "name", 

132 "given_name", 

133 "family_name", 

134 "lang", 

135 "affiliations", 

136 ] 

137 labels = {"name_type": "Creator type"} 

138 

139 

140class RightsForm(DalimaForm): 

141 lang = forms.CharField( 

142 validators=[MinLengthValidator(1)], 

143 widget=forms.Select(choices=LANGUAGES), 

144 ) 

145 

146 class Meta: 

147 model = Rights 

148 fields = [ 

149 "lang", 

150 "rights_uri", 

151 "rights", 

152 ] 

153 

154 

155class DateInput(forms.DateInput): 

156 """ 

157 forms.DateInput renders as input of type text. 

158 In order to force a date input we need to override it 

159 """ 

160 

161 input_type = "date" 

162 

163 def __init__(self) -> None: 

164 super().__init__(format="%Y-%m-%d") 

165 

166 

167class MetadataForm(forms.ModelForm): 

168 fundings = forms.ModelMultipleChoiceField( 

169 queryset=Funding.objects.all().select_related("funder"), required=False 

170 ) 

171 

172 class Meta: 

173 model = Metadata 

174 fields = [ 

175 "url", 

176 "publication_year", 

177 "creators", 

178 "formats", 

179 "rights", 

180 "available", 

181 "collected_start", 

182 "collected_end", 

183 "issued", 

184 "size_information", 

185 "size_increment", 

186 "size_total", 

187 "fundings", 

188 ] 

189 widgets = { 

190 "collected_start": DateInput, 

191 "collected_end": DateInput, 

192 "issued": DateInput, 

193 "available": DateInput, 

194 } 

195 labels = { 

196 "url": "Landing page", 

197 "collected_start": "Collection start date", 

198 "collected_end": "Collection end date", 

199 "issued": "Issued date", 

200 "available": "Data availability date", 

201 "fundings": "Funding references", 

202 } 

203 

204 def __init__(self, *args: Any, **kwargs: Any) -> None: 

205 super().__init__(*args, **kwargs) 

206 self.helper = MetadataFormHelper() 

207 

208 def clean(self) -> None: 

209 super().clean() 

210 # In order to avoid wrong input when embargoed missing. 

211 embargoed_rights_selected = ( 

212 "rights" in self.cleaned_data 

213 and self.cleaned_data["rights"] 

214 .filter(rights__icontains="embargoed") 

215 .count() 

216 > 0 

217 ) 

218 if not embargoed_rights_selected and "available" in self.cleaned_data: 

219 self.cleaned_data["available"] = None 

220 

221 

222class BaseTitleFormSet(forms.BaseInlineFormSet): 

223 one_main_title_error_msg = "Should only be a Main Title." 

224 

225 def clean(self) -> None: 

226 clean = super().clean() 

227 # count number_of_main_titles 

228 number_of_main_titles = 0 

229 for form in self.forms: 

230 if not ( 230 ↛ 237line 230 didn't jump to line 237 because the condition on line 230 was never true

231 self.can_delete 

232 and form.cleaned_data.get(forms.formsets.DELETION_FIELD_NAME, False) 

233 ) and ( 

234 "title_type" in form.cleaned_data 

235 and form.cleaned_data["title_type"] == TitleTypes.MAIN_TITLE 

236 ): 

237 number_of_main_titles += 1 

238 

239 # check if we have too many 

240 if number_of_main_titles > 1: 240 ↛ 241line 240 didn't jump to line 241 because the condition on line 240 was never true

241 raise ValidationError(self.one_main_title_error_msg) 

242 

243 return clean 

244 

245 

246class TitleForm(DalimaForm): 

247 lang = forms.CharField( 

248 validators=[MinLengthValidator(1)], 

249 widget=forms.Select(choices=LANGUAGES), 

250 ) 

251 title_type = forms.CharField( 

252 validators=[MinLengthValidator(1)], 

253 widget=forms.Select(choices=TitleTypes), 

254 initial=TitleTypes.ALTERNATIVELY_TITLE, 

255 ) 

256 

257 class Meta: 

258 model = Title 

259 fields = ["title", "title_type", "lang"] 

260 

261 

262class TitleInline(InlineFormSetFactory): 

263 model = Title 

264 form_class = TitleForm 

265 factory_kwargs = {"extra": 1, "formset": BaseTitleFormSet} 

266 

267 

268class GeolocationForm(DalimaForm): 

269 west_bound_longitude = forms.FloatField(required=True) 

270 east_bound_longitude = forms.FloatField(required=True) 

271 north_bound_latitude = forms.FloatField(required=True) 

272 south_bound_latitude = forms.FloatField(required=True) 

273 

274 class Meta: 

275 model = Geolocation 

276 fields = [ 

277 "place", 

278 "west_bound_longitude", 

279 "east_bound_longitude", 

280 "north_bound_latitude", 

281 "south_bound_latitude", 

282 ] 

283 labels = { 

284 "place": "Place name", 

285 "west_bound_longitude": "West bound", 

286 "east_bound_longitude": "East bound", 

287 "north_bound_latitude": "North bound", 

288 "south_bound_latitude": "South bound", 

289 } 

290 

291 

292class GeolocationInline(InlineFormSetFactory): 

293 model = Geolocation 

294 form_class = GeolocationForm 

295 factory_kwargs = {"extra": 1} 

296 

297 

298class ContributorInline(InlineFormSetFactory): 

299 model = MetadataContributor 

300 form_class = ContributorForm 

301 factory_kwargs = {"extra": 1} 

302 

303 

304class DescriptionInline(InlineFormSetFactory): 

305 model = Description 

306 form_class = DescriptionForm 

307 factory_kwargs = {"extra": 1} 

308 

309 

310class IdentifierInline(GenericInlineFormSetFactory): 

311 model = Identifier 

312 form_class = IdentifierForm 

313 prefix = "identifier_set" 

314 factory_kwargs = { 

315 "extra": 1, 

316 "can_delete": False, 

317 }