Skip to content

Commit 60aed77

Browse files
committed
refactor(dates): Rename date filters for clarity and improve tests
This commit introduces a significant refactoring of the date filtering components to improve clarity, fix bugs, and enhance the test suite. Key Changes: - **Filter Renaming:** - `DateRangeFilter` has been renamed to `DateFilter` to better reflect its general-purpose nature for filtering dates. - `DateInDateRange` has been renamed to `DateInDateRangeFilter` for consistency with other filter classes. - Documentation (`README.md`, `docs/`) and module exports have been updated accordingly. - **Bug Fix:** - Fixed a bug in `DateInDateRangeFilter` where `strptime` was using an incorrect format string that included a time component. It now correctly uses `"%Y-%m-%d"`. - **Test Enhancements:** - Added a `DateRangeField` named `validity` to the `DemoModelField` test model to allow for realistic testing of the `DateInDateRangeFilter`. - Updated `DemoModelFieldFactory` to generate `DateRange` objects for the new field. - Added a comprehensive set of tests for `DateInDateRangeFilter`, covering both standard and negated queries. - Updated existing tests to use the new filter names. - **Database Migration:** - Added a new migration for the `depot` application (`0004_alter_storedfilter_query_string.py`) to change the `query_string` field on the `StoredFilter` model to a `TextField` with a default value. - **Development Dependencies:** - Added `pdbpp` to the development dependencies to provide an enhanced debugger.
1 parent ca9da63 commit 60aed77

File tree

12 files changed

+136
-83
lines changed

12 files changed

+136
-83
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Filters
2626
* RelatedFieldComboFilter
2727
* ChoicesFieldComboFilter
2828
* Dates
29-
* DateRangeFilter
29+
* DateFilter
3030
* DateInDateRange
3131
* Radio
3232
* AllValuesRadioFilter

docs/src/filters/date.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Date Filters
22

3-
## DateRangeFilter
3+
## DateFilter
44

55
Filter dates. It allows complex filter like:
66

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dev = [
4343
"django-factory-boy",
4444
"django-webtest>=1.9.13",
4545
"djlint>=1.36.4",
46+
"pdbpp>=0.11.7",
4647
"pre-commit",
4748
"psycopg2-binary",
4849
"pyproject-fmt>=2.5.1",

src/adminfilters/dates.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
from .value import ValueFilter
1313

1414

15-
class DateInDateRange(ValueFilter):
15+
class DateInDateRangeFilter(ValueFilter):
1616
lookup_name = "contains"
1717
input_type = "date"
1818

1919
def queryset(self, request: HttpRequest, queryset: QuerySet[Model]) -> QuerySet[Model]:
2020
target, exclude = self.value()
2121
if target:
2222
try:
23-
d = datetime.strptime(target, "%Y-%m-%d 00:00:00").replace(tzinfo=UTC).date()
23+
d = datetime.strptime(target, "%Y-%m-%d").replace(tzinfo=UTC).date()
2424
self.filters = {self.lookup_kwarg: d}
2525
queryset = queryset.exclude(**self.filters) if exclude else queryset.filter(**self.filters)
2626
except Exception as e: # noqa: BLE001
@@ -29,7 +29,7 @@ def queryset(self, request: HttpRequest, queryset: QuerySet[Model]) -> QuerySet[
2929
return queryset
3030

3131

32-
class DateRangeFilter(NumberFilter):
32+
class DateFilter(NumberFilter):
3333
rex1 = re.compile(r"^(>=|<=|>|<|=)?(\d{4}-\d{2}-\d{2})$")
3434
re_range = re.compile(r"^(\d{4}-\d{2}-\d{2})..(\d{4}-\d{2}-\d{2})$")
3535
re_list = re.compile(r"(\d{4}-\d{2}-\d{2}),?")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.2.8 on 2025-11-17 15:22
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("depot", "0003_alter_storedfilter_id"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="storedfilter",
14+
name="query_string",
15+
field=models.TextField(blank=True, default=""),
16+
),
17+
]

src/adminfilters/filters.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .autocomplete import AutoCompleteFilter, LinkedAutoCompleteFilter
22
from .checkbox import RelatedFieldCheckBoxFilter
33
from .combo import AllValuesComboFilter, ChoicesFieldComboFilter, RelatedFieldComboFilter
4+
from .dates import DateFilter, DateInDateRangeFilter
45
from .dj import DjangoLookupFilter
56
from .extra import PermissionPrefixFilter
67
from .json_filter import JsonFieldFilter
@@ -25,6 +26,8 @@
2526
"BooleanRadioFilter",
2627
"ChoicesFieldComboFilter",
2728
"ChoicesFieldRadioFilter",
29+
"DateFilter",
30+
"DateInDateRangeFilter",
2831
"DjangoLookupFilter",
2932
"IntersectionFieldListFilter",
3033
"JsonFieldFilter",

tests/demoapp/demo/admin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
AdminAutoCompleteSearchMixin,
1010
AdminFiltersMixin,
1111
BooleanRadioFilter,
12+
DateInDateRangeFilter,
1213
DjangoLookupFilter,
1314
IntersectionFieldListFilter,
1415
JsonFieldFilter,
@@ -21,8 +22,6 @@
2122
ValueFilter,
2223
)
2324

