Coverage for apis_core/apis_relations/forms.py: 34%

83 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-16 07:42 +0000

1import copy 

2 

3from crispy_forms.helper import FormHelper 

4from dal import autocomplete 

5from django import forms 

6from django.conf import settings 

7from django.contrib.contenttypes.models import ContentType 

8from django.db.models import Q 

9from django.urls import reverse 

10from django_tables2 import RequestConfig 

11 

12from apis_core.apis_entities.autocomplete3 import ( 

13 PropertyAutocomplete, 

14) 

15from apis_core.apis_entities.fields import ListSelect2 

16from apis_core.apis_entities.utils import get_entity_classes 

17from apis_core.apis_metainfo.models import Uri 

18from apis_core.apis_relations.models import TempTriple 

19from apis_core.utils.settings import get_entity_settings_by_modelname 

20 

21from .tables import get_generic_triple_table 

22 

23 

24class GenericTripleForm(forms.ModelForm): 

25 # TODO RDF : Add Notes and references 

26 

27 class Meta: 

28 model = TempTriple 

29 fields = [ 

30 "subj", 

31 "obj", 

32 "prop", 

33 "start_date_written", 

34 "end_date_written", 

35 "notes", 

36 ] 

37 widgets = { 

38 "subj": forms.HiddenInput(), 

39 "obj": forms.HiddenInput(), 

40 "prop": forms.HiddenInput(), 

41 } 

42 

43 def __init__(self, entity_type_self_str, entity_type_other_str): 

44 super().__init__() 

45 

46 self.helper = FormHelper() 

47 self.helper.form_tag = False 

48 

49 attrs = { 

50 "data-placeholder": "Type to get suggestions", 

51 "data-minimum-input-length": getattr(settings, "APIS_MIN_CHAR", 3), 

52 "data-html": True, 

53 "style": "width: 100%", 

54 } 

55 help_text_other_entity = ( 

56 "Search and select or use an URL from a reference resource" 

57 ) 

58 attrs_target = copy.deepcopy(attrs) 

59 attrs_target["data-tags"] = "1" 

60 

61 contenttypes = [ 

62 ContentType.objects.get_for_model(model) for model in get_entity_classes() 

63 ] 

64 ct = next( 

65 filter(lambda x: x.model == entity_type_other_str.lower(), contenttypes) 

66 ) 

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

68 

69 self.fields["other_entity"] = autocomplete.Select2ListCreateChoiceField( 

70 label="entity", 

71 widget=ListSelect2( 

72 url=url, 

73 attrs=attrs_target, 

74 ), 

75 help_text=help_text_other_entity, 

76 ) 

77 

78 # This assert only serves as a linking for us devs, to make explicit what internal object the class 

79 # Select2ListCreateChoiceField object afterwards uses. 

80 assert PropertyAutocomplete 

81 

82 self.fields["property"] = autocomplete.Select2ListCreateChoiceField( 

83 label="property", 

84 widget=ListSelect2( 

85 url=reverse( 

86 "apis_core:apis_relations:generic_property_autocomplete", 

87 kwargs={ 

88 "entity_self": entity_type_self_str, 

89 "entity_other": entity_type_other_str, 

90 }, 

91 ), 

92 attrs=attrs_target, 

93 ), 

94 ) 

95 self.helper.include_media = False 

96 

97 def load_subj_obj_prop( 

98 self, 

99 entity_instance_self, 

100 entity_instance_other, 

101 property_instance, 

102 property_direction, 

103 ): 

104 # the more important function here when writing data from an user input via an ajax call into this form. 

105 # Because here the direction of the property is respected. Hence the subject and object position of the 

106 # triple and the property name_forward or name_reverse are loaded correctly here. 

107 

108 if property_direction == PropertyAutocomplete.SELF_SUBJ_OTHER_OBJ_STR: 

109 triple_subj = entity_instance_self 

110 triple_obj = entity_instance_other 

111 property_direction_name = property_instance.name_forward 

112 

113 elif property_direction == PropertyAutocomplete.SELF_OBJ_OTHER_SUBJ_STR: 

