Skip to content

Commit 5453a2c

Browse files
committed
[fix] Fixes by @coderabbitai
1 parent 3f859a9 commit 5453a2c

3 files changed

Lines changed: 54 additions & 17 deletions

File tree

docs/user/settings.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ without having to specify the international prefix.
136136
}
137137
============ ==========================================================================
138138

139+
.. note::
140+
141+
The ``callable`` value must be a Python callable (function or method),
142+
not a string path. Ensure the function is imported in your settings
143+
file before referencing it.
144+
139145
This setting configures the fields exported by the :ref:`export_users`
140146
management command.
141147

openwisp_users/management/commands/export_users.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def normalize_field(field):
2222
class Command(BaseCommand):
2323
help = _("Exports user data to a CSV file")
2424

25-
def _normalize_value(self, value):
25+
@staticmethod
26+
def _normalize_value(value):
2627
"""Convert None to empty string, otherwise stringify the value."""
2728
return "" if value is None else str(value)
2829

@@ -152,6 +153,9 @@ def _get_field_value(self, user, field):
152153
if callable_fn is not None:
153154
try:
154155
val = callable_fn(user)
156+
except CommandError:
157+
# Allow CommandError raised by callable to propagate unchanged.
158+
raise
155159
except Exception as e:
156160
func_name = getattr(callable_fn, "__name__", repr(callable_fn))
157161
raise CommandError(

openwisp_users/tests/test_commands.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,23 +175,50 @@ def test_related_fields_no_n_plus_1(self):
175175
self.assertEqual(csv_data[2][1], "")
176176

177177
def test_callable_error_handling(self):
178-
def _broken_callable(user):
179-
raise ValueError("test error")
178+
with self.subTest("Callable that raises non-CommandError"):
180179

181-
config = {
182-
"fields": ["id", {"name": "broken", "callable": _broken_callable}],
183-
"select_related": [],
184-
"prefetch_related": [],
185-
}
186-
self._create_user()
187-
stderr = StringIO()
188-
with (
189-
patch.object(app_settings, "EXPORT_USERS_COMMAND_CONFIG", config),
190-
self.assertRaises(CommandError) as context,
191-
):
192-
# the command wraps callable errors in CommandError with callable name
193-
call_command("export_users", filename=self.temp_file.name, stderr=stderr)
194-
self.assertIn("Error calling function '", str(context.exception))
180+
def _broken_callable(user):
181+
raise ValueError("test error")
182+
183+
config = {
184+
"fields": ["id", {"name": "broken", "callable": _broken_callable}],
185+
"select_related": [],
186+
"prefetch_related": [],
187+
}
188+
self._create_user()
189+
stderr = StringIO()
190+
with (
191+
patch.object(app_settings, "EXPORT_USERS_COMMAND_CONFIG", config),
192+
self.assertRaises(CommandError) as context,
193+
):
194+
# the command wraps callable errors in CommandError with callable name
195+
call_command(
196+
"export_users", filename=self.temp_file.name, stderr=stderr
197+
)
198+
self.assertIn("Error calling function '", str(context.exception))
199+
200+
with self.subTest("Callable raises CommandError"):
201+
202+
def _broken_command_error_callable(user):
203+
raise CommandError("original msg")
204+
205+
config2 = {
206+
"fields": [
207+
"id",
208+
{"name": "broken", "callable": _broken_command_error_callable},
209+
],
210+
"select_related": [],
211+
"prefetch_related": [],
212+
}
213+
stderr = StringIO()
214+
with (
215+
patch.object(app_settings, "EXPORT_USERS_COMMAND_CONFIG", config2),
216+
self.assertRaises(CommandError) as context2,
217+
):
218+
call_command(
219+
"export_users", filename=self.temp_file.name, stderr=stderr
220+
)
221+
self.assertIn("original msg", str(context2.exception))
195222

196223
@patch.object(
197224
app_settings,

0 commit comments

Comments
 (0)