Skip to content

Commit 02c4a8c

Browse files
committed
add support for ModelAdmin callable - #181
1 parent c3a8704 commit 02c4a8c

File tree

8 files changed

+55
-14
lines changed

8 files changed

+55
-14
lines changed

CHANGES

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Release <dev>
77
* add `dry_run` option to bulk_update
88
* bulk_update now returns a page with pre/post action field values
99
* renamed `get_export_form` as `get_aa_export_form` ( @see https://github.com/saxix/django-adminactions/issues/217)
10-
10+
* add support for ModelAdmin callable (@see https://github.com/saxix/django-adminactions/issues/181)
1111

1212
Release 2.1
1313
===========

src/adminactions/api.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ def write(self, value):
131131

132132

133133
def export_as_csv( # noqa: max-complexity: 20
134-
queryset, fields=None, header=None, filename=None, options=None, out=None
134+
queryset,
135+
fields=None,
136+
header=None,
137+
filename=None,
138+
options=None,
139+
out=None,
140+
modeladmin=None,
135141
): # noqa
136142
"""
137143
Exports a queryset as csv from a queryset with the given fields.
@@ -169,7 +175,6 @@ def export_as_csv( # noqa: max-complexity: 20
169175
config.update(options)
170176
if fields is None:
171177
fields = [f.name for f in queryset.model._meta.fields + queryset.model._meta.many_to_many]
172-
173178
if streaming_enabled:
174179
buffer_object = Echo()
175180
else:
@@ -201,7 +206,7 @@ def yield_rows():
201206
for obj in queryset:
202207
row = []
203208
for fieldname in fields:
204-
value = get_field_value(obj, fieldname)
209+
value = get_field_value(obj, fieldname, modeladmin=modeladmin)
205210
if isinstance(value, datetime.datetime):
206211
try:
207212
value = dateformat.format(
@@ -249,7 +254,7 @@ def yield_rows():
249254

250255

251256
def export_as_xls2( # noqa: max-complexity: 24
252-
queryset, fields=None, header=None, filename=None, options=None, out=None # noqa
257+
queryset, fields=None, header=None, filename=None, options=None, out=None, modeladmin=None # noqa
253258
):
254259
# sheet_name=None, header_alt=None,
255260
# formatting=None, out=None):
@@ -334,7 +339,9 @@ def _get_qs_formats(queryset):
334339
for col_idx, fieldname in enumerate(fields):
335340
fmt = formats.get(col_idx, "general")
336341
try:
337-
value = get_field_value(row, fieldname, usedisplay=use_display, raw_callable=False)
342+
value = get_field_value(
343+
row, fieldname, usedisplay=use_display, raw_callable=False, modeladmin=modeladmin
344+
)
338345
if callable(fmt):
339346
value = xlwt.Formula(fmt(value))
340347
if hash(fmt) not in _styles:
@@ -382,7 +389,7 @@ def _get_qs_formats(queryset):
382389

383390

384391
def export_as_xls3( # noqa: max-complexity: 23
385-
queryset, fields=None, header=None, filename=None, options=None, out=None # noqa
392+
queryset, fields=None, header=None, filename=None, options=None, out=None, modeladmin=None # noqa
386393
): # pragma: no cover
387394
# sheet_name=None, header_alt=None,
388395
# formatting=None, out=None):
@@ -457,7 +464,9 @@ def _get_qs_formats(queryset):
457464
for idx, fieldname in enumerate(fields):
458465
fmt = formats.get(fieldname, formats["_general_"])
459466
try:
460-
value = get_field_value(row, fieldname, usedisplay=use_display, raw_callable=False)
467+
value = get_field_value(
468+
row, fieldname, usedisplay=use_display, raw_callable=False, modeladmin=modeladmin
469+
)
461470
if callable(fmt):
462471
value = fmt(value)
463472
if isinstance(value, (list, tuple)):

src/adminactions/export.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def base_export(
105105
header=form.cleaned_data.get("header", False),
106106
filename=filename,
107107
options=form.cleaned_data,
108+
modeladmin=modeladmin,
108109
)
109110
except Exception as e:
110111
logger.exception(e)

src/adminactions/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def getattr_or_item(obj, name):
9292
return ret
9393

9494

95-
def get_field_value(obj, field, usedisplay=True, raw_callable=False):
95+
def get_field_value(obj, field, usedisplay=True, raw_callable=False, modeladmin=None):
9696
"""
9797
returns the field value or field representation if get_FIELD_display exists
9898
@@ -117,7 +117,9 @@ def get_field_value(obj, field, usedisplay=True, raw_callable=False):
117117
else:
118118
raise ValueError("Invalid value for parameter `field`: Should be a field name or a Field instance")
119119

120-
if usedisplay and hasattr(obj, "get_%s_display" % fieldname):
120+
if modeladmin and hasattr(modeladmin, fieldname):
121+
value = getattr(modeladmin, fieldname)(obj)
122+
elif usedisplay and hasattr(obj, "get_%s_display" % fieldname):
121123
value = getattr(obj, "get_%s_display" % fieldname)()
122124
else:
123125
value = getattr_or_item(obj, fieldname)

tests/conftest.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import logging
22
import os
33
import shutil
4+
import sys
45
import tempfile
6+
from pathlib import Path
57

68
import django_webtest
79
import pytest
@@ -61,9 +63,11 @@ def pytest_addoption(parser):
6163

6264

6365
def pytest_configure(config):
64-
# import warnings
65-
# enable this to remove deprecations
66-
# warnings.simplefilter('once', DeprecationWarning)
66+
here = Path(__file__).parent
67+
sys.path.insert(0, here)
68+
sys.path.insert(0, here.parent / "src")
69+
os.environ["DJANGO_SETTINGS_MODULE"] = "demo.settings"
70+
6771
from django.conf import settings
6872

6973
if (

tests/demo/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,26 @@ class UserDetailModelAdmin(ExtraUrlMixin, ModelAdmin):
6363
list_display = [f.name for f in UserDetail._meta.fields]
6464

6565

66+
def export_one(_modeladmin, _request, queryset):
67+
from adminactions.api import export_as_csv
68+
69+
return export_as_csv(queryset, fields=["get_custom_field"], modeladmin=_modeladmin)
70+
71+
6672
class DemoModelAdmin(ExtraUrlMixin, ModelAdmin):
6773
# list_display = ('char', 'integer', 'logic', 'null_logic',)
6874
list_display = [f.name for f in DemoModel._meta.fields]
75+
actions = (export_one,)
6976

7077
@button()
7178
def import_fixture(self, request):
7279
from adminactions.helpers import import_fixture as _import_fixture
7380

7481
return _import_fixture(self, request)
7582

83+
def get_custom_field(self, instance):
84+
return f"model-attribute-{instance.pk}"
85+
7686

7787
class DemoOneToOneAdmin(ExtraUrlMixin, AdminActionPermMixin, ModelAdmin):
7888
pass

tests/test_exports.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import io
33
import time
44
import unittest
5+
from unittest.mock import Mock
56

67
import mock
78
import xlrd
@@ -442,3 +443,16 @@ def test_faster_export(self):
442443
6.5,
443444
"Response should return under 6.5 " "seconds, was %.2f" % res_time,
444445
)
446+
447+
def test_modeladmin_attributes(self):
448+
from demo.models import DemoModel
449+
from django.contrib.admin import site
450+
451+
from adminactions.api import export_as_csv
452+
453+
def export_model_admin_attr(_modeladmin, _request, queryset):
454+
return export_as_csv(queryset, fields=["get_custom_field"], modeladmin=_modeladmin)
455+
456+
recs = DemoModel.objects.order_by("pk")[:1]
457+
res = export_model_admin_attr(site._registry[DemoModel], Mock(), recs)
458+
assert res.content.decode() == f'"model-attribute-{recs[0].pk}"\r\n'

tox.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ envlist = d{32,42}-py{39,310,311}
55
;skip_missing_interpreters = true
66
;
77
[pytest]
8-
DJANGO_SETTINGS_MODULE = demo.settings
8+
;DJANGO_SETTINGS_MODULE = demo.settings
9+
django_find_project = false
910
norecursedirs = demo .tox
1011
addopts =
1112
-v

0 commit comments

Comments
 (0)