114 triple_subj = entity_instance_other 

115 triple_obj = entity_instance_self 

116 property_direction_name = property_instance.name_reverse 

117 

118 else: 

119 raise Exception("No valid property direction given.") 

120 

121 self.fields["subj"].initial = triple_subj 

122 self.fields["obj"].initial = triple_obj 

123 self.fields["prop"].initial = property_instance 

124 

125 property_initial_value = ( 

126 f"id:{property_instance.pk}__direction:{property_direction}", 

127 property_direction_name, 

128 ) 

129 self.fields["property"].initial = property_initial_value 

130 self.fields["property"].choices = [property_initial_value] 

131 

132 other_entity_initial_value = ( 

133 str( 

134 Uri.objects.filter( 

135 root_object=entity_instance_other, 

136 ).first() 

137 ), 

138 f"<span ><small>db</small> {str(entity_instance_other)}</span>", 

139 ) 

140 self.fields["other_entity"].initial = other_entity_initial_value 

141 self.fields["other_entity"].choices = [other_entity_initial_value] 

142 

143 def load_remaining_data_from_triple(self, triple): 

144 # Most data is loaded via the set_subj_obj function. 

145 # Here, load the rest from a pre-existing triple. 

146 # 

147 # This function is both used in get_form_ajax and save_form_ajax, 

148 # hence it's not feasible to assume existing user input as done 

149 # in 'load_remaining_data_from_input' 

150 

151 self.fields["start_date_written"].initial = triple.start_date_written 

152 self.fields["end_date_written"].initial = triple.end_date_written 

153 self.fields["notes"].initial = triple.notes 

154 self.instance = triple 

155 

156 def load_remaining_data_from_input( 

157 self, 

158 start_date_written, 

159 end_date_written, 

160 ): 

161 # Most data is loaded via the set_subj_obj function. 

162 # Here, load the rest from the user input via the ajax post 

163 

164 self.fields["start_date_written"].initial = start_date_written 

165 self.fields["end_date_written"].initial = end_date_written 

166 

167 def save(self): 

168 if self.instance.pk is None: 

169 self.instance = TempTriple.objects.create( 

170 subj=self.fields["subj"].initial, 

171 obj=self.fields["obj"].initial, 

172 prop=self.fields["prop"].initial, 

173 ) 

174 

175 else: 

176 self.instance.subj = self.fields["subj"].initial 

177 self.instance.obj = self.fields["obj"].initial 

178 self.instance.prop = self.fields["prop"].initial 

179 

180 self.instance.start_date_written = self.fields["start_date_written"].initial 

181 self.instance.end_date_written = self.fields["end_date_written"].initial 

182 self.instance.notes = self.fields["notes"].initial 

183 self.instance.save() 

184 

185 return self.instance 

186 

187 def get_text_id(self): 

188 """ 

189 Function to retrieve the highlighted text. 

190 :return: ID of text that was highlighted 

191 """ 

192 return self.cleaned_data["HL_text_id"][5:] 

193 

194 def get_html_table(self, entity_instance_self, entity_instance_other, request): 

195 table_class = get_generic_triple_table( 

196 other_entity_class_name=entity_instance_other.__class__.__name__.lower(), 

197 entity_pk_self=entity_instance_self.pk, 

198 detail=False, 

199 ) 

200 

201 table_object = table_class( 

202 data=TempTriple.objects.filter( 

203 ( 

204 Q(subj__self_contenttype=entity_instance_other.self_contenttype) 

205 & Q(obj=entity_instance_self) 

206 ) 

207 | ( 

208 Q(obj__self_contenttype=entity_instance_other.self_contenttype) 

209 & Q(subj=entity_instance_self) 

210 ) 

211 ), 

212 prefix=entity_instance_other.__class__.__name__, 

213 request=request, 

214 ) 

215 entity_settings = get_entity_settings_by_modelname( 

216 entity_instance_self.__class__.__name__ 

217 ) 

218 per_page = entity_settings.get("relations_per_page", 10) 

219 RequestConfig(request, paginate={"per_page": per_page}).configure(table_object) 

220 

221 return table_object