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

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

34 

35 

36class GenericImportForm(forms.Form): 

37 class Meta: 

38 fields = [] 

39 

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

53 

54 

55class GenericFilterSetForm(forms.Form): 

56 """ 

57 FilterSet form for generic models 

58 Adds a submit button using the django crispy form helper 

59 """ 

60 

61 columns_exclude = [] 

62 

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 

68 

69 

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

77 

78 class Meta: 

79 fields = "__all__" 

80 

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) 

96 

97 self.helper = FormHelper(self) 

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

99 

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 

119 

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 

125 

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 

139 

140 

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

153 

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

159 

160 

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

166 

167 

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 ) 

182 

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