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
« 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
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
16ABSTRACT_ENTITY_COLUMNS_EXCLUDE = [
17 "rootobject_ptr",
18 "self_contenttype",
19 "triple_set_from_subj",
20 "triple_set_from_obj",
21 "uri",
22]
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]
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)
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 """
57 field_class = PropertyChoiceField
59 def __init__(self, *args, **kwargs):
60 model = kwargs.pop("model", None)
61 super().__init__(*args, **kwargs)
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 )
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
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 """
104 def __init__(self, *args, **kwargs):
105 model = kwargs.pop("model", None)
106 super().__init__(*args, **kwargs)
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}"
121 def filter(self, qs, value):
122 return qs.filter(generate_search_filter(qs.model, value))
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)
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)
153class AbstractEntityFilterSetForm(GenericFilterSetForm):
154 columns_exclude = ABSTRACT_ENTITY_COLUMNS_EXCLUDE
157class AbstractEntityFilterSet(GenericFilterSet):
158 related_entity = django_filters.CharFilter(
159 method=related_entity, label="Related entity contains"
160 )
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 }
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 )
180 if model := getattr(self.Meta, "model", False):
181 self.filters["search"] = ModelSearchFilter(model=model)
182 self.filters.move_to_end("search", False)
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 )