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