Coverage for apis_core/generic/forms/__init__.py: 83%

115 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-10-10 13:36 +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 GenericImportForm(forms.Form): 

24 class Meta: 

25 fields = [] 

26 

27 def __init__(self, *args, **kwargs): 

28 super().__init__(*args, **kwargs) 

29 self.fields["url"] = ModelImportChoiceField( 

30 queryset=self.Meta.model.objects.all() 

31 ) 

32 ct = ContentType.objects.get_for_model(self.Meta.model) 

33 url = reverse("apis_core:generic:autocompleteexternalonly", args=[ct]) 

34 self.fields["url"].widget = ApisModelSelect2( 

35 url, attrs={"data-html": True, "data-tags": 1} 

36 ) 

37 self.fields["url"].widget.choices = self.fields["url"].choices 

38 self.helper = FormHelper() 

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

40 

41 

42class GenericFilterSetForm(forms.Form): 

43 """ 

44 FilterSet form for generic models 

45 Adds a submit button using the django crispy form helper 

46 Adds a `columns` selector that lists all the fields from 

47 the model 

48 """ 

49 

50 columns_exclude = [] 

51 

52 def __init__(self, *args, **kwargs): 

53 super().__init__(*args, **kwargs) 

54 

55 self.helper = FormHelper(self) 

56 self.helper.form_method = "GET" 

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

58 

59 def clean(self): 

60 self.cleaned_data = super().clean() 

61 self.cleaned_data.pop("columns", None) 

62 return self.cleaned_data 

63 

64 

65class GenericModelForm(forms.ModelForm): 

66 """ 

67 Model form for generic models 

68 Adds a submit button using the django crispy form helper 

69 and sets the ModelChoiceFields and ModelMultipleChoiceFields 

70 to use autocomplete replacement fields 

71 """ 

72 

73 class Meta: 

74 fields = "__all__" 

75 

76 def __init__(self, *args, **kwargs): 

77 super().__init__(*args, **kwargs) 

78 try: 

79 skoscollection = apps.get_model("collections.SkosCollection") 

80 self.fields["collections"] = forms.ModelMultipleChoiceField( 

81 required=False, 

82 queryset=skoscollection.objects.all(), 

83 label=_("Collections"), 

84 ) 

85 if instance := kwargs.get("instance"): 

86 self.fields["collections"].initial = skoscollection.objects.by_instance( 

87 instance 

88 ).values_list("pk", flat=True) 

89 except LookupError as e: 

90 logger.debug("Not adding collections to form: %s", e) 

91 

92 self.helper = FormHelper(self) 

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

94 

95 # override the fields pointing to other models, 

96 # to make them use the autocomplete widgets 

97 override_fieldtypes = { 

98 "ModelMultipleChoiceField": ApisModelSelect2Multiple, 

99 "ModelChoiceField": ApisModelSelect2, 

100 "ModelImportChoiceField": ApisModelSelect2, 

101 } 

102 for field in self.fields: 

103 clsname = self.fields[field].__class__.__name__ 

104 if clsname in override_fieldtypes.keys(): 

105 ct = ContentType.objects.get_for_model( 

106 self.fields[field]._queryset.model 

107 ) 

108 if issubclass(ct.model_class(), GenericModel): 

109 url = reverse("apis_core:generic:autocomplete", args=[ct]) 

110 self.fields[field].widget = override_fieldtypes[clsname]( 

111 url, attrs={"data-html": True} 

112 ) 

113 self.fields[field].widget.choices = self.fields[field].choices 

114 

115 def clean(self): 

116 cleaned_data = super().clean() 

117 if not any(cleaned_data.values()): 

118 raise ValidationError(_("Please fill out some of the form fields")) 

119 return cleaned_data 

120 

121 def save(self, *args, **kwargs): 

122 instance = super().save(*args, **kwargs) 

123 try: 

124 skoscollection = apps.get_model("collections.SkosCollection") 

125 if "collections" in self.cleaned_data: 

126 collections = self.cleaned_data.get("collections") 

127 for collection in skoscollection.objects.exclude(pk__in=collections): 

128 collection.remove(instance) 

129 for collection in skoscollection.objects.filter(pk__in=collections): 

130 collection.add(instance) 

131 except LookupError as e: 

132 logger.debug("Not creating collections from form: %s", e) 

133 return instance 

134 

135 

136class GenericSelectMergeOrEnrichForm(forms.Form): 

137 def __init__(self, *args, **kwargs): 

138 if "content_type" in kwargs: 

139 self.content_type = kwargs.pop("content_type") 

140 super().__init__(*args, **kwargs) 

141 self.fields["uri"] = forms.CharField() 

142 uri = reverse("apis_core:generic:autocomplete", args=[self.content_type]) 

143 attrs = {"data-html": True, "data-tags": 1} 

144 self.fields["uri"].widget = ApisListSelect2(uri, attrs=attrs) 

145 self.fields["uri"].label = "Select or paste URI" 

146 self.helper = FormHelper() 

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

148 

149 def clean_uri(self): 

150 uri = self.cleaned_data["uri"] 

151 if uri.isdigit() or self.content_type.model_class().valid_uri(uri): 

152 return uri 

153 raise ValueError(f"{uri} is neither an ID nor something we can import") 

154 

155 

156class GenericMergeWithForm(forms.Form): 

157 def __init__(self, *args, **kwargs): 

158 super().__init__(*args, **kwargs) 

159 self.helper = FormHelper() 

160 self.helper.add_input(Submit("submit", _("Merge"))) 

161 

162 

163class GenericEnrichForm(forms.Form): 

164 def __init__(self, *args, **kwargs): 

165 data = kwargs.pop("data", {}) 

166 instance = kwargs.pop("instance", None) 

167 super().__init__(*args, **kwargs) 

168 for key, value in data.items(): 

169 update_key = f"update_{key}" 

170 if value: 

171 label = f"Add {key} {value}" 

172 if existing := getattr(instance, key, False): 

173 label = f"Update {key} from {existing} to {value}" 

174 self.fields[update_key] = forms.BooleanField( 

175 required=False, label=label 

176 ) 

177 

178 self.fields[key] = forms.CharField(initial=value, required=False) 

179 self.fields[key].widget = self.fields[key].hidden_widget() 

180 self.helper = FormHelper() 

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