Coverage for apis_core / generic / forms / __init__.py: 85%
123 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 11:37 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 11:37 +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 columns = forms.MultipleChoiceField(required=False)
25 remember = forms.BooleanField(required=False, label="Remember selected columns")
27 def __init__(self, *args, **kwargs):
28 choices = kwargs.pop("choices", [])
29 super().__init__(*args, **kwargs)
30 initial = kwargs.get("initial", {}).get("choices", [])
31 self.fields["columns"].choices = choices
32 self.fields["columns"].initial = initial
33 self.helper = FormHelper()
34 self.helper.form_method = "GET"
35 self.helper.form_tag = False
38class GenericImportForm(forms.Form):
39 class Meta:
40 fields = []
42 def __init__(self, *args, **kwargs):
43 super().__init__(*args, **kwargs)
44 self.fields["url"] = ModelImportChoiceField(
45 queryset=self.Meta.model.objects.all()
46 )
47 ct = ContentType.objects.get_for_model(self.Meta.model)
48 url = reverse("apis_core:generic:autocompleteexternalonly", args=[ct])
49 self.fields["url"].widget = ApisModelSelect2(
50 url, attrs={"data-html": True, "data-tags": 1}
51 )
52 self.fields["url"].widget.choices = self.fields["url"].choices
53 self.helper = FormHelper()
54 self.helper.add_input(Submit("submit", _("Submit")))
57class GenericFilterSetForm(forms.Form):
58 """
59 FilterSet form for generic models
60 Adds a submit button using the django crispy form helper
61 """
63 columns_exclude = []
65 def __init__(self, *args, **kwargs):
66 super().__init__(*args, **kwargs)
67 self.helper = FormHelper(self)
68 self.helper.form_method = "GET"
69 self.helper.form_tag = False
72class GenericModelForm(forms.ModelForm):
73 """
74 Model form for generic models
75 Adds a submit button using the django crispy form helper
76 and sets the ModelChoiceFields and ModelMultipleChoiceFields
77 to use autocomplete replacement fields
78 """
80 class Meta:
81 fields = "__all__"
83 def __init__(self, *args, **kwargs):
84 super().__init__(*args, **kwargs)
85 try:
86 skoscollection = apps.get_model("collections.SkosCollection")
87 self.fields["collections"] = forms.ModelMultipleChoiceField(
88 required=False,
89 queryset=skoscollection.objects.all(),
90 label=_("Collections"),
91 )
92 if instance := kwargs.get("instance"):
93 self.fields["collections"].initial = skoscollection.objects.by_instance(
94 instance
95 ).values_list("pk", flat=True)
96 except LookupError as e:
97 logger.debug("Not adding collections to form: %s", e)
99 self.helper = FormHelper(self)
100 self.helper.add_input(Submit("submit", _("Submit")))
102 # override the fields pointing to other models,
103 # to make them use the autocomplete widgets
104 override_fieldtypes = {
105 "ModelMultipleChoiceField": ApisModelSelect2Multiple,
106 "ModelChoiceField": ApisModelSelect2,
107 "ModelImportChoiceField": ApisModelSelect2,
108 }
109 for field in self.fields:
110 clsname = self.fields[field].__class__.__name__
111 if clsname in override_fieldtypes.keys():
112 ct = ContentType.objects.get_for_model(
113 self.fields[field]._queryset.model
114 )
115 if issubclass(ct.model_class(), GenericModel):
116 url = reverse("apis_core:generic:autocomplete", args=[ct])
117 self.fields[field].widget = override_fieldtypes[clsname](
118 url, attrs={"data-html": True}
119 )
120 self.fields[field].widget.choices = self.fields[field].choices
122 def clean(self):
123 cleaned_data = super().clean()
124 if not any(cleaned_data.values()):
125 raise ValidationError(_("Please fill out some of the form fields"))
126 return cleaned_data
128 def save(self, *args, **kwargs):
129 instance = super().save(*args, **kwargs)
130 try:
131 skoscollection = apps.get_model("collections.SkosCollection")
132 if "collections" in self.cleaned_data:
133 collections = self.cleaned_data.get("collections")
134 for collection in skoscollection.objects.exclude(pk__in=collections):
135 collection.remove(instance)
136 for collection in skoscollection.objects.filter(pk__in=collections):
137 collection.add(instance)
138 except LookupError as e:
139 logger.debug("Not creating collections from form: %s", e)
140 return instance
143class GenericSelectMergeOrEnrichForm(forms.Form):
144 def __init__(self, *args, **kwargs):
145 if "content_type" in kwargs:
146 self.content_type = kwargs.pop("content_type")
147 super().__init__(*args, **kwargs)
148 self.fields["uri"] = forms.CharField()
149 uri = reverse("apis_core:generic:autocomplete", args=[self.content_type])
150 attrs = {"data-html": True, "data-tags": 1}
151 self.fields["uri"].widget = ApisListSelect2(uri, attrs=attrs)
152 self.fields["uri"].label = "Select or paste URI"
153 self.helper = FormHelper()
154 self.helper.add_input(Submit("submit", _("Submit")))
156 def clean_uri(self):
157 uri = self.cleaned_data["uri"]
158 if uri.isdigit() or self.content_type.model_class().valid_import_url(uri):
159 return uri
160 raise ValidationError(f"{uri} is neither an ID nor something we can import")
163class GenericMergeWithForm(forms.Form):
164 def __init__(self, *args, **kwargs):
165 super().__init__(*args, **kwargs)
166 self.helper = FormHelper()
167 self.helper.add_input(Submit("submit", _("Merge")))
170class GenericEnrichForm(forms.Form):
171 def __init__(self, *args, **kwargs):
172 data = kwargs.pop("data", {})
173 instance = kwargs.pop("instance", None)
174 super().__init__(*args, **kwargs)
175 for key, value in data.items():
176 update_key = f"update_{key}"
177 if value:
178 label = f"Add {key} {value}"
179 if existing := getattr(instance, key, False):
180 label = f"Update {key} from {existing} to {value}"
181 self.fields[update_key] = forms.BooleanField(
182 required=False, label=label
183 )
185 self.fields[key] = forms.CharField(initial=value, required=False)
186 self.fields[key].widget = self.fields[key].hidden_widget()
187 self.helper = FormHelper()
188 self.helper.add_input(Submit("submit", _("Submit")))