Skip to content

Commit

Permalink
refactor(apis_entities, apis_relations): move filters and methods
Browse files Browse the repository at this point in the history
Move the Property and Triple related filters and methods to the
`apis_relations.filters` module instead of keeping them in the
`apis_entities.filtersets` module. This makes the whole module more
readable and moves the filters and methods closer to the models they are
working with. Additionaly, this makes it possible to only import the
fitlers and methods if the `apis_relations` app is actually part of the
INSTALLED_APPS list.
  • Loading branch information
b1rger committed Nov 21, 2024
1 parent 19cd2f3 commit 0a167cd
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 89 deletions.
98 changes: 9 additions & 89 deletions apis_core/apis_entities/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import django_filters
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Case, Q, Value, When
from django.db.models.functions import Concat
from django.forms import DateInput
from django.utils.encoding import force_str
from simple_history.utils import get_history_manager_for_model

from apis_core.apis_entities.utils import get_entity_classes
from apis_core.apis_metainfo.models import RootObject
from apis_core.apis_relations.models import Property, Triple
from apis_core.generic.filtersets import GenericFilterSet, GenericFilterSetForm
from apis_core.generic.helpers import default_search_fields, generate_search_filter

Expand Down Expand Up @@ -38,60 +32,6 @@
]


class PropertyChoiceField(django_filters.fields.ModelChoiceField):
def label_from_instance(self, obj):
if obj.forward:
targets = [ct.name for ct in obj.obj_class.all()]
else:
targets = [ct.name for ct in obj.subj_class.all()]
return obj.name + " | " + ", ".join(targets)


class PropertyFilter(django_filters.ModelChoiceFilter):
"""
A child of ModelChoiceFilter that only works with
Properties, but in return it filters those so that
only the Properties are listed that can be connected
to the `model` given as argument.
"""

field_class = PropertyChoiceField

def __init__(self, *args, **kwargs):
model = kwargs.pop("model", None)
super().__init__(*args, **kwargs)

if model is not None:
ct = ContentType.objects.get_for_model(model)
self.queryset = (
Property.objects.all()
.filter(Q(subj_class=ct) | Q(obj_class=ct))
.annotate(
name=Case(
When(
obj_class=ct,
subj_class=ct,
then=Concat("name_forward", Value(" / "), "name_reverse"),
),
When(obj_class=ct, then="name_reverse"),
When(subj_class=ct, then="name_forward"),
),
forward=Case(
When(obj_class=ct, then=Value(False)),
When(subj_class=ct, then=Value(True)),
),
)
.order_by("name")
.distinct()
)

def filter(self, queryset, value):
if value:
p = Property.objects.get(name_forward=value)
queryset = queryset.filter(triple_set_from_subj__prop=p).distinct()
return queryset


class ModelSearchFilter(django_filters.CharFilter):
"""
This filter is a customized CharFilter that
Expand All @@ -117,28 +57,6 @@ def filter(self, qs, value):
return qs.filter(generate_search_filter(qs.model, value))


def related_entity(queryset, name, value):
entities = get_entity_classes()
q = Q()
for entity in entities:
name = entity._meta.model_name
q |= Q(**{f"{name}__isnull": False}) & generate_search_filter(
entity, value, prefix=f"{name}__"
)
all_entities = RootObject.objects_inheritance.filter(q).values_list("pk", flat=True)
t = (
Triple.objects.filter(Q(subj__in=all_entities) | Q(obj__in=all_entities))
.annotate(
related=Case(
When(subj__in=all_entities, then="obj"),
When(obj__in=all_entities, then="subj"),
)
)
.values_list("related", flat=True)
)
return queryset.filter(pk__in=t)


def changed_since(queryset, name, value):
history = get_history_manager_for_model(queryset.model)
ids = history.filter(history_date__gt=value).values_list("id", flat=True)
Expand All @@ -150,10 +68,6 @@ class AbstractEntityFilterSetForm(GenericFilterSetForm):


class AbstractEntityFilterSet(GenericFilterSet):
related_entity = django_filters.CharFilter(
method=related_entity, label="Related entity contains"
)

class Meta(GenericFilterSet.Meta):
form = AbstractEntityFilterSetForm
exclude = ABSTRACT_ENTITY_FILTERS_EXCLUDE
Expand All @@ -168,9 +82,15 @@ class Meta(GenericFilterSet.Meta):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters["related_property"] = PropertyFilter(
label="Related Property", model=getattr(self.Meta, "model", None)
)
if "apis_core.apis_relations" in settings.INSTALLED_APPS:
from apis_core.apis_relations.filters import PropertyFilter, related_entity

self.filters["related_property"] = PropertyFilter(
label="Related Property", model=getattr(self.Meta, "model", None)
)
self.filters["related_entity"] = django_filters.CharFilter(
method=related_entity, label="Related entity contains"
)

if model := getattr(self.Meta, "model", False):
self.filters["search"] = ModelSearchFilter(model=model)
Expand Down
90 changes: 90 additions & 0 deletions apis_core/apis_relations/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import django_filters
from django.contrib.contenttypes.models import ContentType
from django.db.models import Case, Q, Value, When
from django.db.models.functions import Concat

from apis_core.apis_entities.utils import get_entity_classes
from apis_core.apis_metainfo.models import RootObject
from apis_core.generic.helpers import generate_search_filter


def related_entity(queryset, name, value):
from apis_core.apis_relations.models import Triple

entities = get_entity_classes()
q = Q()
for entity in entities:
name = entity._meta.model_name
q |= Q(**{f"{name}__isnull": False}) & generate_search_filter(
entity, value, prefix=f"{name}__"
)
all_entities = RootObject.objects_inheritance.filter(q).values_list("pk", flat=True)
t = (
Triple.objects.filter(Q(subj__in=all_entities) | Q(obj__in=all_entities))
.annotate(
related=Case(
When(subj__in=all_entities, then="obj"),
When(obj__in=all_entities, then="subj"),
)
)
.values_list("related", flat=True)
)
return queryset.filter(pk__in=t)


class PropertyChoiceField(django_filters.fields.ModelChoiceField):
def label_from_instance(self, obj):
if obj.forward:
targets = [ct.name for ct in obj.obj_class.all()]
else:
targets = [ct.name for ct in obj.subj_class.all()]
return obj.name + " | " + ", ".join(targets)


class PropertyFilter(django_filters.ModelChoiceFilter):
"""
A child of ModelChoiceFilter that only works with
Properties, but in return it filters those so that
only the Properties are listed that can be connected
to the `model` given as argument.
"""

field_class = PropertyChoiceField

def __init__(self, *args, **kwargs):
from apis_core.apis_relations.models import Property

model = kwargs.pop("model", None)
super().__init__(*args, **kwargs)

if model is not None:
ct = ContentType.objects.get_for_model(model)
self.queryset = (
Property.objects.all()
.filter(Q(subj_class=ct) | Q(obj_class=ct))
.annotate(
name=Case(
When(
obj_class=ct,
subj_class=ct,
then=Concat("name_forward", Value(" / "), "name_reverse"),
),
When(obj_class=ct, then="name_reverse"),
When(subj_class=ct, then="name_forward"),
),
forward=Case(
When(obj_class=ct, then=Value(False)),
When(subj_class=ct, then=Value(True)),
),
)
.order_by("name")
.distinct()
)

def filter(self, queryset, value):
from apis_core.apis_relations.models import Property

if value:
p = Property.objects.get(name_forward=value)
queryset = queryset.filter(triple_set_from_subj__prop=p).distinct()
return queryset

0 comments on commit 0a167cd

Please sign in to comment.