Django-filter: OR ์กฐ๊ฑด์œผ๋กœ ์—ฌ๋Ÿฌ ํ•„๋“œ ํ•„ํ„ฐ๋ง

์— ๋งŒ๋“  2019๋…„ 10์›” 13์ผ  ยท  8์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: carltongibson/django-filter

์•ˆ๋…•,
OR ์กฐ๊ฑด์œผ๋กœ ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•!?
์˜ˆ๋ฅผ ๋“ค์–ด:
first_name = foo OR last_name = bar
๋™๋“ฑ:
model.objects.filter(Q(first_name=foo) | Q(last_name=bar))

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์ง€๋งŒ ๋ชจ๋“  ๊ฒฝ์šฐ์— ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

class BaseFilter(django_filters.FilterSet):
    def OR(self, queryset, field_name, value):
        if not hasattr(self, "groups"):
            setattr(self, "groups", {})

        self.groups[field_name] = value
        return queryset

    <strong i="6">@property</strong>
    def qs(self):
        base_queryset = super().qs

        if not hasattr(self, "groups"):
            return base_queryset

        query = Q()
        for key, value in self.groups.items():
            query |= Q(**{key: value})

        return base_queryset.filter(query)
class PlanFilter(BaseFilter):
    double_visit__or = django_filters.UUIDFilter("double_visit", method="OR")
    visitor__or = django_filters.UUIDFilter("visitor", method="OR")
    class Meta:
        model = models.Plan
        fields = {
            "id": ["exact"],
            "date": ["exact", "gte", "lte",],
            "visitor": ["exact"],
            "doctor": ["exact"],
            "double_visit": ["exact"]
        }

๋ชจ๋“  8 ๋Œ“๊ธ€

์—ด์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ œ๊ฐ€ ๊ฐ€์ง„ ์งˆ๋ฌธ๊ณผ ์ •ํ™•ํžˆ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ๊ณ ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

/usr/local/lib/python3.7/site-packages/django_filters/rest_framework/backends.py:128: 
UserWarning: <class 'project.api.views.FooBarViewSet'> is not compatible with schema generation

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ FilterSet๊ณผ ViewSet์ด ์žˆ์Šต๋‹ˆ๋‹ค.

# models
class FooBarUserAssignment(models.Model):
    user = models.ForeignKey("auth.User", on_delete=models.CASCADE)
    foobar = models.ForeignKey("FooBar", on_delete=models.CASCADE)

    class Meta:
        unique_together = (
            ("user", "foobar"),
        )


class FooBarGroupAssignment(models.Model):
    group = models.ForeignKey("auth.Group", on_delete=models.CASCADE)
    foobar = models.ForeignKey("FooBar", on_delete=models.CASCADE)

    class Meta:
        unique_together = (
            ("group", "foobar"),
        )

class FooBar(models.Model):
    title = models.CharField(max_length=160, unique=True)

    users = models.ManyToManyField(
        to="auth.User",
        through=FooBarUserAssignment,
    )
    groups = models.ManyToManyField(
        to="auth.Group",
        through=FooBarGroupAssignment,
    )

    def __str__(self):
        return self.title

# filters
from django_filters import rest_framework as rest_framework_filters

class FooBarFilter(rest_framework_filters.FilterSet):
    title = rest_framework_filters.CharFilter(field_name="title", lookup_expr="icontains")

    class Meta:
        model = FooBar
        fields = ("title", )

# viewsets
class FooBarViewSet(ModelViewSet):
    queryset = Foobar.objects.order_by("-title")
    serializer_class = FooBarSerializer
    filterset_class = FooBarFilter

    def get_queryset(self):
        queryset = self.queryset

        q_name = Q()
        rel_name = self.request.query_params.get("rel_name", None)
        if rel_name:
            q_name = Q(users__name=rel_name)

        q_groups = Q()
        rel_groups = self.request.query_params.get("rel_groups", "").split(",")
        if any(rel_groups):
            q_groups = Q(groups__name__in=rel_groups)

        qs = queryset.filter(q_name | q_groups).distinct()
        return qs

์ •ํ™•ํžˆ ๋™์ผํ•œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์–ด๋–ป๊ฒŒ FilterSet์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?
FilterSet์˜ ์ผ๋ถ€๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ OR ๋…ผ๋ฆฌ๋ฅผ ViewSet.get_queryset() ์—์„œ FilterSet ๋กœ ์ด๋™ํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ๋ฌด์—‡์ธ์ง€ ์—ฌ์ „ํžˆ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.

