From 4c7889c5c5bd3b9556a569f9c61b7de2a7437697 Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Sat, 26 Apr 2025 20:47:10 +0200 Subject: [PATCH 1/7] try to fix flow --- .github/workflows/django-adminlink-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/django-adminlink-ci.yml b/.github/workflows/django-adminlink-ci.yml index e65da7d..36257c8 100644 --- a/.github/workflows/django-adminlink-ci.yml +++ b/.github/workflows/django-adminlink-ci.yml @@ -121,7 +121,6 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: verbose: true - repository-url: https://test.pypi.org/legacy/ publish: runs-on: ubuntu-latest From 0492c153050d2478b6643a84367a22a11f4c15db Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 13:17:14 +0200 Subject: [PATCH 2/7] adminfilter_factory --- README.md | 37 ++++++++++++++++++++++++++++++++++++- django_adminlink/admin.py | 26 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d017bc..2e30d0f 100644 --- a/README.md +++ b/README.md @@ -113,4 +113,39 @@ class MovieAdmin(GroupedActionAdminMixin, admin.ModelAdmin): # … ``` -The order of the groups is determined by the order of the individual actions: the first action for that group for each group determines how the groups are listed. \ No newline at end of file +The order of the groups is determined by the order of the individual actions: the first action for that group for each group determines how the groups are listed. + +## Simpler `SimpleListFilter`s + +Django allows to add extra filters on the Django admin, but even with a `SimpleListFilter`, there are cumbersome to make. We define a `ChoiceListFilter` which can be +used to make these based on a dictionary, and add a function `adminfilter_factory(..)` to define such classes in a more covenient way. + +Once can define such simple list filter by working with a dict like: + +``` +from django.db.models import Q +from django.db.modles.functions import Now + +class ArchiveStatusFilter(ChoiceListFilter): + title = 'Archive status' + parameter_name = 'archive_status' + choices = { + 'active': ('Active', ~Q(archived_at__lte=Now())) + 'archived': ('Archived', Q(archived_at__lte=Now())) + } +``` + +so a dictionary that maps the key of the filter on a 2-tuple with the verbose name as first item, and the Django filter (a `Q` object) as second one. Because it is a dictionary, it is also imppsible to specify the same key twice. + +We can also define this with the `adminfilter_factory(..)` as follows: + + +``` +ARCHIVE_OPTIONS = [ + ('active', 'Active', ~Q(archived_at__lte=Now())), + ('archived', 'Archived', Q(archived_at__lte=Now())) +] +ArchiveStatusFilter = adminfilter_factory('archive_status', ARCHIVE_OPTIONS) +``` + +here we can use the same dictionary structure, or any iterable of items with at least three items. \ No newline at end of file diff --git a/django_adminlink/admin.py b/django_adminlink/admin.py index 0eea8b6..b0523f4 100644 --- a/django_adminlink/admin.py +++ b/django_adminlink/admin.py @@ -162,3 +162,29 @@ def get_action_choices(self, request, default_choices=models.BLANK_CHOICE_DASH): class GroupedActionAdmin(SingleItemActionMixin, admin.ModelAdmin): pass + + +class ChoiceListFilter(admin.SimpleListFilter): + choices = {} + + def lookups(self, request, model_admin): + return [ + (k, v) for k, (v, *__) in self.choices.items() + ] + + def queryset(self, request, queryset): + val = self.choices.get(self.value()) + if val and len(val) >= 2: + __, cond, *__ = val + return queryset.filter(cond) + return queryset + + +def adminfilter_factory(parameter_name, choices, verbose_name=None): + default_verbose_name = parameter_name.replace('_', ' ') + if verbose_name is None: + verbose_name = default_verbose_name.capitalize() + class_name = default_verbose_name.title().replace(' ', '') + if not isinstance(choices, dict): + choices = {k: vs for for k, *vs in choices} + return type(f'{class_name}Filter', (admin.ChoiceListFilter,), {'title': verbose_name, 'parameter_name': parameter_name, 'choices': choices}) \ No newline at end of file From 133287fac2e557d49fb768ebc63b649f71dfa39b Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 13:22:31 +0200 Subject: [PATCH 3/7] more work on factory --- django_adminlink/admin.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/django_adminlink/admin.py b/django_adminlink/admin.py index b0523f4..757d88d 100644 --- a/django_adminlink/admin.py +++ b/django_adminlink/admin.py @@ -166,12 +166,10 @@ class GroupedActionAdmin(SingleItemActionMixin, admin.ModelAdmin): class ChoiceListFilter(admin.SimpleListFilter): choices = {} - + def lookups(self, request, model_admin): - return [ - (k, v) for k, (v, *__) in self.choices.items() - ] - + return [(k, v) for k, (v, *__) in self.choices.items()] + def queryset(self, request, queryset): val = self.choices.get(self.value()) if val and len(val) >= 2: @@ -181,10 +179,14 @@ def queryset(self, request, queryset): def adminfilter_factory(parameter_name, choices, verbose_name=None): - default_verbose_name = parameter_name.replace('_', ' ') + default_verbose_name = parameter_name.replace("_", " ") if verbose_name is None: verbose_name = default_verbose_name.capitalize() - class_name = default_verbose_name.title().replace(' ', '') + class_name = default_verbose_name.title().replace(" ", "") if not isinstance(choices, dict): - choices = {k: vs for for k, *vs in choices} - return type(f'{class_name}Filter', (admin.ChoiceListFilter,), {'title': verbose_name, 'parameter_name': parameter_name, 'choices': choices}) \ No newline at end of file + choices = {k: vs for k, *vs in choices} + return type( + f"{class_name}Filter", + (admin.ChoiceListFilter,), + {"title": verbose_name, "parameter_name": parameter_name, "choices": choices}, + ) From 63afd6213d8e30ee986d977393d43b17fee78940 Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 18:25:02 +0200 Subject: [PATCH 4/7] admin. -> --- django_adminlink/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_adminlink/admin.py b/django_adminlink/admin.py index 757d88d..57e1b4d 100644 --- a/django_adminlink/admin.py +++ b/django_adminlink/admin.py @@ -187,6 +187,6 @@ def adminfilter_factory(parameter_name, choices, verbose_name=None): choices = {k: vs for k, *vs in choices} return type( f"{class_name}Filter", - (admin.ChoiceListFilter,), + (ChoiceListFilter,), {"title": verbose_name, "parameter_name": parameter_name, "choices": choices}, ) From e3f9058f33def7aec728cca8d48cf283e8ac381e Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 18:29:17 +0200 Subject: [PATCH 5/7] start writing tests --- django_adminlink/tests.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 django_adminlink/tests.py diff --git a/django_adminlink/tests.py b/django_adminlink/tests.py new file mode 100644 index 0000000..e69de29 From 60d6252abc833504042ce4f7f6d59eb4f5fa3070 Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 18:35:41 +0200 Subject: [PATCH 6/7] resolve readme comments --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2e30d0f..9d24774 100644 --- a/README.md +++ b/README.md @@ -117,30 +117,31 @@ The order of the groups is determined by the order of the individual actions: th ## Simpler `SimpleListFilter`s -Django allows to add extra filters on the Django admin, but even with a `SimpleListFilter`, there are cumbersome to make. We define a `ChoiceListFilter` which can be -used to make these based on a dictionary, and add a function `adminfilter_factory(..)` to define such classes in a more covenient way. +Django allows to add extra filters on the Django admin, but even with a `SimpleListFilter`, they are cumbersome to make. We define a `ChoiceListFilter` which can be +used to make these based on a dictionary, and add a function `adminfilter_factory(..)` to define such classes in a more convenient way. Once can define such simple list filter by working with a dict like: -``` +```python3 from django.db.models import Q -from django.db.modles.functions import Now +from django.db.models.functions import Now +from django_adminlink.admin import ChoiceListFilter class ArchiveStatusFilter(ChoiceListFilter): title = 'Archive status' parameter_name = 'archive_status' choices = { - 'active': ('Active', ~Q(archived_at__lte=Now())) + 'active': ('Active', ~Q(archived_at__lte=Now())), 'archived': ('Archived', Q(archived_at__lte=Now())) } ``` -so a dictionary that maps the key of the filter on a 2-tuple with the verbose name as first item, and the Django filter (a `Q` object) as second one. Because it is a dictionary, it is also imppsible to specify the same key twice. +so a dictionary that maps the key of the filter on a 2-tuple with the verbose name as first item, and the Django filter (a `Q` object) as second one. Because it is a dictionary, it is also impossible to specify the same key twice. We can also define this with the `adminfilter_factory(..)` as follows: -``` +```python3 ARCHIVE_OPTIONS = [ ('active', 'Active', ~Q(archived_at__lte=Now())), ('archived', 'Archived', Q(archived_at__lte=Now())) From 9b2cae45c74741639f00d6191d47b29d985f4372 Mon Sep 17 00:00:00 2001 From: Willem Van Onsem Date: Fri, 18 Jul 2025 18:36:00 +0200 Subject: [PATCH 7/7] resolve readme comments --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9d24774..261fa82 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,9 @@ We can also define this with the `adminfilter_factory(..)` as follows: ```python3 +from django.db.models import Q +from django_adminlink.admin import adminfilter_factory + ARCHIVE_OPTIONS = [ ('active', 'Active', ~Q(archived_at__lte=Now())), ('archived', 'Archived', Q(archived_at__lte=Now()))