import contextlib
import datetime
import unittest
from operator import attrgetter
from unittest import mock

import django
from django import forms
from django.http import QueryDict
from django.test import TestCase, override_settings
from django.utils import timezone
from django.utils.timezone import make_aware, now

from django_filters.filters import (
    AllValuesFilter,
    AllValuesMultipleFilter,
    BaseInFilter,
    CharFilter,
    ChoiceFilter,
    DateFromToRangeFilter,
    DateRangeFilter,
    DateTimeFromToRangeFilter,
    DurationFilter,
    IsoDateTimeFromToRangeFilter,
    LookupChoiceFilter,
    ModelChoiceFilter,
    ModelMultipleChoiceFilter,
    MultipleChoiceFilter,
    OrderingFilter,
    RangeFilter,
    TimeRangeFilter,
    TypedMultipleChoiceFilter,
)
from django_filters.filterset import FilterSet
from django_filters.widgets import QueryArrayWidget

from .models import (
    STATUS_CHOICES,
    Account,
    Article,
    BankAccount,
    Book,
    Comment,
    Company,
    DirectedNode,
    Location,
    Node,
    Profile,
    SpacewalkRecord,
    User,
)
from .utils import MockQuerySet


class CharFilterTests(TestCase):
    def test_filtering(self):
        b1 = Book.objects.create(title="Ender's Game", price="1.00", average_rating=3.0)
        b2 = Book.objects.create(title="Rainbow Six", price="1.00", average_rating=3.0)
        b3 = Book.objects.create(title="Snowcrash", price="1.00", average_rating=3.0)

        class F(FilterSet):
            class Meta:
                model = Book
                fields = ["title"]

        qs = Book.objects.all()
        f = F(queryset=qs)
        self.assertQuerySetEqual(
            f.qs, [b1.pk, b2.pk, b3.pk], lambda o: o.pk, ordered=False
        )
        f = F({"title": "Snowcrash"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [b3.pk], lambda o: o.pk)


class IntegerFilterTest(TestCase):
    def test_filtering(self):
        default_values = {
            "in_good_standing": True,
            "friendly": False,
        }
        b1 = BankAccount.objects.create(amount_saved=0, **default_values)
        b2 = BankAccount.objects.create(amount_saved=3, **default_values)
        b3 = BankAccount.objects.create(amount_saved=10, **default_values)

        class F(FilterSet):
            class Meta:
                model = BankAccount
                fields = ["amount_saved"]

        qs = BankAccount.objects.all()
        f = F(queryset=qs)
        self.assertQuerySetEqual(
            f.qs, [b1.pk, b2.pk, b3.pk], lambda o: o.pk, ordered=False
        )
        f = F({"amount_saved": "10"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [b3.pk], lambda o: o.pk)
        f = F({"amount_saved": "0"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [b1.pk], lambda o: o.pk)


class BooleanFilterTests(TestCase):
    def test_filtering(self):
        User.objects.create(username="alex", is_active=False)
        User.objects.create(username="jacob", is_active=True)
        User.objects.create(username="aaron", is_active=False)

        class F(FilterSet):
            class Meta:
                model = User
                fields = ["is_active"]

        qs = User.objects.all()

        # '2' and '3' are how the field expects the data from the browser
        f = F({"is_active": "2"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["jacob"], lambda o: o.username, False)

        f = F({"is_active": "3"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex", "aaron"], lambda o: o.username, False)

        f = F({"is_active": "1"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["alex", "aaron", "jacob"], lambda o: o.username, False
        )


class ChoiceFilterTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

        Article.objects.create(author_id=1, published=now())
        Article.objects.create(author_id=2, published=now())
        Article.objects.create(author_id=3, published=now())
        Article.objects.create(author_id=4, published=now())
        Article.objects.create(author_id=None, published=now())

    def test_filtering(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["status"]

        f = F()
        self.assertQuerySetEqual(
            f.qs, ["aaron", "alex", "jacob", "carl"], lambda o: o.username, False
        )
        f = F({"status": "1"})
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username, False)

        f = F({"status": "2"})
        self.assertQuerySetEqual(f.qs, ["jacob", "aaron"], lambda o: o.username, False)

        f = F({"status": "0"})
        self.assertQuerySetEqual(f.qs, ["carl"], lambda o: o.username, False)

    def test_filtering_on_explicitly_defined_field(self):
        """
        Test for #30.

        If you explicitly declare ChoiceFilter fields you **MUST** pass `choices`.
        """

        class F(FilterSet):
            status = ChoiceFilter(choices=STATUS_CHOICES)

            class Meta:
                model = User
                fields = ["status"]

        f = F()
        self.assertQuerySetEqual(
            f.qs, ["aaron", "alex", "jacob", "carl"], lambda o: o.username, False
        )
        f = F({"status": "1"})
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username, False)

        f = F({"status": "2"})
        self.assertQuerySetEqual(f.qs, ["jacob", "aaron"], lambda o: o.username, False)

        f = F({"status": "0"})
        self.assertQuerySetEqual(f.qs, ["carl"], lambda o: o.username, False)

    def test_filtering_on_empty_choice(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["status"]

        f = F({"status": ""})
        self.assertQuerySetEqual(
            f.qs, ["aaron", "alex", "jacob", "carl"], lambda o: o.username, False
        )

    def test_filtering_on_null_choice(self):
        choices = [(u.pk, str(u)) for u in User.objects.order_by("id")]

        class F(FilterSet):
            author = ChoiceFilter(
                choices=choices,
                null_value="null",
                null_label="NULL",
            )

            class Meta:
                model = Article
                fields = ["author"]

        # sanity check to make sure the filter is setup correctly
        f = F({"author": "1"})
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: str(o.author), False)

        f = F({"author": "null"})
        self.assertQuerySetEqual(f.qs, [None], lambda o: o.author, False)


class MultipleChoiceFilterTests(TestCase):
    def test_filtering(self):
        User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

        class F(FilterSet):
            status = MultipleChoiceFilter(choices=STATUS_CHOICES)

            class Meta:
                model = User
                fields = ["status"]

        qs = User.objects.all().order_by("username")
        f = F(queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["aaron", "jacob", "alex", "carl"], lambda o: o.username, False
        )

        f = F({"status": ["0"]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["carl"], lambda o: o.username)

        f = F({"status": ["0", "1"]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex", "carl"], lambda o: o.username)

        f = F({"status": ["0", "1", "2"]}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["aaron", "alex", "carl", "jacob"], lambda o: o.username
        )

    def test_filtering_on_null_choice(self):
        User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

        Article.objects.create(author_id=1, published=now())
        Article.objects.create(author_id=2, published=now())
        Article.objects.create(author_id=3, published=now())
        Article.objects.create(author_id=4, published=now())
        Article.objects.create(author_id=None, published=now())

        choices = [(u.pk, str(u)) for u in User.objects.order_by("id")]

        class F(FilterSet):
            author = MultipleChoiceFilter(
                choices=choices,
                null_value="null",
                null_label="NULL",
            )

            class Meta:
                model = Article
                fields = ["author"]

        # sanity check to make sure the filter is setup correctly
        f = F({"author": ["1"]})
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: str(o.author), False)

        f = F({"author": ["null"]})
        self.assertQuerySetEqual(f.qs, [None], lambda o: o.author, False)

        f = F({"author": ["1", "null"]})
        self.assertQuerySetEqual(
            f.qs, ["alex", None], lambda o: o.author and str(o.author), False
        )


class TypedMultipleChoiceFilterTests(TestCase):
    def test_filtering(self):
        User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

        class F(FilterSet):
            status = TypedMultipleChoiceFilter(
                choices=STATUS_CHOICES, coerce=lambda x: x[0:2]
            )

            class Meta:
                model = User
                fields = ["status"]

        qs = User.objects.all().order_by("username")
        f = F(queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["aa", "ja", "al", "ca"], lambda o: o.username[0:2], False
        )

        f = F({"status": ["0"]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["ca"], lambda o: o.username[0:2])

        f = F({"status": ["0", "1"]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["al", "ca"], lambda o: o.username[0:2])

        f = F({"status": ["0", "1", "2"]}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["aa", "al", "ca", "ja"], lambda o: o.username[0:2]
        )


class DateFilterTests(TestCase):
    def test_filtering(self):
        today = now().date()
        timestamp = now().time().replace(microsecond=0)
        last_week = today - datetime.timedelta(days=7)
        check_date = str(last_week)
        u = User.objects.create(username="alex")
        Comment.objects.create(author=u, time=timestamp, date=today)
        c2 = Comment.objects.create(author=u, time=timestamp, date=last_week)
        Comment.objects.create(author=u, time=timestamp, date=today)
        c4 = Comment.objects.create(author=u, time=timestamp, date=last_week)

        class F(FilterSet):
            class Meta:
                model = Comment
                fields = ["date"]

        f = F({"date": check_date}, queryset=Comment.objects.all())
        self.assertEqual(len(f.qs), 2)
        self.assertQuerySetEqual(f.qs, [c2.pk, c4.pk], lambda o: o.pk, False)


class TimeFilterTests(TestCase):
    def test_filtering(self):
        today = now().date()
        now_time = now().time().replace(microsecond=0)
        ten_min_ago = now() - datetime.timedelta(minutes=10)
        fixed_time = ten_min_ago.time().replace(microsecond=0)
        check_time = str(fixed_time)
        u = User.objects.create(username="alex")
        Comment.objects.create(author=u, time=now_time, date=today)
        c2 = Comment.objects.create(author=u, time=fixed_time, date=today)
        Comment.objects.create(author=u, time=now_time, date=today)
        c4 = Comment.objects.create(author=u, time=fixed_time, date=today)

        class F(FilterSet):
            class Meta:
                model = Comment
                fields = ["time"]

        f = F({"time": check_time}, queryset=Comment.objects.all())
        self.assertEqual(len(f.qs), 2)
        self.assertQuerySetEqual(f.qs, [c2.pk, c4.pk], lambda o: o.pk, False)


class DateTimeFilterTests(TestCase):
    def test_filtering(self):
        now_dt = now()
        ten_min_ago = now_dt - datetime.timedelta(minutes=10)
        one_day_ago = now_dt - datetime.timedelta(days=1)
        u = User.objects.create(username="alex")
        Article.objects.create(author=u, published=now_dt)
        a2 = Article.objects.create(author=u, published=ten_min_ago)
        Article.objects.create(author=u, published=one_day_ago)

        tz = timezone.get_current_timezone()
        # make naive, like a browser would send
        local_ten_min_ago = timezone.make_naive(ten_min_ago, tz)
        check_dt = str(local_ten_min_ago)

        class F(FilterSet):
            class Meta:
                model = Article
                fields = ["published"]

        qs = Article.objects.all()
        f = F({"published": ten_min_ago}, queryset=qs)
        self.assertEqual(len(f.qs), 1)
        self.assertQuerySetEqual(f.qs, [a2.pk], lambda o: o.pk)

        # this is how it would come through a browser
        f = F({"published": check_dt}, queryset=qs)
        self.assertEqual(
            len(f.qs), 1, "%s isn't matching %s when cleaned" % (check_dt, ten_min_ago)
        )
        self.assertQuerySetEqual(f.qs, [a2.pk], lambda o: o.pk)


class DurationFilterTests(TestCase):
    """Duration filter tests.

    The preferred format for durations in Django is '%d %H:%M:%S.%f'.
    See django.utils.dateparse.parse_duration

    Django is not fully ISO 8601 compliant (yet): year, month, and
    week designators are not supported, so a duration string
    like "P3Y6M4DT12H30M5S" cannot be used.
    See https://en.wikipedia.org/wiki/ISO_8601#Durations

    """

    def setUp(self):
        self.r1 = SpacewalkRecord.objects.create(
            astronaut="Anatoly Solovyev",
            duration=datetime.timedelta(hours=82, minutes=22),
        )
        self.r2 = SpacewalkRecord.objects.create(
            astronaut="Michael Lopez-Alegria",
            duration=datetime.timedelta(hours=67, minutes=40),
        )
        self.r3 = SpacewalkRecord.objects.create(
            astronaut="Jerry L. Ross", duration=datetime.timedelta(hours=58, minutes=32)
        )
        self.r4 = SpacewalkRecord.objects.create(
            astronaut="John M. Grunsfeld",
            duration=datetime.timedelta(hours=58, minutes=30),
        )
        self.r5 = SpacewalkRecord.objects.create(
            astronaut="Richard Mastracchio",
            duration=datetime.timedelta(hours=53, minutes=4),
        )

    def test_filtering(self):
        class F(FilterSet):
            class Meta:
                model = SpacewalkRecord
                fields = ["duration"]

        qs = SpacewalkRecord.objects.all()

        # Django style: 3 days, 10 hours, 22 minutes.
        f = F({"duration": "3 10:22:00"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r1], lambda x: x)

        # ISO 8601: 3 days, 10 hours, 22 minutes.
        f = F({"duration": "P3DT10H22M"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r1], lambda x: x)

        # Django style: 82 hours, 22 minutes.
        f = F({"duration": "82:22:00"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r1], lambda x: x)

        # ISO 8601: 82 hours, 22 minutes.
        f = F({"duration": "PT82H22M"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r1], lambda x: x)

    def test_filtering_with_single_lookup_expr_dictionary(self):
        class F(FilterSet):
            class Meta:
                model = SpacewalkRecord
                fields = {"duration": ["gt", "gte", "lt", "lte"]}

        qs = SpacewalkRecord.objects.order_by("-duration")

        f = F({"duration__gt": "PT58H30M"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r1, self.r2, self.r3], lambda x: x)

        f = F({"duration__gte": "PT58H30M"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, [self.r1, self.r2, self.r3, self.r4], lambda x: x
        )

        f = F({"duration__lt": "PT58H30M"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r5], lambda x: x)

        f = F({"duration__lte": "PT58H30M"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r4, self.r5], lambda x: x)

    def test_filtering_with_multiple_lookup_exprs(self):
        class F(FilterSet):
            min_duration = DurationFilter(field_name="duration", lookup_expr="gte")
            max_duration = DurationFilter(field_name="duration", lookup_expr="lte")

            class Meta:
                model = SpacewalkRecord
                fields = "__all__"

        qs = SpacewalkRecord.objects.order_by("duration")

        f = F({"min_duration": "PT55H", "max_duration": "PT60H"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.r4, self.r3], lambda x: x)


class ModelChoiceFilterTests(TestCase):
    def test_filtering(self):
        alex = User.objects.create(username="alex")
        jacob = User.objects.create(username="jacob")
        date = now().date()
        time = now().time()
        c1 = Comment.objects.create(author=jacob, time=time, date=date)
        Comment.objects.create(author=alex, time=time, date=date)
        c3 = Comment.objects.create(author=jacob, time=time, date=date)

        class F(FilterSet):
            class Meta:
                model = Comment
                fields = ["author"]

        qs = Comment.objects.all()
        f = F({"author": jacob.pk}, queryset=qs)

        self.assertQuerySetEqual(f.qs, [c1.pk, c3.pk], lambda o: o.pk, False)

    @override_settings(FILTERS_NULL_CHOICE_LABEL="No Author")
    def test_filtering_null(self):
        Article.objects.create(published=now())
        alex = User.objects.create(username="alex")
        Article.objects.create(author=alex, published=now())

        class F(FilterSet):
            class Meta:
                model = Article
                fields = ["author", "name"]

        qs = Article.objects.all()
        f = F({"author": "null"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [None], lambda o: o.author, False)

    def test_callable_queryset(self):
        # Sanity check for callable queryset arguments.
        # Ensure that nothing is improperly cached
        alex = User.objects.create(username="alex")
        jacob = User.objects.create(username="jacob")
        aaron = User.objects.create(username="aaron")

        def users(request):
            return User.objects.filter(pk__lt=request.user.pk)

        class F(FilterSet):
            author = ModelChoiceFilter(field_name="author", queryset=users)

            class Meta:
                model = Comment
                fields = ["author"]

        qs = Comment.objects.all()
        request = mock.Mock()

        request.user = jacob
        f = F(queryset=qs, request=request).filters["author"].field
        self.assertQuerySetEqual(f.queryset, [alex.pk], lambda o: o.pk, False)

        request.user = aaron
        f = F(queryset=qs, request=request).filters["author"].field
        self.assertQuerySetEqual(f.queryset, [alex.pk, jacob.pk], lambda o: o.pk, False)


class ModelMultipleChoiceFilterTests(TestCase):
    def setUp(self):
        self.alex = User.objects.create(username="alex")
        User.objects.create(username="jacob")
        aaron = User.objects.create(username="aaron")
        self.b1 = Book.objects.create(
            title="Ender's Game",
            price="1.00",
            average_rating=3.0,
        )
        self.b2 = Book.objects.create(
            title="Rainbow Six",
            price="1.00",
            average_rating=3.0,
        )
        self.b3 = Book.objects.create(
            title="Snowcrash",
            price="1.00",
            average_rating=3.0,
        )
        self.b4 = Book.objects.create(
            title="Stranger in a Strage Land",
            price="1.00",
            average_rating=3.0,
        )
        self.alex.favorite_books.add(self.b1, self.b2)
        aaron.favorite_books.add(self.b1, self.b3)

    def test_filtering(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books"]

        qs = User.objects.all().order_by("username")
        f = F({"favorite_books": [self.b1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b1.pk, self.b3.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b2.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b4.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.username)

    @override_settings(FILTERS_NULL_CHOICE_LABEL="No Favorites")
    def test_filtering_null(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books"]

        qs = User.objects.all()
        f = F({"favorite_books": ["null"]}, queryset=qs)

        self.assertQuerySetEqual(f.qs, ["jacob"], lambda o: o.username)

    def test_filtering_dictionary(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = {"favorite_books": ["exact"]}

        qs = User.objects.all().order_by("username")
        f = F({"favorite_books": [self.b1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b1.pk, self.b3.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b2.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b4.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.username)

    def test_filtering_on_all_of_subset_of_choices(self):
        parent = self

        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books"]

            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                # This filter has a limited number of choices.
                self.filters["favorite_books"].extra.update(
                    {
                        "queryset": Book.objects.filter(
                            id__in=[parent.b1.pk, parent.b2.pk]
                        )
                    }
                )

                self.filters["favorite_books"].extra["required"] = True

        qs = User.objects.all().order_by("username")

        # Select all the given choices.
        f = F({"favorite_books": [self.b1.pk, self.b2.pk]}, queryset=qs)

        # The results should only include matching users - not Jacob.
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

    def test_filtering_on_non_required_fields(self):
        # See issue #132 - filtering with all options on a non-required
        # field should exclude any results where the field is null.
        class F(FilterSet):
            author = ModelMultipleChoiceFilter(queryset=User.objects.all())

            class Meta:
                model = Article
                fields = ["author"]

        published = now()
        Article.objects.create(published=published, author=self.alex)
        Article.objects.create(published=published, author=self.alex)
        Article.objects.create(published=published)

        qs = Article.objects.all()

        # Select all authors.
        authors = [str(user.id) for user in User.objects.all()]
        f = F({"author": authors}, queryset=qs)

        # The results should not include anonymous articles
        self.assertEqual(
            set(f.qs),
            set(Article.objects.exclude(author__isnull=True)),
        )


class NumberFilterTests(TestCase):
    def setUp(self):
        Book.objects.create(
            title="Ender's Game", price="10.0", average_rating=4.7999999999999998
        )
        Book.objects.create(
            title="Rainbow Six", price="15.0", average_rating=4.5999999999999996
        )
        Book.objects.create(
            title="Snowcrash", price="20.0", average_rating=4.2999999999999998
        )

    def test_filtering(self):
        class F(FilterSet):
            class Meta:
                model = Book
                fields = ["price"]

        f = F({"price": 10}, queryset=Book.objects.all())
        self.assertQuerySetEqual(f.qs, ["Ender's Game"], lambda o: o.title)


class RangeFilterTests(TestCase):
    def setUp(self):
        Book.objects.create(
            title="Ender's Game", price="10.0", average_rating=4.7999999999999998
        )
        Book.objects.create(
            title="Rainbow Six", price="15.0", average_rating=4.5999999999999996
        )
        Book.objects.create(
            title="Snowcrash", price="20.0", average_rating=4.2999999999999998
        )
        Book.objects.create(title="Refund", price="-10.0", average_rating=5.0)
        Book.objects.create(title="Free Book", price="0.0", average_rating=0.0)

    def test_filtering(self):
        class F(FilterSet):
            price = RangeFilter()

            class Meta:
                model = Book
                fields = ["price"]

        qs = Book.objects.all().order_by("title")
        f = F(queryset=qs)
        self.assertQuerySetEqual(
            f.qs,
            ["Ender's Game", "Free Book", "Rainbow Six", "Refund", "Snowcrash"],
            lambda o: o.title,
        )
        f = F({"price_min": "5", "price_max": "15"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

        f = F({"price_min": "11"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["Rainbow Six", "Snowcrash"], lambda o: o.title)
        f = F({"price_max": "19"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs,
            ["Ender's Game", "Free Book", "Rainbow Six", "Refund"],
            lambda o: o.title,
        )

        f = F({"price_min": "0", "price_max": "12"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["Ender's Game", "Free Book"], lambda o: o.title)
        f = F({"price_min": "-11", "price_max": "0"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["Free Book", "Refund"], lambda o: o.title)
        f = F({"price_min": "0", "price_max": "0"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["Free Book"], lambda o: o.title)


class DateRangeFilterTests(TestCase):
    class CommentFilter(FilterSet):
        date = DateRangeFilter()

        class Meta:
            model = Comment
            fields = ["date"]

    @contextlib.contextmanager
    def relative_to(self, today):
        today = make_aware(today)
        yesterday = today - datetime.timedelta(days=1)
        five_days_ago = today - datetime.timedelta(days=5)
        two_weeks_ago = today - datetime.timedelta(days=14)
        two_months_ago = today - datetime.timedelta(days=62)
        two_years_ago = today - datetime.timedelta(days=800)

        alex = User.objects.create(username="alex")
        time = now().time()
        self.c1 = Comment.objects.create(date=two_weeks_ago, author=alex, time=time)
        Comment.objects.create(date=two_years_ago, author=alex, time=time)
        self.c3 = Comment.objects.create(date=five_days_ago, author=alex, time=time)
        self.c4 = Comment.objects.create(date=today, author=alex, time=time)
        self.c5 = Comment.objects.create(date=yesterday, author=alex, time=time)
        self.c6 = Comment.objects.create(date=two_months_ago, author=alex, time=time)

        with mock.patch("django_filters.filters.now") as mock_now:
            mock_now.return_value = today
            yield

    def test_filtering_for_year(self):
        f = self.CommentFilter({"date": "year"})
        with self.relative_to(datetime.datetime(now().year, 4, 1)):
            self.assertQuerySetEqual(
                f.qs,
                [self.c1.pk, self.c3.pk, self.c4.pk, self.c5.pk, self.c6.pk],
                lambda o: o.pk,
                False,
            )

    def test_filtering_for_month(self):
        f = self.CommentFilter({"date": "month"})
        with self.relative_to(datetime.datetime(now().year, 4, 21)):
            self.assertQuerySetEqual(
                f.qs,
                [self.c1.pk, self.c3.pk, self.c4.pk, self.c5.pk],
                lambda o: o.pk,
                False,
            )

    def test_filtering_for_week(self):
        f = self.CommentFilter({"date": "week"})
        with self.relative_to(datetime.datetime(now().year, 1, 1)):
            self.assertQuerySetEqual(
                f.qs, [self.c3.pk, self.c4.pk, self.c5.pk], lambda o: o.pk, False
            )

    def test_filtering_for_yesterday(self):
        f = self.CommentFilter({"date": "yesterday"})
        with self.relative_to(datetime.datetime(now().year, 1, 1)):
            self.assertQuerySetEqual(f.qs, [self.c5.pk], lambda o: o.pk, False)

    def test_filtering_for_today(self):
        f = self.CommentFilter({"date": "today"})
        with self.relative_to(datetime.datetime(now().year, 1, 1)):
            self.assertQuerySetEqual(f.qs, [self.c4.pk], lambda o: o.pk, False)


class DateFromToRangeFilterTests(TestCase):
    def test_filtering(self):
        adam = User.objects.create(username="adam")
        kwargs = {"text": "test", "author": adam, "time": "10:00"}
        Comment.objects.create(date=datetime.date(2016, 1, 1), **kwargs)
        Comment.objects.create(date=datetime.date(2016, 1, 2), **kwargs)
        Comment.objects.create(date=datetime.date(2016, 1, 3), **kwargs)
        Comment.objects.create(date=datetime.date(2016, 1, 3), **kwargs)

        class F(FilterSet):
            published = DateFromToRangeFilter(field_name="date")

            class Meta:
                model = Comment
                fields = ["date"]

        results = F(
            data={"published_after": "2016-01-02", "published_before": "2016-01-03"}
        )
        self.assertEqual(len(results.qs), 3)

    def test_filtering_ignores_time(self):
        tz = timezone.get_current_timezone()
        Article.objects.create(
            published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
        )

        class F(FilterSet):
            published = DateFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={"published_after": "2016-01-02", "published_before": "2016-01-03"}
        )
        self.assertEqual(len(results.qs), 3)

    @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
    @override_settings(TIME_ZONE="America/Sao_Paulo")
    def test_filtering_dst_start_midnight(self):
        tz = timezone.get_default_timezone()
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 14, 23, 59), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 15, 0, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 15, 1, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 16, 0, 0), tz, False)
        )

        class F(FilterSet):
            published = DateFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={"published_after": "2017-10-15", "published_before": "2017-10-15"}
        )
        self.assertEqual(len(results.qs), 2)

    @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
    @override_settings(TIME_ZONE="America/Sao_Paulo")
    def test_filtering_dst_ends_midnight(self):
        tz = timezone.get_default_timezone()
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 2, 19, 0, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 2, 18, 23, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 2, 18, 0, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 2, 17, 15, 0), tz, False)
        )

        class F(FilterSet):
            published = DateFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={"published_after": "2017-02-18", "published_before": "2017-02-18"}
        )
        self.assertEqual(len(results.qs), 2)

    @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
    @override_settings(TIME_ZONE="Europe/Paris")
    def test_filtering_dst_start(self):
        tz = timezone.get_default_timezone()
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 3, 25, 23, 59), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 3, 26, 0, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 3, 26, 2, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 3, 26, 3, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 3, 27, 0, 0), tz, False)
        )

        class F(FilterSet):
            published = DateFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={"published_after": "2017-3-26", "published_before": "2017-3-26"}
        )
        self.assertEqual(len(results.qs), 3)

    @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
    @override_settings(TIME_ZONE="Europe/Paris")
    def test_filtering_dst_end(self):
        tz = timezone.get_default_timezone()
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 28, 23, 59), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 29, 0, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 29, 2, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 29, 3, 0), tz, False)
        )
        Article.objects.create(
            published=make_aware(datetime.datetime(2017, 10, 30, 0, 0), tz, False)
        )

        class F(FilterSet):
            published = DateFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={"published_after": "2017-10-29", "published_before": "2017-10-29"}
        )
        self.assertEqual(len(results.qs), 3)


class DateTimeFromToRangeFilterTests(TestCase):
    def test_filtering(self):
        tz = timezone.get_current_timezone()
        Article.objects.create(
            published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
        )

        class F(FilterSet):
            published = DateTimeFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        results = F(
            data={
                "published_after": "2016-01-02 10:00",
                "published_before": "2016-01-03 19:00",
            }
        )
        self.assertEqual(len(results.qs), 2)


class IsoDateTimeFromToRangeFilterTests(TestCase):
    def test_filtering(self):
        tz = timezone.get_current_timezone()
        Article.objects.create(
            published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
        )
        Article.objects.create(
            published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
        )

        class F(FilterSet):
            published = IsoDateTimeFromToRangeFilter()

            class Meta:
                model = Article
                fields = ["published"]

        dt = datetime.datetime.now(tz=tz)
        results = F(
            data={
                "published_after": "2016-01-02T10:00:00.000000" + dt.strftime("%z"),
                "published_before": "2016-01-03T19:00:00.000000" + dt.strftime("%z"),
            }
        )
        self.assertEqual(len(results.qs), 2)


class TimeRangeFilterTests(TestCase):
    def test_filtering(self):
        adam = User.objects.create(username="adam")
        kwargs = {"text": "test", "author": adam, "date": datetime.date.today()}
        Comment.objects.create(time="7:30", **kwargs)
        Comment.objects.create(time="8:00", **kwargs)
        Comment.objects.create(time="9:30", **kwargs)
        Comment.objects.create(time="11:00", **kwargs)

        class F(FilterSet):
            time = TimeRangeFilter()

            class Meta:
                model = Comment
                fields = ["time"]

        results = F(data={"time_after": "8:00", "time_before": "10:00"})
        self.assertEqual(len(results.qs), 2)


class AllValuesFilterTests(TestCase):
    def test_filtering(self):
        User.objects.create(username="alex")
        User.objects.create(username="jacob")
        User.objects.create(username="aaron")

        class F(FilterSet):
            username = AllValuesFilter()

            class Meta:
                model = User
                fields = ["username"]

        self.assertEqual(list(F().qs), list(User.objects.all()))
        self.assertEqual(
            list(F({"username": "alex"}).qs), [User.objects.get(username="alex")]
        )

        # invalid choice
        self.assertFalse(F({"username": "jose"}).is_valid())
        self.assertEqual(list(F({"username": "jose"}).qs), list(User.objects.all()))


class AllValuesMultipleFilterTests(TestCase):
    def test_filtering(self):
        User.objects.create(username="alex")
        User.objects.create(username="jacob")
        User.objects.create(username="aaron")

        class F(FilterSet):
            username = AllValuesMultipleFilter()

            class Meta:
                model = User
                fields = ["username"]

        self.assertEqual(list(F().qs), list(User.objects.all()))
        self.assertEqual(
            list(F({"username": ["alex"]}).qs), [User.objects.get(username="alex")]
        )
        self.assertCountEqual(
            list(F({"username": ["alex", "jacob"]}).qs),
            list(User.objects.filter(username__in=["alex", "jacob"])),
        )

        # invalid choice
        self.assertFalse(F({"username": "jose"}).is_valid())
        self.assertEqual(list(F({"username": "jose"}).qs), list(User.objects.all()))


class FilterMethodTests(TestCase):
    def setUp(self):
        User.objects.create(username="alex")
        User.objects.create(username="jacob")
        User.objects.create(username="aaron")

    def test_filtering(self):
        class F(FilterSet):
            username = CharFilter(method="filter_username")

            class Meta:
                model = User
                fields = ["username"]

            def filter_username(self, queryset, name, value):
                return queryset.filter(**{name: value})

        self.assertEqual(list(F().qs), list(User.objects.all()))
        self.assertEqual(
            list(F({"username": "alex"}).qs), [User.objects.get(username="alex")]
        )
        self.assertEqual(list(F({"username": "jose"}).qs), list())

    def test_filtering_callable(self):
        def filter_username(queryset, name, value):
            return queryset.filter(**{name: value})

        class F(FilterSet):
            username = CharFilter(method=filter_username)

            class Meta:
                model = User
                fields = ["username"]

        self.assertEqual(list(F().qs), list(User.objects.all()))
        self.assertEqual(
            list(F({"username": "alex"}).qs), [User.objects.get(username="alex")]
        )
        self.assertEqual(list(F({"username": "jose"}).qs), list())


class O2ORelationshipTests(TestCase):
    def setUp(self):
        self.a1 = Account.objects.create(
            name="account1", in_good_standing=False, friendly=False
        )
        self.a2 = Account.objects.create(
            name="account2", in_good_standing=True, friendly=True
        )
        self.a3 = Account.objects.create(
            name="account3", in_good_standing=True, friendly=False
        )
        self.a4 = Account.objects.create(
            name="account4", in_good_standing=False, friendly=True
        )
        self.p1 = Profile.objects.create(
            account=self.a1, likes_coffee=True, likes_tea=False
        )
        self.p2 = Profile.objects.create(
            account=self.a2, likes_coffee=False, likes_tea=True
        )
        self.p3 = Profile.objects.create(
            account=self.a3, likes_coffee=True, likes_tea=True
        )
        Profile.objects.create(account=self.a4, likes_coffee=False, likes_tea=False)

    def test_o2o_relation(self):
        class F(FilterSet):
            class Meta:
                model = Profile
                fields = ("account",)

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"account": self.a1.pk})
        self.assertEqual(f.qs.count(), 1)
        self.assertQuerySetEqual(f.qs, [self.p1.pk], lambda o: o.pk)

    def test_o2o_relation_dictionary(self):
        class F(FilterSet):
            class Meta:
                model = Profile
                fields = {
                    "account": ["exact"],
                }

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"account": self.a1.pk})
        self.assertEqual(f.qs.count(), 1)
        self.assertQuerySetEqual(f.qs, [self.p1.pk], lambda o: o.pk)

    def test_reverse_o2o_relation(self):
        class F(FilterSet):
            class Meta:
                model = Account
                fields = ("profile",)

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"profile": self.p1.pk})
        self.assertEqual(f.qs.count(), 1)
        self.assertQuerySetEqual(f.qs, [self.a1.pk], lambda o: o.pk)

    def test_o2o_relation_attribute(self):
        class F(FilterSet):
            class Meta:
                model = Profile
                fields = ("account__in_good_standing",)

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"account__in_good_standing": "2"})
        self.assertEqual(f.qs.count(), 2)
        self.assertQuerySetEqual(f.qs, [self.p2.pk, self.p3.pk], lambda o: o.pk, False)

    def test_o2o_relation_attribute2(self):
        class F(FilterSet):
            class Meta:
                model = Profile
                fields = (
                    "account__in_good_standing",
                    "account__friendly",
                )

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"account__in_good_standing": "2", "account__friendly": "2"})
        self.assertEqual(f.qs.count(), 1)
        self.assertQuerySetEqual(f.qs, [self.p2.pk], lambda o: o.pk)

    def test_reverse_o2o_relation_attribute(self):
        class F(FilterSet):
            class Meta:
                model = Account
                fields = ("profile__likes_coffee",)

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"profile__likes_coffee": "2"})
        self.assertEqual(f.qs.count(), 2)
        self.assertQuerySetEqual(f.qs, [self.a1.pk, self.a3.pk], lambda o: o.pk, False)

    def test_reverse_o2o_relation_attribute2(self):
        class F(FilterSet):
            class Meta:
                model = Account
                fields = ("profile__likes_coffee", "profile__likes_tea")

        f = F()
        self.assertEqual(f.qs.count(), 4)

        f = F({"profile__likes_coffee": "2", "profile__likes_tea": "2"})
        self.assertEqual(f.qs.count(), 1)
        self.assertQuerySetEqual(f.qs, [self.a3.pk], lambda o: o.pk)


