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

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(required=False) 

25 remember = forms.BooleanField(required=False, label="Remember selected columns") 

26 

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 

36 

37 

38class GenericImportForm(forms.Form): 

39 class Meta: 

40 fields = [] 

41 

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

55 

56 

57class GenericFilterSetForm(forms.Form): 

58 """ 

59 FilterSet form for generic models 

60 Adds a submit button using the django crispy form helper 

61 """ 

62 

63 columns_exclude = [] 

64 

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 

70 

71 

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

79 

80 class Meta: 

81 fields = "__all__" 

82 

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) 

98 

99 self.helper = FormHelper(self) 

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

101 

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 

121 

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 

127 

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 

141 

142 

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

155 

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

161 

162 

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

168 

169 

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 ) 

184 

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