Skip to content

Commit

Permalink
Merge pull request #37 from maykinmedia/feature/nested-filterset
Browse files Browse the repository at this point in the history
✨ added filterset support for nested fields
  • Loading branch information
bart-maykin authored Apr 18, 2024
2 parents f224a07 + 64a2862 commit 5406d47
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 6 deletions.
22 changes: 19 additions & 3 deletions django_loose_fk/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,19 @@ def filter(self, qs, value):

parsed_values = [urlparse(value) for value in values]
host = self.parent.request.get_host()
model_field = self.model._meta.get_field(self.field_name)

filters = self.get_filters(model_field, parsed_values, host)
model_field_list = self.field_name.split("__")
model_field_path = (
f"{self.field_name.rsplit('__', 1)[0]}__"
if len(model_field_list) > 1
else None
)
model_field = self.model._meta.get_field(model_field_list.pop(0))

for field_name in model_field_list:
model_field = model_field.target_field.model._meta.get_field(field_name)

filters = self.get_filters(model_field, parsed_values, host, model_field_path)

# In case the query contained both local and remote zaaktypen, then the filters dict will be
# {'_zaaktype__in': ['url'], 'externe_zaaktype__in': ['url']}. These filters need to be OR'd
Expand All @@ -68,10 +78,16 @@ def filter(self, qs, value):
qs = self.get_method(qs)(complex_filter)
return qs.distinct() if self.distinct else qs

def get_filters(self, model_field, parsed_values, host) -> dict:
def get_filters(
self, model_field, parsed_values, host, model_field_path=None
) -> dict:
local_filter_key = f"{model_field.fk_field}__{self.lookup_expr}"
external_filter_key = f"{model_field.url_field}__{self.lookup_expr}"

if model_field_path:
local_filter_key = model_field_path + local_filter_key
external_filter_key = model_field_path + external_filter_key

filters = {}
for value in parsed_values:
local = is_local(host, value.geturl())
Expand Down
27 changes: 26 additions & 1 deletion testapp/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from django_loose_fk.filters import FkOrUrlFieldFilter

from .models import Zaak, ZaakObject, ZaakType
from .models import Zaak, ZaakObject, ZaakObjectFk, ZaakType

# Serializers

Expand All @@ -27,6 +27,12 @@ class Meta:
fields = ("url", "zaak", "name")


class ZaakObjectFkSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ZaakObjectFk
fields = ("zaak_object", "name")


# Filters


Expand All @@ -46,6 +52,17 @@ class Meta:
fields = ("zaak",)


class ZaakObjectFkFilterset(FilterSet):
zaak = FkOrUrlFieldFilter(
queryset=ZaakObjectFk.objects.all(),
field_name="zaak_object__zaak",
)

class Meta:
model = ZaakObjectFk
fields = ("zaak",)


# Viewsets


Expand All @@ -72,9 +89,17 @@ class ZaakObjectViewSet(NoAuthMixin, viewsets.ModelViewSet):
filterset_class = ZaakObjectFilterset


class ZaakObjectFkViewSet(NoAuthMixin, viewsets.ModelViewSet):
queryset = ZaakObjectFk.objects.all()
filter_backends = (DjangoFilterBackend,)
serializer_class = ZaakObjectFkSerializer
filterset_class = ZaakObjectFkFilterset


# URL routing

router = routers.DefaultRouter()
router.register("zaaktypes", ZaakTypeViewSet)
router.register("zaken", ZaakViewSet)
router.register("zaakobjecten", ZaakObjectViewSet)
router.register("zaakobjectfk", ZaakObjectFkViewSet)
38 changes: 38 additions & 0 deletions testapp/migrations/0004_zaakobjectfk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 5.0.4 on 2024-04-17 07:31

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("testapp", "0003_auto_20230705_0917"),
]

operations = [
migrations.CreateModel(
name="ZaakObjectFk",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(blank=True, max_length=80, null=True)),
(
"zaak_object",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="testapp.zaakobject",
),
),
],
),
]
7 changes: 7 additions & 0 deletions testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ class ZaakObject2(models.Model):
zaak = FkOrURLField(fk_field="_zaak", url_field="extern_zaak")


class ZaakObjectFk(models.Model):
name = models.CharField(max_length=80, null=True, blank=True)
zaak_object = models.ForeignKey(
"ZaakObject", null=True, blank=True, on_delete=models.PROTECT
)


class DummyModel(models.Model):
_zaaktype1 = models.ForeignKey(
"ZaakType", null=True, blank=True, on_delete=models.PROTECT, related_name="+"
Expand Down
51 changes: 49 additions & 2 deletions tests/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
from django_filters.rest_framework.filterset import FilterSet
from rest_framework.reverse import reverse

from testapp.api import ZaakFilterSet, ZaakViewSet
from testapp.models import Zaak, ZaakType
from testapp.api import (
ZaakFilterSet,
ZaakObjectFkFilterset,
ZaakObjectFkViewSet,
ZaakViewSet,
)
from testapp.models import Zaak, ZaakObject, ZaakObjectFk, ZaakType

pytestmark = pytest.mark.django_db()

Expand Down Expand Up @@ -107,3 +112,45 @@ def test_filter_with_remote_url(api_client):
assert response.status_code == 200
assert len(response.data) == 1
assert response.data[0]["name"] == "test2"


@override_settings(ALLOWED_HOSTS=["testserver.com"])
def test_filter_with_fk_or_url_field_from_other_model(api_client):
zaaktype1 = ZaakType.objects.create(name=1)
zaak = Zaak.objects.create(name="zaak", zaaktype=zaaktype1)
zaak1_uri = reverse("zaak-detail", kwargs={"pk": zaak.pk})
zaak1_url = f"http://testserver.com{zaak1_uri}"

zaak_object1 = ZaakObject.objects.create(name="zaak object one", zaak=zaak)
zaak_object2 = ZaakObject.objects.create(
name="zaak object two", zaak="https://example.com/zt/456"
)

zaak_object_fk1 = ZaakObjectFk.objects.create(
name="zaak object fk one", zaak_object=zaak_object1
)
zaak_object_fk2 = ZaakObjectFk.objects.create(
name="zaak object fk two", zaak_object=zaak_object2
)

ZaakObjectFkViewSet.filterset_class = ZaakObjectFkFilterset

response = api_client.get(
reverse("zaakobjectfk-list"),
{"zaak": "https://example.com/zt/456"},
HTTP_HOST="testserver.com",
)

assert response.status_code == 200
assert len(response.data) == 1
assert response.data[0]["name"] == zaak_object_fk2.name

response = api_client.get(
reverse("zaakobjectfk-list"),
{"zaak": zaak1_url},
HTTP_HOST="testserver.com",
)

assert response.status_code == 200
assert len(response.data) == 1
assert response.data[0]["name"] == zaak_object_fk1.name

0 comments on commit 5406d47

Please sign in to comment.