class FKRelationshipTests(TestCase):
    def test_fk_relation(self):
        company1 = Company.objects.create(name="company1")
        company2 = Company.objects.create(name="company2")
        loc1 = Location.objects.create(
            company=company1, open_days="some", zip_code="90210"
        )
        Location.objects.create(company=company2, open_days="WEEKEND", zip_code="11111")
        loc3 = Location.objects.create(
            company=company1, open_days="monday", zip_code="12345"
        )

        class F(FilterSet):
            class Meta:
                model = Location
                fields = ("company",)

        f = F()
        self.assertEqual(f.qs.count(), 3)

        f = F({"company": company1.pk})
        self.assertEqual(f.qs.count(), 2)
        self.assertQuerySetEqual(f.qs, [loc1.pk, loc3.pk], lambda o: o.pk, False)

    def test_reverse_fk_relation(self):
        alex = User.objects.create(username="alex")
        self.jacob = User.objects.create(username="jacob")
        date = now().date()
        time = now().time()
        Comment.objects.create(
            text="comment 1", author=self.jacob, time=time, date=date
        )
        self.comment = Comment.objects.create(
            text="comment 2", author=alex, time=time, date=date
        )
        Comment.objects.create(
            text="comment 3", author=self.jacob, time=time, date=date
        )

        class F(FilterSet):
            class Meta:
                model = User
                fields = ["comments"]

        qs = User.objects.all()
        f = F({"comments": [self.comment.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        class F(FilterSet):
            comments = AllValuesFilter()

            class Meta:
                model = User
                fields = ["comments"]

        f = F({"comments": self.comment.pk}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

    def test_fk_relation_attribute(self):
        now_dt = now()
        alex = User.objects.create(username="alex")
        jacob = User.objects.create(username="jacob")
        User.objects.create(username="aaron")

        Article.objects.create(author=alex, published=now_dt)
        Article.objects.create(author=jacob, published=now_dt)
        Article.objects.create(author=alex, published=now_dt)

        class F(FilterSet):
            class Meta:
                model = Article
                fields = ["author__username"]

        self.assertEqual(list(F.base_filters), ["author__username"])
        self.assertEqual(F({"author__username": "alex"}).qs.count(), 2)
        self.assertEqual(F({"author__username": "jacob"}).qs.count(), 1)

        class F(FilterSet):
            author__username = AllValuesFilter()

            class Meta:
                model = Article
                fields = ["author__username"]

        self.assertEqual(F({"author__username": "alex"}).qs.count(), 2)

    def test_reverse_fk_relation_attribute(self):
        alex = User.objects.create(username="alex")
        jacob = User.objects.create(username="jacob")
        date = now().date()
        time = now().time()
        Comment.objects.create(text="comment 1", author=jacob, time=time, date=date)
        Comment.objects.create(text="comment 2", author=alex, time=time, date=date)
        Comment.objects.create(text="comment 3", author=jacob, time=time, date=date)

        class F(FilterSet):
            class Meta:
                model = User
                fields = ["comments__text"]

        qs = User.objects.all()
        f = F({"comments__text": "comment 2"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        class F(FilterSet):
            comments__text = AllValuesFilter()

            class Meta:
                model = User
                fields = ["comments__text"]

        f = F({"comments__text": "comment 2"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

    @unittest.skip("todo - need correct models")
    def test_fk_relation_multiple_attributes(self):
        pass

    @unittest.expectedFailure
    def test_reverse_fk_relation_multiple_attributes(self):
        company = Company.objects.create(name="company")
        Location.objects.create(company=company, open_days="some", zip_code="90210")
        Location.objects.create(company=company, open_days="WEEKEND", zip_code="11111")

        class F(FilterSet):
            class Meta:
                model = Company
                fields = ("locations__zip_code", "locations__open_days")

        f = F({"locations__zip_code": "90210", "locations__open_days": "WEEKEND"})
        self.assertEqual(f.qs.count(), 0)


class M2MRelationshipTests(TestCase):
    def setUp(self):
        self.alex = User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=1)
        aaron = User.objects.create(username="aaron", status=1)
        self.b1 = Book.objects.create(
            title="Ender's Game", price="1.00", average_rating=3.0
        )
        self.b2 = Book.objects.create(
            title="Rainbow Six", price="2.00", average_rating=4.0
        )
        self.b3 = Book.objects.create(
            title="Snowcrash", price="1.00", average_rating=4.0
        )
        self.b4 = Book.objects.create(
            title="Stranger in a Strage Land", price="2.00", average_rating=3.0
        )
        self.alex.favorite_books.add(self.b1, self.b2)
        aaron.favorite_books.add(self.b1, self.b3)

    def test_m2m_relation(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books"]

        qs = User.objects.all().order_by("username")
        f = F({"favorite_books": [self.b1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b1.pk, self.b3.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b2.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        f = F({"favorite_books": [self.b4.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.username)

    def test_reverse_m2m_relation(self):
        class F(FilterSet):
            class Meta:
                model = Book
                fields = ["lovers"]

        qs = Book.objects.all().order_by("title")
        f = F({"lovers": [self.alex.pk]}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

        class F(FilterSet):
            lovers = AllValuesFilter()

            class Meta:
                model = Book
                fields = ["lovers"]

        f = F({"lovers": self.alex.pk}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

    def test_m2m_relation_attribute(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books__title"]

        qs = User.objects.all().order_by("username")
        f = F({"favorite_books__title": "Ender's Game"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron", "alex"], lambda o: o.username)

        f = F({"favorite_books__title": "Rainbow Six"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        # No choices given, so filtering does nothing.
        class F(FilterSet):
            favorite_books__title = MultipleChoiceFilter()

            class Meta:
                model = User
                fields = ["favorite_books__title"]

        f = F()
        self.assertEqual(len(f.filters["favorite_books__title"].field.choices), 0)

        # Specifying choices allows filter to work. (See also AllValues variants.)
        class F(FilterSet):
            favorite_books__title = MultipleChoiceFilter(
                choices=[(b.title, b.title) for b in Book.objects.all()]
            )

            class Meta:
                model = User
                fields = ["favorite_books__title"]

        f = F({"favorite_books__title": ["Ender's Game", "Snowcrash"]}, queryset=qs)
        self.assertIs(
            True,
            f.form.is_valid(),
            list(f.filters["favorite_books__title"].field.choices),
        )
        self.assertQuerySetEqual(
            f.qs,
            ["aaron", "alex"],
            lambda o: o.username,
        )

        class F(FilterSet):
            favorite_books__title = AllValuesFilter()

            class Meta:
                model = User
                fields = ["favorite_books__title"]

        f = F({"favorite_books__title": "Snowcrash"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["aaron"], lambda o: o.username)

    def test_reverse_m2m_relation_attribute(self):
        class F(FilterSet):
            class Meta:
                model = Book
                fields = ["lovers__username"]

        qs = Book.objects.all().order_by("title")
        f = F({"lovers__username": "alex"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

        f = F({"lovers__username": "jacob"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.title)

        class F(FilterSet):
            lovers__username = MultipleChoiceFilter()

            class Meta:
                model = Book
                fields = ["lovers__username"]

        f = F()
        self.assertEqual(len(f.filters["lovers__username"].field.choices), 0)
        # f = F({'lovers__username': ['1', '3']},
        #     queryset=qs)
        # self.assertQuerySetEqual(
        #     f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)

        class F(FilterSet):
            lovers__username = AllValuesFilter()

            class Meta:
                model = Book
                fields = ["lovers__username"]

        f = F({"lovers__username": "alex"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

    @unittest.expectedFailure
    def test_m2m_relation_multiple_attributes(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["favorite_books__price", "favorite_books__average_rating"]

        qs = User.objects.all().order_by("username")
        f = F(
            {"favorite_books__price": "1.00", "favorite_books__average_rating": 4.0},
            queryset=qs,
        )
        self.assertQuerySetEqual(f.qs, ["aaron"], lambda o: o.username)

        f = F(
            {"favorite_books__price": "3.00", "favorite_books__average_rating": 4.0},
            queryset=qs,
        )
        self.assertQuerySetEqual(f.qs, [], lambda o: o.username)

    @unittest.expectedFailure
    def test_reverse_m2m_relation_multiple_attributes(self):
        class F(FilterSet):
            class Meta:
                model = Book
                fields = ["lovers__status", "lovers__username"]

        qs = Book.objects.all().order_by("title")
        f = F({"lovers__status": 1, "lovers__username": "alex"}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title
        )

        f = F({"lovers__status": 1, "lovers__username": "jacob"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.title)

    @unittest.skip("todo")
    def test_fk_relation_on_m2m_relation(self):
        pass

    @unittest.skip("todo")
    def test_fk_relation_attribute_on_m2m_relation(self):
        pass


class SymmetricalSelfReferentialRelationshipTests(TestCase):
    def setUp(self):
        self.n1 = Node.objects.create(name="one")
        self.n2 = Node.objects.create(name="two")
        n3 = Node.objects.create(name="three")
        self.n4 = Node.objects.create(name="four")
        self.n1.adjacents.add(self.n2)
        self.n2.adjacents.add(n3)
        self.n2.adjacents.add(self.n4)
        self.n4.adjacents.add(self.n1)

    def test_relation(self):
        class F(FilterSet):
            class Meta:
                model = Node
                fields = ["adjacents"]

        qs = Node.objects.all().order_by("pk")
        f = F({"adjacents": [self.n1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.n2.pk, self.n4.pk], lambda o: o.pk)


class NonSymmetricalSelfReferentialRelationshipTests(TestCase):
    def setUp(self):
        self.n1 = DirectedNode.objects.create(name="one")
        self.n2 = DirectedNode.objects.create(name="two")
        n3 = DirectedNode.objects.create(name="three")
        self.n4 = DirectedNode.objects.create(name="four")
        self.n1.outbound_nodes.add(self.n2)
        self.n2.outbound_nodes.add(n3)
        self.n2.outbound_nodes.add(self.n4)
        self.n4.outbound_nodes.add(self.n1)

    def test_forward_relation(self):
        class F(FilterSet):
            class Meta:
                model = DirectedNode
                fields = ["outbound_nodes"]

        qs = DirectedNode.objects.all().order_by("pk")
        f = F({"outbound_nodes": [self.n1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.n4.pk], lambda o: o.pk)

    def test_reverse_relation(self):
        class F(FilterSet):
            class Meta:
                model = DirectedNode
                fields = ["inbound_nodes"]

        qs = DirectedNode.objects.all().order_by("pk")
        f = F({"inbound_nodes": [self.n1.pk]}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [self.n2.pk], lambda o: o.pk)


@override_settings(TIME_ZONE="UTC")
class TransformedQueryExpressionFilterTests(TestCase):
    def test_filtering(self):
        now_dt = now()
        after_5pm = now_dt.replace(hour=18)
        before_5pm = now_dt.replace(hour=16)

        u = User.objects.create(username="alex")
        a = Article.objects.create(author=u, published=after_5pm)
        Article.objects.create(author=u, published=before_5pm)

        class F(FilterSet):
            class Meta:
                model = Article
                fields = {"published": ["hour__gte"]}

        qs = Article.objects.all()
        f = F({"published__hour__gte": 17}, queryset=qs)
        self.assertEqual(len(f.qs), 1)
        self.assertQuerySetEqual(f.qs, [a.pk], lambda o: o.pk)


class LookupChoiceFilterTests(TestCase):
    class BookFilter(FilterSet):
        price = LookupChoiceFilter(
            lookup_choices=["lt", "gt"], field_class=forms.DecimalField
        )

        class Meta:
            model = Book
            fields = ["price"]

    @classmethod
    def setUpTestData(cls):
        Book.objects.create(
            title="Ender's Game", price="10.0", average_rating=4.7999999999999998
        )
        Book.objects.create(
            title="Rainbow Six", price="15.0", average_rating=4.5999999999999996
        )
        Book.objects.create(
            title="Snowcrash", price="20.0", average_rating=4.2999999999999998
        )

    def test_filtering(self):
        F = self.BookFilter

        f = F({"price": "15", "price_lookup": "lt"})
        self.assertQuerySetEqual(f.qs, ["Ender's Game"], lambda o: o.title)
        f = F({"price": "15", "price_lookup": "lt"})
        self.assertQuerySetEqual(f.qs, ["Ender's Game"], lambda o: o.title)
        f = F({"price": "", "price_lookup": "lt"})
        self.assertTrue(f.is_valid())
        self.assertQuerySetEqual(
            f.qs,
            ["Ender's Game", "Rainbow Six", "Snowcrash"],
            lambda o: o.title,
            ordered=False,
        )
        f = F({"price": "15"})
        self.assertFalse(f.is_valid())
        self.assertQuerySetEqual(
            f.qs,
            ["Ender's Game", "Rainbow Six", "Snowcrash"],
            lambda o: o.title,
            ordered=False,
        )

    def test_inner_field_class_validation(self):
        f = self.BookFilter({"price": "asdf", "price_lookup": "lt"})
        self.assertFalse(f.is_valid())
        self.assertEqual(
            f.errors,
            {
                "price": ["Enter a number."],
            },
        )

    def test_lookup_choices_validation(self):
        f = self.BookFilter({"price": "1", "price_lookup": "asdf"})
        self.assertFalse(f.is_valid())
        self.assertEqual(
            f.errors,
            {
                "price": [
                    "Select a valid choice. asdf is not one of the available choices."
                ],
            },
        )

    def test_lookup_omitted(self):
        f = self.BookFilter({"price": "1"})
        self.assertFalse(f.is_valid())
        self.assertEqual(
            f.errors,
            {
                "price": ["Select a lookup."],
            },
        )


@override_settings(TIME_ZONE="UTC")
class CSVFilterTests(TestCase):
    def setUp(self):
        self.u1 = User.objects.create(username="alex", status=1)
        self.u2 = User.objects.create(username="jacob", status=2)
        self.u3 = User.objects.create(username="aaron", status=2)
        self.u4 = User.objects.create(username="carl", status=0)

        now_dt = now()
        after_5pm = now_dt.replace(hour=18)
        before_5pm = now_dt.replace(hour=16)

        self.a1 = Article.objects.create(author=self.u1, published=after_5pm)
        self.a2 = Article.objects.create(author=self.u2, published=after_5pm)
        self.a3 = Article.objects.create(author=self.u1, published=before_5pm)
        self.a4 = Article.objects.create(author=self.u2, published=before_5pm)

        class UserFilter(FilterSet):
            class Meta:
                model = User
                fields = {
                    "username": ["in"],
                    "status": ["in"],
                }

        class ArticleFilter(FilterSet):
            class Meta:
                model = Article
                fields = {
                    "author": ["in"],
                    "published": ["in"],
                }

        self.user_filter = UserFilter
        self.article_filter = ArticleFilter

        self.after_5pm = after_5pm.strftime("%Y-%m-%d %H:%M:%S.%f")
        self.before_5pm = before_5pm.strftime("%Y-%m-%d %H:%M:%S.%f")

    def test_numeric_filtering(self):
        F = self.user_filter
        qs = User.objects.order_by("pk")

        cases = [
            (None, [self.u1.pk, self.u2.pk, self.u3.pk, self.u4.pk]),
            (QueryDict("status__in=1&status__in=2"), [self.u2.pk, self.u3.pk]),
            ({"status__in": ""}, [self.u1.pk, self.u2.pk, self.u3.pk, self.u4.pk]),
            ({"status__in": ","}, []),
            ({"status__in": "0"}, [self.u4.pk]),
            ({"status__in": "0,2"}, [self.u2.pk, self.u3.pk, self.u4.pk]),
            ({"status__in": "0,,1"}, [self.u1.pk, self.u4.pk]),
            ({"status__in": "2"}, [self.u2.pk, self.u3.pk]),
        ]

        for params, expected in cases:
            with self.subTest(params=params, expected=expected):
                self.assertQuerySetEqual(
                    F(params, queryset=qs).qs, expected, attrgetter("pk")
                )

    def test_string_filtering(self):
        F = self.user_filter
        qs = User.objects.order_by("pk")

        cases = [
            (None, [self.u1.pk, self.u2.pk, self.u3.pk, self.u4.pk]),
            (QueryDict("username__in=alex&username__in=aaron"), [self.u3.pk]),
            ({"username__in": ""}, [self.u1.pk, self.u2.pk, self.u3.pk, self.u4.pk]),
            ({"username__in": ","}, []),
            ({"username__in": "alex"}, [self.u1.pk]),
            ({"username__in": "alex,aaron"}, [self.u1.pk, self.u3.pk]),
            ({"username__in": "alex,,aaron"}, [self.u1.pk, self.u3.pk]),
            ({"username__in": "alex,"}, [self.u1.pk]),
        ]

        for params, expected in cases:
            with self.subTest(params=params, expected=expected):
                self.assertQuerySetEqual(
                    F(params, queryset=qs).qs, expected, attrgetter("pk")
                )

    def test_datetime_filtering(self):
        F = self.article_filter
        qs = Article.objects.order_by("pk")

        after = self.after_5pm
        before = self.before_5pm

        cases = [
            (None, [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk]),
            (
                QueryDict("published__in=%s&published__in=%s" % (after, before)),
                [self.a3.pk, self.a4.pk],
            ),
            ({"published__in": ""}, [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk]),
            ({"published__in": ","}, []),
            ({"published__in": "%s" % (after,)}, [self.a1.pk, self.a2.pk]),
            (
                {
                    "published__in": "%s,%s"
                    % (
                        after,
                        before,
                    )
                },
                [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk],
            ),
            (
                {
                    "published__in": "%s,,%s"
                    % (
                        after,
                        before,
                    )
                },
                [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk],
            ),
            ({"published__in": "%s," % (after,)}, [self.a1.pk, self.a2.pk]),
        ]

        for params, expected in cases:
            with self.subTest(params=params, expected=expected):
                self.assertQuerySetEqual(
                    F(params, queryset=qs).qs, expected, attrgetter("pk")
                )

    def test_related_filtering(self):
        F = self.article_filter
        qs = Article.objects.order_by("pk")

        cases = [
            (None, [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk]),
            (
                QueryDict(f"author__in={self.u1.pk}&author__in={self.u2.pk}"),
                [self.a2.pk, self.a4.pk],
            ),
            ({"author__in": ""}, [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk]),
            ({"author__in": ","}, []),
            ({"author__in": f"{self.u1.pk}"}, [self.a1.pk, self.a3.pk]),
            (
                {"author__in": f"{self.u1.pk},{self.u2.pk}"},
                [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk],
            ),
            (
                {"author__in": f"{self.u1.pk},,{self.u2.pk}"},
                [self.a1.pk, self.a2.pk, self.a3.pk, self.a4.pk],
            ),
            ({"author__in": f"{self.u1.pk},"}, [self.a1.pk, self.a3.pk]),
        ]

        for params, expected in cases:
            with self.subTest(params=params, expected=expected):
                self.assertQuerySetEqual(
                    F(params, queryset=qs).qs, expected, attrgetter("pk")
                )


@override_settings(TIME_ZONE="UTC")
class CSVRangeFilterTests(TestCase):
    class ArticleFilter(FilterSet):
        class Meta:
            model = Article
            fields = {
                "published": ["range"],
            }

    @classmethod
    def setUpTestData(cls):
        u1 = User.objects.create(username="alex", status=1)
        u2 = User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

        now_dt = now()
        after_5pm = now_dt.replace(hour=18)
        around_5pm = now_dt.replace(hour=17)
        before_5pm = now_dt.replace(hour=16)

        Article.objects.create(author=u1, published=after_5pm)
        Article.objects.create(author=u2, published=around_5pm)
        Article.objects.create(author=u1, published=around_5pm)
        Article.objects.create(author=u2, published=before_5pm)

        cls.after_5pm = after_5pm.strftime("%Y-%m-%d %H:%M:%S.%f")
        cls.around_5pm = around_5pm.strftime("%Y-%m-%d %H:%M:%S.%f")
        cls.before_5pm = before_5pm.strftime("%Y-%m-%d %H:%M:%S.%f")

    def test_filtering(self):
        F = self.ArticleFilter

        f = F()
        self.assertEqual(f.qs.count(), 4)

        # empty value is a noop
        f = F({"published__range": ""})
        self.assertTrue(f.is_valid())
        self.assertEqual(f.qs.count(), 4)

        # empty values are interpreted as None types
        f = F({"published__range": ","})
        self.assertEqual(f.qs.count(), 0)

        f = F({"published__range": "%s" % (self.before_5pm,)})
        self.assertFalse(f.is_valid())

        f = F(
            {
                "published__range": "%s,%s"
                % (
                    self.before_5pm,
                    self.around_5pm,
                )
            }
        )
        self.assertEqual(f.qs.count(), 3)

        f = F(
            {
                "published__range": "%s,,%s"
                % (
                    self.before_5pm,
                    self.after_5pm,
                )
            }
        )
        self.assertFalse(f.is_valid())

        # empty value is interpreted as None type
        f = F({"published__range": "%s," % (self.before_5pm,)})
        self.assertEqual(f.qs.count(), 0)


class OrderingFilterTests(TestCase):
    def setUp(self):
        User.objects.create(username="alex", status=1)
        User.objects.create(username="jacob", status=2)
        User.objects.create(username="aaron", status=2)
        User.objects.create(username="carl", status=0)

    def test_ordering(self):
        class F(FilterSet):
            o = OrderingFilter(fields=("username",))

            class Meta:
                model = User
                fields = ["username"]

        qs = User.objects.all()
        tests = [
            {"o": "username"},
            QueryDict("o=username,"),
        ]
        for data in tests:
            with self.subTest(data=data):
                f = F(data, queryset=qs)
                names = f.qs.values_list("username", flat=True)
                self.assertEqual(list(names), ["aaron", "alex", "carl", "jacob"])

    def test_ordering_with_select_widget(self):
        class F(FilterSet):
            o = OrderingFilter(widget=forms.Select, fields=("username",))

            class Meta:
                model = User
                fields = ["username"]

        qs = User.objects.all()
        f = F({"o": "username"}, queryset=qs)
        names = f.qs.values_list("username", flat=True)
        self.assertEqual(list(names), ["aaron", "alex", "carl", "jacob"])

    def test_csv_input(self):
        class F(FilterSet):
            o = OrderingFilter(
                widget=forms.Select,
                fields=("username",),
            )

            class Meta:
                model = User
                fields = ["username"]

        qs = User.objects.all()
        tests = [
            {"o": ","},
            QueryDict("o=%2c"),
            QueryDict("o=,"),
        ]
        for data in tests:
            with self.subTest(data=data):
                f = F(data, queryset=qs)
                self.assertIs(True, f.is_valid())
                names = f.qs.values_list("username", flat=True)
                self.assertEqual(list(names), ["alex", "jacob", "aaron", "carl"])


class MiscFilterSetTests(TestCase):
    def setUp(self):
        User.objects.create(username="alex", last_name="johnson", status=1)
        User.objects.create(username="jacob", last_name="johnson", status=2)
        User.objects.create(username="aaron", last_name="white", status=2)
        User.objects.create(username="carl", last_name="black", status=0)

    def test_filtering_with_declared_filters(self):
        class F(FilterSet):
            account = CharFilter(field_name="username")

            class Meta:
                model = User
                fields = ["account"]

        qs = MockQuerySet()
        F({"account": "jdoe"}, queryset=qs).qs
        qs.all.return_value.filter.assert_called_with(username__exact="jdoe")

    def test_filtering_without_meta(self):
        class F(FilterSet):
            username = CharFilter()

        f = F({"username": "alex"}, queryset=User.objects.all())
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

    def test_filtering_with_multiple_filters(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["status", "username"]

        qs = User.objects.all()

        f = F({"username": "alex", "status": "1"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)

        f = F({"username": "alex", "status": "2"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.pk)

    def test_filter_with_initial(self):
        # Initial values are a form presentation option - the FilterSet should
        # not use an initial value as a default value to filter by.
        class F(FilterSet):
            status = ChoiceFilter(choices=STATUS_CHOICES, initial=1)

            class Meta:
                model = User
                fields = ["status"]

        qs = User.objects.all()
        users = ["alex", "jacob", "aaron", "carl"]

        f = F(queryset=qs)
        self.assertQuerySetEqual(f.qs.order_by("pk"), users, lambda o: o.username)

        f = F({"status": 0}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["carl"], lambda o: o.username)

    def test_qs_count(self):
        class F(FilterSet):
            class Meta:
                model = User
                fields = ["status"]

        qs = User.objects.all()
        f = F(queryset=qs)
        self.assertEqual(len(f.qs), 4)
        self.assertEqual(f.qs.count(), 4)

        f = F({"status": "0"}, queryset=qs)
        self.assertEqual(len(f.qs), 1)
        self.assertEqual(f.qs.count(), 1)

        f = F({"status": "1"}, queryset=qs)
        self.assertEqual(len(f.qs), 1)
        self.assertEqual(f.qs.count(), 1)

        f = F({"status": "2"}, queryset=qs)
        self.assertEqual(len(f.qs), 2)
        self.assertEqual(f.qs.count(), 2)

    def test_filtering_with_widgets(self):
        class CharInFilter(BaseInFilter, CharFilter):
            pass

        class F(FilterSet):
            last_name = CharInFilter(widget=QueryArrayWidget)
            username = CharInFilter()

            class Meta:
                model = User
                fields = ["last_name", "username"]

        qs = User.objects.all()

        f = F({"last_name": ["johnson"]}, queryset=qs)
        self.assertQuerySetEqual(
            f.qs, ["alex", "jacob"], lambda o: o.username, ordered=False
        )

        f = F({"last_name": ["johnson"], "username": "carl"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, [], lambda o: o.username, ordered=False)

        f = F({"last_name": ["johnson"], "username": "jacob"}, queryset=qs)
        self.assertQuerySetEqual(f.qs, ["jacob"], lambda o: o.username, ordered=False)

        f = F(
            {"last_name": ["johnson", "white"], "username": "jacob, carl, aaron"},
            queryset=qs,
        )
        self.assertQuerySetEqual(
            f.qs, ["jacob", "aaron"], lambda o: o.username, ordered=False
        )
