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

1import logging 

2 

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 _ 

11 

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 

19 

20logger = logging.getLogger(__name__) 

21 

22 

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") 

29 

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 

39 

40 

41class GenericImportForm(forms.Form): 

42 class Meta: 

43 fields = [] 

44 

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"))) 

58 

59 

60class GenericFilterSetForm(forms.Form): 

61 """ 

62 FilterSet form for generic models 

63 Adds a submit button using the django crispy form helper 

64 """ 

65 

66 columns_exclude = [] 

67 

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 

73 

74 

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 """ 

82 

83 class Meta: 

84 fields = "__all__" 

85 

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) 

101 

102 self.helper = FormHelper(self) 

103 self.helper.add_input(Submit("submit", _("Submit"))) 

104 

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 

124 

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 

130 

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 

144 

145 

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"))) 

158 

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") 

164 

165 

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"))) 

171 

172 

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 ) 

187 

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")))