24-
# from adminfilters.mixin import AdminAutoCompleteSearchMixin, AdminFiltersMixin
25-
# from adminfilters.value import MultiValueFilter
2625
from .models import Artist, Band, City, Country, Region
2726

2827

@@ -82,6 +81,7 @@ class DemoModelFieldAdmin(DebugMixin, AdminFiltersMixin, ModelAdmin):
8281
QueryStringFilter,
8382
("choices", ChoicesFieldComboFilter),
8483
("integer", NumberFilter),
84+
("valididty", DateInDateRangeFilter),
8585
)
8686

8787

tests/demoapp/demo/factories.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import random
2+
from datetime import datetime
23

34
import factory.fuzzy
45
import faker
56
from django.contrib.auth.models import User
7+
from django.db.backends.postgresql.psycopg_any import DateRange
68
from factory.base import FactoryMetaClass
79

810
from . import models
@@ -58,6 +60,7 @@ class DemoModelFieldFactory(ModelFactory):
5860
unique = factory.Sequence(lambda a: a)
5961
email = factory.Faker("email")
6062
json = {"char": "string", "integer": 100, "float": 2.0}
63+
validity = factory.LazyFunction(lambda: DateRange(datetime(2000, 1, 1).date(), datetime(2000, 1, 31).date()))
6164

6265
class Meta:
6366
model = models.DemoModelField

tests/demoapp/demo/migrations/0001_initial.py

Lines changed: 16 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Generated by Django 5.0.1 on 2024-03-05 19:40
1+
# Generated by Django 5.2.8 on 2025-11-17 15:22
22

3+
import django.contrib.postgres.fields.ranges
34
import django.db.models.deletion
45
from django.db import migrations, models
56

