Coverage for apis_core/apis_entities/filtersets.py: 0%

76 statements  

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

1import django_filters 

2from django.conf import settings 

3from django.contrib.contenttypes.models import ContentType 

4from django.db import models 

5from django.db.models import Case, Q, Value, When 

6from django.db.models.functions import Concat 

7from django.forms import DateInput 

8from simple_history.utils import get_history_manager_for_model 

9 

10from apis_core.apis_entities.utils import get_entity_classes 

11from apis_core.apis_metainfo.models import RootObject 

12from apis_core.apis_relations.models import Property, Triple 

13from apis_core.generic.filtersets import GenericFilterSet, GenericFilterSetForm 

14from apis_core.generic.helpers import generate_search_filter 

15 

16ABSTRACT_ENTITY_COLUMNS_EXCLUDE = [ 

17 "rootobject_ptr", 

18 "self_contenttype", 

19 "triple_set_from_subj", 

20 "triple_set_from_obj", 

21 "uri", 

22] 

23 

24ABSTRACT_ENTITY_FILTERS_EXCLUDE = ABSTRACT_ENTITY_COLUMNS_EXCLUDE + [ 

25 "review", 

26 "start_date", 

27 "start_start_date", 

28 "start_end_date", 

29 "end_date", 

30 "end_start_date", 

31 "end_end_date", 

32 "notes", 

33 "text", 

34 "published", 

35 "status", 

36 "references", 

37] 

38 

39 

40class PropertyChoiceField(django_filters.fields.ModelChoiceField): 

41 def label_from_instance(self, obj): 

42 if obj.forward: 

43 targets = [ct.name for ct in obj.obj_class.all()] 

44 else: 

45 targets = [ct.name for ct in obj.subj_class.all()] 

46 return obj.name + " | " + ", ".join(targets) 

47 

48 

49class PropertyFilter(django_filters.ModelChoiceFilter): 

50 """ 

51 A child of ModelChoiceFilter that only works with 

52 Properties, but in return it filters those so that 

53 only the Properties are listed that can be connected 

54 to the `model` given as argument. 

55 """ 

56 

57 field_class = PropertyChoiceField 

58 

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

60 model = kwargs.pop("model", None) 

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

62 

63 if model is not None: 

64 ct = ContentType.objects.get_for_model(model) 

65 self.queryset = ( 

66 Property.objects.all() 

67 .filter(Q(subj_class=ct) | Q(obj_class=ct)) 

68 .annotate( 

69 name=Case( 

70 When( 

71 obj_class=ct, 

72 subj_class=ct, 

73 then=Concat("name_forward", Value(" / "), "name_reverse"), 

74 ), 

75 When(obj_class=ct, then="name_reverse"), 

76 When(subj_class=ct, then="name_forward"), 

77 ), 

78 forward=Case( 

79 When(obj_class=ct, then=Value(False)), 

80 When(subj_class=ct, then=Value(True)), 

81 ), 

82 ) 

83 .order_by("name") 

84 .distinct() 

85 ) 

86 

87 def filter(self, queryset, value): 

88 if value: 

89 p = Property.objects.get(name_forward=value) 

90 queryset = queryset.filter(triple_set_from_subj__prop=p).distinct() 

91 return queryset 

92 

93 

94class ModelSearchFilter(django_filters.CharFilter): 

95 """ 

96 This filter is a customized CharFilter that 

97 uses the `generate_search_filter` method to 

98 adapt the search filter to the model that is 

99 searched. 

100 It also extracts sets the help text based on 

101 the fields searched. 

102 """ 

103 

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

105 model = kwargs.pop("model", None) 

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

107 

108 if model is not None and "help_text" not in self.extra: 

109 if hasattr(model, "_default_search_fields"): 

110 fields = model._default_search_fields 

111 else: 

112 modelfields = model._meta.fields 

113 fields = [ 

114 field.name 

115 for field in modelfields 

116 if isinstance(field, (models.CharField, models.TextField)) 

117 ] 

118 fields = ", ".join(fields) 

119 self.extra["help_text"] = f"Search in fields: {fields}" 

120 

121 def filter(self, qs, value): 

122 return qs.filter(generate_search_filter(qs.model, value)) 

123 

124 

125def related_entity(queryset, name, value): 

126 entities = get_entity_classes() 

127 q = Q() 

128 for entity in entities: 

129 name = entity._meta.model_name 

130 q |= Q(**{f"{name}__isnull": False}) & generate_search_filter( 

131 entity, value, prefix=f"{name}__" 

132 ) 

133 all_entities = RootObject.objects_inheritance.filter(q).values_list("pk", flat=True) 

134 t = ( 

135 Triple.objects.filter(Q(subj__in=all_entities) | Q(obj__in=all_entities)) 

136 .annotate( 

137 related=Case( 

138 When(subj__in=all_entities, then="obj"), 

139 When(obj__in=all_entities, then="subj"), 

140 ) 

141 ) 

142 .values_list("related", flat=True) 

143 ) 

144 return queryset.filter(pk__in=t) 

145 

146 

147def changed_since(queryset, name, value): 

148 history = get_history_manager_for_model(queryset.model) 

149 ids = history.filter(history_date__gt=value).values_list("id", flat=True) 

150 return queryset.filter(pk__in=ids) 

151 

152 

153class AbstractEntityFilterSetForm(GenericFilterSetForm): 

154 columns_exclude = ABSTRACT_ENTITY_COLUMNS_EXCLUDE 

155 

156 

157class AbstractEntityFilterSet(GenericFilterSet): 

158 related_entity = django_filters.CharFilter( 

159 method=related_entity, label="Related entity contains" 

160 ) 

161 

162 class Meta(GenericFilterSet.Meta): 

163 form = AbstractEntityFilterSetForm 

164 exclude = ABSTRACT_ENTITY_FILTERS_EXCLUDE 

165 filter_overrides = { 

166 models.CharField: { 

167 "filter_class": django_filters.CharFilter, 

168 "extra": lambda f: { 

169 "lookup_expr": "icontains", 

170 }, 

171 }, 

172 } 

173 

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

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

176 self.filters["related_property"] = PropertyFilter( 

177 label="Related Property", model=getattr(self.Meta, "model", None) 

178 ) 

179 

180 if model := getattr(self.Meta, "model", False): 

181 self.filters["search"] = ModelSearchFilter(model=model) 

182 self.filters.move_to_end("search", False) 

183 

184 if "apis_core.history" in settings.INSTALLED_APPS: 

185 self.filters["changed_since"] = django_filters.DateFilter( 

186 label="Changed since", 

187 widget=DateInput(attrs={"type": "date"}), 

188 method=changed_since, 

189 )