Coverage for datacite/forms.py: 97%
138 statements
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-29 15:38 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-29 15:38 +0000
1from typing import Any
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)
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)
29class DalimaFormHelper(FormHelper):
30 def __init__(self, *args: Any, **kwargs: Any) -> None:
31 super().__init__(*args, **kwargs)
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"
39class MetadataFormHelper(FormHelper):
40 def __init__(self, *args: Any, **kwargs: Any) -> None:
41 super().__init__(*args, **kwargs)
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"
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"
56class DalimaForm(forms.ModelForm):
57 def __init__(self, *args: Any, **kwargs: Any) -> None:
58 super().__init__(*args, **kwargs)
59 self.helper = DalimaFormHelper()
62class ContributorForm(DalimaForm):
63 class Meta:
64 model = MetadataContributor
65 fields = ["contributor", "contributor_type"]
68class DescriptionForm(DalimaForm):
69 class Meta:
70 model = Description
71 fields = ["description", "description_type", "lang"]
74class IdentifierForm(DalimaForm):
75 identifier = forms.URLField()
77 class Meta:
78 model = Identifier
79 fields = [
80 "identifier",
81 "scheme",
82 ]
83 labels = {"scheme": "Identifier scheme"}
86class FormatForm(DalimaForm):
87 class Meta:
88 model = Format
89 fields = ["format"]
92class FundingForm(DalimaForm):
93 class Meta:
94 model = Funding
95 fields = ["funder", "award_title", "award_number", "award_uri"]
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 )
112 class Meta:
113 model = Participant
114 fields = ["name_type", "name", "lang", "is_funder"]
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 )
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"}
140class RightsForm(DalimaForm):
141 lang = forms.CharField(
142 validators=[MinLengthValidator(1)],
143 widget=forms.Select(choices=LANGUAGES),
144 )
146 class Meta:
147 model = Rights
148 fields = [
149 "lang",
150 "rights_uri",
151 "rights",
152 ]
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 """
161 input_type = "date"
163 def __init__(self) -> None:
164 super().__init__(format="%Y-%m-%d")
167class MetadataForm(forms.ModelForm):
168 fundings = forms.ModelMultipleChoiceField(
169 queryset=Funding.objects.all().select_related("funder"), required=False
170 )
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 }
204 def __init__(self, *args: Any, **kwargs: Any) -> None:
205 super().__init__(*args, **kwargs)
206 self.helper = MetadataFormHelper()
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
222class BaseTitleFormSet(forms.BaseInlineFormSet):
223 one_main_title_error_msg = "Should only be a Main Title."
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
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)
243 return clean
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 )
257 class Meta:
258 model = Title
259 fields = ["title", "title_type", "lang"]
262class TitleInline(InlineFormSetFactory):
263 model = Title
264 form_class = TitleForm
265 factory_kwargs = {"extra": 1, "formset": BaseTitleFormSet}
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)
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 }
292class GeolocationInline(InlineFormSetFactory):
293 model = Geolocation
294 form_class = GeolocationForm
295 factory_kwargs = {"extra": 1}
298class ContributorInline(InlineFormSetFactory):
299 model = MetadataContributor
300 form_class = ContributorForm
301 factory_kwargs = {"extra": 1}
304class DescriptionInline(InlineFormSetFactory):
305 model = Description
306 form_class = DescriptionForm
307 factory_kwargs = {"extra": 1}
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 }