Coverage for apis_core/relations/filtersets.py: 0%
48 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-06-25 10:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-06-25 10:00 +0000
1from django.contrib.contenttypes.models import ContentType
2from django.db.models import Q
3from django_filters import CharFilter, MultipleChoiceFilter
5from apis_core.apis_metainfo.models import RootObject
6from apis_core.generic.filtersets import GenericFilterSet
7from apis_core.generic.helpers import generate_search_filter
8from apis_core.relations.forms import RelationFilterSetForm
9from apis_core.relations.utils import get_all_relation_subj_and_obj
12class EntityFilter(CharFilter):
13 """
14 Custom CharFilter that uses the generate_search_filter helper
15 to search in all instances inheriting from RootObject and then
16 uses those results to only list relations that point to one of
17 the results.
18 """
20 def __init__(self, *args, **kwargs):
21 super().__init__(*args, **kwargs)
22 self.extra["help_text"] = "Searches in subclasses of RootObject"
24 def _search_all_entities(self, value) -> list[str]:
25 q = Q()
26 for content_type in get_all_relation_subj_and_obj():
27 name = content_type.model
28 q |= Q(**{f"{name}__isnull": False}) & generate_search_filter(
29 content_type.model_class(), value, prefix=f"{name}__"
30 )
31 return RootObject.objects_inheritance.filter(q).values_list("pk", flat=True)
33 def filter(self, qs, value):
34 if value:
35 all_entities = self._search_all_entities(value)
36 return qs.filter(**{f"{self.field_name}_object_id__in": all_entities})
37 return qs
40class SubjObjClassFilter(MultipleChoiceFilter):
41 """
42 Custom MultipleChoiceFilter
43 * it lists model classes as choices, that are in some way
44 connected to a relation, either as subj or as obj
45 * it filters relations by the content types of the connected
46 subjects and objects
47 """
49 def __init__(self, models, *args, **kwargs):
50 super().__init__(*args, **kwargs)
51 content_types = [ContentType.objects.get_for_model(model) for model in models]
52 self.extra["choices"] = [(item.id, item.name) for item in content_types]
54 def filter(self, qs, value: list[str] | None):
55 # value is the list of contenttypes ids
56 if value:
57 return qs.filter(Q(**{f"{self.field_name}_content_type__in": value}))
58 return qs
61class RelationFilterSet(GenericFilterSet):
62 """
63 Override the GenericFilterSet that is created for the Relation model.
64 It does not really make sense to filter for content type id and object id,
65 so we exclude those.
66 Instead, we add a multiple choice filter for object and subject class, that
67 only lists those choices that actually exists (meaning the classes that are
68 actually set as subj or obj in some relation).
69 Additionaly, we add a search filter, that searches in instances connected
70 to relations (this does only work for instances inheriting from RootObject).
71 """
73 subj_search = EntityFilter(
74 field_name="subj",
75 label="Subject search",
76 )
77 obj_search = EntityFilter(
78 field_name="obj",
79 label="Object search",
80 )
82 class Meta:
83 exclude = [
84 "subj_object_id",
85 "subj_content_type",
86 "obj_object_id",
87 "obj_content_type",
88 ]
89 form = RelationFilterSetForm
91 def __init__(self, *args, **kwargs):
92 super().__init__(*args, **kwargs)
93 if model := getattr(self.Meta, "model", False):
94 all_models = [ct.model_class() for ct in get_all_relation_subj_and_obj()]
95 subj_models = getattr(model, "subj_model", all_models)
96 obj_models = getattr(model, "obj_model", all_models)
97 if isinstance(subj_models, list):
98 self.filters["subj_class"] = SubjObjClassFilter(
99 field_name="subj", label="Subject class", models=subj_models
100 )
101 if isinstance(obj_models, list):
102 self.filters["obj_class"] = SubjObjClassFilter(
103 field_name="obj", label="Object Class", models=obj_models
104 )