Coverage for apis_core/generic/forms/__init__.py: 84%
120 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-12-04 11:32 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-12-04 11:32 +0000
1import logging
3from crispy_forms.helper import FormHelper
4from crispy_forms.layout import Submit
5from django import forms
6from django.apps import apps
7from django.contrib.contenttypes.models import ContentType
8from django.core.exceptions import ValidationError
9from django.urls import reverse
10from django.utils.translation import gettext_lazy as _
12from apis_core.core.fields import (
13 ApisListSelect2,
14 ApisModelSelect2,
15 ApisModelSelect2Multiple,
16)
17from apis_core.generic.abc import GenericModel
18from apis_core.generic.forms.fields import ModelImportChoiceField
20logger = logging.getLogger(__name__)
23class ColumnsSelectorForm(forms.Form):
24 def __init__(self, *args, **kwargs):
25 choices = kwargs.pop("choices", [])
26 super().__init__(*args, **kwargs)
27 initial = kwargs.get("initial", {}).get("choices", [])
28 self.fields["columns"] = forms.MultipleChoiceField(
29 required=False, choices=choices, initial=initial
30 )
31 self.helper = FormHelper()
32 self.helper.form_method = "GET"
33 self.helper.form_tag = False
36class GenericImportForm(forms.Form):
37 class Meta:
38 fields = []
40 def __init__(self, *args, **kwargs):
41 super().__init__(*args, **kwargs)
42 self.fields["url"] = ModelImportChoiceField(
43 queryset=self.Meta.model.objects.all()
44 )
45 ct = ContentType.objects.get_for_model(self.Meta.model)
46 url = reverse("apis_core:generic:autocompleteexternalonly", args=[ct])
47 self.fields["url"].widget = ApisModelSelect2(
48 url, attrs={"data-html": True, "data-tags": 1}
49 )
50 self.fields["url"].widget.choices = self.fields["url"].choices
51 self.helper = FormHelper()
52 self.helper.add_input(Submit("submit", _("Submit")))
55class GenericFilterSetForm(forms.Form):
56 """
57 FilterSet form for generic models
58 Adds a submit button using the django crispy form helper
59 """
61 columns_exclude = []
63 def __init__(self, *args, **kwargs):
64 super().__init__(*args, **kwargs)
65 self.helper = FormHelper(self)
66 self.helper.form_method = "GET"
67 self.helper.form_tag = False
70class GenericModelForm(forms.ModelForm):
71 """
72 Model form for generic models
73 Adds a submit button using the django crispy form helper
74 and sets the ModelChoiceFields and ModelMultipleChoiceFields
75 to use autocomplete replacement fields
76 """
78 class Meta:
79 fields = "__all__"
81 def __init__(self, *args, **kwargs):
82 super().__init__(*args, **kwargs)
83 try:
84 skoscollection = apps.get_model("collections.SkosCollection")
85 self.fields["collections"] = forms.ModelMultipleChoiceField(
86 required=False,
87 queryset=skoscollection.objects.all(),
88 label=_("Collections"),
89 )
90 if instance := kwargs.get("instance"):
91 self.fields["collections"].initial = skoscollection.objects.by_instance(
92 instance
93 ).values_list("pk", flat=True)
94 except LookupError as e:
95 logger.debug("Not adding collections to form: %s", e)
97 self.helper = FormHelper(self)
98 self.helper.add_input(Submit("submit", _("Submit")))
100 # override the fields pointing to other models,
101 # to make them use the autocomplete widgets
102 override_fieldtypes = {
103 "ModelMultipleChoiceField": ApisModelSelect2Multiple,
104 "ModelChoiceField": ApisModelSelect2,
105 "ModelImportChoiceField": ApisModelSelect2,
106 }
107 for field in self.fields:
108 clsname = self.fields[field].__class__.__name__
109 if clsname in override_fieldtypes.keys():
110 ct = ContentType.objects.get_for_model(
111 self.fields[field]._queryset.model
112 )
113 if issubclass(ct.model_class(), GenericModel):
114 url = reverse("apis_core:generic:autocomplete", args=[ct])
115 self.fields[field].widget = override_fieldtypes[clsname](
116 url, attrs={"data-html": True}
117 )
118 self.fields[field].widget.choices = self.fields[field].choices
120 def clean(self):
121 cleaned_data = super().clean()
122 if not any(cleaned_data.values()):
123 raise ValidationError(_("Please fill out some of the form fields"))
124 return cleaned_data
126 def save(self, *args, **kwargs):
127 instance = super().save(*args, **kwargs)
128 try:
129 skoscollection = apps.get_model("collections.SkosCollection")
130 if "collections" in self.cleaned_data:
131 collections = self.cleaned_data.get("collections")
132 for collection in skoscollection.objects.exclude(pk__in=collections):
133 collection.remove(instance)
134 for collection in skoscollection.objects.filter(pk__in=collections):
135 collection.add(instance)
136 except LookupError as e:
137 logger.debug("Not creating collections from form: %s", e)
138 return instance
141class GenericSelectMergeOrEnrichForm(forms.Form):
142 def __init__(self, *args, **kwargs):
143 if "content_type" in kwargs:
144 self.content_type = kwargs.pop("content_type")
145 super().__init__(*args, **kwargs)
146 self.fields["uri"] = forms.CharField()
147 uri = reverse("apis_core:generic:autocomplete", args=[self.content_type])
148 attrs = {"data-html": True, "data-tags": 1}
149 self.fields["uri"].widget = ApisListSelect2(uri, attrs=attrs)
150 self.fields["uri"].label = "Select or paste URI"
151 self.helper = FormHelper()
152 self.helper.add_input(Submit("submit", _("Submit")))
154 def clean_uri(self):
155 uri = self.cleaned_data["uri"]
156 if uri.isdigit() or self.content_type.model_class().valid_import_url(uri):
157 return uri
158 raise ValidationError(f"{uri} is neither an ID nor something we can import")
161class GenericMergeWithForm(forms.Form):
162 def __init__(self, *args, **kwargs):
163 super().__init__(*args, **kwargs)
164 self.helper = FormHelper()
165 self.helper.add_input(Submit("submit", _("Merge")))
168class GenericEnrichForm(forms.Form):
169 def __init__(self, *args, **kwargs):
170 data = kwargs.pop("data", {})
171 instance = kwargs.pop("instance", None)
172 super().__init__(*args, **kwargs)
173 for key, value in data.items():
174 update_key = f"update_{key}"
175 if value:
176 label = f"Add {key} {value}"
177 if existing := getattr(instance, key, False):
178 label = f"Update {key} from {existing} to {value}"
179 self.fields[update_key] = forms.BooleanField(
180 required=False, label=label
181 )
183 self.fields[key] = forms.CharField(initial=value, required=False)
184 self.fields[key].widget = self.fields[key].hidden_widget()
185 self.helper = FormHelper()
186 self.helper.add_input(Submit("submit", _("Submit")))