import django_filters
from django_filters import rest_framework as rest_framework_filters

class CharInFilter(django_filters.BaseInFilter, rest_framework_filters.CharFilter):
    pass

class FooBarFilter(rest_framework_filters.FilterSet):
    title = rest_framework_filters.CharFilter(field_name="title", lookup_expr="icontains")
    rel_name = rest_framework_filters.CharFilter(field_name="users__name", lookup_expr="exact")
    rel_groups = CharInFilter(field_name="groups__name")

    class Meta:
        model = FooBar
        fields = ("title", )

    def filter_queryset(self, queryset):
        qs = super().filter_queryset(queryset)
        return qs.distinct()

์ด๊ฒƒ์€ ์‚ฌ์ด๋“œ django-filter์™€ ํ•จ๊ป˜ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ๋Š” django-filter ํ™•์žฅ์˜ ์ผ๋ถ€์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. https://github.com/philipn/django-rest-framework-filters#complex -operations

์•ˆ๋…• ๋ชจ๋‘. ๊ทธ๋ฃน ์ˆ˜์ค€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ํ•„ํ„ฐ๋ง์„ ์ œ๊ณตํ•ด์•ผ ํ•˜๋Š” #1167์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. OP์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

class UserFilter(FilterSet):
    class Meta:
        model = User
        field = ['username', 'first_name', 'last_name']
        groups = [
            CombinedGroup(filters=['first_name', 'last_name'], combine=operator.or_),
        ]

PR์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์€ ํฌ๊ฒŒ ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์—ฌ์ „ํžˆ ๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ง ์†”๋ฃจ์…˜์ด ์žˆ์Šต๋‹ˆ๊นŒ?

@JeromeK13 ์€ ์•„๋ฌด๊ฒƒ๋„ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค! ๊ฐ™์€ ์งˆ๋ฌธ์ด ์žˆ์Šต๋‹ˆ๋‹ค

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์ง€๋งŒ ๋ชจ๋“  ๊ฒฝ์šฐ์— ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์ง€๋งŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

class BaseFilter(django_filters.FilterSet):
    def OR(self, queryset, field_name, value):
        if not hasattr(self, "groups"):
            setattr(self, "groups", {})

        self.groups[field_name] = value
        return queryset

    <strong i="6">@property</strong>
    def qs(self):
        base_queryset = super().qs

        if not hasattr(self, "groups"):
            return base_queryset

        query = Q()
        for key, value in self.groups.items():
            query |= Q(**{key: value})

        return base_queryset.filter(query)
class PlanFilter(BaseFilter):
    double_visit__or = django_filters.UUIDFilter("double_visit", method="OR")
    visitor__or = django_filters.UUIDFilter("visitor", method="OR")
    class Meta:
        model = models.Plan
        fields = {
            "id": ["exact"],
            "date": ["exact", "gte", "lte",],
            "visitor": ["exact"],
            "doctor": ["exact"],
            "double_visit": ["exact"]
        }

ํ›จ์”ฌ ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

from django.db.models.query_utils import Q
user_contacts = Contact.objects.order_by('-contact_date').filter(Q(sender_id=request.user.user_id)|Q(receiver_id=request.user.user_id)) 

์ œ ๊ฒฝ์šฐ์—๋Š” sender_id ๋˜๋Š” receiver_id ๊ฐ€ ์žˆ๋Š” ๋ชจ๋“  ํ–‰์„ ์ˆ˜์ง‘ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  date ๋กœ ์ฃผ๋ฌธํ•˜๋„๋ก ์„ค์ •๋œ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ์— ํฌํ•จ๋˜๊ธฐ๋ฅผ ์›ํ–ˆ์Šต๋‹ˆ๋‹ค.

์•ˆ๋…•,
OR ์กฐ๊ฑด์œผ๋กœ ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•!?
์˜ˆ๋ฅผ ๋“ค์–ด:
first_name = foo OR last_name = bar
๋™๋“ฑ:
model.objects.filter(Q(first_name=foo) | Q(last_name=bar))

์ˆ˜์ž… Q

django.db.models ๊ฐ€์ ธ์˜ค๊ธฐ Q

๊ทธ๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