@@ -13,15 +14,7 @@ class Migration(migrations.Migration):
1314
migrations.CreateModel(
1415
name="Artist",
1516
fields=[
16-
(
17-
"id",
18-
models.AutoField(
19-
auto_created=True,
20-
primary_key=True,
21-
serialize=False,
22-
verbose_name="ID",
23-
),
24-
),
17+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
2518
("flags", models.JSONField(blank=True, default=dict, null=True)),
2619
("name", models.CharField(max_length=255)),
2720
("last_name", models.CharField(max_length=255)),
@@ -36,35 +29,16 @@ class Migration(migrations.Migration):
3629
migrations.CreateModel(
3730
name="Band",
3831
fields=[
39-
(
40-
"id",
41-
models.AutoField(
42-
auto_created=True,
43-
primary_key=True,
44-
serialize=False,
45-
verbose_name="ID",
46-
),
47-
),
32+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
4833
("name", models.CharField(max_length=255)),
49-
(
50-
"genre",
51-
models.IntegerField(choices=[(1, "Rock"), (2, "Blues"), (3, "Soul"), (4, "Other")]),
52-
),
34+
("genre", models.IntegerField(choices=[(1, "Rock"), (2, "Blues"), (3, "Soul"), (4, "Other")])),
5335
("active", models.BooleanField(default=True)),
5436
],
5537
),
5638
migrations.CreateModel(
5739
name="City",
5840
fields=[
59-
(
60-
"id",
61-
models.AutoField(
62-
auto_created=True,
63-
primary_key=True,
64-
serialize=False,
65-
verbose_name="ID",
66-
),
67-
),
41+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
6842
("name", models.CharField(max_length=255)),
6943
],
7044
options={
@@ -74,15 +48,7 @@ class Migration(migrations.Migration):
7448
migrations.CreateModel(
7549
name="Country",
7650
fields=[
77-
(
78-
"id",
79-
models.AutoField(
80-
auto_created=True,
81-
primary_key=True,
82-
serialize=False,
83-
verbose_name="ID",
84-
),
85-
),
51+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
8652
("name", models.CharField(max_length=255)),
8753
],
8854
options={
@@ -93,15 +59,7 @@ class Migration(migrations.Migration):
9359
migrations.CreateModel(
9460
name="DemoModelField",
9561
fields=[
96-
(
97-
"id",
98-
models.AutoField(
99-
auto_created=True,
100-
primary_key=True,
101-
serialize=False,
102-
verbose_name="ID",
103-
),
104-
),
62+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
10563
("flags", models.JSONField(blank=True, default=dict, null=True)),
10664
("char", models.CharField(max_length=255)),
10765
("integer", models.IntegerField()),
@@ -120,13 +78,13 @@ class Migration(migrations.Migration):
12078
("unique", models.CharField(max_length=255, unique=True)),
12179
("nullable", models.CharField(max_length=255, null=True)),
12280
("blank", models.CharField(blank=True, max_length=255, null=True)),
81+
("not_editable", models.CharField(blank=True, editable=False, max_length=255, null=True)),
82+
("choices", models.IntegerField(choices=[(1, "Choice 1"), (2, "Choice 2"), (3, "Choice 3")])),
12383
(
124-
"not_editable",
125-
models.CharField(blank=True, editable=False, max_length=255, null=True),
126-
),
127-
(
128-
"choices",
129-
models.IntegerField(choices=[(1, "Choice 1"), (2, "Choice 2"), (3, "Choice 3")]),
84+
"validity",
85+
django.contrib.postgres.fields.ranges.DateRangeField(
86+
blank=True, null=True, verbose_name="Validity range"
87+
),
13088
),
13189
],
13290
),
@@ -192,20 +150,9 @@ class Migration(migrations.Migration):
192150
migrations.CreateModel(
193151
name="Region",
194152
fields=[
195-
(
196-
"id",
197-
models.AutoField(
198-
auto_created=True,
199-
primary_key=True,
200-
serialize=False,
201-
verbose_name="ID",
202-
),
203-
),
153+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
204154
("name", models.CharField(max_length=255)),
205-
(
206-
"country",
207-
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="demo.country"),
208-
),
155+
("country", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="demo.country")),
209156
],
210157
options={
211158
"ordering": ("name",),

tests/demoapp/demo/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.contrib.postgres.fields import DateRangeField
12
from django.db import models
23

34
try:
@@ -35,6 +36,8 @@ class DemoModelField(JSONMixin, models.Model):
3536
not_editable = models.CharField(max_length=255, editable=False, blank=True, null=True)
3637
choices = models.IntegerField(choices=((1, "Choice 1"), (2, "Choice 2"), (3, "Choice 3")))
3738

39+
validity = DateRangeField(verbose_name="Validity range")
40+
3841
class Meta:
3942
app_label = "demo"
4043

0 commit comments

Comments
 (0)